[smartics : respond!]

After finally getting some frontend and visualizations done, I would not want to stop there. The logical next step in my opinion is to invest a little bit into the responsiveness of the application. Especially since an app that has a browser fontend is going to be opened on a mobile some time. In the case of a tool that is to implement the visualization and operation of smart home data it makes even more sense to adapt it for mobile, tablet as well as widescreen usage. In this post I walk through the first steps of going responsive: adapting the user interface (UI) for different screen sizes.

Responsive design: collapsing menu, different amount of details, variable size and space

In the picture above, screenshots of a small 400+ pixel wide mobile screen and a 1200+ pixel wide big screen version of the current smartics app can be seen. For easy handling and quick implementation of these styles I chose to go with the widely used bootstrap library [1.]. I went with react-bootstrap [2.] for the JavaScript part of it though, since I am using react and did not want to include other JS libraries like jQuery. I will customize it much more and include further features in the following days. Right now it is a first approach.


implementation

Let me start with the general enabling part for the app. The dependencies (libraries: bootstrap, react-bootstrap) need to be added to the package.json and webpack needs to get told in the webpack.config.js what to do with some .css files it might come across.

  "dependencies": {
    "bootstrap": "^4.4.1",
    "css-loader": "^3.4.2",
    "react-bootstrap": "^1.0.0-beta.16",
    "style-loader": "^1.1.3"
  },
   module : {
      rules : [ {
         test: /\.css$/,
         use: ['style-loader',
               'css-loader']
      } ]
   },

Aside from small adaptations to main.css and the import of the bootstrap.min.css file (line 10), that is all that’s needed to import and use the bootstrap components. Using bootstrap 4.x here comes with the Flexbox approach [3.]. This is the updated index.jsx to include bootstrap, a fluid container, flexible navigation (collapsing, including menu entries – without functionality for now), and the already known react apps for inverter and charger data.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import ChargerApp from './component/ChargerApp.jsx';
import InverterApp from './component/InverterApp.jsx';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import 'bootstrap/dist/css/bootstrap.min.css';

class Index extends Component {
   render() {
      return (
         <Container fluid>
            <Row>
               <Col>
                  <Navbar bg="light" expand="md" fixed="top" collapseOnSelect>
                     <Navbar.Brand href="#home">
                        <img src="images/logo.png" width="26" height="30" className="d-inline-block align-top" alt="logo" />
                        {' '}smartics
                     </Navbar.Brand>
                     <Navbar.Toggle aria-controls="basic-navbar-nav" />
                     <Navbar.Collapse id="basic-navbar-nav">
                        <Nav className="mr-auto">
                           <Nav.Link href="#history">history</Nav.Link>
                           <Nav.Link href="#about">about</Nav.Link>
                        </Nav>
                     </Navbar.Collapse>
                  </Navbar>
               </Col>
            </Row>
            <Row>
               <Col lg={4}>
                  <ChargerApp />
               </Col>
               <Col lg={6}>
                  <InverterApp />
               </Col>
            </Row>
         </Container>
      );
   }
}

export default Index;

const rootElement = document.getElementById('root');
ReactDOM.render(<Index />, rootElement);

The general data that gets displayed I changed a little to support the flexible size handling better (see f.e. ChargerSetting.jsx or ChargerApp.jsx). But the most important change is the adaptation of InverterApp.jsx to be able to display different data depending on screen size. For this see line 12, 18+19, 25. Further the functions: updateWindowDimensions in 28, and getComponentWidth in 83 and its usage in 55 and 59.

class InverterApp extends Component {

   constructor(props) {
      super(props);
      this.handleNavPrev = this.handleNavPrev.bind(this);
      this.handleNavNext = this.handleNavNext.bind(this);
      this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
      this.state = {
         inverter: [],
         selectDate: new Date(),
         prevDate: this.selectPreviousDay(new Date()),
         nextDate: this.selectNextDay(new Date()),
         width: window.innerWidth,
         height: window.innerHeight
      };
   }

   componentDidMount() {
      this.onNavigate(this.state.selectDate);
      window.addEventListener('resize', this.updateWindowDimensions);
   }

   updateWindowDimensions() {
      this.setState({
         width: window.innerWidth,
         height: window.innerHeight
      });
   }

   render() {
      return (
         <div>
            <table width='100%'>
               <thead></thead>
               <tbody>
                  <tr>
                     <td><img src='images/inverter.png' width='100px' /></td>
                     <td>
                        <h3>inverter {this.getCurrentDate(this.state.selectDate)}</h3>
                        <div>status: {this.state.inverter.status}</div>
                        <br />
                        <div>produced: {this.state.inverter.powerProduced}</div>
                        <div>consumed: {this.state.inverter.powerConsumed}</div>
                        <div>autonomy: {this.state.inverter.autonomy}</div>
                     </td>
                  </tr>
                  <tr>
                     <td colSpan='2'>
                        <div>
                           <PowerChart data={this.getPowerForChart(this.state.inverter)} size={[this.getComponentWidth(), 80]} />
                        </div>

                        <div>
                           <EnergyChart data={this.getEnergyForMins(this.state.inverter.meteringDataMinDtos)} size={[this.getComponentWidth(), 300]}
                              showline={this.getCurrentDate(new Date()) == this.getCurrentDate(this.state.selectDate)}
                              maxwh={5500} />
                        </div>
                     </td>
                  </tr>
                  <tr>
                     <td>
                        <Button variant="outline-secondary" key="prev" onClick={this.handleNavPrev}>
                           < previous day</Button>
                        {this.prettyDate(this.state.inverter.fromTime)}
                     </td>
                     <td>
                        {this.prettyDate(this.state.inverter.untilTime)}
                        <Button variant="outline-secondary" key="next" onClick={this.handleNavNext}>
                           next day ></Button>
                     </td>
                  </tr>
               </tbody>
            </table>
         </div >
      );
   }

   getComponentWidth() {
      if (this.state.width !== undefined && this.state.width > 1200) {
         return 800;
      }
      return 400;
   }
}

export default InverterApp

This as well as a few helper functions enable a graph that shows more detailed information if presented on a bigger screen. At the moment this includes the whole bootstrap css file, which I will adapt in webpack in one of the next days.

— Raphael


references

  1. bootstrap https://getbootstrap.com/
  2. react-bootstrap https://react-bootstrap.github.io
  3. flexbox guide https://css-tricks.com/snippets/css/a-guide-to-flexbox/

Leave a comment