jQuery Mobile Sample: Executive Dashboard

In this tutorial you will build a jQuery Mobile app based on an Executive Dashboard prototype that I put together some time ago while working on my  jQuery Mobile book. The app consists of a screen that renders charts and a summary table, all displaying important performance indicators for a hypothetical business. You will find it useful as a starting point for a dashboard-type application using jQuery Mobile.

executive-dashboard-1

Creating the app’s jQuery Mobile page

To build this app, you will use a combination of Html markup and JavaScript. The Html markup will provide most of the layout elements for the app, while the JavaScript code will handle the rendering of the charts and values shown in the summary table.

Along with the jQuery Mobile library and its dependencies, you will use the Flot Charts library to generate the app’s charts.

Let’s get started by creating an Html file with a plain jQuery Mobile page in it, just like the one below. You will name the file index.html.

executive-dashboard-4

If you are already familiar with jQuery Mobile, you will recognize the elements present on this page. Essentially, inside the app’s single Html file, you will use a single jQuery Mobile page with a header and content areas. In the header of the Html document, not to be confused with the header of the jQuery Mobile page, there are references to the jQuery Mobile, jQuery and flot libraries, as well as to the App.css file, which you will use to host the custom styles used by the app.

Building the regions of the page

The header area will simply display the app’s name:

executive-dashboard-5

In the content area of the page, you will define three distinct regions:

  • A region will render a radio button group that will allow app users to select the type of time period for which they want to show financial statistics.
  • A region that will render the charts
  • A region that will render the summary table.

Creating a jQuery Mobile radio button group

You will define the radio button group like so:

executive-dashboard-6

Later you will also define an event handler that will detect when a user changes the selected time period through these radio buttons. Upon this change, you will need to re-render the charts, as well as the data in the summary table.

Defining containers for the charts

The next content region will host the charts. For this region, you will use a jQuery Mobile grid:

executive-dashboard-7

The charts region is a div element, a 3×2 matrix where each cell is also a div element decorated with the charts-container css class. Inside each chart container div, there is a child div decorated with the chart css class. You will define these classes in the App.css file, which for now should look like this:

.charts-container
{
    margin:auto;
    padding:5px 0 20px 0;
}
.chart
{
    width: 80%;
    height: 120px;
    margin:auto;
}

Back in the index.html file, the divs decorated with the chart class will serve as the anchors for the charts. When we move on the JavaScript code, you will see how you can create these charts using the Flot Charts library.

Creating the summary table

Next you need to work on the summary table region. This is a simple Html table element, decorated with jQuery Mobile data attributes that will attach a column toggle to it:

executive-dashboard-8

The column toggle is a very cool feature because it allows users to have some control over the layout of the table depending on the device’s screen size. You can read more about it in the jQuery Mobile documentation.

You also need a few adjustments to the table’s style. In the App.css file, add the following items. They will fix the alignment and border of the table.

#stats-table thead th
{
    text-align:center;
}
#stats-table tbody td
{
    text-align:right;
}
.ui-body-c {
    border: 1px solid #aaa!important;
}

Defining the app’s JavaScript module

Once you have defined the markup for the three regions of the page’s content area, you can move on to the JavaScript portion of the app. For this small app, you can define it inside the index.html file, and you will start creating the application’s namespace like so:

var App = App || {};
App.Dashboard = function ($) {     };

