RoundingWellOS/marionette.toolkit

View on GitHub
docs/mixins/state.md

Summary

Maintainability
Test Coverage
# Marionette.Toolkit.StateMixin

`StateMixin` is a public mixin for [`App`](../app.md) or any Marionette class definition that uses a `Backbone.Model` for keeping state.

Use a `StateMixin` if your object/view needs to maintain information that isn't business data.  This provides a consistent method for storing and getting state, along with triggering related events.

## Documentation Index

* [Using StateMixin](#using-statemixin)
* [Setting default state](#setting-default-state)
* [StateMixin's `StateModel`](#statemixins-statemodel)
* [StateMixin's `stateEvents`](#statemixins-stateevents)
* [StateMixin API](#statemixin-api)
  * [Setting State `setState`](#setting-state)
  * [Resetting State `resetStateDefaults`](#resetting-state-defaults)
  * [Getting State `getState`](#getting-state)
  * [Toggling State `toggleState`](#toggle-state)
  * [Checking State `hasState`](#checking-state)
  * [Binding State Events `delegateStateEvents`](#binding-events)
  * [Unbinding State Events `undelegateStateEvents`](#unbinding-events)

### Using StateMixin

While `StateMixin` comes pre-mixined with `Marionette.Toolkit.App` and `Marionette.Toolkit.Component`, you can extend your own class with `StateMixin` by calling `initState` in your class's `initialize` passing any desired options.

```js
const MyStateModel = Backbone.Model.extend({});

const myClass = MnObject.extend({
  StateModel: MyStateModel

  initialize(options) {
    this.initState(options);
  }
});

_.extend(myClass.prototype, StateMixin)
```

You can also use `Marionette.Toolkit.mixinState` which is a utility to mixin the `StateMixin` into any `Marionette.MnObject`s or `Marionette.View`s. If there is no `StateModel` definition on your class then the `StateModel` will be defined as a vanilla `Backbone.Model`. However, if you have already defined `StateModel` on your class, your `StateModel` definition **will not be overwritten**.

```js
const MyStateModel = Backbone.Model.extend({});

const myClass = MnObject.extend({
  StateModel: MyStateModel

  initialize(options) {
    this.initState(options);
  }
});

mixinState(MyClass);
```

### Setting default state

Because the `StateModel` of the `StateMixin` has to be a `Backbone.Model`, it has access to model `defaults`. `defaults` should be defined on the `StateModel` definition.

```js
const MyToolKitApp = App.extend({
  StateModel: {
    defaults: {
      fooState: 'bar'
    }
  }
});

const myToolkitApp = new MyToolKitApp();

myToolkitApp.getState('fooState') === 'bar';
```

### StateMixin's `StateModel`

Define a `StateModel` on your class definition or pass as an option when calling `initState(options)`. This must be
a `Backbone.Model` object definition, not an instance.  If you do not
specify a `StateModel`, a vanilla `Backbone.Model` definition will be used.

#### Declared on Class
```js
const MyStateModel = Backbone.Model.extend({});

const MyClass = MnObject.extend({
  StateModel: MyStateModel

  initialize(options) {
    this.initState(options);
  }
});
```

#### Passed as Option on Initialization
```js
const MyStateModel = Backbone.Model.extend({});

const MyClass = MnObject.extend({
  initialize(options) {
    this.initState(options);
  }
});

const myClass = new MyClass({
  StateModel: MyStateModel
});
```

You can also define `StateModel` as a function. In this form, the value
returned by this method is the `StateModel` class that will be instantiated.
When defined as a function, it will receive the `options` passed to the `constructor`.

```js
const MyStateModel = Backbone.Model.extend({});

App.extend({
  StateModel(options){
    if(options.foo){
      return MyStateModel;
    }
    return Backbone.Model;
  }
});
```

#### Passed as Option on Initialization

Alternatively, you can specify a `StateModel` in the options for
the `constructor`:

```js
const MyToolKitApp = App.extend({...});

new MyToolKitApp({
  StateModel: MyStateModel
});
```

### StateMixin's `State`

Optionally define a `state` attributes object on your class initialization or pass as an option when calling `initState(options)`.

```js
const MyStateModel = Backbone.Model.extend({});

const MyClass = MnObject.extend({
  StateModel: MyStateModel

  initialize(options) {
    this.initState(options);
  }
});

new MyClass({
  state: {
    foo: 'bar'
  }
});
```

### StateMixin's `stateEvents`

`StateMixin` can bind directly to state events in a declarative manner:

```js
const MyToolKitApp = App.extend({
  stateEvents: {
    'change': 'stateChanged'
  },
  stateChanged(model, options){
    console.log('Changed!');
  }
});

const myToolkitApp = new MyToolKitApp();

// will log "Changed!"
myToolkitApp.setState('foo', 'bar');

```

For more information on the various declarative options, see the
implementations of `modelEvents` and `collectionEvents` in the [Marionette.View](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#viewmodelevents-and-viewcollectionevents) documentation.

## StateMixin API

### Setting State

`StateMixin` has a `setState` method that exposes the `Backbone.Model.set`
for the `StateMixin`'s attached `StateModel`.  Implementation will match [Backbone.Model.set](http://backbonejs.org/#Model-set) documentation.

```js
const myToolKitApp = new App({
    stateEvents: {
      'change:foo': 'alert'
    },
    alert(){
      console.log('alert!');
    }
});

// This will trigger the "change:foo" event and log "alert!" to the console.
myToolKitApp.setState('foo', 'bar');
```

### Resetting State

`StateMixin` has a `resetStateDefaults` method that sets the `StateModel` instance attributes back to the [defined defaults](#setting-default-state).  Implementation will match [Backbone.Model.defaults](http://backbonejs.org/#Model-defaults) documentation.

```js
const MyStateModel = Backbone.Model.extend({
  defaults: {
    foo: 'bar'
  }
});

const myToolKitApp = new App({
  StateModel: MyStateModel
});

// This will trigger the "change:foo" event and log "alert!" to the console.
myToolKitApp.setState('foo', 'hello');

console.log(this.getState('foo')); // hello

myToolKitApp.resetStateDefaults();

console.log(this.getState('foo')); // bar
```

### Getting State

`StateMixin` has a `getState` method that exposes the `Backbone.Model.get`
for the `Statemixin`'s attached `StateModel`.  Implementation will match [Backbone.Model.get](http://backbonejs.org/#Model-get) documentation with the
exception that not passing any attribute to "get" will return the state model
instance.

```js
const MyStateModel = Backbone.Model.extend({
  defaults: {
    foo: 'bar'
  }
});

const myToolKitApp = new App({
  StateModel: MyStateModel
});

myToolKitApp.start()

// returns "bar"
myToolKitApp.getState('foo');

// returns myToolKitApp's MyStateModel instance.
myToolKitApp.getState();
```

### Toggling State

`StateMixin` has a `toggleState` method that sets the `StateModel` instance attribute to a boolean value.
Attributes that do not exist on the state will be created.
Not passing in a value will toggle the attribute's current value, while non-boolean values will be coerced to `true` or `false`.

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

myToolKitApp.setState('foo', true);

// sets "foo" attribute to false
myToolKitApp.toggleState('foo');

// coerces "bar" string into boolean, setting "foo" attribute to true
myToolKitApp.toggleState('foo', 'bar');

// sets a "baz" attribute on the state with a true value
myToolKitApp.toggleState('baz');
```

### Checking State

`StateMixin` has a `hasState` method that checks the `StateModel` instance for a specified attribute.
Passing an attribute that does not exist on the state will return `false`.

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

// returns false
myToolKitApp.hasState('foo')

myToolKitApp.setState('foo', 'bar');

// returns true
myToolKitApp.hasState('foo');

// coerces "bar" string into boolean, setting "foo" attribute to true
myToolKitApp.setState('foo', false);

// Still returns true
myToolKitApp.hasState('foo');
```

### Binding State Events

`StateMixin` has a `delegateStateEvents` that will bind all events specified
in the `stateEvents` option. Implementation matches [Backbone.View.delegateEvents](http://backbonejs.org/#View-delegateEvents).

```js
const myToolKitApp = new App({
    stateEvents: {
      'change:foo': 'alert'
    },
    alert(){
      console.log('alert!');
    }
});

// This will trigger the "change:foo" event and log "alert!" to the console.
myToolKitApp.setState('foo', 'bar');

myToolKitApp.undelegateStateEvents();

// This will still trigger the `change:foo` event, but will NOT log 'alert!'
// in the console
myToolKitApp.setState('foo', 'baz');

myToolKitApp.delegateStateEvents();

// This will trigger the "change:foo" event and log "alert!" to the console
// once again.
myToolKitApp.setState('foo', 'bar');

```

### Unbinding State Events

`StateMixin` has a `undelegateStateEvents` that will unbind all event listeners
specified on the `StateModel` option. Implementation matches [Backbone.View.undelegateEvents](http://backbonejs.org/#View-undelegateEvents).

```js
const myToolKitApp = new App({
    stateEvents: {
      'change:foo': 'alert'
    },
    alert(){
      console.log('alert!');
    }
});

// This will trigger the "change:foo" event and log "alert!" to the console.
myToolKitApp.setState('foo', 'bar');

myToolKitApp.undelegateStateEvents();

// This will still trigger the `change:foo` event, but will NOT log 'alert!' in the console
myToolKitApp.setState('foo', 'baz');
```