Ext JS to React: Layouts

   JavaScript
Ext JS to React: Layouts

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.


Container classes in Ext JS are responsible for sizing and positioning any direct child components rendered to them. The sizing and positioning is done using the Ext JS layout system which incorporates CSS and JavaScript to manage how the child items are rendered. The classic toolkit used JavaScript to calculate the dimensions and spacing of the child items while the modern toolkit is able to use newer CSS specs. This shift allows the browser to handle the setting of child dimensions and spacing. Your React components can leverage the same CSS principles to manage the most common layouts in a simple and performant manner using CSS flexbox (IE11+). A number of pre-built layout libraries such as react-bootstrap and a couple we’ll demonstrate here may also come in handy as you architect the look and feel of your application.


In this article, we’ll explore how to manage parent / child component layouts using some of the more popular layouts from the Ext JS framework. Each layout technique will be shown in isolation, but can ultimately work in a larger composition as you’re used to seeing in Ext JS. Meaning, for example, a border-type layout’s west region could have child components structured in an accordion-type layout. In the following examples we’ll keep the React code as simple as possible so that our focus is narrowed to the layout techniques themselves instead of on the React API.


Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

Box / Card Layout Class

To start off, we’re going to define a box / card layout component that will take care of the layouts for our fit, card, hbox, and vbox examples. Below is the Layout class that we’ll place at src/Layout.js:

import React, { Fragment } from 'react';
import './Layout.css';

export default ({ children, type }) => {
  const layoutClassName = type ? `layout-${type} ` : '';

  return (
    <Fragment>
      {React.Children.map(children, child => {
        const { className: childClassName = '' } = child.props;
        const className = `${layoutClassName}${childClassName}`;

        return React.cloneElement(child, { className });
      })}
    </Fragment>
  );
};



The Layout class accepts a type prop and a child item (though technically the layout will be applied to any and all direct child items). The Layout class returns a React Fragment which is essentially a DOM-free component. It may be used to wrap / interact with child items composed within it without returning any unnecessary DOM elements. Within the wrapping Fragment we loop over the child items composed within the Layout and apply a className of “layout-type” using the type string passed in. This is done by cloning the child element and passing in as its className our “layout-type” className along with the className prop if set on the child instance.


And that’s really all there is to the Layout class. Its job is to poke one more class name on to the component passed in. The heavy lifting is then done by CSS.

Box / Card Layout CSS

The CSS imported by our Layout class at src/Layout.css is:

/* FIT */
.layout-fit {
  display: flex;
  box-sizing: border-box;
}
.layout-fit > * {
  flex-grow: 1;
}

/* CARD */
.layout-card {
  display: flex;
  box-sizing: border-box;
}
.layout-card > * {
  flex-grow: 1;
}

/* HBOX */
.layout-hbox {
  display: flex;
}

/* VBOX */
.layout-vbox {
  display: flex;
  flex-direction: column;
}



We’ll use this Layout class and CSS for the following fit, card, hbox, and vbox examples.

Fit Layout

The fit layout is pretty straightforward. For the fit layout we want a single child to take up 100% height and width of the parent. To test this, we’ll just add a style prop to the parent with explicit dimensions and the Layout class will add a single className of “layout-fit”. In the Layout.css file we’ll define a rule for layout-fit saying its direct child has a height of 100%.

import React from 'react';
import Layout from './Layout';

const containerStyle = { width: 200, height: 200, border: '4px solid #923131' };
const itemStyle = { backgroundColor: '#ff4b4b' };

const App = props => {
  return (
    <Layout type="fit">
      <div style={containerStyle}>
        <div style={itemStyle} />
      </div>
    </Layout>
  );
};

export default App;


Ext JS to React: Layouts, Fit Layout



You can review the sample code on the Git repo.

Card Layout

The card layout aims to show one child item at a time as though it was the only item in a “fit” layout. While all of the card views will be represented in the class, only one needs to be rendered at a time. This is a bit of a philosophical departure from the line of thinking used in Ext JS. Let’s say that card 0 is a form and it’s been populated, but not submitted, and you navigate to the next card. If structured properly, the form data is managed by an ancestor component or a global state manager, not the form component itself. When the form card is active once again, the data is fed back to it via its props.