Then, you need to declare quite a few variables. Most of them to hold the values that will render inside the summary table. Here they are:

 App.Dashboard = function ($) {

	 var RESIZE_END_DELAY_MSEC = 200;
	 var LOAD_DELAY_MSEC = 200;
	 var periodTypeMenu;
	 var stats = { "years": [2011, 2010, 2009, 2008], "records": [{ "FieldName": "BillableHoursWorked", "Description": "Billable Hours Worked", "MTD1": 99.07, "YTD1": 399.07, "MTD2": 18.28, "YTD2": 818.28, "MTD3": 28.72, "YTD3": 828.72, "MTD4": 74.17, "YTD4": 974.17, "YE2": 10549.05, "YE3": 10346.90, "YE4": 11284.15 }, { "FieldName": "BillableValueWorked", "Description": "Billable Value Worked", "MTD1": 1133.94, "YTD1": 201133.94, "MTD2": 8613.89, "YTD2": 398613.89, "MTD3": 1219.22, "YTD3": 381219.22, "MTD4": 8946.44, "YTD4": 428946.44, "YE2": 5100134.24, "YE3": 4819948.30, "YE4": 4983307.34}, { "FieldName": "BilledHours", "Description": "Billed Hours", "MTD1": 67.07, "YTD1": 267.07, "MTD2": 65.06, "YTD2": 765.06, "MTD3": 36.34, "YTD3": 736.34, "MTD4": 94.02, "YTD4": 894.02, "YE2": 10510.57, "YE3": 10390.38, "YE4": 11534.46 }, { "FieldName": "FeesBilled", "Description": "Fees Billed", "MTD1": 8073.29, "YTD1": 118073.29, "MTD2": 2086.13, "YTD2": 342086.13, "MTD3": 7708.35, "YTD3": 307708.35, "MTD4": 8281.43, "YTD4": 378281.43, "YE2": 4616589.81, "YE3": 4411602.66, "YE4": 4732848.45 }, { "FieldName": "FeeCollections", "Description": "Fee Collections", "MTD1": 1626.12, "YTD1": 117626.12, "MTD2": 2816.58, "YTD2": 172816.58, "MTD3": 4250.15, "YTD3": 164250.15, "MTD4": 3568.02, "YTD4": 183568.02, "YE2": 4431397.36, "YE3": 4207038.60, "YE4": 4558375.58 }, { "FieldName": "TotalWriteoffs", "Description": "Total Writeoffs", "MTD1": -774.36, "YTD1": -19774.36, "MTD2": -835.34, "YTD2": -46023.78, "MTD3": -874.09, "YTD3": -36867.06, "MTD4": -326.93, "YTD4": -35626.78, "YE2": -829427.23, "YE3": -704257.86, "YE4": -611219.85 }, { "FieldName": "Realizations", "Description": "Realization", "MTD1": 0.0000, "YTD1": 85.7100, "MTD2": 0.0000, "YTD2": 0.0000, "MTD3": 0.0000, "YTD3": 0.0000, "MTD4": 0.0000, "YTD4": 0.0000, "YE2": 84.0400, "YE3": 85.6300, "YE4": 88.0800 }] };
	 var tbl1Row1Hdr;
	 var tbl1Row1Y4;
	 var tbl1Row1Y3;
	 var tbl1Row1Y2;
	 var tbl1Row1Y1;

	 var tbl1Row2Hdr;
	 var tbl1Row2Y4;
	 var tbl1Row2Y3;
	 var tbl1Row2Y2;
	 var tbl1Row2Y1;

	 var tbl1Row3Hdr;
	 var tbl1Row3Y4;
	 var tbl1Row3Y3;
	 var tbl1Row3Y2;
	 var tbl1Row3Y1;

	 var tbl1Row4Hdr;
	 var tbl1Row4Y4;
	 var tbl1Row4Y3;
	 var tbl1Row4Y2;
	 var tbl1Row4Y1;

	 var tbl1Row5Hdr;
	 var tbl1Row5Y4;
	 var tbl1Row5Y3;
	 var tbl1Row5Y2;
	 var tbl1Row5Y1;

	 var tbl1Row6Hdr;
	 var tbl1Row6Y4;
	 var tbl1Row6Y3;
	 var tbl1Row6Y2;
	 var tbl1Row6Y1;

	 var tbl1Row7Hdr;
	 var tbl1Row7Y4;
	 var tbl1Row7Y3;
	 var tbl1Row7Y2;
	 var tbl1Row7Y1;

	 var optionsCurrency;
	 var optionsHours;
	 var data1;
	 var data2;
	 var data3;
	 var data4;
	 var data5;
	 var data6;

 };

Be mindful that for this example you are hard-coding the data needed by the app and storing it in the stats variable. One of the items that I will leave as homework for you is to implement an approach for loading the stats from a server.

Next, you will define an init method, which you will invoke to run the app:

var App = App || {};

