@wq/app provides a configuration-driven JavaScript application controller to facilitate the creation of complete mobile web apps for viewing and submitting field data. @wq/app is primarily intended for use as a client for wq.db.rest, but can be customized for use with any REST service.
Overview
@wq/app is the highest-level wq.app module, and brings together a number of lower-level modules and wq conventions into an integrated API. The specific concepts leveraged by @wq/app include:
- A specific URL structure that is observed by both the application itself and its REST API.
- A client-side URL router (@wq/router) and page renderer (@wq/react + @wq/material)
- A model-driven,
localForage
-backed cache of REST API responses (@wq/model), with an offline-capable outbox to sync form submissions back to the server (@wq/outbox). - A wq configuration object generated by the URL router on the server, that describes the available routes and underlying models.
- An optional authentication endpoint provided by the REST API, including CSRF tokens to prevent cross-site attacks.
- A simple plugin API to facilitate adding optional functionality such as interactive maps or even a fully custom data model (via plugin hooks).
Installation
@wq/app is available via a PyPI package (wq.app) as well as an npm package (@wq/app). If you are not sure which one to use, use the PyPI package to get started. It is possible to later convert a PyPI wq.app project into a npm @wq/app project (with a few changes).
wq.app for PyPI
python3 -m venv venv # create virtual env (if needed)
. venv/bin/activate # activate virtual env
python3 -m pip install wq # install wq framework (wq.app, wq.build, wq.create, wq.db)
# pip install wq.app # install wq.app only
@wq/app for npm
npm install @wq/app
Project Layout
Whether using wq.app for Python or @wq/app for npm, we highly recommend using wq.create to initialize your project. In particular, wq.create will automatically configure your JavaScript build settings and link the proper imports for @wq/app. If you are not using wq.create, you can create a similar layout using the examples below.
wq.app for PyPI
// "wq create --without-npm" creates a layout similar to this:
// static/app/js/myapp.js
import wq from './wq.js';
import config from './data/config.js';
wq.init(config).then(() => {
wq.prefetchAll();
});
// index.html
// <script type="module" src="/static/app/js/myapp.js"></script>
@wq/app for npm
// "wq create --with-npm" is similar to create-react-app, but
// replaces the contents of src/index.js with something like this:
import app from '@wq/app';
import config from './config';
app.init(config).then(() => {
app.prefetchAll();
});
// public/index.html
// <link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/css/myapp.css" />
API
@wq/app
is typically imported as app
, or as wq
when imported from the wq.js bundle. Any local variable name can be used. The app module provides the following methods and properties.
Configuration
app.init(config)
The main required usage of @wq/app is to initialize by passing a wq Configuration object to app.init()
. The function returns a Promise that will be resolved when the initialization is complete. After the app is configured,app.prefetchAll()
can be used to preload and cache data from all registered models (i.e. all entries in the pages
configuration that have list
set to true
).
app.init(config).then(() => {
app.prefetchAll(); // Optional
});
The available configuration options are shown below with their default values.
{
// @wq/app options
'debug': false,
'autoStart': true, // If false, then call app.start() after init
'backgroundSync': true,
'loadMissingAsHtml': true, // alternatively, loadMissingAsJson: false
'pages': { /* ... */ }, // from wq config
// Configuration for core modules
'router': { /* ... */ },
'store': { /* ... */ },
'outbox': { /* ... */ },
// Configuration for registered plugins
'map': { /* ... */ }
}
The configuration sections for the other core modules are passed on to the init()
function for each module. In a few instances, @wq/app overrides the default settings for the respective modules. See the documentation for wq Configuration object, @wq/router, @wq/store, @wq/outbox, and @wq/model 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.
Scalar Options
The debug
option enables console logging in @wq/app and the other core modules. If specified as a number, debug
will set the verbosity level in @wq/store.
The autoStart
option tells app.init()
to immediately trigger app.start()
on startup. The default is true, but you can set to false if you need to register additional custom routes before initializing React and Redux.
The boolean backgroundSync
flag controls the default behavior for @wq/outbox form submissions. When true
(the default), form submissions are synced in the background while the user is navigated to the next screen. When false
, forms remain on screen until the server responds. backgroundSync
can also be enabled or disabled on a per-form basis by setting the backgroundSync
prop. For example, the Login view sets backgroundSync
to false since it makes more sense to wait for a successful login before continuing.
The loadMissingAsHtml
and loadMissingAsJson
options tell @wq/app 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 will assume the entire model collection is stored locally, assume the page does not exist, and call therouter.notFound()
404 page. - If the associated model page is configured with
partial: true
, andloadMissingAsHtml
is set, @wq/app 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
, andloadMissingAsJson
is set, @wq/app will attempt to load the missing data from the server as JSON and render it locally.
pages
: URL routes
The 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 instances for all model-backed pages (those with list: true
) will be added to app.models
for convenience.
Creating a Configuration Module
The configuration object is typically defined as a module config.js
that depends on the server-created wq config, then adds the additional attributes needed to initialize @wq/app.
import config from './data/config.js';
// config.pages already exists in server-generated wq config
export default {
material: {
theme: {
primary: '#99999',
secondary: '#333333',
}
},
...config
}
Methods
app.use(plugin)
Register a plugin to customize @wq/app functionality. See Plugins.
app.nav(path)
Trigger a route change to the specified path, which should not include the application base URL.
app.nav('items/1');
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 render context and auth plugin state.
app.config
, app.wq_config
A copy of the @wq/app configuration object (see above) 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.models
After initialization, app.models
will contain a @wq/model 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);
});
});
Source
The source code for @wq/app is in the wq.app repository.