The fact that the views in React need only display data fed to them, not manage the data itself, means that our card layout can be very lean. In the example below, we pass in an activeCard prop (defaulting to 0). We set the card items in an array and render only the item from the card array at the index indicated by the activeCard prop.

import React from 'react';
import Layout from './Layout';

const containerStyle = { width: 100, height: 100, 
  border: '1px solid gray', color: 'white' };
const itemStyleA = { background: '#ee4d77', padding: 4 }
const itemStyleB = { background: '#b15b90', padding: 4 }

const App = ({ activeCard = 0 }) => (
  <Layout type="card">
    <div style={containerStyle}>
      {[<div style={itemStyleA}>one</div>, <div  style={itemStyleB}>two</div>][activeCard]}
    </div>
  </Layout>
);

export default App;


Ext JS to React: Layouts, Card Layout



Passing in an activeCard prop value of 1 renders the second card in the items array (in src/index.js):

ReactDOM.render(<App activeCard={1}/>, document.getElementById('root'));



You can review the sample code on the Git repo.

hbox Layout

The hbox layout is where we will really start to see the benefit of the CSS flexbox layout system. The hbox layout organizes its child items horizontally with no wrapping. This is the layout that’s applied by default to Header and Toolbar components in Ext JS. Child items can have an explicitly set width, sized naturally based on content, or can have a style attribute of flexGrow that’s similar to the flex config option you’re used to in Ext JS. In the following example, the container element has its className set to “layout-hbox”, which sets its CSS display to flex. Its child items are explicitly sized, configured with a flex of 1, configured with a flex of 2, and finally naturally sized.

import React, { Component } from 'react';
import Layout from './Layout';

const containerStyle = { width: 300, height: 100, color: 'white' };

class App extends Component {
  render() {
    return (
      <Layout type="hbox">
        <div style={containerStyle}>
          <div style={{ width: 40, background: '#df8f2e' }}>fixed</div>
          <div style={{ flexGrow: 1, background: '#f26f38' }}>flex 1</div>
          <div style={{ flexGrow: 2, background: '#ee4d77' }}>flex 2</div>
          <div style={{ background: '#b15b90' }}>wrap</div>
        </div>
      </Layout>
    );
  }
}

export default App;


Ext JS to React: Layouts, hbox Layout



You can review the sample code on the Git repo.

Vbox Layout

The vbox layout is very nearly identical to hbox except that that items are arranged in a column. Let’s look at the previous example with a vertical twist. This is the layout that’s applied by vertically oriented Header and Toolbar components in Ext JS.

import React, { Component } from 'react';
import Layout from './Layout';

const containerStyle = { width: 100, height: 300, color: 'white' };

class App extends Component {
  render() {
    return (
      <Layout type="vbox">
        <div style={containerStyle}>
          <div style={{ height: 40, background: '#df8f2e' }}>fixed</div>
          <div style={{ flexGrow: 1, background: '#f26f38' }}>flex 1</div>
          <div style={{ flexGrow: 2, background: '#ee4d77' }}>flex 2</div>
          <div style={{ background: '#b15b90' }}>wrap</div>
        </div>
      </Layout>
    );
  }
}

export default App;



The only difference in our CSS is that we add flex-direction: column to orient our child items vertically instead of horizontally.

Ext JS to React: Layouts, vbox Layout



You can review the sample code on the Git repo.

Border Layout

Ext JS made a resizable border layout very easy to achieve. While a bit less elegant than the implementation you’re accustomed to, it’s easy enough to create a layout with the four cardinal regions (north, south, east, and west) and a center region that takes up the remaining space. To do this we’ll use an existing third-party package: react-split-pane.


The following example sets up the SplitPane components needed to organize our border layout. Each SplitPane component will manage two child items which is why we use multiple SplitPanes below to accommodate all border regions. By default the first sub-component is the one that receives the sizing props. To specify the second component (like we do with both the “south” and the “east” components) the property primary="second" is set on the SplitPane.