App.Dashboard = function ($) {

// Declarations section omitted.

 var init = function () {

 };

 var public = {
	 init: init
 };

 return public;

};

How will you start the app? Well, as recommended in the jQuery Mobile documentation, you will use a page event to do so. For this app, you will hook into the pagebeforecreate event like so:

var App = App || {};

App.Dashboard = function ($) {

// Declarations section omitted.

 var init = function () {

 };

 var public = {
	 init: init
 };

 return public;

};

 $(document).delegate("#dashboard-page", "pagebeforecreate", function () {
	 var dashboard = new App.Dashboard(jQuery);
	 dashboard.init();
 });

Another good point to start the app is the pagecreate event. However, in this case it’s more convenient for us to invoke the init method just before the jQuery Mobile page has been created because we want to insert values in the summary table before jQuery Mobile enhances the page’s markup.

Inside init, you will first create the table’s headers:

var App = App || {};

App.Dashboard = function ($) {

// Declarations section omitted.

 var init = function () {

	setTableHeaders();
 };

 var public = {
	 init: init
 };

 return public;

};

 $(document).delegate("#dashboard-page", "pagebeforecreate", function () {
	 var dashboard = new App.Dashboard(jQuery);
	 dashboard.init();
 });

The setTableHeaders method will look like this:

var App = App || {};

App.Dashboard = function ($) {

// Declarations section omitted.

     var setTableHeaders = function () {

		 $("#tbl1-hdr-y4").html(stats.years[3]);
		 $("#tbl1-hdr-y3").html(stats.years[2]);
		 $("#tbl1-hdr-y2").html(stats.years[1]);
		 $("#tbl1-hdr-y1").html(stats.years[0]);

		 $("#tbl1-row1-hdr").html(stats.records[0].Description);
		 $("#tbl1-row2-hdr").html(stats.records[1].Description);
		 $("#tbl1-row3-hdr").html(stats.records[2].Description);
		 $("#tbl1-row4-hdr").html(stats.records[3].Description);
		 $("#tbl1-row5-hdr").html(stats.records[4].Description);
		 $("#tbl1-row6-hdr").html(stats.records[5].Description);
		 $("#tbl1-row7-hdr").html(stats.records[6].Description);
     };

 var init = function () {

	setTableHeaders();
 };

 var public = {
	 init: init
 };

 return public;

};

 $(document).delegate("#dashboard-page", "pagebeforecreate", function () {
	 var dashboard = new App.Dashboard(jQuery);
	 dashboard.init();
 });

Then, you will call the toggleSummaryTableHeaders method:

 var init = function () {

	 setTableHeaders();

	 toggleSummaryTableValues();
 };

