Puppets
An opinionated pattern for building modular components for Marionette.js
About
Marionette provides the necessary elements to build decoupled, modular applications. But one of its virtues – its (relative) lack of opinion – can also be a flaw. It is tempting, and sometimes easy, to write tightly coupled Marionette applications.
Puppets is an opinionated way to build components with Marionette to solve two issues: making them decoupled, and making them reusable.
Principles
- Components (Puppets) should be constructed of one or more pieces that work together to accomplish a single task
- Puppets should expose an API for interactions through a messaging protocol
- Messaging in your application should be explicitly namespaced
- Puppets should be reusable, plug-and-play pieces of functionality
- Puppets should be customizable by passing in options
Getting Started
Get the source by direct download, cloning this repo, or Bower.
bower install puppets
Include the source in your site's Javascript bundle. Be sure to load it after Marionette.
Wreqr Radio
The communication system of Puppets
should be familiar to anyone who has used Marionette: it's just Wreqr
. What this means is that
you'll be able to use the Event Aggregator, Commands, and Request/Response protocols that you may already be used to. There is one difference, though.
Puppets uses the Wreqr Radio library, which allows you to explicitly namespace instances of Wreqr into groups called Channels.
Sound complicated? It's really not. Take a look at how it works:
// Get an instance of the global channelvar globalCh = Backboneradio; // Get an instance of some other channel. It is created for you if it doesn't existvar someNewCh = Backboneradio; // Fire an event on someNewCh's ventsomeNewChvent;
Note that the global vent
is not the same vent
that comes with a new Marionette.Application
. They are two different things. It is recommended that
you overwrite myApp.vent
when using Puppets.
myApp = ; // I recommend overwriting vent and attaching the global commands and reqres to your Applicationvar globalCh = Backboneradio;myAppvent = globalChvent;myAppcommands = globalChcommands;myAppreqres = globalChreqres;
Puppet.prototype
The Puppets prototype is accessible via window.Puppets.Puppet
. It's an extension of Marionette.Module
, so it can be attached to your
application just like any other module. Simply pass it as the moduleClass
of the module that you're instantiating.
// Instantiate a Puppet by adding a module that extends from the Puppets.Puppet prototypeapp;
All Puppets, by default, have startWithParent
set to false.
You are encouraged to extend the base class to build your own puppets.
var CustomPuppet = PuppetsPuppet;
Options
Puppets can be passed in options when they're instantiated.
app;
The defaults
hash of a puppet can be used to both specify which options should be kept and what their default values should be.
// Pass in `someProperty` or `anotherProperty` to have them automatically be attached to this Puppetvar PuppetClass = PuppetsPuppet;
You can access these options with the option
method.
// Get the value of the someProperty optionapp;
Puppets Local Channel
Every Puppet has its own local channel, which is automatically set up when you instantiate it. The name of the channel is puppet.{puppetName}
.
app; // Get a handle of that puppet's channelvar somePuppetCh = Backboneradio;
The three protocols of a Puppet's local channel are attached directly to it.
app;var myPuppet = app; // The puppet's local channel is directly available on the puppetmyPuppetvent;myPuppetcommands;myPuppetreqres;
Communicating on the global channel
There is a convenience function available for communicating on the global channel, emit
.
This appends the name of whatever event you trigger with :{puppetName}
.
app;var myPuppet = app; // Triggers 'anEvent:somePuppet' on the global ventmyPuppet;
I mentioned the following fact above, but do note that the global vent
is not the same vent
that comes with Marionette Applications. It is the vent
from Backbone.radio.channel( 'global' )
, which is another thing entirely.
Attaching event handlers
Pass a localEvents
or globalEvents
hash to quickly attach handlers to events on the respective channel.
var PuppetClass = PuppetsPuppet;
Each hash is passed through Marionette's normalizeMethods function. What this means is that you can provide strings that will be converted into references to functions of the same name, if they exist on the Puppet.
var PuppetClass = PuppetsPuppet
Puppet Pieces
Puppets can have Pieces. These are simply instances of any other object that are attached directly to the Puppet, and connected through its local channel.
Specify the Classes of your pieces – not instances – with the pieces hash:
var PuppetClass = PuppetsPuppet;
Pieces are instantiated alongside the puppet itself.
Options passed to pieces
The options sent to the constructor
and initialize
functions of the pieces are the same options passed as the Puppet definition.
This allows you to quickly pass data down from the module initializer to its individual pieces.
// Set up our piecevar MyPiece = PuppetItemView; app; // '#434343'appcolor;
Getting and Setting Pieces
Once a piece has been created, you can access it with the pieces
method.
app; // Get the newly-created instance of Marionette.ItemViewapp;
You can dynamically add pieces with the same method. Simply pass an already-instantiated object as the second argument.
app; var somePiece = ; // Set a new pieceapp;
You cannot overwrite a piece that already exists. Attempts to do so will be ignored, and the function will return false
.
app; var somePiece = ;var anotherPiece = ; // Set the piece...app; // Returns false. This piece has already been set.app;
Pieces Local Channel
Pieces are given direct access to the local channel, just like its parent Puppet.
app; var piece = app; // The local channel messaging protocolspiecevent;piececommands;piecereqres;
Configuring Events on the Local Channel
Just like its parent Puppet, pieces can set a localEvents
hash.
var CustomItemView = MarionetteCompositeView;
Configuring Events on the Global Channel
There is no easy way for pieces to communicate globally, as they aren't meant to. Messages that need to
'bubble' up to the global channel should first pass through the main Puppet, which then share the event through emit
.
Event Forwarding
All events emitted by any piece are automatically forwarded to the local channel with the :{elementName}
suffix.
app; var piece = app; // This will automatically forward the render events to the local channel as:// before:render:somePiece// render:somePiecepiece;
Shutting down a Puppet
Stopping a Puppet calls reset
on its local channel. This removes all of the listeners from the channel.
For each of its pieces, it will call 'off' if it can be found. Lastly it calls close
or remove
on its pieces, depending on which is found.