Skip to content

Commit

Permalink
Merge pull request #845 from jmeas/master
Browse files Browse the repository at this point in the history
Extended `module` functionality
  • Loading branch information
samccone committed Jan 20, 2014
2 parents f1d182d + 0109191 commit 3ad4a65
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 4 deletions.
56 changes: 56 additions & 0 deletions docs/marionette.application.module.md
Expand Up @@ -22,9 +22,11 @@ your application, and serve as an event aggregator in themselves.
* [Module Definitions](#module-definitions)
* [Module Initializers](#module-initializers)
* [Module Finalizers](#module-finalizers)
* [Initialize function](#initialize-function)
* [The Module's `this` Argument](#the-modules-this-argument)
* [Custom Arguments](#custom-arguments)
* [Splitting A Module Definition Apart](#splitting-a-module-definition-apart)
* [Extending Modules](#extending-modules)

## Basic Usage

Expand Down Expand Up @@ -332,6 +334,8 @@ console.log(MyApp.MyModule.someData); //=> public data
MyApp.MyModule.someFunction(); //=> public data
```



### Module Initializers

Modules have initializers, similarly to `Application` objects. A module's
Expand Down Expand Up @@ -367,6 +371,35 @@ MyApp.module("Foo", function(Foo){
Calling the `stop` method on the module will run all that module's
finalizers. A module can have as many finalizers as you wish.

### Initialize Function

Modules have an `initialize` function which is distinct from its collection of initializers. `initialize` is immediately called when the Module is invoked, unlike initializers, which are only called once the module is started. You can think of the `initialize` function as an extension of the constructor.

```js
MyApp.module("Foo", {
startWithParent: false,
initialize: function( options ) {
// This code is immediately executed
this.someProperty = 'someValue';
},
define: function() {
// This code is not executed until the Module has started
console.log( this.someProperty ); // Logs 'someValue' once the module is started
}
});
```

The `initialize` function is passed a single argument, which is the object literal definition of the module itself. This allows you to pass arbitrary values to it.

```js
MyApp.module("Foo", {
initialize: function( options ) {
console.log( options.someVar ); // Logs 'someString'
},
someVar: 'someString'
});
```

## The Module's `this` Argument

The module's `this` argument is set to the module itself.
Expand Down Expand Up @@ -412,3 +445,26 @@ MyApp.module("MyModule", function(MyModule){
MyApp.MyModule.definition1; //=> true
MyApp.MyModule.definition2; //=> true
```

## Extending Modules

Modules can be extended. This allows you to make custom Modules to include in your Application.

```
var CustomModule = Marionette.Module.extend({
constructor: function() {
// Configure your module
}
});
```

When attaching a Module to your Application you can specify what class to use with the parameter `moduleClass`.

```
MyApp.module("Foo", {
moduleClass: CustomModule,
define: function() {} // You can still use the definition function on custom modules
});
```

When `moduleClass` is omitted, Marionette will default to instantiating a new `Marionette.Module`.
117 changes: 117 additions & 0 deletions spec/javascripts/module.start.spec.js
Expand Up @@ -222,4 +222,121 @@ describe("module start", function(){

});

describe("when passing a custom module class in the object literal", function(){
var MyApp, MyModule, moduleStart, customModule, initializer;

beforeEach(function(){

MyApp = new Backbone.Marionette.Application();
initializer = sinon.spy();
customModule = Backbone.Marionette.Module.extend( { initialize: initializer } );

MyApp.module("MyModule", {
startWithParent: false,
moduleClass: customModule
});

MyApp.start();
});

it("should run the initialize function once", function(){
expect(initializer).toHaveBeenCalledOnce;
});
});

describe("when passing an extended custom module class in the object literal", function(){
var MyApp, MyModule, moduleStart,
customModule, customModuleTwo,
initializer, initializerTwo,
p1, p2, r1, r2;

beforeEach(function(){

p1 = 'val1';
p2 = 'val2';

MyApp = new Backbone.Marionette.Application();
initializer = sinon.spy();
initializerTwo = sinon.spy();
customModule = Backbone.Marionette.Module.extend({
initialize: initializer,
val1: p1,
val2: p1
});
customModuleTwo = customModule.extend({
initialize: initializerTwo,
val1: p2
});

MyApp.module("MyModule", {
startWithParent: false,
moduleClass: customModuleTwo,
define: function() {
r1 = this.val1;
r2 = this.val2;
}
});

MyApp.start();
});

it("should only run the latest initialize function once, and not the prototype initialize", function(){
expect(initializer).not.toHaveBeenCalled();
expect(initializerTwo).toHaveBeenCalledOnce;
});

it("should extend properties correctly", function() {
expect(r1).toBe(p2);
expect(r2).toBe(p1);
});

});

describe("when passing an initialize function as an option when calling `Application.module`", function(){
var MyApp, MyModule, moduleStart, initializer;

beforeEach(function(){

MyApp = new Backbone.Marionette.Application();
initializer = sinon.spy();

MyApp.module("MyModule", {
initialize: initializer
});

MyApp.start();
});

it("should run the initialize function once", function(){
expect(initializer).toHaveBeenCalledOnce;
});

});

describe("when passing an arbitrary set of arguments to `Application.module`", function(){
var MyApp, MyModule, moduleStart, initializer, options;

beforeEach(function(){

MyApp = new Backbone.Marionette.Application();
initializer = sinon.spy();

options = {
startWithParent: false,
p1: 'testValueOne',
p2: 'testValueTwo',
initialize: initializer
};

MyApp.module("MyModule", options);

MyApp.start();
});

it("should pass them to the initialize function", function(){
expect(initializer).toHaveBeenCalledWithExactly(options);
});

});

});
9 changes: 8 additions & 1 deletion src/marionette.application.js
Expand Up @@ -77,13 +77,20 @@ _.extend(Marionette.Application.prototype, Backbone.Events, {

// Create a module, attached to the application
module: function(moduleNames, moduleDefinition){
var ModuleClass = Marionette.Module;

// Overwrite the module class if the user specifies one
if (moduleDefinition) {
ModuleClass = moduleDefinition.moduleClass || ModuleClass;
}

// slice the args, and add this application object as the
// first argument of the array
var args = slice(arguments);
args.unshift(this);

// see the Marionette.Module object for more information
return Marionette.Module.create.apply(Marionette.Module, args);
return ModuleClass.create.apply(ModuleClass, args);
},

// Internal method to set up the region manager
Expand Down
24 changes: 21 additions & 3 deletions src/marionette.module.js
Expand Up @@ -3,8 +3,10 @@

// A simple module system, used to create privacy and encapsulation in
// Marionette applications
Marionette.Module = function(moduleName, app){
Marionette.Module = function(moduleName, app, options){
this.moduleName = moduleName;
this.options = _.extend({}, this.options, options);
this.initialize = options.initialize || this.initialize;

// store sub-modules
this.submodules = {};
Expand All @@ -16,12 +18,22 @@ Marionette.Module = function(moduleName, app){
this.startWithParent = true;

this.triggerMethod = Marionette.triggerMethod;

if (_.isFunction(this.initialize)){
this.initialize(this.options);
}
};

Marionette.Module.extend = Marionette.extend;

// Extend the Module prototype with events / listenTo, so that the module
// can be used as an event aggregator or pub/sub.
_.extend(Marionette.Module.prototype, Backbone.Events, {

// Initialize is an empty function by default. Override it with your own
// initialization logic when extending Marionette.Module.
initialize: function(){},

// Initializer for a specific module. Initializers are run when the
// module's `start` method is called.
addInitializer: function(callback){
Expand Down Expand Up @@ -136,7 +148,7 @@ _.extend(Marionette.Module, {
// Loop through all the parts of the module definition
_.each(moduleNames, function(moduleName, i){
var parentModule = module;
module = this._getModule(parentModule, moduleName, app);
module = this._getModule(parentModule, moduleName, app, moduleDefinition);
this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
}, this);

Expand All @@ -145,12 +157,18 @@ _.extend(Marionette.Module, {
},

_getModule: function(parentModule, moduleName, app, def, args){
var ModuleClass = Marionette.Module;
var options = _.extend({}, def);
if (def) {
ModuleClass = def.moduleClass || ModuleClass;
}

// Get an existing module of this name if we have one
var module = parentModule[moduleName];

if (!module){
// Create a new module if we don't have one
module = new Marionette.Module(moduleName, app);
module = new ModuleClass(moduleName, app, options);
parentModule[moduleName] = module;
// store the module on the parent
parentModule.submodules[moduleName] = module;
Expand Down

0 comments on commit 3ad4a65

Please sign in to comment.