The toggleSummaryTableValues method reads the selected Period Type and refreshes the values that will populate the summary table with the statistics for the period. You will define this method inside the App.Dashboard module:

 var toggleSummaryTableValues = function () {

	 var periodType = $("input[type='radio'][name='radio-period']:checked").val();

	 switch (periodType) {
		 case 'YTD':

			 tbl1Row1Y4 = stats.records[0].YTD4;
			 tbl1Row1Y3 = stats.records[0].YTD3;
			 tbl1Row1Y2 = stats.records[0].YTD2;
			 tbl1Row1Y1 = stats.records[0].YTD1;

			 tbl1Row2Y4 = stats.records[1].YTD4;
			 tbl1Row2Y3 = stats.records[1].YTD3;
			 tbl1Row2Y2 = stats.records[1].YTD2;
			 tbl1Row2Y1 = stats.records[1].YTD1;

			 tbl1Row3Y4 = stats.records[2].YTD4;
			 tbl1Row3Y3 = stats.records[2].YTD3;
			 tbl1Row3Y2 = stats.records[2].YTD2;
			 tbl1Row3Y1 = stats.records[2].YTD1;

			 tbl1Row4Y4 = stats.records[3].YTD4;
			 tbl1Row4Y3 = stats.records[3].YTD3;
			 tbl1Row4Y2 = stats.records[3].YTD2;
			 tbl1Row4Y1 = stats.records[3].YTD1;

			 tbl1Row5Y4 = stats.records[4].YTD4;
			 tbl1Row5Y3 = stats.records[4].YTD3;
			 tbl1Row5Y2 = stats.records[4].YTD2;
			 tbl1Row5Y1 = stats.records[4].YTD1;

			 tbl1Row6Y4 = stats.records[5].YTD4;
			 tbl1Row6Y3 = stats.records[5].YTD3;
			 tbl1Row6Y2 = stats.records[5].YTD2;
			 tbl1Row6Y1 = stats.records[5].YTD1;

			 tbl1Row7Y4 = stats.records[6].YE4;
			 tbl1Row7Y3 = stats.records[6].YE3;
			 tbl1Row7Y2 = stats.records[6].YE2;
			 tbl1Row7Y1 = stats.records[6].YTD1;
			 break;
		 case 'YE':
			 tbl1Row1Y4 = stats.records[0].YE4;
			 tbl1Row1Y3 = stats.records[0].YE3;
			 tbl1Row1Y2 = stats.records[0].YE2;
			 tbl1Row1Y1 = stats.records[0].YTD1;

			 tbl1Row2Y4 = stats.records[1].YE4;
			 tbl1Row2Y3 = stats.records[1].YE3;
			 tbl1Row2Y2 = stats.records[1].YE2;
			 tbl1Row2Y1 = stats.records[1].YTD1;

			 tbl1Row3Y4 = stats.records[2].YE4;
			 tbl1Row3Y3 = stats.records[2].YE3;
			 tbl1Row3Y2 = stats.records[2].YE2;
			 tbl1Row3Y1 = stats.records[2].YTD1;

			 tbl1Row4Y4 = stats.records[3].YE4;
			 tbl1Row4Y3 = stats.records[3].YE3;
			 tbl1Row4Y2 = stats.records[3].YE2;
			 tbl1Row4Y1 = stats.records[3].YTD1;

			 tbl1Row5Y4 = stats.records[4].YE4;
			 tbl1Row5Y3 = stats.records[4].YE3;
			 tbl1Row5Y2 = stats.records[4].YE2;
			 tbl1Row5Y1 = stats.records[4].YTD1;

			 tbl1Row6Y4 = stats.records[5].YE4;
			 tbl1Row6Y3 = stats.records[5].YE3;
			 tbl1Row6Y2 = stats.records[5].YE2;
			 tbl1Row6Y1 = stats.records[5].YTD1;

			 tbl1Row7Y4 = stats.records[6].YE4;
			 tbl1Row7Y3 = stats.records[6].YE3;
			 tbl1Row7Y2 = stats.records[6].YE2;
			 tbl1Row7Y1 = stats.records[6].YTD1;
			 break;
		 case 'MTD':
			 tbl1Row1Y4 = stats.records[0].MTD4;
			 tbl1Row1Y3 = stats.records[0].MTD3;
			 tbl1Row1Y2 = stats.records[0].MTD2;
			 tbl1Row1Y1 = stats.records[0].MTD1;

			 tbl1Row2Y4 = stats.records[1].MTD4;
			 tbl1Row2Y3 = stats.records[1].MTD3;
			 tbl1Row2Y2 = stats.records[1].MTD2;
			 tbl1Row2Y1 = stats.records[1].MTD1;

			 tbl1Row3Y4 = stats.records[2].MTD4;
			 tbl1Row3Y3 = stats.records[2].MTD3;
			 tbl1Row3Y2 = stats.records[2].MTD2;
			 tbl1Row3Y1 = stats.records[2].MTD1;

			 tbl1Row4Y4 = stats.records[3].MTD4;
			 tbl1Row4Y3 = stats.records[3].MTD3;
			 tbl1Row4Y2 = stats.records[3].MTD2;
			 tbl1Row4Y1 = stats.records[3].MTD1;

			 tbl1Row5Y4 = stats.records[4].MTD4;
			 tbl1Row5Y3 = stats.records[4].MTD3;
			 tbl1Row5Y2 = stats.records[4].MTD2;
			 tbl1Row5Y1 = stats.records[4].MTD1;

			 tbl1Row6Y4 = stats.records[5].MTD4;
			 tbl1Row6Y3 = stats.records[5].MTD3;
			 tbl1Row6Y2 = stats.records[5].MTD2;
			 tbl1Row6Y1 = stats.records[5].MTD1;

			 tbl1Row7Y4 = stats.records[6].YE4;
			 tbl1Row7Y3 = stats.records[6].YE3;
			 tbl1Row7Y2 = stats.records[6].YE2;
			 tbl1Row7Y1 = stats.records[6].YTD1;
			 break;
	 }
 };

Next, you will use the setTimeout function to invoke the yet to be created refreshAllViews method like so:

 var init = function () {

	setTableHeaders();

	toggleSummaryTableValues();

    setTimeout(function () {
	       refreshAllViews();
	}, LOAD_DELAY_MSEC);
 };

You might ask, why invoke refreshAllviews through setTimeout here? You are introducing this delay, which is configurable through the LOAD_DELAY_MSEC constant, because before creating the charts, you want to allow time for the app’s chart containing elements to be properly laid out on the page. If you try invoking refreshAllViews immediately, you will see that the charts render without taking all the available width of their containing divs.

The refreshAllViews method is a template method that invokes the refreshChartViews and refreshSummaryView methods:

 var refreshAllViews = function () {

	 refreshChartsViews();

	 refreshSummaryView();
 };

Creating the charts

RefreshChartViews is the method where you generate the line charts. You will define it like so:

 var refreshChartsViews = function () {

	 data1 = [{ label: stats.records[0].Description, data: [[2008, tbl1Row1Y4], [2009, tbl1Row1Y3], [2010, tbl1Row1Y2], [2011, tbl1Row1Y1]] }];
	 data2 = [{ label: stats.records[1].Description, data: [[2008, tbl1Row2Y4], [2009, tbl1Row2Y3], [2010, tbl1Row2Y2], [2011, tbl1Row2Y1]] }];
	 data3 = [{ label: stats.records[2].Description, data: [[2008, tbl1Row3Y4], [2009, tbl1Row3Y3], [2010, tbl1Row3Y2], [2011, tbl1Row3Y1]] }];
	 data4 = [{ label: stats.records[3].Description, data: [[2008, tbl1Row4Y4], [2009, tbl1Row4Y3], [2010, tbl1Row4Y2], [2011, tbl1Row4Y1]] }];
	 data5 = [{ label: stats.records[4].Description, data: [[2008, tbl1Row5Y4], [2009, tbl1Row5Y3], [2010, tbl1Row5Y2], [2011, tbl1Row5Y1]] }];
	 data6 = [{ label: stats.records[5].Description, data: [[2008, tbl1Row6Y4], [2009, tbl1Row6Y3], [2010, tbl1Row6Y2], [2011, tbl1Row6Y1]] }];

	 var series = {
		 lines: { show: true },
		 points: { show: true },

	 };
	 var xaxis = {
		 ticks: [0, [2008, "2008"], [2009, "2009"], [2010, "2010"], [2011, "2011"]],
		 min: 2007.8,
		 max: 2011.2
	 };
	 var grid = {
		 backgroundColor: '#f5f5f5'
	 };
	 var legend = {
		 backgroundOpacity: 0,
		 position: 'sw'
	 };

	 optionsHours = {
		 series: series,
		 grid: grid,
		 legend: legend,
		 xaxis: xaxis,
		 yaxis: {
			 tickFormatter: function (val, axis) {
				 return formatHours(val);
			 }
		 }
	 };

	 optionsCurrency = {
		 series: series,
		 grid: grid,
		 legend: legend,
		 xaxis: xaxis,
		 yaxis: {
			 ticks: 3,
			 tickFormatter: function (val, axis) {
				 return formatCurrency(val);
			 }
		 }
	 };

	 $.plot($("#chart1"), data1, optionsHours);
	 $.plot($("#chart2"), data2, optionsCurrency);
	 $.plot($("#chart3"), data3, optionsHours);

	 $.plot($("#chart4"), data4, optionsCurrency);
	 $.plot($("#chart5"), data5, optionsCurrency);
	 $.plot($("#chart6"), data6, optionsCurrency);
 };

