I continue to develop the embodiment of my dream of developing applications for HTML5 for mobile platforms: I wrote once - it works everywhere and always. In the
last article I received a not exactly mobile application, rather a mobile site, since there was no work without a network. I will try to fix it, as well as everyone (almost) will be able to try how it all works on their personal devices.
So, we get a small mobile web application that we can run under several mobile platforms, while clicking only a few mouse buttons.
Small note: The article was written with the aim of consolidating the material studied on the study of new technology. In connection with the complete lack of real experience in creating applications of this kind, I apologize in advance for any flaws.
')
architecture
We implement something simple:
- routes of the sales agent (the person who goes shopping and orders the goods). The route is the day of the week.
- list of outlets (stores) associated with the route.
- sales outlet orders
Get the necessary data from the server and save it locally.
The server part is a separate .net MVC application with OData. In this article, I see no reason to consider the implementation, since the server simply has to send data using the OData protocol.
The client part is a phonegap application with libraries:
PhoneJS and
BreezeJS .
So let's get started ...
To speed up, we will use the PhoneJS application pattern with the following structure:
- css
- js
- layouts
- views
- index.css
- index.html
- index.js
It seems everything is clear for what. In the
index.html file we add links to the necessary libraries for BreezeJS.
Application launch
The application itself is created in the
index.js file, and navigation and other service functions are described. PhoneJS offers us several
layouts . I chose the
slideout . So create an application:
$(function () { app = ms.app = new DevExpress.framework.html.HtmlApplication(APP_SETTINGS); app.router.register(":view/:item", { view: "Home", item: undefined }); ms.app.viewShown.add(onViewShown); ms.app.navigationManager.navigating.add(onNavigate); startApp(!ms.dataservice.initUserData()); setTimeout(function () { document.addEventListener("deviceready", onDeviceReady, false); console.log("delay"); if (device.platform == "tizen") { document.addEventListener("tizenhwkey", function (e) { if (e.keyName === "back") onBackButton(); }); } }, 1000); });
APP_SETTINGS - application
parameters in which we specify navigation and Layout:
Hidden text APP_SETTINGS = { namespace: ms, navigationType: "slideout", navigation: [ { "id": "Home", "title": "Home", "action": "#Home", "icon": "home" }, { "id": "Settings", "title": "Settings", "action": "#Settings", "icon": "card" }, { "id": "About", "title": "About", "action": "#about", "icon": "info" } ] };
First, I set up the “routing” and view (default), after which we start the application, and it checks if there is already data in the local storage
using the ms.dataservice.initUserData () function (we will consider a little later). The function returns true if data is available. Those. if we start the first time, then go to the
Settings view to get data.
function startApp(needToSynchronize) { if (needToSynchronize) ms.app.navigate("Settings/1"); else ms.app.navigate(); }
Also, before launching the application, I introduce a small delay - in the future I will make a screensaver.
Work with data
To work with the data, create a separate
dataservice.js file:
Hidden text MobileSales.dataservice =function ($, DX, app, undefined) { var DATA_VERSION_KEY = "mobilesales-version", DATA_KEY = "mobilesales-data", logger = app.logger; serviceName = "http://mobsalessrv.azurewebsites.net/odata/"; breeze.config.initializeAdapterInstances({ dataService: "OData" }); var manager = new breeze.EntityManager(serviceName); var store = manager.metadataStore; var queries = { Routes: { name: "Routes", query: breeze.EntityQuery.from("Routes").orderBy("RouteID"), }, Customers: { name: "Customers", query: breeze.EntityQuery.from("Customers").orderBy("CustomerName"), }, ProductTypes: { name: "ProductTypes", query: breeze.EntityQuery.from("ProductTypes").orderBy("ProductTypeName"), }, Products: { name: "Products", query: breeze.EntityQuery.from("Products").orderBy("ProductName"), }, Orders: { name: "Orders", query: breeze.EntityQuery.from("Orders").orderBy("Date"), }, OrderDetails: { name: "OrderDetails", query: breeze.EntityQuery.from("OrderDetails"), }, }; function initUserData() { var dataFromStorage = localStorage.getItem(DATA_KEY); if (dataFromStorage) { manager.importEntities(dataFromStorage); return true; } else { return false; } } function loadData(query) { return manager.executeQuery(query); } function getRoutes(){ return manager.executeQueryLocally(queries.Routes.query); }; function getCustomers() { return manager.executeQueryLocally(queries.Customers.query); }; function getProduct(productID) { var query = queries.Products.query.where("ProductID", "==", productID); return manager.executeQueryLocally(query)[0]; }; function getOrders(customerID) { var query = queries.Orders.query; if (typeof customerID != "undefined" && customerID > 0) query= query.where("CustomerID", "==", customerID); return manager.executeQueryLocally(query); }; function getOrderDetails(orderID) { var query = queries.OrderDetails.query; if (typeof orderID != "undefined" && orderID > 0) query = query.where("OrderID", "==", orderID); var result = manager.executeQueryLocally(query); result.forEach(function (item) { item.ProductName = getProduct(item.ProductID()).ProductName; }); return result; }; function saveDataLocally() { var exportData = manager.exportEntities(); localStorage.setItem(DATA_KEY, exportData); } var dataservice = { manager: manager, metadataStore: manager.metadataStore, initUserData: initUserData, queries: queries, loadData: loadData, getRoutes: getRoutes, getCustomers: getCustomers, saveDataLocally: saveDataLocally, getOrders: getOrders, getOrderDetails: getOrderDetails, }; return dataservice; }(jQuery, DevExpress, MobileSales);
How to work with breeze read in the last article. I will dwell only on a few points. The query object contains a list of requests for data retrieval, I plan to expand it in the future with various parameters, and also generally receive this list from the server, which will allow to manage the data dynamically.
Breeze has a great opportunity to save and restore all data locally using
manager.exportEntities () and
manager.importEntities (dataFromStorage), respectively. The
initUserData function checks the availability of data in the local storage using the
DATA_KEY key and retrieves it. All other functions of the type
getXXX select the necessary entities locally and are used in the necessary types.
To download all data from the server, we use a separate view of
settings , which has a button for downloading data, a download panel and a list of entities:
<div data-options="dxView : { name: 'Settings', title: 'Settings' } "> <div data-options="dxContent : { targetPlaceholder: 'content' } "> <div class="actions"> <div data-bind="dxButton: { text: 'Synchronize', clickAction: synchData }"></div> </div> <div data-bind="dxLoadPanel: { message: message, visible: loading().length>0 }"></div> <div data-bind="dxList: { items: entityList }"> <div data-options="dxTemplate : { name: 'item' }"> <div data-bind="text: $data.name" class="entity-name"> </div> <div data-bind="text: $data.status" class="entity-status"></div> </div> </div> </div> </div>
I completely forgot to tell you that both breeze and Phonejs use the wonderful
knockout library, so it’s very convenient to group it all together.
settings.js MobileSales.Settings = function (params) { var app = MobileSales, needToSynchonize = params.item==="1", self = this; var vm = { entityList: ko.observableArray([]), loading: ko.observableArray(), viewShowing: function () { if (needToSynchonize) getEntities(); }, viewShown: function () { $(".dx-active-view .dx-scrollable").data("dxScrollView").scrollTo(0); }, synchData: getEntities, }; vm.message = ko.computed(function () { return "Loading ...(left:" + this.loading().length + ")" }, vm); function getEntities() { var mapped = $.map(app.dataservice.queries, function (item) { item.status = ko.observable("Loading"); vm.loading.push(true); app.dataservice.loadData(item.query).then(function (data) { app.logger.log("Loaded data: " + item.query.resourceName); item.status("Succeded"); app.logger.log(app.dataservice.getRoutes()); vm.loading.pop(); if (vm.loading().length === 0) app.dataservice.saveDataLocally(); }).fail(function (error) { item.status("Error"); app.logger.error("Error Loading data"); app.logger.log(error); vm.loading.pop(); }); return item; }); vm.entityList(mapped); }; return vm; };
At the input we take the parameter that is passed in the case of forced synchronization. All requests to the breeze server are asynchronous, which allows you to make an elegant solution for uploading entities into an
entityList . To do this, we need only one observable array (observableArray)
loading .
Let's
analyze the getEntities function: we go through all the elements of
dataservice.queries , add
status to each field and add any value to the
vm.loading.push (true) array. In the handlers for successful execution or error, we update the element status and decrease the
loading array. As a result, we very simply get asynchronous loading of several entities with control completion. This is how it all looks.
The rest of the program is not very interesting, just displays the data uploaded to the repository, and duplicates the previous article. If interested, check out the
source code .
In the
Home view, you can select a sales point on the route of the route, as well as view orders for this point. This is how it looks like:
It remains to get a mobile application in a few clicks:
● Go to
http://build.phonegap.com .
● Login using GitHub.
● Add a new application by selecting the desired repository
● Click the magic button “Ready to build”
● And we get the application on 5 of 6 platforms (unfortunately I do not have a key on IOS):
Small specifics: the picture above was taken on PhoneGap version 2.9, by the way, WP version 7 here. When choosing version 3. + we’ll get this message
: Blackberry, Symbian, and WebOS are no longer supported as of PhoneGap 3 , but for that there is support for WP8 .
It remains to add a little tar (maybe a lot): performance. It is simply impossible to work on old or weak devices - the brake is complete. But as soon as I took the device with more or less normal parameters, everything worked out. I agree, this is not a native application, but I don’t even want to compare - these are different things. And I am sure that such an approach (Well, in the sense of HTML5 in general) there is a future, and a niche will be found.
Links to the application:
● Sources
https://github.com/gfhfk/MobSales● Test in browser (I tried Chrome and IE11)
http://mobsales.azurewebsites.net/ .
● Who is interested in the server part:
http://mobsalessrv.azurewebsites.net/odata/● the program itself:
Version 3.4
https://build.phonegap.com/apps/727149Who is interested in version 2.9
https://build.phonegap.com/apps/733422/Note 1 : There is a problem with installing the application on Windows Phone through the qr-code, an error occurs. You need to follow the link from the computer, download the xap and upload it through the Application Deployment Tool. Likely glitch Phonegap Build.
Note 2 : Sometimes, especially when you first start it or when you start it on the emulator, you get this error:
The solution to this problem is very simple:
1. Rename the index.html file to main.html.
2. Create a new index.html and insert a piece:
<!doctype html> <html> <head> <title>tittle</title> <script> window.location='./main.html'; </script> <body> </body> </html>
3. All - there are no more errors (Although with version 2.9 on some devices it still produced).
Note 3 : In the WP8 emulator, the application does not start the first time, until it figured out why. On real devices did not try. In WP8.1, it did not start at all (I will not understand it yet).
Note 4: Sites are hosted on a free Windows Azhure WebSites, where resources are limited. Given the habr effect sites can lie, then let me know - I will transfer to a paid one for a while.
I will not write the result, I suggest everyone to test and answer the survey, according to which I will decide what to do next: