We have the application infrastructure in place, so it's time to build out the components that are going to rest on this infrastructure. First up is the dashboard, something that requires us to carefully consider how to implement the rest of our application.
The dashboard consists of two live charts and two historical charts. The live charts are very similar to each other, as are the historical charts (just some minor formatting and binding configuration between them). However, in order to build a chart, you also have to build the axes and the series being drawn on it, which results in a fairly long-winded configuration object. Here's what the historical log chart from the dashboard will look like:
{ xtype: 'cartesian', title: 'Last 30 Days', margin: 10, flex: 1 bind: '{historicalWebLogs}' axes: [{ type: 'numeric', position: 'left', fields: ['value'], title: { text: 'Avg. Response Time (ms)', fontSize: 15 }, grid: true, minimum: 0, maximum: 20 }, { type: 'time', fields: 'time', dateFormat: 'd M' }], series: { type: 'line', xField: 'time', yField: 'value', style: { 'stroke': 'red' } } }
Excellent! We have configured a chart with a line series that has a numeric left axis and a time-base bottom axis. This is exactly what we need for the dashboard, so where's the problem?
Duplication is the problem. The majority of this configuration object would be duplicated, one copy for the web logs and one copy for the SQL logs. We've mentioned before in this chapter that we'd like to battle duplication where possible, so in this case, we will create a new class that we can reuse in places where we need a chart to plot historical requests from the logs.
Here's the class:
// app/ux/chart/HistoricalRequestChart.js Ext.define('Instrumatics.ux.chart.HistoricalRequestChart', { extend: 'Ext.chart.CartesianChart', xtype: 'historical-request-chart', frame: true, axes: [{ type: 'numeric', position: 'left', fields: ['value'], title: { text: 'Avg. Response Time (ms)', fontSize: 15 }, grid: true, minimum: 0, maximum: 20 }, { type: 'time', position: 'bottom', fields: ['time'], dateFormat: 'd M' style: { axisLine: false } }], series: { type: 'line', xField: 'time', yField: 'value', style: { 'stroke': 'red' } } });
It combines the basic definition of the chart, such as the axes and series, with things we know we'll reuse across the application, such as the title on the left axis.
Note that we're putting this class in a different location from anything we've seen so far in our example applications, but one that we discussed in Chapter 3, Application Structure. The ux
namespace and corresponding directory is a fairly standard location for reusable classes in the Ext JS community, so we will follow this convention here.
We will create another reusable class, this time for the live request chart:
// app/ux/chart/LiveRequestChart.js Ext.define('Instrumatics.ux.chart.LiveRequestChart', { extend: 'Ext.chart.CartesianChart', xtype: 'live-request-chart', redrawCounter: 0, frame: true, axes: [{ type: 'numeric', position: 'left', fields: ['value'], title: { text: 'Avg. Response Time (ms)', fontSize: 15 }, grid: true, minimum: 0, maximum: 20 }, { type: 'time', position: 'bottom', step: [Ext.Date.SECOND, 1], fields: ['time'], dateFormat: 'H:i:s', fromDate: new Date(new Date().setMinutes( new Date().getMinutes() - 1)).setSeconds(0), toDate: new Date(new Date().setMinutes( new Date().getMinutes() + 5)).setSeconds(0) }], series: { type: 'line', xField: 'time', yField: 'value', style: { 'stroke-width': 2 } }, constructor: function() { this.callParent(arguments); this.on('redraw', this.onRedraw, this); }, onRedraw: function() { this.redrawCounter++; if(this.redrawCounter > 15) { this.redrawCounter = 0; var timeAxis = this.getAxes()[1], oldFrom = new Date(timeAxis.getFromDate()), oldTo = new Date(timeAxis.getToDate()), newFrom = Ext.Date.add(oldFrom, Ext.Date.SECOND, 15), newTo = Ext.Date.add(oldTo, Ext.Date.SECOND, 15); timeAxis.setFromDate(newFrom); timeAxis.setToDate(newTo); } } });
You'll notice that this code has a lot in common with the code from our code investigation earlier in the chapter. It's just that we've wrapped it up into a reusable class.
With these new classes ready to go, creating the dashboard is just a matter of piecing together what we've already written, as shown in the following code:
// app/view/dashboard/Dashboard.js Ext.define("Instrumatics.view.dashboard.Dashboard", { extend: "Ext.panel.Panel", xtype: 'app-dashboard', title: 'hello', requires: [ 'Instrumatics.ux.chart.LiveRequestChart', 'Instrumatics.ux.chart.HistoricalRequestChart' ], viewModel: { type: 'dashboard-dashboard' }, controller: 'dashboard-dashboard', layout: { type: 'vbox', align: 'stretch' }, items: [ { xtype: 'container', flex: 1, layout: { type: 'hbox', align: 'stretch' }, items: [ { xtype: 'live-request-chart', title: 'Live Web Requests', bind: '{webLogs}', series: { style: { 'stroke': 'red' } }, margin: '10 5 0 10', flex: 1 }, { xtype: 'live-request-chart', title: 'Live SQL Requests', bind: '{sqlLogs}', series: { style: { 'stroke': 'green' } }, margin: '10 10 0 5', flex: 1 } ] }, { xtype: 'container', flex: 1, layout: { type: 'hbox', align: 'stretch' }, items: [ { xtype: 'historical-request-chart', title: 'Last 30 Days', bind: '{historicalWebLogs}', series: { style: { 'stroke': 'red' } }, margin: '10 5 10 10', flex: 1 }, { xtype: 'historical-request-chart', title: 'Last 30 Days', bind: '{historicalSqlLogs}', series: { style: { 'stroke': 'green' } }, margin: '10 10 10 5', flex: 1 } ] } ] });
Rather than duplication of the configuration that forms the charts, we'll just put together the containers—which use the vbox
and hbox
layouts—and set up titles, formatting, and bindings.
This is great. Rather than duplicating code and having lots of unnecessary configuration in the dashboard view itself, we've moved this code to a more logical location, promoting reuse and making for a tidier code base.
At this point, let's look at what we've built in comparison with our design. It matches our implementation so far, but there wasn't any mention of these reusable classes. There are two ways of looking at this:
There's something to be said for both these viewpoints; however, in Chapter 5, Practical – a CMS Application, we discussed YAGNI—you aren't going to need it—which dictates that there's little point in planning for reuse if the component in question is never going to be reused.
In this case, the implementation process revealed that we'd have some duplication, so we refactored. While taking careful consideration at the design stage is important, re-evaluating our decisions and code should be an ongoing process, so it's critical to realize that even if there's a feeling that something should have been noticed in the design phase, nothing is set in stone. We can always change things as long as we understand why they need to change and use it as a learning experience.