jQuery and jQuery Mobile together are a compelling platform for building mobile user experiences, but on their own they are missing one of the most important ingredients of native mobile apps – offline access. The good news is that all modern mobile browsers support one of the competing standards for HTML5 offline storage; the bad news is that there are competing standards, and the tools available for managing persistence in JavaScript are poor at best.
In this talk we will present a practical approach to building offline-enabled, data-driven applications using jQuery, jQuery Mobile, and PersistenceJS as an O-R-M abstraction on top of HTML5’s varying offline storage standards. Using this combination, we introduce a simple data synchronization paradigm that allows mobile HTML5 apps to download data, store and manipulate that data locally, and re-synchronize that data whenever the device is online. In addition, we present some of the benefits of viewing data stored in a database, including sorting, grouping, filtering, and full text indexing lists of data objects.
With offline storage added to the mix, mobile HTML5 starts to deliver on the promise of a fully cross-platform, browser driven experience that is just as good as any native app.
2. Qbasic
10: HTML5 Mobile Apps
20: Why?
30: State of the state
40: Offline is key
100: The Mobile Helix SDK
110: Why?
120: A fundamental shift in design model
130: Data sync ( what and how )
140: Improved UI components
200: Mobile Helix?
300: GOTO 10
SDK for offline
storage,
synch, and
display
2
3. Now here's a little story I've got to tell
2008
2009
2010
2011
2012
2013
2014
…
3
4. So many app stores – so little time
What is the main driver for writing HTML5 mobile apps
in an Enterprise setting?
4
6. @HTML5AppMan’s Timeline of HTML5
2009: www.w3.org/2009/dap/
2012: the party’s over…
2014: or wait, is it?
www.telegraph.co.uk
Before 2009 2012ish 2014 Soon…
6
7. So where are we now?
Broken promises of HTML5 and what's next?
2012 Chris Heilmann, @codepo8 http://icant.co.uk/talks/h5/html5devcon.html
7
8. So where are we now?
HTML5: Doomed to fail or just getting started?
2014 Matt Asay, @mjasay http://www.techrepublic.com/article/html5-doomed-to-fail-or-just-getting-started/
8
10. Offline with HTML5
HTML5 Browser Storage: the Past, Present and Future
Oct 2013 Craig Buckler,@craigbuckler http://www.sitepoint.com/html5-browser-storage-past-present-future/
Variables, Cookies, window.name, etc
WebSQL
robust client-side data storage and access
uses SQL like many server side applications
support on Webkit/Blink desktop and mobile browsers
WebSQL Disadvantages:
the database schema must be defined up-front
marginal browser support and the Webkit/Blink teams may eventually drop it
the W3C specification was abandoned in 2010
10
11. WebSQL Support – August 12, 2014
http://caniuse.com/#feat=sql-storage
11
12. Offline with HTML5
HTML5 Browser Storage: the Past, Present and Future
Oct 2013 Craig Buckler,@craigbuckler http://www.sitepoint.com/html5-browser-storage-past-present-future/
Variables, Cookies, window.name
WebSQL
robust client-side data storage and access
uses SQL like many server side applications
support on Webkit/Blink desktop and mobile browsers
WebSQL Disadvantages:
the database schema must be defined up-front
marginal browser support and the Webkit/Blink teams may eventually drop it
the W3C specification was abandoned in 2010
12
13. Common implementation of client/server
Login user “ilya” success?
Get conversation threads
Load chat with “seth”
Get first X messages
Change sort order
get new X msgs
Next page, load
next X msgs
Search for “test21”
13
17. A new paradigm
Login user “ilya” success?
get new X msgs
Get conversation threads
Open chat with “seth”
Display first X messages
Change sort order
Next page, load next X msgs
Search for “test21”
17
18. Our SDK and jQuery Mobile
jQuery Mobile has a great mechanism and ecosystem for
creating and extending widgets
18
24. How it works
Specify a schema object
Send JSON objects from the server and sync
them to local storage
Updated jQMobile controls to pull data from local
storage
24
29. Object-Relational mapping
Last step is to generate the appropriate table structure in
the underlying persistence layer
Helix.DB.generatePersistenceSchema(
schema,
“User",
function () {
console.log(“local DB is ready”);
}
);
29
30. Store the data
Ok, now we can store real data from the server:
Helix.DB.synchronizeObject(
< object from server >,
Helix.DB.getSchemaForTable(“User"),
function(persistentObj) {
// callback: local data is ready
},null,null,null
);
30
31. Store the data
var d = $.parseJSON(data);
if ( d.success === true ) {
Helix.DB.synchronizeObject(
d.payload,
Helix.DB.getSchemaForTable("User"),
function( persistentObj ) {
alert( "user " +
d.payload.User.user +
" stored in DB“
);
});
}
31
32. Retrieve the data
Helix.DB.loadAllObjects(
Helix.DB.getSchemaForTable("User"),
function(obj) {
var u = $("#username").val();
obj.filter( 'user', 'like', u).newEach( {
. . .
});
}
);
32
33. Use the data
eachFn: function(elem) {
alert ("offline user: " + elem.user );
},
doneFn: function(ct) {
},
startFn: function(ct) {
if (ct == 0)
alert("user not provisioned for offline use");
}
33
34. Retrieve the data
Helix.DB.synchronizeObjectByKey(
stateManager.thread_id,
Helix.DB.getSchemaForTable("Thread“),
function(obj){
if ( obj ){
var u = $("#username").val();
obj.filter( 'user', 'like', u).newEach( {
. . .
});
}
);
34
35. Automatic schema migrations
Oh no – now we need to audit our users’ logins
{
User: {
user_id: 1,
user: “ilya”,
last_login: “date time”,
login_ip: “192.168.1.1”
}
}
35
36. Automatic schema migrations
Client changes: 0.0
The SDK resolves changes to the schema and
updates all internal tables accordingly
36
43. Adds Example
Helix.DB.synchronizeObject(
new_messages,
Helix.DB.getSchemaForTable("Thread"),
function(obj) {
console.log(“new messages received");
confirmSync(); // update server with synch key
}
});
43
44. Deletes example
var delta = {
__hx_type: 1001,
__hx_schema_type: 'Message',
adds: '',
deletes: window.d3.payload, // list of IDs to delete
updates: '‘
};
Helix.DB.synchronizeObject(
delta, Helix.DB.getSchemaForTable(“Message"),
function(obj) { console.log(“Messages delleted"); }
});
44
45. listview? No, this is a Datalist
(function($) {
$.widget("helix.helixDatalist", {
options: {
. . . // soooo many options…
},
_create: function() {
. . .
// implements infinite scrolling list which pulls from local DB,
not from a large array in memory
45
46. listview? No, this is a Datalist
<div data-role="page" id="thread-page">
. . .
<div data-role="content" class="content">
<div id=“Threads" class="hx-layout-full-height" />
</div>
</div>
46
47. listview? No, this is a Datalist
$(document).on('pageinit', '#thread-page', function(){
threadlist = $(‘#Threads').helixDatalist({
itemList: null,
condition: Helix.DB.getSchemaForTable("Thread"),
rowRenderer: function(parentDiv, list, row) {
. . .
}
});
});
47
48. listview? No, this is a Datalist
var s = Helix.DB.getSchemaForTable("Thread");
Helix.DB.loadAllObjects( s, function(obj){
if (obj){
threadlist.helixDatalist(
"refreshList",
obj,
s, s, function(){
Helix.Layout.layoutPage();
console.log ( "refresh for threadlist" );
});
}
});
48
49. Local search
1. Add this to the schema:
__hx_text_index:["msg_to", "msg_from", "message"]
2. Add this to the Datalist initialization:
var msg = Helix.DB.getSchemaForTable("Message");
indexedSearch: function (searchText, completion){
if (!searchText)
return;
completion( msg.search(searchText) );
}
49
50. Datalist – other options
selectAction,
swipeLeftAction
swipeRightAction
itemContextMenu
Context menu to display if the user tap-holds (for touch devices) or
double clicks (for non-touch devices)
holdAction
Action to perform if the user tap-holds (for touch devices) or double
clicks (for non-touch devices) on a list item
Pull to refresh
itemsPerPage
50
52. Minimize page loads from the server
CSS sprites
We use Smart Sprites: (http://csssprites.org/) and YUI’s
minifier, but Google’s Webkit is another option we like
Concat and minify all JS and CSS
load a single JS/CSS file for all of your code
Use query params to handle versioning and make
JS/CSS infinitely cacheable
change the name of the URL used to load the
JS/CSS/Sprite so that you don’t need to put a cache
expiration or force revalidation of your JS/CSS/Sprite
52
53. Minimize DOM changes - they are so slow!
Our Datalist paginates and refreshes the underlying data
by making the smallest number of DOM changes possible
Much faster to show and hide elements than add or delete
them
All of the markup needed to view and edit a single data
object is rendered up front.
Toggling between view/edit modes only shows or hides
underlying elements
53
54. Tread lightly on memory
A list with > 200 items in jQM starts to get awfully
slow on a phone
Use pagination ALWAYS, but make scrolling fast and
seamless to the user
Be careful what you keep in memory
PersistenceJS by default keeps a reference to each
inserted object
PersistenceJS runs everything with “SELECT * FROM”
54
55. Mobile Helix SDK
The SDK is available here (on Github)
github.com/shallem/LinkHTML5SDK.git
Demo chat project is a work in progress
55
56. Join in the fun – make this even better!
Contact me with any questions, comments,
@HTML5AppMan
ilya@mobilehelix.com
56
Object-Relational mapping. You send JSON objects from the server; the SDK figures out how to store them.
Abstract away the HTML storage standard using a heavily upgraded version of Persistence JS (see topic c for why we did this)
Dynamic schema updates – we figure out how to alter the underlying schema when you change a serialized object. You never have to worry about the underlying schema.
Convenience features like full-text indexing of selected database fields.
Incrementality – you can send changes from the last sync when the server-side supports it.
High performance – huge amount of effort spent on optimizing database access over WebSQL.
Object-Relational mapping. You send JSON objects from the server; the SDK figures out how to store them.
Abstract away the HTML storage standard using a heavily upgraded version of Persistence JS (see topic c for why we did this)
Dynamic schema updates – we figure out how to alter the underlying schema when you change a serialized object. You never have to worry about the underlying schema.
Convenience features like full-text indexing of selected database fields.
Incrementality – you can send changes from the last sync when the server-side supports it.
High performance – huge amount of effort spent on optimizing database access over WebSQL.
Object-Relational mapping. You send JSON objects from the server; the SDK figures out how to store them.
Abstract away the HTML storage standard using a heavily upgraded version of Persistence JS (see topic c for why we did this)
Dynamic schema updates – we figure out how to alter the underlying schema when you change a serialized object. You never have to worry about the underlying schema.
Convenience features like full-text indexing of selected database fields.
Incrementality – you can send changes from the last sync when the server-side supports it.
High performance – huge amount of effort spent on optimizing database access over WebSQL.
Object-Relational mapping. You send JSON objects from the server; the SDK figures out how to store them.
Abstract away the HTML storage standard using a heavily upgraded version of Persistence JS (see topic c for why we did this)
Dynamic schema updates – we figure out how to alter the underlying schema when you change a serialized object. You never have to worry about the underlying schema.
Convenience features like full-text indexing of selected database fields.
Incrementality – you can send changes from the last sync when the server-side supports it.
High performance – huge amount of effort spent on optimizing database access over WebSQL.
Specify a schema object – this is just a “dummy” object – i.e., an empty version of the objects that you will later sync to the client.
Send JSON objects from the server and sync them to local storage; can either send a list of objects (as an array) or send a delta object (adds/modifies/deletes)
Updated jQMobile controls to pull data from local storage – and added several features that make sense in this context. Using the datalist as an example, we have added:
Sort, filter, search
“infinite scroll” pagination
tap, tap-hold, and swipe events to allow for actions on individual list items
For example, Persistence JS “out of the box” will…
keep a reference to all objects in local storage – what happens when you try to sync 10000 objects and it keeps a reference to all 10000 of them in memory?
run all DB queries with “SELECT *” - what happens if you load the body of 150 emails, some of which may be very large?