wq/app.js is a wq.app module that provides a configuration-driven JavaScript application controller to facilitate the creation of complete mobile web apps for viewing and submitting field data. wq/app.js is primarily intended for use as a client for wq.db.rest, but can be used with any REST service with the same URL structure.
wq/app.js is the highest-level wq.app module, and brings together a number of lower-level modules and wq conventions into an integrated API. As a primarily configuration-driven module, wq/app.js is relatively easy to use, but enforces a number of constraints on the design of your project. In particular, it works best with wq.db.rest or a compatible REST service.
The specific concepts leveraged by wq/app.js include:
localForage
-backed cache of REST API responses (wq/model.js), with an offline-capable outbox to sync form submissions back to the server (wq/outbox.js).wq/app.js
is typically imported via AMD as app
, though any local variable name can be used.
// myapp.js
define(['wq/app', ...], function(app, ...) {
app.init(config);
});
The app module provides the following methods and properties.
In a simple project, the only required usage of wq/app.js is to initialize it with a configuration. The configuration object is described in the Configuration section below. app.init()
returns a Promise that will be resolved when the initialization is complete.
The version of jQuery Mobile included with wq.app is customized to disable automatic initialization on startup. The purpose of this change is to make it easier to register custom plugins and events before jQuery Mobile starts up, to ensure they are executed when it does. To start up jQuery mobile, call app.jqmInit()
after calling app.init()
and registering your custom routes. If you have no custom events, you can set config.jqmInit = true
to have app.init()
call app.jqmInit()
for you.
While not required, it is often useful to call app.prefetchAll()
after initialization. app.prefetchAll()
preloads and caches data from all registered models (i.e. all entries in the pages
configuration that have list
set to true
).
// No custom events, no prefetch
config.jqmInit = true;
app.init(config);
// Custom events via low-level router API, then prefetch all
app.init(config).then(function() {
router.addRoute(...);
app.jqmInit();
app.prefetchAll();
});
wq/app.js provides a simple plugin API and a number of predefined optional plugins such as wq/map.js and wq/chartapp.js. Plugins should always be registered via app.use(plugin)
before calling app.init()
.
// map is a wq/app.js plugin
config.jqmInit = true;
app.use(map);
app.init(config);
While it is possible to register arbitrary jQuery Mobile event handlers and/or low-level routes using wq/router.js, the plugin API is the recommended way to add custom functionality to pages before and/or after they render.
<form>
Handlerapp.init()
registers a custom submit handler that takes normal form POSTs and converts them to wq/outbox.js entries. For model-backed list pages, these forms would normally be placed in [page]_edit.html
templates and accessed via /[page_url]/new
and/or /[page_url]/[id]/edit
.
In addition to the other standard form input types, the <form>
handler supports saving photos and other files, which are stored in wq/outbox.js as Blob
instances. See the wq/photos.js documentation for more information about this feature.
To avoid conflicts with jQuery Mobile's own AJAX form handler, it is recommended to set data-ajax="false"
on all forms using the wq/outbox.js functionality.
<form method="post" action="/items" data-ajax="false">
By default wq/app.js' form handler is applied to every form in the website. This functionality can be disabled on a per-form basis by setting data-wq-json=false
on the form tag:
<form method="post" action="/custom" data-wq-json="false">
To disable both jQuery Mobile's AJAX form handler and wq/app.js' AJAX+JSON form handlers, specify both properties:
<form method="post" action="/custom" data-wq-json="false" data-ajax="false">
When working with list pages, the same template ([page]_edit.html
) is used for both new and existing items. The presence of an id
attribute in the context object can be used to distinguish between the two use cases when rendering the template. In addition, a number of other useful context variables are provided to assist with rendering e.g. <select>
menus for foreign keys. See the template context documentation for more details.
<!-- Different action/method depending on whether this is a new or existing item -->
<form action="/items/{{id}}" method="post" data-ajax="false">
{{#id}}
<!-- Existing item; override HTTP method for Django REST Framework -->
<input type="hidden" name="_method" value="PUT">
{{/id}}
app.go()
app.go()
is called automatically whenever the URL changes, e.g. in response to the user clicking on a link. However, there are rare cases where you may need to call it directly. app.go() is essentially a wrapper for router.go() that automatically generates the appropriate template context for list, detail, edit, and simple views.
app.go(page, ui, params, [itemid], [edit], [url], [context])
app.go()
accepts up to seven arguments:
name | purpose |
---|---|
page |
The name of a page listed in config.pages (described below) |
ui |
A jQuery Mobile ui object describing options for an event |
params |
Any additional URL parameters (?name1=value1&name2=value2 etc.) |
itemid |
(optional) The id for an individual item in a list (triggers a detail view instead of a list view) |
edit |
(optional) Boolean, if true triggers an edit view instead of a detail view |
url |
(optional) URL to display for the rendered page. Usually this is automatically computed. |
context |
(optional) Initial template context variable (will be merged with the automatically generated context). |
The default template contexts for detail and edit views contain a number of context variables that assist in looking up and displaying information from related models, e.g. a list of potential values for a foreign key. These lookup contexts can be customized by overriding hooks (see Advanced Customization, below).
app.sync()
Triggers a background sync of pending outbox items in wq/outbox.js. Called automatically at regular intervals if backgroundSync
is enabled (see Configuration below). Outbox items causing server errors will be excluded from further syncs after 3 sync attempts (This threshold can be controlled via wq/outbox.js' maxRetries
option.) To try re-sending all unsaved items, including those causing server errors (for example in response to a user-initiated sync), call app.sync(true)
instead of app.sync()
.
app.user
If the application supports authentication and the user is logged in, app.user
will be set with information about the current user provided by the server. This information will also be available in the template context, e.g. {{#is_authenticated}}{{user.username}}{{/is_authenticated}}
.
app.config
, app.wq_config
A copy of the wq/app.js configuration object (see below) and the wq configuration object, respectively. Initially app.config.pages
and app.wq_config.pages
are the same, but after logging in, app.wq_config
is overwritten with an updated wq configuration object with permissions information specific to the logged-in user. app.config
is made available in the template context as {{app_config}}
, while app.wq_config
is provided as {{wq_config}}
.
app.models
After initialization, app.models
will contain a wq/model.js instances for each registered model (i.e. each item in the pages
configuration with list: true
).
app.models.item.filter({'type_id': 2}).then(function(type2items) {
type2items.forEach(function(item) {
console.log(item.id + ' - ' + item.label);
});
});
app.native
Whether the application is running under PhoneGap / Cordova or as a web app (true
and false
respectively). Available in the template context as {{native}}
.
As noted above, wq/app.js is primarily configuration driven. The available configuration options are shown below with their default values.
{
// wq/app.js options
'debug': false,
'jqmInit': false, // See init() above
'backgroundSync': true, // alternatively, noBackgroundSync: false
'loadMissingAsHtml': true, // alternatively, loadMissingAsJson: false
'pages': { /* ... */ }, // from wq config
'transitions': { /* ... */ },
'hookName': function() {} // See hook names below
// Configuration for core modules
'router': { /* ... */ },
'store': { /* ... */ },
'outbox': { /* ... */ },
'template': { /* ... */ },
// Configuration for registered plugins
'map': { /* ... */ },
'chartapp': { /* ... */}
}
The configuration sections for the other core modules are passed on to the init()
function for each module. In a few instances, wq/app.js overrides the default settings for the respective modules. See the documentation for wq/router.js, wq/store.js, wq/outbox.js, and wq/template.js for more information about the available configuration options for each module. Similarly, any registered plugins can be configured via sections with the same name as the respective plugins.
The debug
option enables console logging in wq/app.js and the other core modules. If specified as a number, debug
will set the verbosity level in wq/store.js.
The jqmInit
option tells app.init()
to immediately trigger app.jqmInit()
on startup. The default is false, to give you a chance to register additional custom routes before initializing jQuery Mobile.
The backgroundSync
option tells wq/app.js not to make the user wait for forms to be submitted to the server, and instead to handle all wq/store.js syncing in the background. If specified as a number, backgroundSync
will set the number of seconds between sync attempts. Setting backgroundSync
to true (the default) will trigger 30 seconds between sync attempts. It can be disabled by setting
noBackgroundSyncto
true.
backgroundSynccan also be enabled or disabled on a per-form basis by setting the
data-wq-background-syncattribute. For example, the [login.html] provided by the wq Django template sets
data-wq-background-sync` to false since it makes more sense to wait for a successful login before continuing.
The loadMissingAsHtml
and loadMissingAsJson
options tell wq/app.js what to do if the user navigates to a model instance that is not stored locally. There are three possible outcomes in this case:
* If the associated model page is configured with partial: false
, wq/app.js will assume the entire model collection is stored locally, assume the page does not exist, and call the router.notFound()
404 page.
* If the associated model page is configured with partial: true
, and loadMissingAsHtml
is set, wq/app.js will attempt to load the page from the server and assume the server is capable of rendering content as HTML.
* If the associated model page is configured with partial: true
, and loadMissingAsJson
is set, wq/app.js will attempt to load the missing data from the server as JSON and render it locally.
pages
: URL routesThe pages
configuration section is equivalent to the option with the same name in the wq configuration object. The pages
configuration is typically generated by the REST service and describes the URL routes in the application. The full list of page options is described in the documentation for the wq configuration object.
Note: If you need to customize an option in the server generated
pages
, you should specify it when calling router.register_model() in wq.db.rest rather than overriding thepages
section in yourconfig.js
. This ensures that the client and the server are on the same "page".
As noted above, wq/model.js instances for all model-backed pages (those with list: true
) will be added to app.models
for convenience.
transitions
: Page TransitionsConfiguration for jQuery Mobile's built in page transitions. Where applicable, this information is mapped to jQuery Mobile's built-in configuration options.
Name | Usage |
---|---|
default |
A shortcut for $.mobile.defaultPageTransition . Often set to slide . |
dialog |
A shortcut for $.mobile.defaultDialogTransition . |
save |
Sets the default transition to use when moving from an edit view back to a detail view after a save. This is often set to flip . |
maxwidth |
A shortcut for $.mobile.maxTransitionWidth . Defaults to 800 (note that vanilla jQuery Mobile defaults to false), |
The configuration object can be defined as a variable in the same file as the call to app.init()
. However, the conventional approach is to create a separate AMD module js/myapp/config.js
that depends on the server-created wq config and then adds the additional attributes needed to initialize wq/app.js.
define(['data/config', 'data/templates', function(config, templates) {
// config.pages already exists on server-generated wq config
config.templates = {
// Configure templates and default context variables
templates: templates,
partials: templates.partials,
defaults: { /* ... */ }
}
config.transitions = {
// Configure default jQuery Mobile page transitions
}
config.store = {
// Configure wq/store.js
}
return config;
});
If you are not utilizing the wq.db-generated configuration, you can define the entire configuration object in config.js:
define({
'pages': { /* ... */ },
'template': { /* ... */ },
'transitions': { /* ... */ },
'store': { /* ... */ }
});
The templates
can be created as regular Mustache HTML files and then inlined into a JavaScript object through the collectjson step in the wq build process. The resulting object should be of the form:
{
'[modelname]_list': "<html>...</html>",
'[modelname]_detail': "<html>...</html>",
'[modelname]_edit': "<html>...</html>",
// so on for each model...
'[staticpage]': "<html>...</html>",
'partials': {
'head': "..."
}
}
By convention, this object is typically wrapped in an AMD define()
and placed in the file js/data/templates.js
.
app.init()
should typically be called immediately when the JavaScript loads. Assuming data/template.js
and myapp/config.js
are created as above, this can be accomplished by creating a main module for your application:
// js/myapp.js
requirejs.config({
'baseUrl': 'lib', // wq and third party libs
'paths': {
'myapp': '../myapp',
'data': '../data'
}
});
requirejs(['myapp/main']);
// js/myapp/main.js
define(['wq/app', './config'],
function(app, config) {
app.init(config);
});
And then configuring RequireJS to load it in your index.html
:
<script src="js/lib/require.js" data-main="js/myapp"></script>
If you are utilizing both wq.app and wq.db in your project, you may find it useful to leverage the Django wq template which will set up the above project layout for you.
wq/app.js provides a number of events and hooks for additional customization.
login
Callbacks registered with the login
event will be called whenever the user logs in (as well as at the start of the application if the user is already logged in).
$('body').on('login', function(){
/*...*/
});
logout
Callbacks registered with the logout
event will be called whenever the user logs out.
$('body').on('logout', function() {
/*...*/
});
These hooks can be customized by adding additional options to the wq/app.js config object.
postsave(item, backgroundSync)
postsave()
is called after a form submission with the item
created by outbox.save(). It is primarily used to handle navigation to the next screen. postsave()
can usually be configured as needed via the postsave
property in the page configuration. The postsave()
hook is meant to be overridden in cases where config.pages[name].postsave
does not provide enough flexibility.
The backgroundSync
argument indicates whether or not the form is being synced in the background, in which case the result of the submission might not be known yet. If backgroundSync
is set, the default implementation of postsave()
will navigate to the list view of the model that was just saved. If it's not set (i.e. result from the server is already known), postsave()
will navigate to the detail view of the newly saved item by default.
config.postsave = function(item, backgroundSync) {
if (backgroundSync) {
app.go('outbox');
} else if (item.synced) {
$('#result').html("Successfully saved.");
}
}
app.init(config);
saveerror(item, reason, $form)
If background sync is disabled, saveerror()
is called when outbox.save() fails, with the item
from outbox.save(), the reason for the failure, and the jQuery-wrapped <form>
that initiated the save. The reason
will be one of the constants app.OFFLINE
, app.FAILURE
, or app.ERROR
. app.FAILURE
usually indicates a server 500 failure, while app.ERROR
usually indicates a 400 validation error.
If background sync is enabled (the default), then saveerror
will not be used.
config.saveerror = function(item, reason, $form) {
if (reason == app.OFFLINE) {
alert("You are currently working offline. Your record is in the outbox");
} else {
app.showOutboxErrors(item, $form);
}
}
app.init(config);
showOutboxErrors(item, $form)
showOutboxErrors()
is used to populate the screen with information about why a form was not successfully submitted. It is called by saveerror()
by default, or when navigating to a previously saved outbox item.
The default implementation of showOutboxErrors()
can automatically display any error messages returned from the REST API - both per-field errors and general validation errors. This functionality can be leveraged by placing <span>
or <p>
tags next to the fields and near the submit button. The tags should have two classes: error
and [page]-[field]-errors
, where page
is the name of the of the wq page config and field
is the name of the form field.
<!-- item_edit.html -->
<form action="/items/{{id}}" method="post" data-ajax="false" data-wq-background-sync="false">
{{#id}}
<input type="hidden" name="_method" value="PUT">
{{/id}}
<ul>
<li>
<label for="item-name">Name</label>
<input name="name" value="{{name}}" id="item-name">
<!-- Placeholder for "name" errors -->
<span class="error item-name-errors"></span>
</li>
<li>
<label for="item-date">Date</label>
<input name="date" type="date" value="{{date}}" id="item-date">
<!-- Placeholder for "date" errors -->
<span class="error item-date-errors"></span>
</li>
<li>
<!-- Placeholder for general errors -->
<span class="error item-errors"></span>
<button type="submit">Save Changes</button>
</li>
</ul>
</form>
presync()
Triggered at the beginning of app.sync()
. Useful for debugging.
postsync(result)
Triggered after an app.sync()
, with the result from outbox.sendAll()
in wq/outbox.js. Useful for alerting the user of persistent sync issues. The default implementation will check for and refresh any screens with data-wq-sync-refresh
set to true. (This helps ensure that e.g. list views update automatically after a sync).
wq/app.js relies on three low level modules that are discussed in depth elsewhere:
Last modified on 2019-07-10 10:42 AM
Edit this Page |
Suggest Improvement
© 2013-2019 by S. Andrew Sheppard