marionette.toolkit
Advanced tools
Comparing version 0.3.0 to 0.4.0
{ | ||
"name": "marionette.toolkit", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "A collection of opinionated Backbone.Marionette extensions for large scale application architecture.", | ||
@@ -5,0 +5,0 @@ "main": "./dist/marionette.toolkit.js", |
@@ -0,1 +1,9 @@ | ||
#### v0.4.0 | ||
* Toolkit now exports `App`, `Component` and `StateClass` to [npm separately](https://www.npmjs.com/browse/keyword/marionette.toolkit-modularized). | ||
* `StateModel` can now be defined as a function returning a `Backbone.Model` | ||
* A `Component` `ViewClass` can now be defined as a function returning a View | ||
* If defining `childApps` as a function, it is now passed the same `options` as `initialize` | ||
* Prevent an `App` from destroying more than once | ||
#### v0.3.0 | ||
@@ -25,3 +33,3 @@ | ||
* Added `Toolkit.noConflict` | ||
* `App` now has a `triggerStart` method that can be overridden to introduce async app starts. | ||
* `App` now has a `triggerStart` method that can be overridden to introduce async app starts | ||
* `App` `buildApp` is now easier to override | ||
@@ -28,0 +36,0 @@ * `Component` `buildView` is now easier to override |
/** | ||
* marionette.toolkit - A collection of opinionated Backbone.Marionette extensions for large scale application architecture. | ||
* @version v0.3.0 | ||
* @version v0.4.0 | ||
* @link https://github.com/RoundingWellOS/marionette.toolkit | ||
@@ -34,3 +34,3 @@ * @license MIT | ||
var StateModel = this.getStateModelClass(); | ||
var StateModel = this._getStateModel(options); | ||
@@ -47,12 +47,24 @@ this._stateModel = new StateModel(_.result(this, "stateDefaults")); | ||
* Get the StateClass StateModel class. | ||
* If you need a dynamic StateModel override this function | ||
* Checks if the `StateModel` is a model class (the common case) | ||
* Then check if it's a function (which we assume that returns a model class) | ||
* | ||
* @public | ||
* @abstract | ||
* @method getStateModelClass | ||
* @private | ||
* @method _getStateModel | ||
* @param {Object} [options] - Options that can be used to determine the StateModel. | ||
* @memberOf StateClass | ||
* @returns {Backbone.Model} | ||
*/ | ||
getStateModelClass: function getStateModelClass() { | ||
return this.StateModel; | ||
_getStateModel: function _getStateModel(options) { | ||
var StateModel = this.getOption("StateModel"); | ||
if (StateModel.prototype instanceof Backbone.Model || StateModel === Backbone.Model) { | ||
return StateModel; | ||
} else if (_.isFunction(StateModel)) { | ||
return StateModel.call(this, options); | ||
} else { | ||
throw new Marionette.Error({ | ||
name: "InvalidStateModelError", | ||
message: "\"StateModel\" must be a model class or a function that returns a model class" | ||
}); | ||
} | ||
}, | ||
@@ -106,4 +118,6 @@ | ||
var AbstractApp = StateClass.extend({ | ||
var state_class = StateClass; | ||
var AbstractApp = state_class.extend({ | ||
/** | ||
@@ -178,3 +192,3 @@ * Internal flag indiciate when `App` has started but has not yet stopped. | ||
// Will call initialize | ||
StateClass.call(this, options); | ||
state_class.call(this, options); | ||
@@ -305,7 +319,11 @@ if (_.result(this, "startAfterInitialized")) { | ||
destroy: function destroy() { | ||
this.stop(); | ||
if (this._isDestroyed) { | ||
return; | ||
} | ||
this._isDestroyed = true; | ||
StateClass.prototype.destroy.apply(this, arguments); | ||
this.stop(); | ||
state_class.prototype.destroy.apply(this, arguments); | ||
}, | ||
@@ -353,3 +371,3 @@ | ||
} | ||
return StateClass.prototype.on.apply(this, arguments); | ||
return state_class.prototype.on.apply(this, arguments); | ||
}, | ||
@@ -371,3 +389,3 @@ | ||
} | ||
return StateClass.prototype.listenTo.apply(this, arguments); | ||
return state_class.prototype.listenTo.apply(this, arguments); | ||
}, | ||
@@ -390,8 +408,10 @@ | ||
return StateClass.prototype.listenToOnce.apply(this, arguments); | ||
return state_class.prototype.listenToOnce.apply(this, arguments); | ||
} | ||
}); | ||
var App = AbstractApp.extend({ | ||
var abstract_app = AbstractApp; | ||
var App = abstract_app.extend({ | ||
/** | ||
@@ -421,3 +441,3 @@ * @public | ||
this._initChildApps(); | ||
this._initChildApps(options); | ||
@@ -432,3 +452,3 @@ // The child apps should be handled while the app is running; | ||
AbstractApp.call(this, options); | ||
abstract_app.call(this, options); | ||
}, | ||
@@ -443,5 +463,12 @@ | ||
*/ | ||
_initChildApps: function _initChildApps() { | ||
if (this.childApps) { | ||
this.addChildApps(_.result(this, "childApps")); | ||
_initChildApps: function _initChildApps(options) { | ||
var childApps = this.childApps; | ||
if (childApps) { | ||
if (_.isFunction(childApps)) { | ||
childApps = childApps.call(this, options); | ||
} | ||
this.addChildApps(childApps); | ||
} | ||
@@ -720,4 +747,6 @@ }, | ||
var Component = StateClass.extend({ | ||
var app = App; | ||
var Component = state_class.extend({ | ||
/** | ||
@@ -763,3 +792,3 @@ * The view class to be managed. | ||
StateClass.call(this, options); | ||
state_class.call(this, options); | ||
@@ -853,2 +882,30 @@ this._setStateDefaults(stateAttrs); | ||
/** | ||
* Get the Component ViewClass class. | ||
* Checks if the `ViewClass` is a view class (the common case) | ||
* Then check if it's a function (which we assume that returns a view class) | ||
* | ||
* @private | ||
* @method _getViewClass | ||
* @memberOf Component | ||
* @param {Object} [options] - Options that can be used to determine the ViewClass. | ||
* @returns {View} | ||
*/ | ||
_getViewClass: function _getViewClass(options) { | ||
options = options || {}; | ||
var ViewClass = this.getOption("ViewClass"); | ||
if (ViewClass.prototype instanceof Backbone.View || ViewClass === Backbone.View) { | ||
return ViewClass; | ||
} else if (_.isFunction(ViewClass)) { | ||
return ViewClass.call(this, options); | ||
} else { | ||
throw new Marionette.Error({ | ||
name: "InvalidViewClassError", | ||
message: "\"ViewClass\" must be a view class or a function that returns a view class" | ||
}); | ||
} | ||
}, | ||
/** | ||
* Shows or re-shows a newly built view in the component's region | ||
@@ -865,5 +922,7 @@ * | ||
renderView: function renderView(options) { | ||
var ViewClass = this._getViewClass(options); | ||
var viewOptions = this.mixinOptions(options); | ||
var view = this.buildView(this.ViewClass, viewOptions); | ||
var view = this.buildView(ViewClass, viewOptions); | ||
@@ -958,3 +1017,3 @@ // Attach current built view to component | ||
if (this._shouldDestroy) { | ||
StateClass.prototype.destroy.apply(this, arguments); | ||
state_class.prototype.destroy.apply(this, arguments); | ||
} | ||
@@ -995,2 +1054,4 @@ }, | ||
var component = Component; | ||
var previousToolkit = Marionette.Toolkit; | ||
@@ -1005,8 +1066,10 @@ | ||
Toolkit.StateClass = StateClass; | ||
Toolkit.VERSION = "0.4.0"; | ||
Toolkit.App = App; | ||
Toolkit.StateClass = state_class; | ||
Toolkit.Component = Component; | ||
Toolkit.App = app; | ||
Toolkit.Component = component; | ||
var marionette_toolkit = Toolkit; | ||
@@ -1013,0 +1076,0 @@ |
@@ -1,2 +0,8 @@ | ||
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i(require("backbone.marionette"),require("underscore"),require("backbone")):"function"==typeof define&&define.amd?define(["backbone.marionette","underscore","backbone"],i):t.Marionette.Toolkit=i(t.Marionette,t._,t.Backbone)}(this,function(t,i,e){"use strict";var n=t.Object.extend({StateModel:e.Model,constructor:function(e){e=e||{},i.extend(this,i.pick(e,["StateModel","stateEvents","stateDefaults"]));var n=this.getStateModelClass();this._stateModel=new n(i.result(this,"stateDefaults")),this.bindEntityEvents(this._stateModel,i.result(this,"stateEvents")),t.Object.call(this,e)},getStateModelClass:function(){return this.StateModel},setState:function(){return this._stateModel.set.apply(this._stateModel,arguments)},getState:function(t){return t?this._stateModel.get.apply(this._stateModel,arguments):this._stateModel},destroy:function(){this._stateModel.stopListening(),t.Object.prototype.destroy.apply(this,arguments)}}),s=n.extend({_isRunning:!1,_isDestroyed:!1,preventDestroy:!1,startAfterInitialized:!1,startWithParent:!1,stopWithParent:!0,constructor:function(t){t=t||{},i.bindAll(this,"start","stop");var e=["startWithParent","stopWithParent","startAfterInitialized","preventDestroy"];i.extend(this,i.pick(t,e)),n.call(this,t),i.result(this,"startAfterInitialized")&&this.start(t)},_ensureAppIsIntact:function(){if(this._isDestroyed)throw new t.Error({name:"AppDestroyedError",message:"App has already been destroyed and cannot be used."})},isRunning:function(){return this._isRunning},start:function(t){return this._ensureAppIsIntact(),this._isRunning?this:(this.triggerMethod("before:start",t),this._isRunning=!0,this.triggerStart(t),this)},triggerStart:function(t){this.triggerMethod("start",t)},stop:function(t){return this._isRunning?(this.triggerMethod("before:stop",t),this._isRunning=!1,this.triggerMethod("stop",t),this._stopRunningListeners(),this._stopRunningEvents(),this):this},isDestroyed:function(){return this._isDestroyed},destroy:function(){this.stop(),this._isDestroyed=!0,n.prototype.destroy.apply(this,arguments)},_stopRunningEvents:function(){i.each(this._runningEvents,function(t){this.off.apply(this,t)},this)},_stopRunningListeners:function(){i.each(this._runningListeningTo,function(t){this.stopListening.apply(this,t)},this)},on:function(){return this._isRunning&&(this._runningEvents=this._runningEvents||[],this._runningEvents.push(arguments)),n.prototype.on.apply(this,arguments)},listenTo:function(){return this._isRunning&&(this._runningListeningTo=this._runningListeningTo||[],this._runningListeningTo.push(arguments)),n.prototype.listenTo.apply(this,arguments)},listenToOnce:function(){return this._isRunning&&(this._runningListeningTo=this._runningListeningTo||[],this._runningListeningTo.push(arguments)),n.prototype.listenToOnce.apply(this,arguments)}}),r=s.extend({constructor:function(t){t=t||{},this._childApps={},i.extend(this,i.pick(t,["childApps"])),this._initChildApps(),this.on({start:this._startChildApps,"before:stop":this._stopChildApps,"before:destroy":this._destroyChildApps}),s.call(this,t)},_initChildApps:function(){this.childApps&&this.addChildApps(i.result(this,"childApps"))},_startChildApps:function(){i.each(this._childApps,function(t){i.result(t,"startWithParent")&&t.start()})},_stopChildApps:function(){i.each(this._childApps,function(t){i.result(t,"stopWithParent")&&t.stop()})},_destroyChildApps:function(){i.each(this._childApps,function(t){i.result(t,"preventDestroy")||t.destroy()})},_buildAppFromObject:function(t){var e=t.AppClass,n=i.omit(t,"AppClass");return this.buildApp(e,n)},_buildApp:function(t,e){return i.isFunction(t)?this.buildApp(t,e):i.isObject(t)?this._buildAppFromObject(t):void 0},buildApp:function(t,i){return new t(i)},_ensureAppIsUnique:function(i){if(this._childApps[i])throw new t.Error({name:"DuplicateChildAppError",message:'A child App with name "'+i+'" has already been added.'})},addChildApps:function(t){i.each(t,function(t,i){this.addChildApp(i,t)},this)},addChildApp:function(e,n,s){this._ensureAppIsUnique(e);var r=this._buildApp(n,s);if(!r)throw new t.Error({name:"AddChildAppError",message:"App build failed. Incorrect configuration."});return r._name=e,this._childApps[e]=r,r.on("destroy",i.partial(this._removeChildApp,e),this),this.isRunning()&&i.result(r,"startWithParent")&&r.start(),r},getName:function(){return this._name},getChildApps:function(){return i.clone(this._childApps)},getChildApp:function(t){return this._childApps[t]},_removeChildApp:function(t){delete this._childApps[t]._name,delete this._childApps[t]},removeChildApps:function(){var t=this.getChildApps();return i.each(this._childApps,function(t,i){this.removeChildApp(i)},this),t},removeChildApp:function(t,e){e=e||{};var n=this.getChildApp(t);if(n)return e.preventDestroy||i.result(n,"preventDestroy")?this._removeChildApp(t):n.destroy(),n}}),o=n.extend({ViewClass:t.ItemView,viewEventPrefix:"view",viewOptions:{},constructor:function(t,e){e=e||{},i.extend(this,i.pick(e,["viewEventPrefix","ViewClass","viewOptions","region"])),n.call(this,e),this._setStateDefaults(t)},_shouldDestroy:!0,_setStateDefaults:function(t){this.setState(t,{silent:!0})},showIn:function(t,i){return this.region=t,this.show(i),this},show:function(i){if(this._isShown)throw new t.Error({name:"ComponentShowError",message:"Component has already been shown in a region."});if(!this.region)throw new t.Error({name:"ComponentRegionError",message:"Component has no defined region."});return this.triggerMethod("before:show"),this.renderView(i),this._isShown=!0,this.triggerMethod("show"),this.listenTo(this.region,"empty",this._destroy),this},renderView:function(t){var i=this.mixinOptions(t),e=this.buildView(this.ViewClass,i);return this.currentView=e,this._proxyViewEvents(e),this.triggerMethod("before:render:view",e),this._shouldDestroy=!1,this.region.show(e),this._shouldDestroy=!0,this.triggerMethod("render:view",e),this},_proxyViewEvents:function(t){var e=this.getOption("viewEventPrefix");t.on("all",function(){var n=i.toArray(arguments),s=n[0];n[0]=e+":"+s,n.splice(1,0,t),this.triggerMethod.apply(this,n)},this)},mixinOptions:function(t){var e=i.result(this,"viewOptions");return i.extend({stateModel:this.getState()},e,t)},buildView:function(t,i){return new t(i)},_destroy:function(){this._shouldDestroy&&n.prototype.destroy.apply(this,arguments)},_emptyRegion:function(t){this.region&&(this.stopListening(this.region,"empty"),this.region.empty(t))},destroy:function(t){this._emptyRegion(t),this._shouldDestroy=!0,this._destroy(t)}}),h=t.Toolkit,p=t.Toolkit={};p.noConflict=function(){return t.Toolkit=h,this},p.StateClass=n,p.App=r,p.Component=o;var u=p;return u}); | ||
/** | ||
* marionette.toolkit - A collection of opinionated Backbone.Marionette extensions for large scale application architecture. | ||
* @version v0.4.0 | ||
* @link https://github.com/RoundingWellOS/marionette.toolkit | ||
* @license MIT | ||
*/ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("backbone.marionette"),require("underscore"),require("backbone")):"function"==typeof define&&define.amd?define(["backbone.marionette","underscore","backbone"],e):t.Marionette.Toolkit=e(t.Marionette,t._,t.Backbone)}(this,function(t,e,i){"use strict";var s=t.Object.extend({StateModel:i.Model,constructor:function(i){i=i||{},e.extend(this,e.pick(i,["StateModel","stateEvents","stateDefaults"]));var s=this._getStateModel(i);this._stateModel=new s(e.result(this,"stateDefaults")),this.bindEntityEvents(this._stateModel,e.result(this,"stateEvents")),t.Object.call(this,i)},_getStateModel:function(s){var n=this.getOption("StateModel");if(n.prototype instanceof i.Model||n===i.Model)return n;if(e.isFunction(n))return n.call(this,s);throw new t.Error({name:"InvalidStateModelError",message:'"StateModel" must be a model class or a function that returns a model class'})},setState:function(){return this._stateModel.set.apply(this._stateModel,arguments)},getState:function(t){return t?this._stateModel.get.apply(this._stateModel,arguments):this._stateModel},destroy:function(){this._stateModel.stopListening(),t.Object.prototype.destroy.apply(this,arguments)}}),n=s,r=n.extend({_isRunning:!1,_isDestroyed:!1,preventDestroy:!1,startAfterInitialized:!1,startWithParent:!1,stopWithParent:!0,constructor:function(t){t=t||{},e.bindAll(this,"start","stop");var i=["startWithParent","stopWithParent","startAfterInitialized","preventDestroy"];e.extend(this,e.pick(t,i)),n.call(this,t),e.result(this,"startAfterInitialized")&&this.start(t)},_ensureAppIsIntact:function(){if(this._isDestroyed)throw new t.Error({name:"AppDestroyedError",message:"App has already been destroyed and cannot be used."})},isRunning:function(){return this._isRunning},start:function(t){return this._ensureAppIsIntact(),this._isRunning?this:(this.triggerMethod("before:start",t),this._isRunning=!0,this.triggerStart(t),this)},triggerStart:function(t){this.triggerMethod("start",t)},stop:function(t){return this._isRunning?(this.triggerMethod("before:stop",t),this._isRunning=!1,this.triggerMethod("stop",t),this._stopRunningListeners(),this._stopRunningEvents(),this):this},isDestroyed:function(){return this._isDestroyed},destroy:function(){this._isDestroyed||(this._isDestroyed=!0,this.stop(),n.prototype.destroy.apply(this,arguments))},_stopRunningEvents:function(){e.each(this._runningEvents,function(t){this.off.apply(this,t)},this)},_stopRunningListeners:function(){e.each(this._runningListeningTo,function(t){this.stopListening.apply(this,t)},this)},on:function(){return this._isRunning&&(this._runningEvents=this._runningEvents||[],this._runningEvents.push(arguments)),n.prototype.on.apply(this,arguments)},listenTo:function(){return this._isRunning&&(this._runningListeningTo=this._runningListeningTo||[],this._runningListeningTo.push(arguments)),n.prototype.listenTo.apply(this,arguments)},listenToOnce:function(){return this._isRunning&&(this._runningListeningTo=this._runningListeningTo||[],this._runningListeningTo.push(arguments)),n.prototype.listenToOnce.apply(this,arguments)}}),o=r,h=o.extend({constructor:function(t){t=t||{},this._childApps={},e.extend(this,e.pick(t,["childApps"])),this._initChildApps(t),this.on({start:this._startChildApps,"before:stop":this._stopChildApps,"before:destroy":this._destroyChildApps}),o.call(this,t)},_initChildApps:function(t){var i=this.childApps;i&&(e.isFunction(i)&&(i=i.call(this,t)),this.addChildApps(i))},_startChildApps:function(){e.each(this._childApps,function(t){e.result(t,"startWithParent")&&t.start()})},_stopChildApps:function(){e.each(this._childApps,function(t){e.result(t,"stopWithParent")&&t.stop()})},_destroyChildApps:function(){e.each(this._childApps,function(t){e.result(t,"preventDestroy")||t.destroy()})},_buildAppFromObject:function(t){var i=t.AppClass,s=e.omit(t,"AppClass");return this.buildApp(i,s)},_buildApp:function(t,i){return e.isFunction(t)?this.buildApp(t,i):e.isObject(t)?this._buildAppFromObject(t):void 0},buildApp:function(t,e){return new t(e)},_ensureAppIsUnique:function(e){if(this._childApps[e])throw new t.Error({name:"DuplicateChildAppError",message:'A child App with name "'+e+'" has already been added.'})},addChildApps:function(t){e.each(t,function(t,e){this.addChildApp(e,t)},this)},addChildApp:function(i,s,n){this._ensureAppIsUnique(i);var r=this._buildApp(s,n);if(!r)throw new t.Error({name:"AddChildAppError",message:"App build failed. Incorrect configuration."});return r._name=i,this._childApps[i]=r,r.on("destroy",e.partial(this._removeChildApp,i),this),this.isRunning()&&e.result(r,"startWithParent")&&r.start(),r},getName:function(){return this._name},getChildApps:function(){return e.clone(this._childApps)},getChildApp:function(t){return this._childApps[t]},_removeChildApp:function(t){delete this._childApps[t]._name,delete this._childApps[t]},removeChildApps:function(){var t=this.getChildApps();return e.each(this._childApps,function(t,e){this.removeChildApp(e)},this),t},removeChildApp:function(t,i){i=i||{};var s=this.getChildApp(t);if(s)return i.preventDestroy||e.result(s,"preventDestroy")?this._removeChildApp(t):s.destroy(),s}}),a=h,p=n.extend({ViewClass:t.ItemView,viewEventPrefix:"view",viewOptions:{},constructor:function(t,i){i=i||{},e.extend(this,e.pick(i,["viewEventPrefix","ViewClass","viewOptions","region"])),n.call(this,i),this._setStateDefaults(t)},_shouldDestroy:!0,_setStateDefaults:function(t){this.setState(t,{silent:!0})},showIn:function(t,e){return this.region=t,this.show(e),this},show:function(e){if(this._isShown)throw new t.Error({name:"ComponentShowError",message:"Component has already been shown in a region."});if(!this.region)throw new t.Error({name:"ComponentRegionError",message:"Component has no defined region."});return this.triggerMethod("before:show"),this.renderView(e),this._isShown=!0,this.triggerMethod("show"),this.listenTo(this.region,"empty",this._destroy),this},_getViewClass:function(s){s=s||{};var n=this.getOption("ViewClass");if(n.prototype instanceof i.View||n===i.View)return n;if(e.isFunction(n))return n.call(this,s);throw new t.Error({name:"InvalidViewClassError",message:'"ViewClass" must be a view class or a function that returns a view class'})},renderView:function(t){var e=this._getViewClass(t),i=this.mixinOptions(t),s=this.buildView(e,i);return this.currentView=s,this._proxyViewEvents(s),this.triggerMethod("before:render:view",s),this._shouldDestroy=!1,this.region.show(s),this._shouldDestroy=!0,this.triggerMethod("render:view",s),this},_proxyViewEvents:function(t){var i=this.getOption("viewEventPrefix");t.on("all",function(){var s=e.toArray(arguments),n=s[0];s[0]=i+":"+n,s.splice(1,0,t),this.triggerMethod.apply(this,s)},this)},mixinOptions:function(t){var i=e.result(this,"viewOptions");return e.extend({stateModel:this.getState()},i,t)},buildView:function(t,e){return new t(e)},_destroy:function(){this._shouldDestroy&&n.prototype.destroy.apply(this,arguments)},_emptyRegion:function(t){this.region&&(this.stopListening(this.region,"empty"),this.region.empty(t))},destroy:function(t){this._emptyRegion(t),this._shouldDestroy=!0,this._destroy(t)}}),u=p,l=t.Toolkit,d=t.Toolkit={};d.noConflict=function(){return t.Toolkit=l,this},d.VERSION="0.4.0",d.StateClass=n,d.App=a,d.Component=u;var c=d;return c}); | ||
//# sourceMappingURL=marionette.toolkit.min.js.map |
@@ -27,6 +27,7 @@ # Marionette.Toolkit.App | ||
`childApps` can be passed to an `App` at instantiation or defined on the definition. | ||
If defined as a function it will receive the `options` passed to the `constructor`. | ||
```js | ||
var MyApp = Marionette.Toolkit.App.extend({ | ||
childApps: function(){ | ||
childApps: function(options){ | ||
return { | ||
@@ -33,0 +34,0 @@ childName: MyChildApp, |
@@ -70,2 +70,20 @@ # Marionette.Toolkit.Component | ||
``` | ||
You can also define `ViewClass` as a function. In this form, the value | ||
returned by this method is the `ViewClass` class that will be instantiated. | ||
When defined as a function, it will receive the `options` passed to [`renderView`](#component-renderview). | ||
```js | ||
var MyViewClass = Marionette.ItemView.extend({}); | ||
Marionette.Toolkit.Component.extend({ | ||
ViewClass: function(options){ | ||
if(options.foo){ | ||
return MyViewClass; | ||
} | ||
return Marionette.ItemView; | ||
} | ||
}); | ||
``` | ||
The `ViewClass` can be provided in the component definition or | ||
@@ -72,0 +90,0 @@ in the constructor function call, to get a component instance. |
@@ -13,3 +13,2 @@ # Marionette.Toolkit.StateClass | ||
* [StateClass API](#stateclass-api) | ||
* [StateClass `getStateModelClass`](#stateclass-getstatemodelclass) | ||
* [Setting State `setState`](#setting-state) | ||
@@ -33,8 +32,21 @@ * [Getting State `getState`](#getting-state) | ||
The state model must be defined before it is referenced by the | ||
`StateModel` attribute in a state class definition. | ||
Use `getStateModelClass` to lookup the definition as state classes are instantiated. | ||
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 | ||
var MyStateModel = Backbone.Model.extend({}); | ||
Marionette.Toolkit.StateClass.extend({ | ||
StateModel: function(options){ | ||
if(options.foo){ | ||
return MyStateModel; | ||
} | ||
return Backbone.Model; | ||
} | ||
}); | ||
``` | ||
Alternatively, you can specify a `StateModel` in the options for | ||
the constructor: | ||
the `constructor`: | ||
@@ -92,25 +104,2 @@ ```js | ||
### StateClass `getStateModelClass` | ||
The value returned by this method is the `StateModel` class that will be instantiated when a `StateClass` is instatiated. | ||
Override this method for dynamic `StateModel` definitions. | ||
```js | ||
var FooModel = Backbone.Model.extend({}); | ||
var BarModel = Backbone.Model.extend({}); | ||
var MyStateClass = Marionette.Toolkit.StateClass.extend({ | ||
getStateModelClass: function() { | ||
if(this.getOption('isFoo')) { | ||
return FooModel; | ||
} | ||
else { | ||
return BarModel; | ||
} | ||
} | ||
}); | ||
var myFooStateClass = new MyStateClass({ isFoo: true }); | ||
var myBarStateClass = new MyStateClass(); | ||
``` | ||
### Setting State | ||
@@ -117,0 +106,0 @@ |
@@ -14,3 +14,4 @@ var gulp = require('gulp'); | ||
const source = require('vinyl-source-stream'); | ||
const _ = require('lodash'); | ||
const Promise = require('bluebird'); | ||
const _ = require('underscore'); | ||
@@ -79,21 +80,34 @@ const manifest = require('./package.json'); | ||
// Build two versions of the library | ||
gulp.task('build', ['lint-src', 'clean'], function(done) { | ||
esperanto.bundle({ | ||
function _build(entryFileName, destFolder, expFileName, expVarName, umd){ | ||
return esperanto.bundle({ | ||
base: 'src', | ||
entry: config.entryFileName, | ||
entry: entryFileName, | ||
transform: function(source) { | ||
var js_source = _.template(source)(manifest); | ||
// Poor way of modifying dependency for modular build | ||
if(!umd){ | ||
return js_source.replace('./state-class', 'marionette.toolkit.state-class'); | ||
} | ||
return js_source; | ||
} | ||
}).then(function(bundle) { | ||
var res = bundle.toUmd({ | ||
banner: getBanner(), | ||
var banner = getBanner(); | ||
var bundleMethod = umd? 'toUmd' : 'toCjs'; | ||
var res = bundle[bundleMethod]({ | ||
banner: banner, | ||
sourceMap: true, | ||
sourceMapSource: config.entryFileName + '.js', | ||
sourceMapFile: exportFileName + '.js', | ||
name: config.exportVarName | ||
sourceMapSource: entryFileName + '.js', | ||
sourceMapFile: expFileName + '.js', | ||
name: expVarName | ||
}); | ||
// Write the generated sourcemap | ||
mkdirp.sync(destinationFolder); | ||
fs.writeFileSync(path.join(destinationFolder, exportFileName + '.js'), res.map.toString()); | ||
mkdirp.sync(destFolder); | ||
fs.writeFileSync(path.join(destFolder, expFileName + '.js'), res.map.toString()); | ||
$.file(exportFileName + '.js', res.code, { src: true }) | ||
$.file(expFileName + '.js', res.code, { src: true }) | ||
.pipe($.plumber()) | ||
@@ -103,14 +117,50 @@ .pipe($.sourcemaps.init({ loadMaps: true })) | ||
.pipe($.sourcemaps.write('./', {addComment: false})) | ||
.pipe(gulp.dest(destinationFolder)) | ||
.pipe(gulp.dest(destFolder)) | ||
.pipe($.filter(['*', '!**/*.js.map'])) | ||
.pipe($.rename(exportFileName + '.min.js')) | ||
.pipe($.rename(expFileName + '.min.js')) | ||
.pipe($.uglifyjs({ | ||
outSourceMap: true, | ||
inSourceMap: destinationFolder + '/' + exportFileName + '.js.map', | ||
inSourceMap: destFolder + '/' + expFileName + '.js.map', | ||
})) | ||
.pipe(gulp.dest(destinationFolder)) | ||
.on('end', done); | ||
.pipe($.header(banner)) | ||
.pipe(gulp.dest(destFolder)); | ||
}); | ||
} | ||
// Build two versions of the library | ||
gulp.task('build-lib', ['lint-src', 'clean'], function() { | ||
return _build(config.entryFileName, destinationFolder, exportFileName, config.exportVarName, 'umd'); | ||
}); | ||
function _buildPackage(destFolder, entryName, exportName){ | ||
var data = { | ||
version: manifest.version, | ||
exportVarName: exportName, | ||
entryName: entryName, | ||
dependencies: JSON.stringify(manifest.dependencies, null, 4) | ||
}; | ||
gulp.src('./packages/LICENSE') | ||
.pipe(gulp.dest(destFolder)); | ||
gulp.src('./packages/README.md') | ||
.pipe($.template(data)) | ||
.pipe(gulp.dest(destFolder)); | ||
gulp.src('./packages/package.json') | ||
.pipe($.template(data)) | ||
.pipe(gulp.dest(destFolder)); | ||
} | ||
gulp.task('build-packages', ['lint-src', 'clean'], function() { | ||
var tasks = _.map(config.exportPackageNames, function(entryName, exportName){ | ||
var destFolder = './packages/' + exportName + '/'; | ||
var exportVarName = 'Marionette.Toolkit.' + exportName; | ||
return _build(entryName, destFolder, exportName, exportVarName) | ||
.then(_.partial(_buildPackage, destFolder, entryName, exportName)); | ||
}); | ||
return Promise.all(tasks); | ||
}); | ||
// Bundle our app for our unit tests | ||
@@ -177,3 +227,5 @@ gulp.task('browserify', function() { | ||
gulp.task('build', ['build-lib', 'build-packages']); | ||
// An alias of test | ||
gulp.task('default', ['test']); |
{ | ||
"name": "marionette.toolkit", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "A collection of opinionated Backbone.Marionette extensions for large scale application architecture.", | ||
@@ -37,2 +37,3 @@ "main": "./dist/marionette.toolkit.js", | ||
"babelify": "^5.0.3", | ||
"bluebird": "^2.9.27", | ||
"browserify": "^8.1.1", | ||
@@ -47,2 +48,3 @@ "chai": "^2.0.0", | ||
"gulp-filter": "^2.0.0", | ||
"gulp-header": "^1.2.2", | ||
"gulp-istanbul": "^0.6.0", | ||
@@ -58,2 +60,3 @@ "gulp-jscs": "^1.4.0", | ||
"gulp-sourcemaps": "^1.3.0", | ||
"gulp-template": "^3.0.0", | ||
"gulp-uglifyjs": "^0.6.0", | ||
@@ -64,3 +67,2 @@ "isparta": "^2.2.0", | ||
"jshint-stylish": "^1.0.0", | ||
"lodash": "^3.2.0", | ||
"mkdirp": "^0.5.0", | ||
@@ -71,2 +73,3 @@ "mocha": "^2.1.0", | ||
"sinon-chai": "^2.7.0", | ||
"underscore": "^1.8.3", | ||
"vinyl-source-stream": "^1.0.0" | ||
@@ -77,2 +80,7 @@ }, | ||
"exportVarName": "Marionette.Toolkit", | ||
"exportPackageNames": { | ||
"App": "app", | ||
"Component": "component", | ||
"StateClass": "state-class" | ||
}, | ||
"mochaGlobals": [ | ||
@@ -90,4 +98,4 @@ "stub", | ||
"backbone": "1.0.0 - 1.1.2", | ||
"underscore": "1.4.4 - 1.8.2" | ||
"underscore": "1.4.4 - 1.8.3" | ||
} | ||
} |
@@ -14,2 +14,3 @@ Marionette.Toolkit | ||
In addition to the full library, each element in Toolkit is available as it's own [npm module](https://www.npmjs.com/browse/keyword/marionette.toolkit-modularized). | ||
@@ -44,8 +45,13 @@ ## Documentation | ||
**Via [npm](https://www.npmjs.com/package/marionette.toolkit)** | ||
``` | ||
$ npm install marionette.toolkit | ||
``` | ||
**Via [bower](http://bower.io/search/?q=marionette.toolkit)** | ||
``` | ||
$ npm i marionette.toolkit | ||
$ bower install marionette.toolkit | ||
``` | ||
Currently Marionette.Toolkit is available via npm. If you would like add it to another channel, please | ||
Currently Marionette.Toolkit is available via npm and bower. If you would like add it to another channel, please | ||
[open an issue](#github-issues). | ||
@@ -59,10 +65,11 @@ | ||
Marionette.Toolkit currently requires [Marionette](http://marionettejs.com) 2.1.0+ as it extends | ||
Marionette.Toolkit currently requires [Marionette](http://marionettejs.com) 2.2.0+ as it utilizes [`Marionette.Error`](https://github.com/marionettejs/backbone.marionette/blob/v2.2.0/src/marionette.error.js) and extends | ||
[`Marionette.Object`](https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.object.md) | ||
It's possible this library would work with earlier versions by shimming | ||
It's possible this library would work with earlier versions by manually adding `Marionette.Error` and shimming | ||
`Marionette.Object = Marionette.Controller;` | ||
This library is currently only tested with the latest version of Marionette. | ||
This library is tested with v2.2+ and a mix of underscore and lodash versions. | ||
Marionette.Toolkit supports IE8+ and modern browsers. | ||
@@ -69,0 +76,0 @@ |
@@ -216,6 +216,10 @@ import _ from 'underscore'; | ||
destroy: function() { | ||
this.stop(); | ||
if(this._isDestroyed) { | ||
return; | ||
} | ||
this._isDestroyed = true; | ||
this.stop(); | ||
StateClass.prototype.destroy.apply(this, arguments); | ||
@@ -222,0 +226,0 @@ }, |
@@ -39,3 +39,3 @@ import _ from 'underscore'; | ||
this._initChildApps(); | ||
this._initChildApps(options); | ||
@@ -60,5 +60,12 @@ // The child apps should be handled while the app is running; | ||
*/ | ||
_initChildApps: function() { | ||
if(this.childApps) { | ||
this.addChildApps(_.result(this, 'childApps')); | ||
_initChildApps: function(options) { | ||
var childApps = this.childApps; | ||
if(childApps) { | ||
if(_.isFunction(childApps)) { | ||
childApps = childApps.call(this, options); | ||
} | ||
this.addChildApps(childApps); | ||
} | ||
@@ -65,0 +72,0 @@ }, |
import _ from 'underscore'; | ||
import Backbone from 'backbone'; | ||
import Marionette from 'backbone.marionette'; | ||
@@ -144,2 +145,30 @@ import StateClass from './state-class'; | ||
/** | ||
* Get the Component ViewClass class. | ||
* Checks if the `ViewClass` is a view class (the common case) | ||
* Then check if it's a function (which we assume that returns a view class) | ||
* | ||
* @private | ||
* @method _getViewClass | ||
* @memberOf Component | ||
* @param {Object} [options] - Options that can be used to determine the ViewClass. | ||
* @returns {View} | ||
*/ | ||
_getViewClass: function(options) { | ||
options = options || {}; | ||
var ViewClass = this.getOption('ViewClass'); | ||
if (ViewClass.prototype instanceof Backbone.View || ViewClass === Backbone.View) { | ||
return ViewClass; | ||
} else if (_.isFunction(ViewClass)) { | ||
return ViewClass.call(this, options); | ||
} else { | ||
throw new Marionette.Error({ | ||
name: 'InvalidViewClassError', | ||
message: '"ViewClass" must be a view class or a function that returns a view class' | ||
}); | ||
} | ||
}, | ||
/** | ||
* Shows or re-shows a newly built view in the component's region | ||
@@ -156,5 +185,7 @@ * | ||
renderView: function(options){ | ||
var ViewClass = this._getViewClass(options); | ||
var viewOptions = this.mixinOptions(options); | ||
var view = this.buildView(this.ViewClass, viewOptions); | ||
var view = this.buildView(ViewClass, viewOptions); | ||
@@ -161,0 +192,0 @@ // Attach current built view to component |
@@ -20,2 +20,4 @@ import Marionette from 'backbone.marionette'; | ||
Toolkit.VERSION = '<%= version %>'; | ||
Toolkit.StateClass = StateClass; | ||
@@ -22,0 +24,0 @@ |
@@ -35,3 +35,3 @@ import _ from 'underscore'; | ||
var StateModel = this.getStateModelClass(); | ||
var StateModel = this._getStateModel(options); | ||
@@ -48,12 +48,24 @@ this._stateModel = new StateModel(_.result(this, 'stateDefaults')); | ||
* Get the StateClass StateModel class. | ||
* If you need a dynamic StateModel override this function | ||
* Checks if the `StateModel` is a model class (the common case) | ||
* Then check if it's a function (which we assume that returns a model class) | ||
* | ||
* @public | ||
* @abstract | ||
* @method getStateModelClass | ||
* @private | ||
* @method _getStateModel | ||
* @param {Object} [options] - Options that can be used to determine the StateModel. | ||
* @memberOf StateClass | ||
* @returns {Backbone.Model} | ||
*/ | ||
getStateModelClass: function(){ | ||
return this.StateModel; | ||
_getStateModel: function(options){ | ||
var StateModel = this.getOption('StateModel'); | ||
if (StateModel.prototype instanceof Backbone.Model || StateModel === Backbone.Model) { | ||
return StateModel; | ||
} else if (_.isFunction(StateModel)) { | ||
return StateModel.call(this, options); | ||
} else { | ||
throw new Marionette.Error({ | ||
name: 'InvalidStateModelError', | ||
message: '"StateModel" must be a model class or a function that returns a model class' | ||
}); | ||
} | ||
}, | ||
@@ -60,0 +72,0 @@ |
@@ -99,13 +99,32 @@ describe('App Manager', function() { | ||
it('should accept a function', function() { | ||
var childApps = function(){ | ||
return { | ||
cA1: Marionette.Toolkit.App, | ||
cA2: Marionette.Toolkit.App | ||
describe('when passing childApps as a function', function() { | ||
beforeEach(function () { | ||
var childApps = function(){ | ||
return { | ||
cA1: Marionette.Toolkit.App, | ||
cA2: Marionette.Toolkit.App | ||
}; | ||
}; | ||
}; | ||
this.myApp = new Marionette.Toolkit.App({ childApps: childApps }); | ||
this.MyApp2 = Marionette.Toolkit.App.extend({ | ||
childApps: childApps | ||
}); | ||
}); | ||
expect(_.keys(this.myApp._childApps)).to.have.length(2); | ||
it('should use the results of the childApps function', function() { | ||
this.myApp = new this.MyApp2(); | ||
expect(_.keys(this.myApp._childApps)).to.have.length(2); | ||
}); | ||
it('should pass options to childApps', function() { | ||
var opts = { fooOption: 'bar' }; | ||
this.sinon.stub(this.MyApp2.prototype, 'childApps'); | ||
this.myApp = new this.MyApp2(opts); | ||
expect(this.MyApp2.prototype.childApps) | ||
.to.have.been.calledOnce | ||
.and.calledWith(opts); | ||
}); | ||
}); | ||
@@ -112,0 +131,0 @@ }); |
@@ -9,2 +9,3 @@ import AbstractApp from '../../src/abstract-app'; | ||
this.stopStub = this.sinon.stub(); | ||
this.destroyStub = this.sinon.stub(); | ||
this.myApp = new AbstractApp(); | ||
@@ -15,2 +16,3 @@ this.myApp.on('before:start', this.beforeStartStub); | ||
this.myApp.on('stop', this.stopStub); | ||
this.myApp.on('destroy', this.destroyStub); | ||
}); | ||
@@ -75,13 +77,19 @@ | ||
describe('when an application is yet to be destroyed', function () { | ||
it('should have isDestroyed() to return false', function () { | ||
expect(this.myApp.isDestroyed()).to.equal(false); | ||
}); | ||
}); | ||
describe('when destroying an application', function () { | ||
beforeEach(function () { | ||
this.myApp.start(); | ||
this.myApp.destroy(); | ||
}); | ||
it('should have isDestroyed() to return false PRIOR to destroying', function () { | ||
expect(this.myApp.isDestroyed()).to.equal(false); | ||
it('should be stopped', function () { | ||
expect(this.stopStub).to.have.been.calledOnce; | ||
}); | ||
it('should successfully be destroyed', function () { | ||
this.myApp.destroy(); | ||
expect(this.myApp.isDestroyed()).to.equal(true); | ||
@@ -92,3 +100,2 @@ }); | ||
it('should throw an error', function () { | ||
this.myApp.destroy(); | ||
expect(_.bind(function(){ | ||
@@ -99,4 +106,15 @@ this.myApp.start(); | ||
}); | ||
describe('and destroying it again', function () { | ||
beforeEach(function () { | ||
this.myApp.destroy(); | ||
}); | ||
it('should not destroy', function () { | ||
expect(this.destroyStub).to.have.not.been.calledTwice; | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -131,2 +131,41 @@ describe('Marionette.Toolkit.Component', function () { | ||
// SETTING ViewClass | ||
describe('when defining ViewClass as a function that returns a view class', function() { | ||
beforeEach(function() { | ||
this.MyComponent = Marionette.Toolkit.Component.extend({ | ||
region: this.myRegion, | ||
ViewClass: function(options){ | ||
if(options.foo){ | ||
return Marionette.ItemView.extend({ | ||
customViewOption: 'bar', | ||
template: _.template('<div></div>') | ||
}); | ||
} | ||
return Marionette.ItemView; | ||
} | ||
}); | ||
this.myComponent = new this.MyComponent(); | ||
this.myComponent.show({ foo: true }); | ||
}); | ||
it('should use the correct view class for passed options', function() { | ||
expect(this.myComponent.currentView.getOption('customViewOption')).to.equal('bar'); | ||
}); | ||
}); | ||
describe('when defining ViewClass as neither a function or a class', function() { | ||
beforeEach(function() { | ||
this.MyComponent = Marionette.Toolkit.Component.extend({ | ||
region: this.myRegion, | ||
ViewClass: 'Invalid View' | ||
}); | ||
this.myComponent = new this.MyComponent(); | ||
}); | ||
it('should throw an error saying the ViewClass is invalid', function() { | ||
expect(_.bind(this.myComponent.show, this.myComponent)).to.throw('"ViewClass" must be a view class or a function that returns a view class'); | ||
}); | ||
}); | ||
// INSTANTIATING A COMPONENT WITH OPTIONS | ||
@@ -133,0 +172,0 @@ describe('when instantiating a component', function () { |
@@ -47,8 +47,36 @@ describe('State Class', function() { | ||
describe('when calling getStateModelClass()', function () { | ||
it('should return the correct stateModel that will be used in instantiation', function () { | ||
expect(this.myStateClass.getStateModelClass()).to.deep.equal(this.MyModel); | ||
// SETTING StateModel | ||
describe('when defining StateModel as a function that returns a model class', function() { | ||
beforeEach(function() { | ||
this.MyStateClass = Marionette.Toolkit.StateClass.extend({ | ||
StateModel: function(options){ | ||
if(options.foo){ | ||
return Backbone.Model.extend({ | ||
customAttr: 'bar' | ||
}); | ||
} | ||
return Backbone.Model; | ||
} | ||
}); | ||
this.myStateClass = new this.MyStateClass({ foo: true }); | ||
}); | ||
it('should use the correct model class for the passed options', function() { | ||
expect(this.myStateClass.getState().customAttr).to.equal('bar'); | ||
}); | ||
}); | ||
describe('when defining StateModel as neither a function or a class', function() { | ||
beforeEach(function() { | ||
this.MyStateClass = Marionette.Toolkit.StateClass.extend({ | ||
StateModel: 'Invalid Class' | ||
}); | ||
}); | ||
it('should throw an error saying the StateModel is invalid', function() { | ||
expect(_.bind(function(){ this.myStateClass = new this.MyStateClass(); }, this)).to.throw('"StateModel" must be a model class or a function that returns a model class'); | ||
}); | ||
}); | ||
describe('when triggering a stateEvent', function () { | ||
@@ -55,0 +83,0 @@ it('should call the correct stateEvent', function () { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
449064
62
4033
93
0
36
5
+ Addedunderscore@1.8.3(transitive)
- Removedunderscore@1.8.2(transitive)
Updatedunderscore@1.4.4 - 1.8.3