import React, { Component } from 'react';
import './App.css';

import SplitPane from 'react-split-pane';
class App extends Component {
  render () {
    return (
      <SplitPane split="horizontal" minSize={50} maxSize={300} defaultSize={100}>
        <div>North</div>
        <SplitPane split="horizontal" primary="second">
          <SplitPane split="vertical">
            <div>West</div>
            <SplitPane split="vertical" primary="second" defaultSize={200} maxSize={400} minSize={100}>
              <div>Center</div>
              <div>East</div>
            </SplitPane>
          </SplitPane>
          <div>South</div>
        </SplitPane>
      </SplitPane>
    );
  }
}

export default App;



The CSS used to decorate the splitters is:

.Resizer {
  background: #f1f1f1;
  z-index: 1;
  box-sizing: border-box;
}
.Resizer:hover {
  transition: all .25s ease;
}
.Resizer.horizontal {
  height: 11px;
  cursor: row-resize;
  width: 100%;
}
.Resizer.horizontal:hover,
.Resizer.vertical:hover {
  background: #d6d6d6;
}
.Resizer.vertical {
  width: 11px;
  cursor: col-resize;
}
.Resizer.disabled {
  cursor: not-allowed;
}
.Resizer.disabled:hover {
  background: #f1f1f1;
}


Ext JS to React: Layouts, Border Layout



You can review the sample code on the Git repo.

Accordion Layout

For the accordion layout, we’ll turn once again to a third party solution instead of writing our own collapse markup / manager. The react-sanfona module serves pretty well for our purposes allowing us to configure the accordion for only a single panel open at a time or multiple. Each child panel has a title and body component resulting in a similar look to the panels used in an Ext JS accordion layout. To allow multiple child items to be opened at the same time you can use the prop allowMultiple={true} on the parent Accordion node.

import React, { Component } from 'react';
import { Accordion, AccordionItem } from 'react-sanfona';
import './App.css';

class App extends Component {
  render () {
    return (
      <Accordion style={{width: '400px'}}>
        {[1, 2, 3, 4, 5].map(item => (
            <AccordionItem title={`Item ${item}`} expanded={item === 1}>
                {`Item ${item} content`}
            </AccordionItem>
          )
        )}
      </Accordion>
    );
  }
}

export default App;



The react-sanfona package doesn’t ship with its own CSS package, but does provide a clear structure for us to attach our own styling to. Below are the CSS rules we’ll add to App.css to produce the example seen below:

.react-sanfona {
  border: 1px solid #ccc;
  border-radius: 3px;
}  
.react-sanfona-item-title {
  background-color: #FFF2EE;
  border-top: 1px solid #ccc;
  color: #555;
  padding: 20px;
  text-transform: uppercase;
}  
.react-sanfona-item:first-child .react-sanfona-item-title {
  border-top: none;
}
.react-sanfona-item-expanded .react-sanfona-item-title {
  background-color: #f76915;
  color: #fff;
}

.react-sanfona-item-body-wrapper {
  color: #555;
  padding: 20px;
  position: relative;
}


Ext JS to React: Layouts, Accordion Layout



You can review the sample code on the Git repo.

Conclusion

Child components often need to be organized into a developer-designated layout to be user friendly. Fortunately, in React applications the common layouts needed are easily accomplished with a little CSS or by importing a pre-built package. Even the border layout could be simplified. If all that is needed is the layout and not user-resizable spacers, that can easily be accomplished with CSS flexbox like we used for hbox and vbox layouts. Ultimately, combining third party solutions like accordion, resizable panes, and collapsible components will result in the basic utility you’re looking for when emulating the layout capabilities found in the Ext JS framework. If you’ve discovered a great layout manager for any of the above layout types in your travels, please feel free to post it in the comments below!


This website uses cookies

These cookies are used to collect information about how you interact with our website and allow us to remember you. We use this information in order to improve and customize your browsing experience, and for analytics and metrics about our visitors both on this website and other media. To find out more about the cookies we use, see our Privacy Policy.

Please consent to the use of cookies before continuing to browse our site.

Like What You See?

Got any questions?


>