A while ago one of our customers asked for a sample showing how to create a “dynamic dashboard”—one like those you can create in the Google Analytics app. The image below shows an example:


01_dynamicdashboard

The key features of interest to the customer were:



  • There is a catalog of pre-defined configurable tile types.

  • Tiles may contain arbitrary UI elements such as rich text, charts, grids and gauges.

  • Users can create custom dashboards by adding, removing, reordering, and configuring “segments” (aka “tiles”).

  • Mobile device users can drag tiles using touch.



View the sample


We thought many customers might be interested in a sample like this, so we created a Dynamic Dashboard sample and made that available to our users. Since then, we received a significant amount of feedback, including interest in the drag-drop implementation, touch support, and component design.


The original sample was implemented using Wijmo and Angular 1.x. When we released our Wijmo React interop a few weeks ago, we thought this would be a great React sample as well, and decided to create a React-based version of the sample.


The React version of the sample looks exactly like the original, but the implementation is quite different and illustrates some interesting aspects of React development.


React Implementation and TSX files


The React version of the Dynamic Dashboard application is a TypeScript application. It uses TSX files which are the TypeScript equivalent of React JSX files.


Each TSX file typically contains one React component. Adding TSX files to Visual Studio projects is trivial. Right-click the project node in the Solution Explorer, select “Add” and then “TypeScript JSX File”. You can then write regular TypeScript code with the usual DOM-like extensions typical of React code.


The tooling provided by Visual Studio for React and TypeScript is excellent. You don’t have to waste any time worrying about configuration and can get right into coding, at which point you will get all the design-time error checking and productivity tools you are used to.


The screen below shows the project with the TSX files and the options offered by Visual Studio to fine-tune the handing of TSX files should you want to do so:



02_tsx

Application Architecture


The Dynamic Dashboard app has two types of components: the dashboard frame and the tiles in it.


The dashboard frame is a stateful component. It defines a catalog of tile types available and a list of the tiles that make up the current dashboard. It also provides logic for adding, removing, and reordering the tiles.


The tiles have no state (in the React sense). They receive data from the frame via properties (passed in as element attributes). Tiles are quite simple; the app defines nine type of tiles, including various charts, gauges, bullet graphs, and a grid.


In a traditional OOP application, all tiles would extend a base tile class. In React (and many other JavaScript frameworks), you are encouraged to use composition instead of inheritance. I see this as a limitation. Composition and inheritance are different mechanisms, each has its uses, and one does not fully replace the other.


In our app, we followed the React team’s advice and implemented our tiles using composition rather than inheritance. We define a Tile component that provides the tile border (with a header and buttons). The Tile component has a “content” property that defines the actual tile content.


Dashboard Frame Component


The “App” component implements our dashboard frame. It is instantiated with the following standard snippet of HTML in application’s “default.htm” file:


<body>
<div id="app"></div>
<script>
ReactDOM.render(React.createElement(App), document.getElementById('app'));
</script>
</body>

The App parameter in the call to React.createElement is our dashboard frame component. It is implemented as follows:


<body>
<div id="app"></div>
<script>
ReactDOM.render(React.createElement(App), document.getElementById('app'));
</script>
</body>

The App parameter in the call to React.createElement is our dashboard frame component. It is implemented as follows:


