StateMixin
is a public mixin for App
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.
- Using StateMixin
- Setting default state
- StateMixin's
StateModel
- StateMixin's
stateEvents
- StateMixin API
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.
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.
const MyStateModel = Backbone.Model.extend({});
const myClass = MnObject.extend({
StateModel: MyStateModel
initialize(options) {
this.initState(options);
}
});
mixinState(MyClass);
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.
const MyToolKitApp = App.extend({
StateModel: {
defaults: {
fooState: 'bar'
}
}
});
const myToolkitApp = new MyToolKitApp();
myToolkitApp.getState('fooState') === 'bar';
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.
const MyStateModel = Backbone.Model.extend({});
const MyClass = MnObject.extend({
StateModel: MyStateModel
initialize(options) {
this.initState(options);
}
});
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
.
const MyStateModel = Backbone.Model.extend({});
App.extend({
StateModel(options){
if(options.foo){
return MyStateModel;
}
return Backbone.Model;
}
});
Alternatively, you can specify a StateModel
in the options for
the constructor
:
const MyToolKitApp = App.extend({...});
new MyToolKitApp({
StateModel: MyStateModel
});
Optionally define a state
attributes object on your class initialization or pass as an option when calling initState(options)
.
const MyStateModel = Backbone.Model.extend({});
const MyClass = MnObject.extend({
StateModel: MyStateModel
initialize(options) {
this.initState(options);
}
});
new MyClass({
state: {
foo: 'bar'
}
});
StateMixin
can bind directly to state events in a declarative manner:
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 documentation.
StateMixin
has a setState
method that exposes the Backbone.Model.set
for the StateMixin
's attached StateModel
. Implementation will match Backbone.Model.set documentation.
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');
StateMixin
has a resetStateDefaults
method that sets the StateModel
instance attributes back to the defined defaults. Implementation will match Backbone.Model.defaults documentation.
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
StateMixin
has a getState
method that exposes the Backbone.Model.get
for the Statemixin
's attached StateModel
. Implementation will match Backbone.Model.get documentation with the
exception that not passing any attribute to "get" will return the state model
instance.
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();
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
.
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');
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
.
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');
StateMixin
has a delegateStateEvents
that will bind all events specified
in the stateEvents
option. Implementation matches Backbone.View.delegateEvents.
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');
StateMixin
has a undelegateStateEvents
that will unbind all event listeners
specified on the StateModel
option. Implementation matches Backbone.View.undelegateEvents.
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');