Brunch / Marionette sample application

The other day I ran into Brunch. You can visit the site to get more information, but in a nutshell it’s a build tool. I decided to play with it a bit.

Installation

Installation is simple:

$ npm install -g brunch

Creating a sample project

In order to create a new project, a skeleton is used. At the moment, there are a few dozen skeletons listed here. I am going to use this, which is “Brunch with Coffee Script, Stylus, Backbone, Marrionette, and jQuery.”.

To create the project, this needs to be done:

$ brunch new https://github.com/monokrome/brunch-with-grits

After the skeleton is cloned and the libraries downloaded, you will have something like this:

app
bower_components
bower.json
config.coffee
node_modules
package.json
README.md
vendor

Here:

  • config.coffee is a brunch configuration
  • bower.json is bower configuration
  • vendor is where brunch will put the bower-fetched dependencies (which are managed by bower under bower_components)
  • app is where files related to our application should go

In order to build it for the first time, do this:

$ npm install
$ brunch build

After this, another folder appears – public. This one contains compiled files:

index.html
javascripts
stylesheets

Watching

When developing, all the compilation that needs to be done can be done automatically. This is the watching functionality of brunch. Just invoke this:

$ brunch watch --server

The --server parameter will start a development Web server (defaults to localhost:3333).

Pieces

The above skeleton already contains application.coffee file. This one initializes Backbone.Marionette.Application and does nothing else. In order to display something, Marionette has a few concepts. This is I think best explained on the main page here, but briefly:

  • Template translates into a HTML that will be shown
  • View renders a template and handles events
  • Region defines a jQuery selector that will hold a View instance

View also depends on Backbone.Model or Backbone.Collection to hold the data that is used by a Template instance when rendering.

Basic app

Let’s do a small app – a bounded (to [-5, 5] integer range) counter. In brief:

  • Our model is a number
  • Our view is a text box and a button
  • Button click will make the model’s number to be increased by one

I’m also going to use Handlebars templates, so will first add this:

    "handlebars-brunch": ">= 1.0 < 1.8"

to the dependencies secdion of package.json, followed by:

$ npm install

Let's do this step by step.

Modules

I'm going to use the regular CommonJS modules here. Thus, when I say "this goes to file abc", it needs to be put in that file for the require logic to work. If you change the file names or the folders where they belong, please update the require lines appropriately. Just to make it simple, all files here will go to the root of app folder.

Main HTML

The main HTML in the skeleton is not surprisingly index.html. We just need to add one line in the body:

    <div id="main"></div>

As to why, it will be clear when we discuss the controller a bit later. This edit goes to assets/index.html.

Model

Our model is simple:

module.exports = class Number extends Backbone.Model
  defaults:
    number: 1

  inc: ->
    @set 'number', @get('number') + 1

This goes to number-model.coffee. So we have:

  • Number class that extends Backbone.Model class
  • Default number is 1
  • Inc method that increases the number property
  • Emphasis on property here - calling set on number will send a change:number event, which we will use later

Template

Template is simple as well:

<input id="number" type="text" value="{{number}}" readonly="false" />
<button id="inc">Inc</button>

This goes to number-template.hbs. We have this:

  • This is a Handlebars template
  • It contains two HTML elements - an input box to display our number
  • Both have the IDs that we'll reference later (#number and #inc)
  • Input has it's value set to {{number}} which Handlebars will render appropriately
  • It also has readonly set to false, just so user cannot change the value, as we don't have the binding for this update

View

View is simple, though a bit more complex than model:

module.exports = class NumberView extends Backbone.Marionette.ItemView
  template: require('number-template')
  events:
    'click #inc': 'inc'
  ui:
    incInp: '#number'

  initialize: ->
    @listenTo @model, "change:number", @numberChanged

  numberChanged: ->
    @ui.incInp.attr 'value', @model.get 'number'

  inc: (evt) ->
    @trigger 'number:inc'

