📜 ⬆️ ⬇️

HTML5 mobile application: error or success. Attempt # 1

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.

image


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:


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:

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.
image

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:
image


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):
image

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/727149
image

Who is interested in version 2.9 https://build.phonegap.com/apps/733422/
image


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:
image


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:

Source: https://habr.com/ru/post/226107/


All Articles