In refreshChartViews, you first define the arrays that will contain each chart’s data. Next, you need to create the configuration options for the two types of charts you will use. One for the charts that will display time data, and one for the charts that will display currency data.

The optionsHours and optionsCurrency objects use the formatHours and formatCurrency methods respectively. These helper methods format the supplied value as hours or currency. You can define them right before the setTableHeaders method, like so:

 var formatHours = function (num) {
	 num = num.toString().replace(/\$|\,/g, '');
	 if (isNaN(num))
		 num = "0";

	 num = Math.floor(num * 100 + 0.50000000001);
	 cents = num % 100;
	 num = Math.floor(num / 100).toString();
	 if (cents < 10)
		 cents = "0" + cents;
	 for (var i = 0; i < Math.floor((num.length - (1 + i)) / 3) ; i++)
		 num = num.substring(0, num.length - (4 * i + 3)) + ',' +
		 num.substring(num.length - (4 * i + 3));

	 var out = num + '.' + cents;

	 return out;
 };

 var formatPercent = function (num) {
	 num = num.toString().replace(/\$|\,/g, '');
	 if (isNaN(num))
		 num = "0";

	 return num + '%';
 };

Back inside refreshChartViews, your last step will be to invoke Flot’s plot method in order to render the charts to the page:

 $.plot($("#chart1"), data1, optionsHours);
 $.plot($("#chart2"), data2, optionsCurrency);
 $.plot($("#chart3"), data3, optionsHours);

 $.plot($("#chart4"), data4, optionsCurrency);
 $.plot($("#chart5"), data5, optionsCurrency);
 $.plot($("#chart6"), data6, optionsCurrency);

At this point you can test the app by browsing to the index.html with your favorite WebKit-based browser. You should be able to see the charts you just created:

executive-dashboard-2

Populating the summary table

You will populate the summary table inside the refreshSummaryView method. Essentially, you need to place the correct value in each table cell. This is how to do it:

 var refreshSummaryView = function () {

	 $("#tbl1-row1-y4").html(formatHours(tbl1Row1Y4));
	 $("#tbl1-row1-y3").html(formatHours(tbl1Row1Y3));
	 $("#tbl1-row1-y2").html(formatHours(tbl1Row1Y2));
	 $("#tbl1-row1-y1").html(formatHours(tbl1Row1Y1));

	 $("#tbl1-row2-y4").html(formatCurrency(tbl1Row2Y4));
	 $("#tbl1-row2-y3").html(formatCurrency(tbl1Row2Y3));
	 $("#tbl1-row2-y2").html(formatCurrency(tbl1Row2Y2));
	 $("#tbl1-row2-y1").html(formatCurrency(tbl1Row2Y1));

	 $("#tbl1-row3-y4").html(formatHours(tbl1Row3Y4));
	 $("#tbl1-row3-y3").html(formatHours(tbl1Row3Y3));
	 $("#tbl1-row3-y2").html(formatHours(tbl1Row3Y2));
	 $("#tbl1-row3-y1").html(formatHours(tbl1Row3Y1));

	 $("#tbl1-row4-y4").html(formatCurrency(tbl1Row4Y4));
	 $("#tbl1-row4-y3").html(formatCurrency(tbl1Row4Y3));
	 $("#tbl1-row4-y2").html(formatCurrency(tbl1Row4Y2));
	 $("#tbl1-row4-y1").html(formatCurrency(tbl1Row4Y1));

	 $("#tbl1-row5-y4").html(formatCurrency(tbl1Row5Y4));
	 $("#tbl1-row5-y3").html(formatCurrency(tbl1Row5Y3));
	 $("#tbl1-row5-y2").html(formatCurrency(tbl1Row5Y2));
	 $("#tbl1-row5-y1").html(formatCurrency(tbl1Row5Y1));

	 $("#tbl1-row6-y4").html(formatCurrency(tbl1Row6Y4));
	 $("#tbl1-row6-y3").html(formatCurrency(tbl1Row6Y3));
	 $("#tbl1-row6-y2").html(formatCurrency(tbl1Row6Y2));
	 $("#tbl1-row6-y1").html(formatCurrency(tbl1Row6Y1));

	 $("#tbl1-row7-y4").html(formatPercent(tbl1Row7Y4));
	 $("#tbl1-row7-y3").html(formatPercent(tbl1Row7Y3));
	 $("#tbl1-row7-y2").html(formatPercent(tbl1Row7Y2));
	 $("#tbl1-row7-y1").html(formatPercent(tbl1Row7Y1));
 };

