HTML5 mobile application: error or success. Attempt number 0
For several years, reading news and events in the world of Web development, I had a pink dream: I wrote once - it works everywhere and always. At the same time, I often see negative reviews about the development of mobile applications in HTML5 ( here and comments on articles 1 and 2 ). The main arguments of the strikers: inconsistency with the native interface, glitchiness and slowdown, problems with data storage, etc., etc. By no means do I want to launch another holi vara on this topic. But the dream lives on and it can be confirmed or rejected only after one’s own attack on the rake.
So, the goal is to write a mobile application for collecting orders with a sales agent in retail outlets in HTML5. I came across these decisions of different companies, therefore I am familiar with the subject area, and this topic is ideal for a dream.
To the main requirements, I will add a few notes from my own experience:
Small note: The article was written in order to consolidate the material covered for the study of new technology. Due to the complete lack of real experience in creating applications of this kind, I apologize in advance for possible flaws.
Backend - .net MVC with OData. Globally, it doesn’t matter what I will use in this role, the main thing is to comply with the new WEB API standards. Frontend - everything is complicated for me. In the absence of experience, choosing something is very difficult. After some browsing, I settled on PhoneJS . I was impressed by the fact that this is a full-fledged framework for a SPA application, so there is no need to link as many libraries as possible, as well as using knockoutjs. I decided to use breeze to work with data. I am sure that the list will change during the development process. Then pack it all with PhoneGap and get a semblance of application.
In this article, we’ll build something simple to start with: viewing the data of a point of sale on a specific route of a sales agent.
Create a new ASP.NET MVC 4 Web Application project and call it “ MSales ”. In the New ASP.NET MVC 4 Project dialog , select the Web API template .
Update Packages:
Unfortunately, there is no package for PhoneJS, so with pens we add all the necessary css and js to the project. There are several layout types to choose from, I used NavbarLayout by changing the _Layout.cshtml file :
In the BundleConfig file, we prescribe all the content and scripts. I got it like this:
At the moment, we will include two files in the model: classes for routes (a sales agent goes along these routes) and outlets (stores):
The controllers will be very simple (you can read more about OData here ):
When registering a route for the OData protocol, you must specify the line
In the Scripts / app folder, create the script file app.init.js to initialize the libraries:
We create an HTML application in which we specify the layout and navigation parameters, which consists of two points: routes and about; and initialize the breeze library.
In the Index.cshtml file, you need to place the dxView and a special area called “content”, which displays a regular list:
To these few lines have earned you must create a ViewModel, so the folder Scripts / app will create a file app.viewmodel.js :
I want to note that the name Viewmodel matches the name dxView, and contains only the dataSource object, in which we define one load method to load data. The refresh parameter determines whether the widget data should be updated completely. In the method, we construct the request by sorting by the RouteID field and execute it.
Add another View - About :
Result for IPhone:

You probably noticed that an event was hung on the list item
Due to the fact that there may be many buyers, I added the ability to search: added dxCommand - a search button that calls the find function , and an input field in front of the list.
Viewmodel:
The skip and PAGE_SIZE variables are needed to load part of the data (in this case 10 records), and additional loading will take place as needed.
Variables searchString and showSearch for search, at which the search is triggered with a half second delay after entering a character.
Result:

Well, finally, we display information about the selected buyer:
View:
ViewModel:

