Git Product home page Git Product logo

puppets-old's Introduction

Puppets

An (experimental) opinionated pattern for building modular components for Marionette.js. Note: The API is subject to major changes.

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

  1. Components (Puppets) should be constructed of one or more pieces that work together to accomplish a single task
  2. Puppets should expose an API for interactions through a messaging protocol
  3. Messaging in your application should be explicitly namespaced
  4. Puppets should be reusable, plug-and-play pieces of functionality
  5. 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 channel
var globalCh = Backbone.radio.channel( 'global' );

// Get an instance of some other channel. It is created for you if it doesn't exist
var someNewCh = Backbone.radio.channel( 'lalala' );

// Fire an event on someNewCh's vent
someNewCh.vent.trigger( 'someEvent' );

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 = new Marionette.Application();

// I recommend overwriting vent and attaching the global commands and reqres to your Application
var globalCh = Backbone.radio.channel( 'global' );
myApp.vent = globalCh.vent;
myApp.commands = globalCh.commands;
myApp.reqres = globalCh.reqres;

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 prototype
app.module( 'myFirstPuppet', Puppets.Puppet );

All Puppets, by default, have startWithParent set to false.

You are encouraged to extend the base class to build your own puppets.

var CustomPuppet = Puppets.Puppet.extend({
  // Custom methods and properties
});

Options

Puppets can be passed in options when they're instantiated.

app.module( 'myPuppet', {
  // Options
});

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 Puppet
var PuppetClass = Puppets.Puppet.extend({
  defaults: {
    someProperty: true,
    anotherProperty: 'defaultValue'
  }
});

You can access these options with the option method.

// Get the value of the someProperty option
app.module( 'myPuppet' ).option( 'someProperty' );

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.module( 'somePuppet', Puppets.Puppet );

// Get a handle of that puppet's channel
var somePuppetCh = Backbone.radio.channel( 'puppet.somePuppet' );

The three protocols of a Puppet's local channel are attached directly to it.

app.module( 'somePuppet', Puppets.Puppet );
var myPuppet = app.module( 'somePuppet' );

// The puppet's local channel is directly available on the puppet
myPuppet.vent;
myPuppet.commands;
myPuppet.reqres;

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.module( 'somePuppet', Puppets.Puppet );
var myPuppet = app.module( 'somePuppet' );

// Triggers 'anEvent:somePuppet' on the global vent
myPuppet.emit( 'anEvent' );

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 = Puppets.Puppet.extend({
  localEvents: {
    vent: {
      someEvent: someCb,
      someOtherEvent: someOtherCb
    },
    commands: {
      someCommand: someHandler
    },
    reqres: {
      someReqest: function() {}
    }
  },
  // Configure the global responses, too
  globalEvents: {}
});

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 = Puppets.Puppet.extend({
  localEvents: {
    vent: {
      someEvent: 'myMethod',
  },
  // This is called when someEvent is triggered
  myMethod: function() {}
});

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 = Puppets.Puppet.extend({
  pieces: {
    somePiece: Marionette.ItemView,
    anotherPiece: Marionette.Controller
    modelPiece: Backbone.Model
  }
});

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 piece
var MyPiece = Puppet.ItemView.extend({
  initialize: function( options ) {
    this.color = options.color;
  }
});

app.module( 'myPuppet', {
  moduleClass: Puppets.Puppet,
  pieces: {
    myPiece: MyPiece
  },
  color: '#434343'
});

// '#434343'
app.module( 'myPuppet' ).piece( 'myPiece' ).color;

Getting and Setting Pieces

Once a piece has been created, you can access it with the pieces method.

app.module( 'myPuppet', {
  moduleClass: Puppets.Puppet,
  pieces: {
    somePiece: Marionette.ItemView,
  }
});

// Get the newly-created instance of Marionette.ItemView
app.module( 'myPuppet' ).piece( 'somePiece' );

You can dynamically add pieces with the same method. Simply pass an already-instantiated object as the second argument.

app.module( 'myPuppet', {
  moduleClass: Puppets.Puppet,
});

var somePiece = new Backbone.Collection();

// Set a new piece
app.module( 'myPuppet' ).piece( 'somePiece', somePiece );

You cannot overwrite a piece that already exists. Attempts to do so will be ignored, and the function will return false.

app.module( 'myPuppet', Puppets.Puppet );

var somePiece = new Backbone.Collection();
var anotherPiece = new Marionette.ItemView();

// Set the piece...
app.module( 'myPuppet' ).piece( 'somePiece', somePiece );

// Returns false. This piece has already been set.
app.module( 'myPuppet' ).piece( 'somePiece', anotherPiece );

Pieces Local Channel

Pieces are given direct access to the local channel, just like its parent Puppet.

app.module( 'myPuppet', {
  moduleClass: Puppets.Puppet,
  pieces: {
    somePiece: Marionette.ItemView,
  }
});

var piece = app.module( 'myPuppet' ).piece( 'somePiece' );

// The local channel messaging protocols
piece.vent;
piece.commands;
piece.reqres;

Configuring Events on the Local Channel

Just like its parent Puppet, pieces can set a localEvents hash.

var CustomItemView = Marionette.CompositeView.extend({
  localEvents: {
    anEvent: 'myCallback'
  }
});

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.module( 'myPuppet', {
  moduleClass: Puppets.Puppet,
  pieces: {
    somePiece: Marionette.ItemView,
  }
});

var piece = app.module( 'myPuppet' ).piece( 'somePiece' );

// This will automatically forward the render events to the local channel as:
// before:render:somePiece
// render:somePiece
piece.render();

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.

puppets-old's People

Contributors

jamesplease avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

puppets-old's Issues

Expose method to lazy attach pieces

Users may need to pass in objects to pass as pieces after the puppet has been instantiated. The most common case is for view-related puppets that require data. They should pass in a model or collection, and it should be attached as a piece.

Expose an API to enable this. Presently you must write:

this._pieces[ pieceName ] = collection;
this._setUpNewPiece( collection, 'collection' );

Combine that into a single function

Better handling of defaults

Don't pick from the defaults. Simply set the defaults on top of the options the user passes in.

The current situation makes it more tedious to set up pieces. Imagine an ItemView piece that has an $el passed in to it. Why should you need to write

defaults: {
  el: undefined
}

to have that sent to the ItemView? You shouldn't!

Handle views as disposable objects

Marionette is structured such that views should come and go as you need them. They shouldn't hold state, and you shouldn't worry about deleting them.

Puppets manages them as stateful, persistent objects right now by placing them on the same level as pieces.

Rethink this.

Fix up unit tests

The unit tests need some fixing up.

  • Make them more atomic at the cost of repetition
  • Check for unused variables between copy & pasted tests
  • Add test for pieces finalizer

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.