@wq/app
======

[@wq/app]

**@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 and page renderer that can fall back to server-rendered pages via [shared client/server templates][templates].
 * 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][config] generated by the URL router on the server, that describes the available routes and underlying models.
 * An optional [authentication][auth] 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][@wq/map] or even a fully custom data model (via plugin hooks).

## Installation

@wq/app is available via a PyPI package ([wq.app](http://pypi.org/project/wq.app)) as well as an npm package ([@wq/app](https://www.npmjs.com/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

```bash
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.db, wq.start, etc.)
# pip install wq.app      # install wq.app only
```

#### @wq/app for npm

```bash
npm install @wq/app
```

### Project Layout

Whether using wq.app for Python or @wq/app for npm, we highly recommend using [wq.start] to initialize your project.  In particular, wq.start will automatically configure your JavaScript build settings and link the proper imports for @wq/app.  If you are not using wq.start, you can create a similar layout using the examples below.

#### wq.app for PyPI

```javascript
// "wq start --without-npm" creates a layout similar to this:

// 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.init(config).then(function() {
        app.jqmInit();
    });
});

// index.html
// <script src="js/lib/require.js" data-main="js/myapp"></script>
```

#### @wq/app for npm

```javascript
// "wq start --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(function() {
    app.jqmInit();
});

// public/index.html
// <link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/css/myapp.css" />
```

## API

`@wq/app` is typically imported as `app`, though any local variable name can be used.  The app module provides the following methods and properties.

### Initialization

#### `app.init(config)`

The main required usage of @wq/app is to initialize it with `app.init()`.  The function returns a [Promise] that will be resolved when the initialization is complete.  After the app is configured, `app.jqmInit()` starts up jQuery Mobile and renders the initial screen.  Finally, `app.prefetchAll()` preloads and caches data from all registered models (i.e. all entries in the `pages` configuration that have `list` set to `true`).

```javascript
app.init(config).then(function() {
    app.jqmInit();
    app.prefetchAll();  // Optional
});
```

The available configuration options are shown below with their default values.

```javascript
{
    // @wq/app options
    'debug': false,
    'jqmInit': false,
    'backgroundSync': true, // alternatively, noBackgroundSync: false
    'loadMissingAsHtml': true, // alternatively, loadMissingAsJson: false
    
    'pages': { /* ... */ }, // from wq config
    'transitions': { /* ... */} // see below
 
    // Configuration for core modules
    'router': { /* ... */ },
    'store': { /* ... */ },
    'outbox': { /* ... */ },
    'template': { /* ... */ },
    
    // 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/router], [@wq/store], [@wq/outbox], and [@wq/template] 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 `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 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 `data-wq-background-sync` attribute.  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.

> **As of wq.app 1.2**, the `noBackgroundSync` flag is no longer supported - instead, set `backgroundSync: false`.  Also, setting `backgroundSync` to a numeric sync interval is no longer supported, as all retry logic is handled internally by Redux Offline.  See [@wq/outbox] for more information.

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 the `router.notFound()` 404 page.
  * If the associated model page is configured with `partial: true`, and `loadMissingAsHtml` 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`, and `loadMissingAsJson` 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][config].  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][config].

> **Note**: If you need to customize an option in the server generated `pages`, you should specify it when calling [router.register_model()][router] in [wq.db.rest] rather than overriding the `pages` section in your `config.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.

##### `transitions`: Page Transitions

Configuration 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`.
`maxwidth` | A shortcut for `$.mobile.maxTransitionWidth`.  Defaults to 800 (note that vanilla jQuery Mobile defaults to false)
`save` | **Removed in wq.app 1.2**

#### Creating a Configuration Module

The configuration object is typically defined as a module `config.js` that depends on the [server-created wq config][router] and a template fixture, then adds the additional attributes needed to initialize @wq/app.

##### wq.app for PyPI

```javascript
define(['data/config', 'data/templates', function(config, templates) {
// config.pages already exists on server-generated wq config

config.template = {
    // Configure templates and default context variables
    templates: templates,
    partials: templates.partials,
    defaults: { /* ... */ }
}

return config;
});
```

##### @wq/app for npm

```javascript
import config from './data/config';
import templates from './data/templates';

// config.pages already exists on server-generated wq config

config.template = {
    // Configure templates and default context variables
    templates: templates,
    partials: templates.partials,
    defaults: { /* ... */ }
}

export default config;
```

#### Inlining Templates
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][build].  The resulting object should be of the form:

```javascript
{
     '[modelname]_[mode]': "<html>...</html>",
     'partials': { ... }
}
```

#### Deprecated Options

> **Note**: The `login` and `logout` jQuery events, as well as the top-level hooks `postsave`, `saveerror`, `showOutboxErrors`, `presync`, and `postsync` (which were to be defined as functions on the @wq/app `config` object), have all been removed and replaced with plugin hooks providing similar functionality.  See below for more information.

#### `<form>` Handler

`app.init()` registers a custom submit handler that takes normal form POSTs and converts them to [@wq/outbox] 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] as `Blob` instances.  See the @wq/app:photos documentation below 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 functionality.

```xml
<form method="post" action="/items" data-ajax="false">
```

By default @wq/app's 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:

```xml
<form method="post" action="/custom" data-wq-json="false">
```

### Other Methods

#### `app.use(plugin)`

Register a plugin to customize @wq/app functionality.  See Plugins below.

#### `app.go()`

> **As of wq.app 1.2** `app.go()` has been removed.  Use `app.nav()` instead.

#### `app.nav(path)`

Trigger a route change to the specified path, which should not include the application base URL.

```javascript
app.nav('items/1');
```

#### `app.sync()`

> **As of wq.app 1.2**, `app.sync()` has been removed.  Use `app.retryAll()` instead.

#### `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][templates], e.g. `{{#is_authenticated}}{{user.username}}{{/is_authenticated}}`.

#### `app.config`, `app.wq_config`
A copy of the @wq/app configuration object (see above) and the [wq configuration object][config], 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 contexts][templates] as `{{app_config}}`, while `app.wq_config` is provided as `{{wq_config}}`.

#### `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`).

```javascript
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][templates] as `{{native}}`.


## Plugins

@wq/app provides a simple plugin API to facilitate the incorporation of arbitrary custom functionality beyond the built-in page rendering and offline storage APIs.  By using a central plugin API, @wq/app can ensure that any custom code you incorporate is executed at the right time, no matter whether the referenced page is the first one to load or is loaded via AJAX during jQuery Mobile navigation events.  Most plugin hooks even work the same whether a page is [rendered on the client or on the server][templates].

A plugin is defined as a simple object with one or more of the hooks below.  Plugins should always be registered via `app.use(plugin)` before calling `app.init()`.

For example, if you wanted to provide a `{{date}}` variable for use in HTML templates, you could define the following plugin:

```javascript
var datePlugin = {
   context: function() {
       return {'date': new Date().toDateString()}
   }
}
app.use(datePlugin);
```

### Plugin Hooks

name | processed by | purpose
-----|-------------|---------
`name` | | Uniquely identify the plugin.  The `init` and `reducer` hooks require a name to function properly; it is optional otherwise.
`init(config)` | @wq/app | Initialize the plugin during @wq/app initialization.  If the plugin is named `"example"`, `app.config.example` will be passed to this function.
`context(ctx, routeInfo)` | [@wq/router] | Add information to the context before rendering.
`run($page, routeInfo)` | [@wq/router] | Customize UI (with jQuery) after the page is rendered and shown
`ajax(url, data, method, headers)` | [@wq/store] | Override how requests are submitted and/or responses are parsed
`onsync(item)` | [@wq/outbox] | Process outbox item just after it is synced to server
`postsaveurl(item, alreadySynced)` | @wq/app | Generate a custom URL to navigate to after form submission.  Note that in many cases, the [postsave page configuration option][config] can be used instead.
`actions` | [@wq/store] | Define Redux action creators
`thunks` | [@wq/router] | Define asynchronous tasks in response to Redux actions
`reducer(pluginState, action)` | [@wq/store] | Update plugin state based on Redux actions
`render(state)` | [@wq/router] | Render global state changes (outside of the page rendering pipeline)

> **Note:** The following hooks and events were removed in wq.app 1.2:

name | type | suggested migration path
-----|------|-------------------------
`onsave(item, result)` | Plugin Hook | Use an `onsync()` hook instead.  The server result will be available as `item.result`.
`saveerror(item, reason, $form)` | Config Hook | Use an `onsync()` hook instead.  The error will be available as `item.error`.
`showOutboxErrors()` | Config Hook | Use an `onsync()` and/or `run()` plugin hook instead.
`postsave()` | Config Hook | Use a `postsaveurl()` plugin hook instead.
`presync()` / `postsync()` | Config Hook | Use the template context as needed for UI customizations.  Pages displaying outbox contents are automatically re-rendered after each sync.
`"login"`, `"logout"` | jQuery events | Use the template context as needed for UI customizations.  As of wq.app 1.2, all pages (including server-rendered pages) are automatically re-rendered on the client if the login state changes.

### Available Plugins

wq.app comes with a number of predefined plugins for common use cases.  The most essential plugin is probably [@wq/map], which seamlessly integrates configurable Leaflet maps into the @wq/app page rendering flow.  The full list of included plugins is below.  The source code for each plugin should be useful as a reference for creating your own custom plugin.  In particular, note the use of the `name`, `init()`, `context()`, and `run()` properties on each module.

| Module | Description |
|--------|-------------|
| @wq/app:spinner | Start and stop jQuery Mobile spinner |
| @wq/app:patterns | Support for nested forms |
| @wq/app:photos | Helpers for requesting and displaying user photos on a mobile device |
| [@wq/map] | Leaflet integration for displaying and editing geographic information via GeoJSON |
| [@wq/map:locate] | Utilities for requesting the user's location |

### Defining a Custom Plugin

Plugins are typically defined separate JavaScript files and imported.  `app.use()` registers the plugin and any provided hooks. 

#### wq.app for PyPI

```javascript
// myapp/myplugin.js
define({
    // Name and init are optional if no config is needed
    'name': 'myPlugin',
    'init': function(config) {
        this.config = config;
        // Note: @wq/app is available as this.app
    },

    // Customize context before rendering page
    'context': function(context, routeInfo) {
        // ...
     }
});

// myapp/main.js
define(['wq/app', './myplugin', './config'],
function(app, myPlugin, config) {

app.use(myPlugin);
app.init(config).then(function() {
    app.jqmInit();
    app.prefetchAll();
});

});
```

#### @wq/app for npm

```javascript
// src/myplugin.js
export default {
    // Name and init are optional if no config is needed
    name: 'myPlugin',
    init(config) {
        this.config = config;
        // Note: @wq/app is available as this.app
    },

    // Customize context before rendering page
    context(context, routeInfo) {
        // ...
    }
};

// src/index.js
import app from '@wq/app';
import myPlugin from './myplugin';
import config from './config';

app.use(myPlugin);
app.init(config).then(function() {
    app.jqmInit();
    app.prefetchAll();
});
```

## `patterns` plugin

[@wq/app:patterns]

**@wq/app:patterns** is a @wq/app plugin providing support for [nested forms].

#### wq.app for PyPI

```javascript
define(['wq/app', 'wq/patterns', './config'],
function(app, patterns, config) {

app.use(patterns);
app.init(config).then(...);

});
```

#### @wq/app for npm

```javascript
import app, { patterns } from '@wq/app';

app.use(patterns);
app.init(config).then(...);
```

This plugin provides logic for adding and removing copies of a nested form.  To use it, the edit template should contain one or more `<button>`s with `data-wq-action` and `data-wq-section` attributes.  The `data-wq-section` should match the name of the nested form and corresponding array template block (`{{#section}}{{/section}}`).

data-wq-action | description
-------|-------------
`addattachment` | Add another nested form, by rendering a copy of the section template block.
`removeattachment` | Remove the current nested form.

An `addattachment` button is provided in the default edit templates for nested forms.

## `photos` plugin

[@wq/app:photos]

**@wq/app:photos** is a @wq/app plugin that integrates with the [PhoneGap Camera API][camera] and shows previews for user-selected photos.  Together with the file processing functions in [@wq/app] and [@wq/outbox], @wq/app:photos provides a complete solution for allowing volunteers to capture and upload photos via your offline-capable web or mobile app.  Captured photos are saved in an outbox ([@wq/outbox]) until they can be synchronized to the server.

<div data-interactive id='photo-example'>
  <ul data-role="listview">
    <li class="ui-field-contain">
      <img src="https://wq.io/images/empty.png" id="preview">
      <label for="image">Photo</label>
      <input type="file" name="photos" id="image" data-wq-preview="preview">
    </li>
  </ul>
</div>

The [Species Tracker](http://species.wq.io) application provides a complete demonstration of the offline capabilities of @wq/app:photos.

### API
@wq/app:photos does not require a global configuration, as it is configured via data-wq- attributes on the related form elements.

#### wq.app for PyPI

```javascript
define(['wq/app', 'wq/photos', './config'],
function(app, photos, config) {

app.use(photos);
app.init(config).then(...);
```

#### @wq/app for npm

```javascript
import app, { photos } from '@wq/app';

app.use(photos);
app.init(config).then(...);
```

To leverage @wq/app:photos in your template, create your form elements with data-wq- attributes.  If you are using [wq.start] and the default project layout, these will be set automatically in the generated edit templates for models with image fields.

element | attribute | purpose
--------|-----------|---------
`<input type=file>` | `data-wq-preview` | Indicates the id of an `<img>` element to display a preview image in after a photo is selected.
`<button>` | `data-wq-action` | Indicates the function to call (`take` or `pick`) when the button is clicked
`<button>` | `data-wq-input` | The name of a hidden input to populate with the name of the captured photo.  (The photo itself will be saved in offline storage).
`<input type=hidden>` | `data-wq-type` | Notifies @wq/app that the hidden element is intended to be interpreted as the name of a photo captured via @wq/app:photos.  The element should typically have the `data-wq-preview` attribute set as well.

The `take` and `pick` actions are wrappers for PhoneGap/Cordova's camera.getPicture() API, meant to be used in hybrid apps where `<input type=file>` doesn't work (e.g. on older devices or broken Android implementations).

Below is an example template with the appropriate attributes set:

```xml
<img id=preview-image>

{{^native}}
<input type=file name=file data-wq-preview=preview-image>
{{/native}}

{{#native}}
<button data-wq-action=take data-wq-input=file>
  Take Picture
</button>
<button data-wq-action=pick data-wq-input=file>
  Choose Picture
</button>
<input id=filename type=hidden name=file
   data-wq-type=file data-wq-preview=preview-image>
{{/native}}
```

Note the use of the `{{#native}}` context flag which is set automatically by [@wq/app].  See the [Species Tracker template](https://github.com/powered-by-wq/species.wq.io/blob/master/templates/partials/new_photo.html) for a working example.

[@wq/app]: https://github.com/wq/wq.app/blob/master/packages/app
[@wq/app:spinner]: https://github.com/wq/wq.app/blob/master/packages/app/src/spinner.js
[@wq/app:patterns]: https://github.com/wq/wq.app/blob/master/packages/app/src/patterns.js
[@wq/app:photos]: https://github.com/wq/wq.app/blob/master/packages/app/src/photos.js

[wq.app]: https://wq.io/wq.app
[wq.db]: https://wq.io/wq.db
[wq.start]: https://wq.io/wq.start

[@wq/store]: https://wq.io/docs/store-js
[@wq/model]: https://wq.io/docs/model-js
[@wq/outbox]: https://wq.io/docs/outbox-js
[@wq/router]: https://wq.io/docs/router-js
[@wq/template]: https://wq.io/docs/template-js

[@wq/map]: https://wq.io/docs/map-js
[@wq/map:locate]: https://wq.io/docs/map-js

[URL structure]: https://wq.io/docs/url-structure
[auth]: https://wq.io/docs/auth
[router]: https://wq.io/docs/router
[wq.db.rest]: https://wq.io/docs/about-rest
[config]: https://wq.io/docs/config
[templates]: https://wq.io/docs/templates
[patterns]: https://wq.io/docs/about-patterns
[nested forms]: https://wq.io/docs/nested-forms

[PhoneGap]: http://phonegap.com
[Cordova]: http://cordova.io
[page transitions]: http://demos.jquerymobile.com/1.4.5/transitions/
[built-in configuration options]: https://api.jquerymobile.com/global-config/

[collectjson]: https://wq.io/docs/collectjson
[build]: https://wq.io/docs/build

[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

[camera]: https://www.npmjs.com/package/cordova-plugin-camera

[create-react-app]: https://facebook.github.io/create-react-app/