Note: screenshots are taken from the Ripple Emulator (Beta) emulator.
We got a fairly simple full-fledged SPA application for mobile devices with navigation and data loading. At the moment, it is difficult to judge the quality / speed / etc. applications, so in the next article I will expand the functionality a bit and lay it out on Azure so that everyone can try.
So, the goal is to write a mobile application for collecting orders with a sales agent in retail outlets in HTML5. I came across these decisions of different companies, therefore I am familiar with the subject area, and this topic is ideal for a dream.
To the main requirements, I will add a few notes from my own experience:
- The program should work on many devices and on different platforms. Typically, companies, especially large ones, already have a fleet of mobile devices. Some distribution companies even force them to use their own phones (voluntary forced BYOD, so to speak ).
- Support offline work. Unfortunately, internet coverage is poor. Native solutions do a good job of this problem.
- The program should be easy to expand. For some reason, providers of such solutions have a problem of normal version updates.
- Use of iron (camera, GPS).
Small note: The article was written in order to consolidate the material covered for the study of new technology. Due to the complete lack of real experience in creating applications of this kind, I apologize in advance for possible flaws.
Advanced architecture:
Backend - .net MVC with OData. Globally, it doesn’t matter what I will use in this role, the main thing is to comply with the new WEB API standards. Frontend - everything is complicated for me. In the absence of experience, choosing something is very difficult. After some browsing, I settled on PhoneJS . I was impressed by the fact that this is a full-fledged framework for a SPA application, so there is no need to link as many libraries as possible, as well as using knockoutjs. I decided to use breeze to work with data. I am sure that the list will change during the development process. Then pack it all with PhoneGap and get a semblance of application.
In this article, we’ll build something simple to start with: viewing the data of a point of sale on a specific route of a sales agent.
Create a project.
Create a new ASP.NET MVC 4 Web Application project and call it “ MSales ”. In the New ASP.NET MVC 4 Project dialog , select the Web API template .
Update Packages:
Update-Package knockoutjs и Update-Package jQuery
and establish: Install-Package Breeze.WebApi и Install-Package datajs
. Unfortunately, there is no package for PhoneJS, so with pens we add all the necessary css and js to the project. There are several layout types to choose from, I used NavbarLayout by changing the _Layout.cshtml file :
@ViewBag.Title
@Styles.Render("~/Content/css")
@Styles.Render("~/Content/dx")
@Styles.Render("~/Content/layouts")
@Scripts.Render("~/bundles/modernizr")
@Html.Partial("NavbarLayout")
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
In the BundleConfig file, we prescribe all the content and scripts. I got it like this:
Bundleconfig
// Сокращено для упрощения
bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
"~/Scripts/knockout-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/breeze").Include(
"~/Scripts/q.js",
"~/Scripts/datajs-{version}.js",
"~/Scripts/breeze.debug.js"
));
bundles.Add(new ScriptBundle("~/bundles/dx").Include(
"~/Scripts/dx.phonejs.js",
"~/Scripts/globalize"
));
bundles.Add(new ScriptBundle("~/bundles/app").Include(
"~/Scripts/App/app.init.js",
"~/Scripts/App/app.viewmodel.js",
"~/Scripts/App/NavbarLayout.js"
));
bundles.Add(new StyleBundle("~/Content/dx").Include("~/Content/dx/dx.*"));
bundles.Add(new StyleBundle("~/Content/layouts").Include("~/Content/layouts/NavbarLayout.css"));
Model and controllers
At the moment, we will include two files in the model: classes for routes (a sales agent goes along these routes) and outlets (stores):
Model
public class Route
{
public int RouteID { get; set; }
[Required]
[StringLength(30)]
public string RouteName { get; set; }
}
public class Customer
{
public int CustomerID { get; set; }
[Required]
[StringLength(50)]
public string CustomerName { get; set; }
[StringLength(150)]
public string Address { get; set; }
public string Comment { get; set; }
[ForeignKey("Route")]
public int RouteID { get; set; }
virtual public Route Route { get; set; }
}
The controllers will be very simple (you can read more about OData here ):
Controllers
public class RoutesController : EntitySetController
{
private MSalesContext db = new MSalesContext();
public override IQueryable Get()
{
return db.Routes; ;
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
public class CustomersController : EntitySetController
{
private MSalesContext db = new MSalesContext();
public override IQueryable Get()
{
return db.Customers; ;
}
protected override Customer GetEntityByKey(int key)
{
return db.Customers.FirstOrDefault(p => p.CustomerID == key);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
A small stroke in the WebApiConfig file:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapODataRoute("odata", "odata", GetEdmModel());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnableQuerySupport();
config.EnableSystemDiagnosticsTracing();
}
public static IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet("Routes");
builder.EntitySet("Customers");
builder.Namespace = "MSales.Models";
return builder.GetEdmModel();
}
}
When registering a route for the OData protocol, you must specify the line
builder.Namespace = "MSales.Models";
necessary for the breeze and datajs libraries to work.Frontend
In the Scripts / app folder, create the script file app.init.js to initialize the libraries:
window.MyApp = {};
$(function () {
MyApp.app = new DevExpress.framework.html.HtmlApplication({
namespace: MyApp,
defaultLayout: "navbar",
navigation: [
{
title: "Routes",
action: "#route",
icon: "home"
},
{
title: "About",
action: "#about",
icon: "info"
}
]
});
MyApp.app.router.register(":view/:id", { view: "route", id: 0 });
MyApp.app.navigate();
var serverAddress = "/odata/";
breeze.config.initializeAdapterInstances({ dataService: "OData" });
MyApp.manager = new breeze.EntityManager(serverAddress);
});
We create an HTML application in which we specify the layout and navigation parameters, which consists of two points: routes and about; and initialize the breeze library.
In the Index.cshtml file, you need to place the dxView and a special area called “content”, which displays a regular list:
To these few lines have earned you must create a ViewModel, so the folder Scripts / app will create a file app.viewmodel.js :
MyApp.route = function (params) {
var viewModel = {
dataSource: {
load: function (loadOptions) {
if (loadOptions.refresh) {
var deferred = new $.Deferred();
var query = breeze.EntityQuery.from("Routes").orderBy("RouteID");
MyApp.manager.executeQuery(query, function (result) {
deferred.resolve(result.results);
});
return deferred;
}
}
}
}
return viewModel;
};
I want to note that the name Viewmodel matches the name dxView, and contains only the dataSource object, in which we define one load method to load data. The refresh parameter determines whether the widget data should be updated completely. In the method, we construct the request by sorting by the RouteID field and execute it.
Add another View - About :
This is my first SPA application.
Result for IPhone:

You probably noticed that an event was hung on the list item
dxAction: '#customers/{RouteID}'
, where, according to the specified navigation, '#customers
this is the called View, and the RouteID
parameter passed to this View:Due to the fact that there may be many buyers, I added the ability to search: added dxCommand - a search button that calls the find function , and an input field in front of the list.
Viewmodel:
MyApp.customers = function (params) {
var skip = 0;
var PAGE_SIZE = 10;
var viewModel = {
routeId: params.id,
searchString: ko.observable(''),
showSearch: ko.observable(false),
find: function () {
viewModel.showSearch(!viewModel.showSearch());
viewModel.searchString('');
},
dataSource: {
changed: new $.Callbacks(),
load: function (loadOptions) {
if (loadOptions.refresh) {
skip = 0;
}
var deferred = new $.Deferred();
var query = breeze.EntityQuery.from("Customers")
.where("CustomerName", "substringof", viewModel.searchString())
.where("RouteID", "eq", viewModel.routeId)
.skip(skip)
.take(PAGE_SIZE)
.orderBy("CustomerID");
MyApp.manager.executeQuery(query, function (result) {
skip += PAGE_SIZE;
console.log(result);
var mapped = $.map(result.results, function (data) {
return {
name: data.CustomerName,
id: data.CustomerID
}
});
deferred.resolve(mapped);
});
return deferred;
}
}
};
ko.computed(function () {
return viewModel.searchString();
}).extend({
throttle: 500
}).subscribe(function () {
viewModel.dataSource.changed.fire();
});
return viewModel;
};
The skip and PAGE_SIZE variables are needed to load part of the data (in this case 10 records), and additional loading will take place as needed.
Variables searchString and showSearch for search, at which the search is triggered with a half second delay after entering a character.
Result:

Well, finally, we display information about the selected buyer:
View:
Id: Name: Address: Comment:
ViewModel:
MyApp['customer-details'] = function (params) {
var viewModel = {
id: parseInt(params.id),
name: ko.observable(''),
address: ko.observable(''),
comment:ko.observable('')
};
var data = MyApp.manager.getEntityByKey("Customer", viewModel.id);
console.log(data);
viewModel.name(data.CustomerName());
viewModel.address(data.Address());
viewModel.comment(data.Comment());
return viewModel;
};

Note: screenshots are taken from the Ripple Emulator (Beta) emulator.
Summary.
We got a fairly simple full-fledged SPA application for mobile devices with navigation and data loading. At the moment, it is difficult to judge the quality / speed / etc. applications, so in the next article I will expand the functionality a bit and lay it out on Azure so that everyone can try.