var App = React.createClass({

// define the component’s initial state
getInitialState: function () {

// tile names and types
var tileCatalog = [
{ name: 'Grid', tile: Grid },
{ name: 'Radial Gauge', tile: RadialGauge },
{ name: 'Linear Gauge', tile: LinearGauge },

];

// tiles currently in use
var tiles = [],
key = 0;
for (var i = 0; i < 4; i++) {
tiles.push({ name: tileCatalog[i].name, key: key++ });
}

// generate some data to show in the tiles
var data = …;

// this is our app state
return {
tileCatalog: new wijmo.collections.CollectionView(tileCatalog),
tiles: tiles,
key: key,
data: new wijmo.collections.CollectionView(data)
}
},


The App component’s state has four elements:



  • tileCatalog: list of tile types that are available and can be added to the dashboard.

  • tiles: list of the tiles currently on the dashboard.

  • key: identifier to use when creating the next tile (more on this later)

  • data: data to be shown on the dashboard (passed as a prop to all tiles)


Let’s move on to the component’s render method to see how the state is used to generate the dashboard.


We start by rendering a header with the application title and a logo. Notice how the TSX syntax looks a lot like HTML, with some minor differences. Instead of “class”, for example, we must use “className”. That is because although it looks like HTML, this is really JavaScript code, and the DOM element’s property is called “className”, not “class”, The same applies to the “for” attribute of label elements, which in TSX must be specified as “htmlFor”.


// render the dashboard
render: function () {
var data = this.state.data;
return <div>
<div className="header">
<div className="container">
<img src="resources/wijmo5.png" alt="Wijmo 5" />
<h1>
Dynamic Dashboard (React)
</h1>
</div>
</div>

Next, we render a section that contains a drop-down where the user can select the type of tile he wants and a button to add the tile to the dashboard.


We use a Wijmo ComboBox to show the tile catalog and to keep track of the tile type currently selected. The itemsSource and displayMameberPath properties are used to populate the list and to determine which property of the items should be displayed on the list. The current tile type is exposed by the catalog’s currentItem property. Next to the combo, there is a button that invoked the “addTile” method on the component:


    <div className="container">
<div className="container row">
<label>
Select a tile type: {' '}
<Wj.ComboBox
itemsSource={ this.state.tileCatalog }
displayMemberPath="name" />
{' '}
<button
className="btn btn-primary"
onClick={ this.addTile }>
Add Tile to Dashboard
</button>
</label>
</div>

The “addTile” method is implemented as follows:


addTile: function () {
var tiles = this.state.tiles.slice(),
key = this.state.key + 1;
tiles.push({ name: this.state.tileCatalog.currentItem.name, key: key });
this.setState({ tiles: tiles, key: key });
},

The method starts by making a copy of the current tiles array and incrementing the key used to uniquely identify tile instances. It adds a new item to the tiles array with the name specified by the catalog’s currentItem, and calls setState to update the state with the new tiles array.


The call to setState causes React to update the UI showing the updated tiles array.


The code that renders the dashboard tiles is typical React. Is uses the array.map method to generate a list of components that make up the UI. In this case, the components are instances of Tile following properties:



  • header: name of the tile (displayed in the tile header).

  • content: component that represents the tile content (e.g. a chart or gauge).

  • remove: a callback invoked when the user clicks the “delete” button on the tile header.

  • index: index of the tile within the tile array.

  • key: item identifier used by React to optimize rendering.


Note that the key and index parameters are used for different purposes. The index parameter is used by the App component to identify tiles in the tiles array, specifically when deleting tiles. The index of a tile may change if tiles are removed or reordered. The key parameter, on the other hand, is used by the React framework to establish a mapping between array elements and components. The key does not change when tiles are removed or reordered.


      <div className="dashboard">
{
this.state.tiles.map((item, index) => {
return <Tile
header={item.name}
content={this.getTileContent(item.name)}
remove={this.removeTile}
index={index}
key={item.key} />;
})
}
</div>
</div>
</div>;
}

The getTileContent method creates and returns a component of a given type. It looks up the component type based on the name and calls React.createElement to instantiate the tile:


getTileContent(name: string) {
var arr = this.state.tileCatalog.items;
for (var i = 0; i < arr.length; i++) {
if (arr[i].name == name) {
return React.createElement(arr[i].tile, { data: this.state.data })
}
}
throw '*** tile not found: ' + name;
},

The removeTile method acts as an event handler. It is called by the Tile component when the user clicks the delete button on the tile header. The code uses the array.filter method to select all tiles except the one that was clicked, and calls setState to update the state with the new tiles array.


As before, the call to setState causes React to update the UI showing the updated tiles array.


removeTile: function (tileIndex) {
var tiles = this.state.tiles.filter((item, index) => {
return index != tileIndex;
});
this.setState({ tiles: tiles });
},

Tile Components


The Tile component represents a frame to hold tile content (of all types). It is a simple stateless component implemented as follows:


var Tile = React.createClass({
remove: function () {
this.props.remove(this.props.index)
},

render: function () {
return <div className="tile" draggable="true">
<div style={{ borderBottom: '1px solid #e0e0e0', … }}>
<div style={{ flexGrow:1, color: '#005c9c', fontWeight: 'bold' }}>
{ this.props.header }
</div>
<div className="buttons">
<span className="glyphicon glyphicon-pencil"></span>
<span className="glyphicon glyphicon-remove" onClick={ this.remove }></span>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', … }}>
<div>{ this.props.content }</div>
</div>
</div>
}
});

The render method creates a frame with a header and content determined by the header and content properties. The header includes a “remove” span that when clicked invokes the remove callback which is also a property (this.props.remove).


Notice how the outer div has its draggable attribute set to true. This enables HTML drag/drop operations, which we will discuss later.


The Tile component’s most interesting property is content, which represents the child component that contains the tile information. Our application defines nine types of tile content component, including charts, gauges, grid, etc. They all have a data property that is set by the parent component and contains the data to be shown on the tile.


Tile components may be as simple or complex as you like. Adding new tile types is easy: create the component to show the data in “props.data” in whatever way you want, and add the new tile type to the application component’s tileCatalog array.


Tile components may contain other components, including Wijmo data visualization controls. For example, here is the implementation of the BarChart tile component:


var BarChart = React.createClass({
render: function () {
return <Wj.FlexChart
chartType="Bar"
itemsSource={this.props.data}
bindingX="date"
axisX={{ format: 'MMM-yy' }}
series={[
{ name: 'Sales', binding: 'sales' },
{ name: 'Expenses', binding: 'expenses' },
{ name: 'Profit', binding: 'profit', chartType: 'LineSymbols' }
]} />
}
});

And here is the implementation of the Grid tile component:


var Grid = React.createClass({
render: function () {
return <Wj.FlexGrid
isReadOnly={true}
headersVisibility="Column"
selectionMode="ListBox"
itemsSource={this.props.data}
columns={[
{ binding: 'id', header: 'ID', width: 40 },
{ binding: 'date', header: 'Date', format: 'MMM yyyy' },
{ binding: 'sales', header: 'Sales', format: 'c' },
{ binding: 'expenses', header: 'Expenses', format: 'c' },
{ binding: 'profit', header: 'Profit', format: 'c' }
]} />
}
});

The main advantage of the React implementation of this app is the neat encapsulation of the tile components. Each one is represented by a single TSX file that contains the logic and UI.


Dragging Tiles


We have shown how users can customize the dashboard by adding new tiles or removing existing ones. The part that is missing is the use of drag and drop to reorder tiles, which was one of the main initial requirements for the app.


We already had this code written for the Angular version of the app. Fortunately, the code used standard HTML5 drag/drop functionality which was very easy to port to React. This was done by moving the original drag/drop code into a method called enableItemReorder and calling that from the main component’s componentDidMount method:


componentDidMount() {

// enable tile drag/drop
var panel = document.querySelector('.dashboard');
this.enableItemReorder(panel);
},

The enableItemReorder method adds handlers to the standard “dragstart”, “dragover”, and “drop” HTML5 events.


Because the HTML drag/drop events work with the actual HTML elements on the DOM (and not with the React virtual DOM), we must update the state when a drop operation is finalized. This is done when handling the “drop” event, as shown in the code snippet below:


enableItemReorder(panel) {
var dragSource = null,
dropTarget = null;

// add drag/drop event listeners
panel.addEventListener('drop', (e) => {
if (dragSource && dropTarget) {

// re-order HTML elements (optional here, we're updating the state later)
var srcIndex = getIndex(dragSource),
dstIndex = getIndex(dropTarget),
refChild = srcIndex > dstIndex ? dropTarget : dropTarget.nextElementSibling;
dragSource.parentElement.insertBefore(dragSource, refChild);

// update state
var tiles = this.state.tiles.slice();
tiles.splice(srcIndex, 1);
tiles.splice(dstIndex, 0, this.state.tiles[srcIndex]);
this.setState({ tiles: tiles });
}
});


The final piece is touch support. Since the code relies on native HTML5 drag/drop events, we added drag/drop touch support simply by including the DragDropTouch polyfill in our project.


The screenshot below shows the running application, with a tile being dragged into a new position:


03_dynamicdashboard_react

Conclusion


Porting the Dynamic Dashboard sample to React was surprisingly easy given the relative complexity of the app. Creating new tiles of arbitrary types, supporting drag/drop operations, and encapsulating arbitrary components within tiles are not trivial tasks.


In my opinion, the most significant benefits of using React in this sample were:



  • The ability to use TSX and get full design-time error checking, IntelliSense, and rich debugging for the entire app (including the TSX markup!).

  • The clean state-driven architecture imposed by React (which may take a little getting used to, but tends to pay off later).

  • The ability to encapsulate tiles neatly into single-file components.


The Dynamic Dashboard sample could be used as a starting point for actual dashboard apps. Obvious missing features are:



  • The ability to save and restore dashboard layouts to local storage.

  • Adding state to tiles so they can be customized by users.


To learn more about Wijmo, please visit our home page. To learn more about using Wijmo in React applications, please visit our React page.



View the sample