This goes to number-view.coffee. Here we have:

  • NumberView extends Backbone.Marionette.ItemView. This class is responsible for showing a single item, hence the name
  • We specify the template as number-template
  • The events says that when our button (HTML ID is #inc, our Inc button) is clicked, we invoke inc method on the view
  • ui property is just an alias map. It allows us to say @ui.incInp and reference the HTML element specified, in this case #number, which is our input element
  • initialize method binds a listener to our model. It says: whenever the model triggers change:number event, call our numberChanged function on this view
  • numberChanged is just updating the input with the number whenever it changes
  • inc method is the event listener for button click - it triggers number:inc event, which our controller is going to catch and then act appropriately

Controller

The controller looks like this:

module.exports = class NumberController extends Marionette.Controller
  initialize: (options) ->
    @model = options.model
    @view = options.view
    @listenTo @view, 'number:inc', @numberInc

  numberInc: ->
    newNumber = @model.get 'number'
    newNumber++
    newNumber = -5 if newNumber > 5
    @model.set 'number', newNumber

Note that this (as per this doc paragraph) is not equivalent to the C in MVC. I'm however using it for this purpose only.

This goes to number-controller.coffee. It has two things:

  • initialize method remembers the provided model and view instances and subscribes to number:inc event. This event is triggered from view when button is clicked, see above
  • numberInc is the method that encapsulates the business logic of our bounded counter. It will increase the number and make sure it stays in [-5, 5] range
  • Just a reminder here: doing @model.set will trigger a change:number event, which will complete the cycle by going to our view and rendering it via numberChanged method, see above

Application initialization - wiring all this together

We need to update application.coffee to wire this together. We can also do this in initialize.coffee, depending on how we want to do the separation of concerns. Here's how it looks:

NumberModel = require('number-model')
NumberView = require('number-view')
NumberController = require('number-controller')

class Application extends Backbone.Marionette.Application
  initialize: =>
    @on 'initialize:after', @startHistory

    model = new NumberModel
    view = new NumberView model: model
    new NumberController model: model, view: view

    @addRegions mainRegion: '#main'
    @mainRegion.show view

    @start()

  startHistory: (options) => Backbone.history.start()

module.exports = Application

Here we do the following:

  • Import the necessary classes, NumberModel, NumberView and NumberController
  • In initialize, instantiate the model, view and controller and wire them up as needed
  • Add a region - this references the #main div that we added to index.html
  • Finally, show the view into this region and start the app

Conclusion

I can say this about the simple app:

  • Brunch makes it very easy to start the project - installation and getting up and running from the appropriate skeleton is just a few commands away
  • It's watch command is a one-step way to a very good development experience - it compiles templates, CoffeeScript files, bundles everything and a refresh of the dev Web server just brings the latest - like it a lot
  • Bower allows for a very nice dependency management
  • Marionette provides a very good and simple framework for decoupling things into sensible, well-contained units (models, templates, views, controllers, application - and there are also layouts and under-the-cover event aggregator that we actually used above)

All in all, I think this is a very good model - I hope to use it going forward.

Be Sociable, Share!

6 Responses to “Brunch / Marionette sample application”

Litts on October 7th, 2013 15:38:

Nice tutorial , Do you have an example here your views are in separate directories

[WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.


Dan on October 15th, 2013 08:23:

Hi,

great introduction to Marionette!

One question though: what’s the purpose of this one:

inc: -> @set ‘number’, @get(‘number’) + 1

Doesn’t the controller already take care of that here:

numberInc: -> newNumber = @model.get ‘number’ newNumber++ newNumber = -5 if newNumber > 5 @model.set ‘number’, newNumber


icyrock.com on October 15th, 2013 21:47:

Thanks Litts! In order to have views in separate folders, you follow the same logic. E.g. if you put your view in app/view1/view1-view.coffee, you’d just use a different require: require(‘view1/view1-view’). Otherwise, things would look the same.


icyrock.com on October 15th, 2013 21:49:

Thanks Dan, glad you liked it! The inc method of the model is not used – I guess just wanted to emphasize that you cannot just do number++, but instead need to use set / get, which trigger the ‘change:number’ event. Agree however – the active logic is in the controller.


Poplinr on February 24th, 2014 18:27:

This helped me get setup really quickly! Thanks for the post.


icyrock.com on February 27th, 2014 23:54:

Sure thing, glad it was useful!


Leave a Reply


eight − = 0