Introduction
A couple of months back I wrote an article titled TaskTracker
Offline Web Application for Mobile using HTML5, jQuery-UI and KNOCKOUT.js. In that the focus of the article
was offline programming using HTML5 and exploring the features of different powerful frameworks such as
jQuery, jQuery-UI, and Knockout. This article is an extension of it and covers
some additional features such as HTML5 Canvas, integration with back-end database using jQuery AJAX, and AJAX enabled WCF Services.
The key features explored in this article are given below:
- Application Cache, Webstorage, and Canvas features of HTML5 are used in this application
- jQuery AJAX makes asynchronous programming
easier and enables you to interact with Web Services from JavaScript.
- AJAX enabled WCF Services and Entity Framework
Background
SPC is a very important part of quality control in any manufacturing process. Currently the data capture process for doing SPC analysis is mostly manual. This is mainly due to
the harsh environment in
the shop floor and it is difficult to use conventional computers or laptops there.
With the advancement in smart phone technology it is now possible to write an application for this purpose to automate
the data capture process and do quick SPC analysis in real time. Also, with key features such as offline programming of HTML5, you can now save data locally in the absence of internet and then save it to
the backend
database once connected to the internet, for future analysis.
Hence Real Time SPC (Statistical Process Control) applications for iPad/Android
tablets will be very useful to engineering, manufacturing, and process industries. This will enable quality inspectors and operators of
this industry to capture data in real time from the shop floor and do quick SPC analysis and check control charts in
real time to ensure that the manufacturing process is in control.
You can check the working version of the application here.
Design Overview
The application is built using the popular MVVM design pattern. MVVM is a design pattern very similar to
the old MVC design pattern and addresses the separation of concerns.
Like MVC it has a model, view, but the controller role is done by the ViewModel.
The View Model is responsible for handling user interactions and also interacts with
the model
and updates the view. It can have additional attributes apart from the Model, which can be view specific. Also, in a complex web application comprising of multiple views, each view
can have a corresponding view model. In our application there is only one view and one view model. The key files of the application are
newmain.html, SPCNewController.js, StorageController.js, controlCharts.js,
SPCAjaxHelper.js, and SPCService.svc.
The class diagram of the application is shown in Figure 1.
From figure 1, the RealTimeSPCUI
class is a conceptual class and represents the UI for the application and is implemented as plain
HTML. SPCViewModel
is a key class
responsible for handling all the user interactions and updating the view and interacting with
the model. Measurement
is an entity class which acts as
a model for this application. StorageController
is a wrapper class and is responsible for saving and retrieving data from WebStorage.
XBarRangeChart
is the class responsible for the control charts. SPCAjaxHelper
is a
helper class for handling the jQuery AJAX interaction with the WCF Service class SPCService
.
JavaScript is not like conventional object oriented programming languages such as C++, C#, or Java and it does not have
the Class
construct, so all the classes are implemented differently.
I will first explain the business layer implementation, then the data layer, and finally
the UI.
Business Layer
The business layer consists of a key entity Measurement
and represents
the Model. Another important class is SPCViewModel
. The section
below shows the implementation details of the Measurment
class.
function Measurement() {
var self = this;
self.parameter= ko.observable("");
self.id = 1,
self.TimeStamp = ko.observable($.datepicker.formatDate('yy-mm-dd',new Date())),
self.PartNo = ko.observable(""),
self.Machine= ko.observable(""),
self.Operator=ko.observable(""),
self.Parameter= ko.observable("Inner Diameter");
self.P1=ko.observable(0),
self.P2=ko.observable(0),
self.P3=ko.observable(0),
self.P4=ko.observable(0),
self.P5=ko.observable(0),
self.PAve = ko.dependentObservable(function () {
var aveValue=(eval(self.P1())+eval(self.P2())+
eval(self.P3())+eval(self.P4())+eval(self.P5()))/5;
return Math.round(aveValue*1000)/1000;
});
self.PRange = ko.dependentObservable(function () {
var m= Math.max(self.P1(),self.P2(),self.P3(),self.P4(),self.P5());
var n= Math.min(self.P1(),self.P2(),self.P3(),self.P4(),self.P5());
return Math.round((m-n)*1000)/1000;
});
}
var curMeasurement = new Measurement();
var machine= curMeasurement.machine();
curMeasurement.machine('M1');
If you look at the above code you might have noticed that the class contains only properties as it is an entity class
and does not expose any methods. But also, you might notice that these properties are
initialized with Knockout constructs. These properties
are declared as observables. This will enable these properties to bind to
the UI and will provide two way binding. So any change
done to these properties in the UI will automatically update the properties. Or if you change their values programmatically, then those changes will
be reflected in the UI. One thing to remember is that the Knockout observables are functions and you can't access or
initialize them as normal properties. Also notice the
declaration for dependent observable properties. These properties are derived from other properties.
See the above code showing an example of setting and accessing a Knockout observable property.
Let us now see the implementation details of SPCViewModel
. This is the key class responsible for handling user interaction and will update the UI and model.
function SPCViewModel() {
var self = this;
self.onLine = ko.observable(false);
self.filterDate = ko.observable("");
self.filterPoints = ko.dependentObservable(function () {
var partno = self.lastPart();
var parameter = self.lastParameter();
return ko.utils.arrayFilter(self.dataPoints(), function (item) {
var cp, cparam;
if ($.isFunction(item.PartNo))
cp = item.PartNo();
else
return false;
if ($.isFunction(item.Parameter))
cparam = item.Parameter();
else
return false;
if ((partno === cp) && (parameter === cparam))
return true;
else
return false;
});
});
self.removeMeasurement = function (iDP) {
self.dataPoints.remove(iDP);
var mDP = ko.toJSON(self.dataPoints);
self.normalDP = JSON.parse(mDP);
tsk_ns.storage.saveItem("measurments", mDP);
self.updateDPArray();
};
self.editMeasurement = function (iDP) {
self.selected(iDP);
self.currentDP(iDP);
self.taskMenu("Edit Data");
$("#accordion").accordion("activate", 0);
self.editFlag = true;
};
}
SPCViewModel
is a core class of this application and is responsible for handling user interactions
and interacts with the model and updates the view. The above code is not the complete source code, but just
a highlight of the key methods.
The view model has two observable arrays defined for storing all the measurment points. Also note the use of dependent observables
to filter the measurment points by filtering criteria. The key methods are responsible for
the following tasks:
- Retrieve and save measurement data to local webstorage.
- Retrieve and save measurement data to back-end database
- Analyse data and display data in tabular fashion
- Update control charts
Also note that the status of internet connection is updated in real time, and the buttons used to save the data to the database and retrieve history
are
enabled only when the user is connected to the internet. The abbove section shows only some part of the code and I request you to refer
to the source code for the full implementation details.
Data Layer
Data layer is implemented in two stages: storing the data to local Webstorage and storing it to the back-end database.
Local storage is handled by the StorageManager
class. Details of its implementation are given below:
var tsk_ns = tsk_ns || {};
tsk_ns.storage = function () {
var local = window.localStorage,
saveItem = function (key, item) {
local.setItem(key, item);
},
getItem = function (key) {
return local.getItem(key);
},
hasLocalStorage = function () {
return ('localStorage' in window && window['localStorage'] != null);
};
return {
saveItem: saveItem,
getItem: getItem,
hasLocalStorage: hasLocalStorage
};
} ();
The above class is just a wrapper to window.localStorage
and provides
a wrapper method to save and get data from
the local storage. The class has a hasLocalStorage
method which checks whether
the browser supports localStorage.
Saving and retrieval of data to the backend database is implemented using jQuery AJAX and
an AJAX enabled WCF
service. This is implemented using two classes: SPJAjaxHelper.js is a helper class responsible for making all
AJAX calls to invoke
the WCF service, SPCService
is a class implemented as a WCF service.
The implementation of these classes is given below:
function CheckIfOnLine(successMethod, failMethod) {
var sUrl = "SPCService.svc/CheckIfConnected";
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: sUrl,
dataType: "json",
processdata: false,
success: successMethod,
error: failMethod
});
}
function GetMeasurementData(msFilter,successFunction) {
var sUrl = "SPCService.svc/GetMeasurmentDataArray";
var ms = { measFilter:msFilter };
var sparam = JSON.stringify(ms);
InvokeWebService(sUrl, sparam, successFunction);
}
function UpdateMeasurment(msArray,successMethod)
{
var sUrl="SPCService.svc/UpdateMeasurmentArray";
var ps = { parray: msArray };
var sparam = JSON.stringify(ps);
InvokeWebService(sUrl,sparam,successMethod);
};
function InvokeWebService(sUrl,sparam,successMethod) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: sUrl,
data: sparam,
dataType: "json",
processdata: false,
success:successMethod,
error: function (result) {
alert('Service call failed: ' + result.status + ' ' + result.textStatus);
}
});
};
</reference>
The SPCAJAXHelper.js file has three key functions: checking online status, retrieving
measurement data based on filter criteria, and saving the data to the database.
All these methods call the InvokeWebService
method which in turn calls
the ajax
method of jQuery. Each method passes the appropriate
parameters and success callback methods. In case of any error the error message is displayed to the user.
Let us now see the implementation of SPCService
. The SPCService
class exposes
the following methods:
CheckIfOnline
GetMeasurmentDataArray
UpdateMeasurmentArray
The below code shows the details of each of these methods. Please note the attribute defined for the WCF Service class. See the ServiceContract
attribute
for the class. Each exposed method is declared with an attribute OperationContract
and
any parameters passed to the method such as entity classes have the attribute
DataContract
and each of the properties of the entity class is decorated with the attribute DataMember
.
Also, the AspNetCompatibilityRequirements
attribute to the class is also required.
Message exchange between the WCF service and AJAX
calls made from JavaScript use the JSON format. Please refer to the WebInvoke
attribute in
the below code where the message format is defined.
An important point to remember is parameter names passed to invoke the methods should match
the exact signature of the WCF method. I have created similar entity
classes in JavaScript so that the same type of parameters can be passed. In our case, there are two entities: MeasurmentSummary
and MeasurmentFilter
.
I won't explain the details of each method as the code is self explanatory.
[ServiceContract(Namespace = "SPCAnalysisWeb")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SPCService
{
[OperationContract]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
public string CheckIfConnected()
{
return "SUCCESS";
}
[OperationContract]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
public MeasurementSummary [] GetMeasurmentDataArray(MeasuremntFilter measFilter)
{
return null;
}
[OperationContract]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
public string UpdateMeasurmentArray(MeasurementSummary[] parray)
{
}
}
[DataContract(Namespace = "SPCAnalysisWeb", Name = "MeasurementFilter")]
public class MeasuremntFilter
{
[DataMember(IsRequired = true, Name = "PartNo")]
public string PartNo { get; set; }
[DataMember(IsRequired = true, Name = "Parameter")]
public string Parameter { get; set; }
}
[DataContract(Namespace = "SPCAnalysisWeb", Name = "MeasurementSummary")]
public class MeasurementSummary
{
[DataMember(IsRequired = true, Name = "PartNo")]
public string PartNo { get; set; }
[DataMember(IsRequired = true, Name = "Machine")]
public string Machine { get; set; }
[DataMember(IsRequired = true, Name = "Operator")]
[DataMember(IsRequired = true, Name = "PAve")]
public double PAve { get; set; }
[DataMember(IsRequired = true, Name = "PRange")]
public double PRange { get; set; }
}
Web.config has configuration details for the WCF Service. If you are using
the Visual studio 2010 IDE then it has a template for AJAX enabled WCF Services and it creates all the boilerplate code and configuration for you. Details of Web.Config are given below:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="SPCAnalysisWeb.SPCServiceAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<services>
<service behaviorConfiguration="ServiceBehavior" name="SPCAnalysisWeb.SPCService">
<endpoint address="" behaviorConfiguration="SPCAnalysisWeb.SPCServiceAspNetAjaxBehavior"
binding="webHttpBinding" contract="SPCAnalysisWeb.SPCService" />
</service>
</services>
</system.serviceModel>
All the configuration for the WCF service is under the system.servicemodel
section.
The key point to note is the
binding
attribute whose value is set to webHttpBinding
. Those who want to know more about
the WCF
service's working
and configuration of WCF services, please refer to the
MSDN documentation.
I have used Entity framework to interact with the database. But you can also use LINQ
to SQL.
UI Layer
The UI layer is implemented as a plain HTML file. I have used the Accordion
widget of
jQuery-UI as well as Tabbed Layout.
Once all the document is loaded, it executes the below initialization code. The code defines
the validation rules, jQuery-UI initialization code, and the binding of the view model to
the UI.
$(function () {
$('#tabs').tabs();
$('#measForm').validate({
rules: {
partno: "required",
machine: "required",
operator:"required",
dp1: {
required:true,
number: true
},
dp2: {
required:true,
number: true
},
dp3: {
required:true,
number: true,
},
dp4: {
required:true,
number: true
},
dp5: {
required:true,
number: true
}
}
});
$("#hisForm").validate({
rules: {
hispart: {
required:true
},
filterdatefrom: {
required:true,
date:true
},
filterdateto: {
required:true,
date:true
}
}
});
$("#accordion").accordion({active:1});
$("#taskNew,#taskSave,#taskUpdate,#btnRefresh,#btnRetrieve,#btnTest").button();
$("#taskNew").button("option", "disabled", false);
$("#taskUpdate").button("option", "disabled", true);
var viewModel = new SPCViewModel();
viewModel.init();
ko.applyBindings(viewModel);
$("#dlgSave").dialog({
autoOpen: false,
modal: true,
buttons: {
Ok: function () {
$(this).dialog("close");
viewModel.saveToDatabase();
}
}
});
var id= setInterval(viewModel.updateOnLineStatus,10000);
});
The above code does initialization of the GUI widgets and the field validation for
the Measurement
form. Also, the JavaScript timer is setup
to update online status. Finally, the view model is initialized and the binding is applied to the view.
One of the key parts of the UI is Control Charts. I have implemented it using HTML Canvas. The key class implementing the charting functionality is XBarRangeChart
. Please refer to the source code for details of the implementation.
I have used different types of bindings such as for-each
, with
, form
binding features of
KnockoutJS.
Please refer to the source code for complete details.
Configuring for Offline Working
With HTML5, we can now configure the pages, assets, and resources of the application which should be served offline. This
is possible by defining the Application Manifest file, which is a plain text file. Also, in
the html
tag, you need
to define the manifest
attribute to specify the name of the manifest file. <html manifest="manfiest.appcache">
I have added
a manifest file with the extension of .appcache.
But you can use any extension. But we need to configure the webserver to include this new extension type as well as specify its
MIME type as text/cache-manifest
. Please refer to the code below to
see the content of the manifest file.
Please remember that the fist line in the manifest file should be CACHE MANIFEST
. Don't put any comments before this line.
The structure of the manifest file is simple. It has three sections: CACHE
, NETWORK
, and FALLBACK
.
All the pages and resources used off-line need to be specified under the CACHE:
section. Pages/resources
required only online are under the NETWORK
section. In the FALLBACK
section you can specify alternate pages
in case of fallback. That's it!
The browser supporting the application cache will initially save all the offline resources on
the client side and then it will always serve these resources from the application cache
irrespective of whether you are online or offline. So if any changes are done to these pages,
they will reflect only when the manifest file is modified. This is an important point to remember.
Modify the manifest file and update the version to indicate to the browser that there
are some changes so that it will bring the latest changes from
the server and save it again on the client side.
CACHE MANIFEST
## version 2.3
CACHE:
themes/ui-darkness/jquery-ui-1.8.21.custom.css
themes/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png
themes/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png
themes/ui-darkness/images/ui-bg_glass_20_555555_1x400.png
themes/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png
themes/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png
themes/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png
themes/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png
themes/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png
themes/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png
themes/ui-darkness/images/ui-icons_222222_256x240.png
themes/ui-darkness/images/ui-icons_4b8e0b_256x240.png
themes/ui-darkness/images/ui-icons_a83300_256x240.png
themes/ui-darkness/images/ui-icons_cccccc_256x240.png
themes/ui-darkness/images/ui-icons_ffffff_256x240.png
Styles/spcmain.css
Scripts/jquery-1.7.1.min.js
Scripts/json2.js
Scripts/jquery-ui-1.8.17.custom.min.js
Scripts/jquery-ui-timepicker-addon.js
Scripts/knockout-latest.js
Scripts/ko-protected-observable.js
Scripts/StorageManager.js
Scripts/SPCNewController.js
Scripts/jquery.validate.min.js
Scripts/additional-methods.js
Scripts/ControlCharts.js
Scripts/jquery.dateFormat-1.0.js
Scripts/SPCAjaxHelper.js
Images/app-note-icon.png
Images/Actions-document-edit-icon.png
Images/edit-trash-icon.png
newmain.htm
NETWORK:
SPCService.svc
SPCService.svc.cs
Styling for Mobile
I don't have to do a major change to configure this application for mobile.
The only thing you need to specify is the viewport
setting in the head
section of your HTML file. I had to define an extra style for
the textarea
tag as its width
was overflowing outside its parent container. Please refer to the code below for details.
.fixwidth
{
width:80%;
}
Points of Interest
HTML Canvas
is not yet supported by all browsers. So this code may not work on all browsers. Luckily, all the
Smartphones and tablets have browser
support for it and I have tested it on iPad, Android, and Blackberry tablets. When you download the source code you need to provide the database connection string
for information such as
server name, userID, and password.
Android Phone/iPhone/iPad Users
A working version of the application is available
here. You can bookmark this page. It will work both in off-line and online modes.
History
I was thinking of extending my
TaskTracker application to provide support for saving and retrieving data to the back-end database. But I thought it will be better to do
a completely new application and also I wanted to explore the features of HTML Canvas, and hence I developed this application which is also useful to
the manufacturing and process industry.
Conclusion
Due to the new features in HTML5 it is now easy to develop off-line web applications. The application explores
the key features of HTML5, jQuery, jQuery-UI, jQuery-Validation,
and KnockoutJs. You can also see how the power of jQuery AJAX is used to retrieve and save data to
the back-end database using an AJAX Enabled WCF Service. Also we can use
an Open Source framework such as PhoneGap to convert it to a native application for different mobile platforms.
Please let me know your comments and suggestions. You can e-mail me for any queries or clarifications about
the implementation. Thank you.
I am Solution Architect with 20+ years of IT experience in the field of real time,embedded,client/server and web based applications and Business Intelligence . I am currently working as Senior Consultant for Infor.