At this point you can test the app by browsing to the index.html with your favorite WebKit-based browser. You should now see the summary table:

executive-dashboard-3

Handling period type changes

Having the app render the charts and summary table upon launch is an important step. However, you also need to refresh the page when the user selects a different period though the radio buttons. You can accomplish this by attaching a handler for the change event on the buttons. Right at the top of the App.Dashboard’s init method, you will add the handler like so:

 periodTypeMenu = $("input[type='radio'][name='radio-period']");

 periodTypeMenu.change(function () {
	 toggleSummaryTableValues();
	 refreshAllViews();
 });

As you would expect, the event handler toggles the headers of the summary table to match the period selection and then refreshes the charts and summary table data. You should be able to confirm this in the browser by refreshing the page and tapping on the radio buttons.

Handling page orientation changes

Two more steps before you are done. One is that you need to make sure to lay out the different elements on the page correctly if the user changes the tablet’s orientation. While you do not need to worry about the table (it will be re-rendered automatically be the browser), you will need to refresh the charts so they adjust to the new screen size when the device orientation’s changes. To accomplish this you can hook into the orientationchange event of the document. Right after the change handler you just created for the radio buttons in the init method, add the following event handler:

$(document).bind('orientationchange', function () {
    refreshChartsViews();
});

In the handler you simply call the refreshChartViews method, which will take care of re-rendering the charts.

The last step is making sure to refresh the charts if the size of the viewport changes for whatever reason. This is very important if you have users accessing the app from desktop or laptop computers, where users frequently change the size of the browser’s window. To accomplish this, you need to hook into the resize event of the window object:

 $(window).resize(function () {
	 if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
	 this.resizeTimeout = setTimeout(function () {
		 $(this).trigger('resizeend');
	 }, RESIZE_END_DELAY_MSEC);
 });

 $(window).bind('resizeend', function () {
	 refreshChartsViews();
 });

This code might look more complicated than you expected – the resize handler is creating a timeout, and the timeout’s handler fires a new custom resizeend event; then, a handler for the resizeend event is declared, and in it, a refresh to the charts.

Why this approach? The reason is that the resize event fires multiple times while the user resizes the browser window. In fact, you do not know how many times this event will fire – and it is overkill to refresh the charts every time the event fires. In order to invoke the refreshChartViews method only once per resize, you created a custom application event, resizeend, along with a mechanism to trigger it a few milliseconds after the user has finished resizing the window.

We made it! This is all it takes to create this very simple jQuery Mobile app.

Homework for you

Here’s homework for you. I want you to go ahead and think about what is required to obtain the app’s data from a server endpoint instead of hard-coding it. Device a solution and implement it in the app. Also, add a “Refresh” button to the page’s toolbar so the user can refresh the dashboard data by triggering a request to the server endpoint. Sounds interesting?

Want to learn more?

If you want to learn how to create a jQuery Mobile application, not just a website, check out my jQuery Mobile book.

The source code

Download this tutorial’s source code: MiamiCoder-jQM-Tut-Dashboard.zip

Stay Tuned

Don’t miss the next article. Get free updates in your inbox.



MiamiCoder will never sell your email address.

Comments

  1. says

    This is a brilliant tutorial, though a little sticky to follow in some places. Certainly quite entertaining to fiddle with the finished product on my tablet.

  2. Terry says

    Hi Jorge,
    really good tutorial and a very interesting upcoming area “dash boarding”, would you mind sending me a zip of the code since I can’t seem to track it down on the website?

    Thanks,
    Terry

  3. Myriam says

    Excellent tutorial.

    Would anyone know tell me can I get data from a MySQL database that is on the internet?

    and be able to create these graphs.

    Thanks in advance.

Leave a Reply

Your email address will not be published. Required fields are marked *