RoundingWellOS/marionette.toolkit

View on GitHub
docs/app.md

Summary

Maintainability
Test Coverage
# Marionette.Toolkit.App

`Marionette.Toolkit.App` is an extension of `Marionette.Application`. Its purpose is to provide an object with a `initialize`/`start`/`stop`/`destroy` lifecycle. `App` has several mixins:

* [`StateMixin`](./mixins/state.md) to maintain application state.
* [`EventListenersMixin`](./mixins/event-listeners.md) to bind all events to an `App` while running (and only those) will be remove when stopped.
* [`ChildAppsMixin`](./mixins/child-apps.md) to manage the addition and removal of child `App`s and relating the child `App` lifecycle with the parent `App` lifecycle.
* [`ViewEventsMixin`](./mixins/view-events.md) for proxying events from the app's view to the app.

## Documentation Index
* [Using Toolkit App](#using-toolkit-app)
* [Lifecycle Settings](#lifecycle-settings)
  * [App's `startAfterInitialized`](#apps-startafterinitialized)
  * [App's `preventDestroy`](#apps-preventdestroy)
  * [App's `startWithParent`](#apps-startwithparent)
  * [App's `restartWithParent`](#apps-restartwithparent)
  * [App's `stopWithParent`](#apps-stopwithparent)
* [Lifecycle API](#lifecycle-api)
  * [App `start`](#app-start)
  * [App `restart`](#app-restart)
  * [App `stop`](#app-stop)
  * [App `isRunning`](#app-isrunning)
  * [App `isRestarting`](#app-isrestarting)
  * [App `destroy`](#app-destroy)
* [Lifecycle Events](#lifecycle-events)
  * ["before:start" / "start" events](#beforestart--start-events)
  * ["before:stop" / "stop" events](#beforestop--stop-events)
* [Application State](#application-state)
  * [App `StateModel`](#app-statemodel)
  * [App `stateEvents`](#app-stateevents)
* [Application Region](#application-region)
  * [App `setRegion`](#app-setregion)
  * [App `getRegion`](#app-getregion)
* [Application View](#application-view)
  * [App `setView`](#application-setview)
  * [App `getView`](#application-getview)
  * [App `showView`](#application-showview)
  * [App `showChildView`](#app-showchildview)
  * [App `getChildView`](#app-getchildview)
  * [View Events](#view-events)

## Using Toolkit App

Beyond the Toolkit App's lifecycle, its biggest feature is [childApps](./mixings/child-apps.md#apps-childapps).
This allows children to be tied to the lifecycle of their parent's lifecycle.
As Toolkit App extends [`Marionette.Application`](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.application.md) it can be used as a drop-in replacement.

## Lifecycle Settings

### App's `startAfterInitialized`

Call `start` immediately after `initialize` if `true`.  Default value is `false`.
Can be added as an option when instantiated or defined on the `App` definition.
It can also be defined as a function returning a boolean value.

```js
const MyApp = App.extend({
  initialize(){
    this.isRunning() === false;
  },
  startAfterInitialized: true
});

const myApp = new MyApp();

myApp.isRunning() === true;

```

### App's `preventDestroy`

If set `true` this `App` will not be destroyed when its parent `App` is destroyed.
Default value is `false`.
Can be added as an option when instantiated or defined on the `App` definition.
It can also be defined as a function returning a boolean value.

```js
const myApp = new App();

const myChildApp = myApp.addChildApp('myChildApp', {
  AppClass: Marionette.Toolkit.App,
  preventDestroy: false
});

const myPreventDestroyApp = myApp.addChildApp('myPreventDestroyApp', {
  AppClass: App,
  preventDestroy: true
});

myApp.destroy();

// logs true
console.log(myChildApp.isDestroyed());

// logs false
console.log(myPreventDestroyApp.isDestroyed());

```

### App's `startWithParent`

If set `true` this `App` will start when its parent `App` starts.
Default value is `false`.
Can be added as an option when instantiated or defined on the `App` definition.
It can also be defined as a function returning a boolean value.

```js
const myApp = new App();

const myChildApp = myApp.addChildApp('myChildApp', {
  AppClass: App,
  startWithParent: false
});

const myStartWithParentApp = myApp.addChildApp('myStartWithParentApp', {
  AppClass: App,
  startWithParent: true
});

myApp.start();

// logs false
console.log(myChildApp.isRunning());

// logs true
console.log(myStartWithParentApp.isRunning());
```

### App's `restartWithParent`

Default value is `null`.
It can also be defined as a function returning a boolean value.

If `restartWithParent` is `true` the child app will stop and start with the parent when `restart`ing.
If `restartWithParent` is `false` the child app will neither stop nor start with the parent when `restart`ing.
If `restartWithParent` is `null` the child app will respect `startWithParent` and `stopWithParent` when `restart`ing.

```js
const myApp = new App();

const persistantChildApp = myApp.addChildApp('persistantChildApp', {
  AppClass: App,
  restartWithParent: false
});

persistantChildApp.on('stop start', function(options) {
    console.log(this.isRestarting());
});

// does not log
myApp.restart();

const restartingChildApp = myApp.addChildApp('restartingChildApp', {
  AppClass: App,
  restartWithParent: true
});

myApp.start();

restartingChildApp.on('stop start', function(options) {
    console.log(this.isRestarting());
});

// logs true twice
myApp.restart();

```

### App's `stopWithParent`

If set `true` this `App` will stop when its parent `App` stops.
Default value is `true`.
Can be added as an option when instantiated or defined on the `App` definition.
It can also be defined as a function returning a boolean value.

```js
const myApp = new App();

const myChildApp = myApp.addChildApp('myChildApp', {
  AppClass: App,
  stopWithParent: false
});

const myStopWithParentApp = myApp.addChildApp('myStopWithParentApp', {
  AppClass: App,
  stopWithParent: true
});

myApp.start();
myChildApp.start();
myStopWithParentApp.start();

myApp.stop();

// logs true
console.log(myChildApp.isRunning());

// logs false
console.log(myStopWithParentApp.isRunning());
```

## Lifecycle API

### App `start`

This method sets the `App` to its running state.
Events added after `start` are registered for removal `onStop`.
This triggers ["before:start" / "start" events](#beforestart--start-events).

Initial state can be passed as an option to `start`.

```js
const myApp = new App();

myApp.on('start', function(options){
  console.log('My App Started!');
  options.foo === true;
  this.getState('bar') === 'baz';
});

// false
myApp.isRunning();

const initialState = {
  bar: 'baz'
};

// "My App Started!" logged
myApp.start({
  foo: true,
  state: initialState
});

// true
myApp.isRunning();

// Nothing is logged
myApp.start();
```

### App `restart`

This method saves the current state of the app before stopping it.
It then starts the app again with the preserved state attributes.

```js
const myApp = new App();

myApp.on('before:start', function(options) {
    console.log(options.state);
});

const initialState = {
  foo: 'bar'
};

// logs { foo: 'bar' }
myApp.start({
  state: initialState
});

myApp.setState('foo', 'baz');

// logs { foo: 'baz' }
myApp.restart();
```

### App `stop`

This method stops the `App`'s running state.
Events added after `start` are registered for removal `onStop`.
This triggers ["before:stop" / "stop" events](#beforestop--stop-events).

```js
const myApp = new App();

myApp.on('stop', function(options){
  console.log('My App Stopped!');
  options.foo === true;
});

// Nothing is logged
myApp.stop();


myApp.start();

// true
myApp.isRunning();

// "My App Stopped!" logged
myApp.stop({
  foo: true
});

// false
myApp.isRunning();

// Nothing is logged
myApp.stop();
```

### App `isRunning`

Returns a Boolean indicating whether or not the `App` is running.

```js
const myApp = new App();

myApp.start();

myApp.isRunning() === true;

myApp.stop();

myApp.isRunning() === false;

```

### App `isRestarting`

Returns a Boolean indicating whether or not the `App` is restarting.

```js
const myApp = new App();

myApp.on('before:stop', function(options) {
    console.log(this.isRestarting());
});

myApp.start();

// logs true
myApp.restart();

// logs false
myApp.stop();

```

### App `destroy`

This method stops the `App` if running and sets the `App`'s state to destroyed.

```js
const myApp = new App();

myApp.start();

myApp.isRunning() === true;
myApp.isDestroyed() === false;

myApp.destroy();

myApp.isRunning() === false;
myApp.isDestroyed() === true;

```

## Lifecycle Events

### `before:start` / `start` events

The "before:start" event and corresponding `onBeforeStart`
method are triggered just before the `App` `isRunning` is set `true`. This is the appropriate place to set up your app's initial state. Calling `setState` in `onBeforeStart` will not trigger any events.

The "start" event and corresponding `onStart`
method are triggered after the `App` `isRunning` is set `true`. Once `onStart` is run, state event listeners have been applied.

```js
const MyApp = App.extend({
  // ...

  onBeforeStart(options){
    // ...
  },

  onStart(options){
    // ...
  }
});

const myApp = new MyApp({...});

myApp.on('before:start', function(options){
  // ...
});

myApp.on('start', function(options){
  // ...
});
```

### `before:stop` / `stop` events

The "before:stop" event and corresponding `onBeforeStop`
method are triggered just before the `App` `isRunning` is set `false`.

The "stop" event and corresponding `onStop`
method are triggered after the `App` `isRunning` is set `false`.

```js
const MyApp = App.extend({
  // ...

  onBeforeStop(options){
    // ...
  },

  onStop(options){
    // ...
  }
});

const myApp = new MyApp({...});

myApp.on('before:stop', function(options){
  // ...
});

myApp.on('stop', function(options){
  // ...
});
```

### App `StateModel`

A [`StateModel`](./mixins/state.md#statemixins-statemodel) class can be passed to App instantiation as an option or defined on the App.

```js
const myApp = new MyApp({
  StateModel: MyStateModel
});
```

### App `stateEvents`

A [`stateEvents`](./mixins/state.md#statemixins-stateevents) hash can be passed to App instantiation as an option or defined on the App.

```js
const MyApp = App.extend({
  stateEvents: {
    'change': 'onChangeState'
  }
  onChangeState() {
    // Handle state change event
  }
});
```

## Application State

Application state can be passed to [App `start`](#app-start) as a `state` option. The state is maintained while the app is running.

```js
myApp.start({
  state: {
    limit: 10
  }
});

myApp.getState('limit') === 10;
```

## Application Region

A `Marionette.Region` instance can be passed to [App `start`](#app-start) as a `region` option,
[setting the App region](#app-setregion), making it available to both [`before:start` and `start`](#beforestart--start-events) events.

```js
myApp.start({
  region: myRegion
});

myApp.getRegion() === myRegion;
```

### App `setRegion`

Calling `setRegion` will replace the `App`'s region making it available to the App's [Region API](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.application.md#getregion).
Unlike `Marionette.Appliation`'s [region attribute](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.application.md#root-layout), `setRegion` only accepts a Region instance.

```js
myApp.setRegion(myView.getRegion('appRegion'));

```

### App `getRegion`

`getRegion` performs two functions. When passed no arguments returns the app's region.
You can also give `getRegion` a region name string which will attempt to return a region from the app's view.
It is sugar for `myApp.getView().getRegion('regionName') === myApp.getRegion('regionName');`.

```js
const MyApp = Toolkit.App.extend({
  region: '#app-hook'
});

const myView = new Mn.View({
  template: MyTemplate,
  regions: {
    foo: '#foo'
  }
});

myApp.getRegion(); // #app-hook region

myApp.showView(myView);
myApp.getRegion('foo');  // foo region on myView
```

## Application View

A [`Marionette.Application`](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.application.md) can have an associated view shown in its region with `showView`.
Toolkit takes this a step further and proxies that view's `showChildView` and `getChildView`. This is simply sugar for common patterns.
A View instance can be passed to [App `start`](#app-start) as an option,
[setting the App view](#app-setview), making it available to both [`before:start` and `start`](#beforestart--start-events) events.

```js
myApp.start({
  view: myView
});

myApp.getView() === myView;
```

### App `setView`

Assign a view to an App. There are two notable use cases for this.
First, this allows you to use `App.getRegion('regionName')` or `App.showChildView`
without first showing the view in the App's region. This way all of the App's children
can be setup and rendered prior to attaching anything to the DOM.

```js
const MyApp = Toolkit.App.extend({
  onStart() {
    this.setView(myLayoutView);
    this.startChildApp('child', { region: this.getRegion('child') });
    this.showChildView('title', 'My App');
    this.showView();
  }
});
```

The second is when you want to associate an App with a view already in a region or one that
won't be shown in a region.

```js
const myView = new Mn.View.extend({
  el: $('#existing-dom'),
  regions: {
    foo: '#foo'
  }
});

const myApp = new MyApp();

myApp.start();

myApp.setView(myView);

myApp.getRegion('foo') === myView.getRegion('foo');
```

`setView` returns the view.

```js

const myView = myApp.setView(new MyView());

// myApp.listenTo(myView, ...);
```

### App `getView`

Returns the view instance associated with the App.

```js
myApp.setView(fooView);

myApp.getView() === fooView;

myApp.showView(barView);

myApp.getView() === barView;

myApp.getRegion().show(bazView);

myApp.getView() === bazView;
```

### App `showView`

Shows a view instance in the App's region.
The first argument for `showView` is the view instance, but if left undefined the App will
attempt to use the current view ie: `myApp.getView()`. The second argument is region.show options

```js
myApp.showView(myView, { replaceElement: true });


// Alternatively
myApp.setView(fooView);

// Show fooView
myApp.showView();
```

`showView` returns the view.

```js

const myView = myApp.showView(new MyView());

// myApp.listenTo(myView, ...);
```

### App `showChildView`

This method will help when a region from the App's view is needed.

It has the same API as a `Marionette.View`'s `showChildView` and returns the show view.

```js
myApp.showChildView('fooRegion', myChildView, 'fooArg');

//is equivalent to
myApp.getView().getRegion('fooRegion').show(myChildView, 'fooArg');
```

`showChildView` returns the child view.

```js

const myView = myApp.showChildView('fooRegion', new MyView());

// myApp.listenTo(myView, ...);
```

### App `getChildView`

Like `showChildView`, `getChildView` is a helper for getting a view shown in a region belonging to the App's view.

It has the same API as a `Marionette.View`'s `getChildView`.

```js
myApp.getChildView('fooRegion');

//is equivalent to
myApp.getView().getRegion('fooRegion').currentView;
```

### View events

View events for an App's view can be proxied following a very similar API to what you would
expect on a `Marionette.View` and `Marionette.CollectionView` with their children.

You can use `viewEvents`, `viewTriggers` and `viewEventPrefix` for auto-proxying events.

For more information see the [ViewEventsMixin documentation](./mixins/view-events.md).