Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Object oriented MVC framework for node.js with rich scaffolding and default behaviors
JavaScript Shell
Tree: 03d3290396

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
classes
docs
middleware
plugins
templates
README.md
generateDocs.sh
index.js
package.json

README.md

merlin is an object-oriented, MVC framework for node.js and CouchDB. It's built on top of Express and Cradle. Its name comes from Drupal, under which the author has slaved for far too long.

Getting started

Firstly, you should understand Model-View-Control architecture in order to use merlin. If you don't yet, you might want to read up on it somewhere a little more verbose than here.

Now, before building out your classes, you'll need to load up merlin and whichever of his plugins you think you might need.

var merlin = require('merlin').initMerlin(
  // global merlin settings come in the first argument
  { // this must be a cradle object and is a required arg
    db: cradleDb, 

    // don't change this unless you're a true wizard!
    layoutController: 'layoutController'
  },

  // example of loading a plugin with its default settings
  // (we don't actually need this, though, so you can remove it)
  'messages',

  // views plugin
  { name: 'views', settings: {
      templatePaths: [__dirname + '/templates'] }
  },

  // objects plugin
  { name: 'objects', settings: {
      // specify includePaths if you intend to build classes beyond
      // the basics that are included with merlin.  it's almost assured
      // that you will, so go ahead and create these directories and
      // tell merlin which paths to examine.  order is irrelevant.
      includePaths: [
        __dirname + '/classes/model',
        __dirname + '/classes/view',
        __dirname + '/classes/controller',
      ] }
  }
);

The 'views' and 'objects' plugins are sufficient for now. You can eliminate 'messages' -- just make sure you don't accidentally erase the global merlin config object in the first argument.

Now simply add the merlinRouter middleware to your Express stack and requests will be routed to your layoutController class (this, by the way, is why you have to specify a layoutController parameter in your global merlin settings).

app.use(merlin.layoutControllerRouter())

or...

express.createServer(
  ...,
  merlin.layoutControllerRouter()
)

Writing your classes

Now that Express is routing requests to your layoutController, you'll probably want to write some classes so that you can capture those requests.

A quick note that might turn a few stomachs and shatter a few hearts: merlin is brave and strong, and he has decided that his kingdom will support multiple inheritance. There are (or at least WILL be) details about the chosen implementation in the full merlin documentation. You might want to keep a few bottles of Pepto Bismol on hand from this point forward.

Anyway. Let's say you're in an incredible band and you want to use merlin to make a website to show off the incredible gigs that your incredible band expends absolutely no effort in acquiring. What's first thing you might want to model? The gigs, obviously. I'll call the model "show" and build it out something like this:

A model

function show() { this.fullConstruct() }

  show.parents = ['merlinModel', 'renderable']

  show.construct = function() { return this }
  show.bootstrap = function() {
    show.bootstrap.super('merlinModel').call(this)
    show.bootstrap.super('renderable').call(this)
    return this
  }

  show.properties = {
    date: null,
    venue: null,
    venue_url: null,
    other_bands: [],
    location: null
  }

  show.methods = {
    getCouchViews: function() {
      return {
        all: {
          map: 'function(doc) { \
                  if (doc.type == "' + this.getClassname() + '") { \
                    emit(doc.id, doc) \
                  } \
                }'
        },
        activeByDate: {
          map: 'function(doc) { \
                  if (doc.type == "' + this.getClassname() + '") { \
                    if (doc.active == true || typeof doc.active == "undefined") { \
                      emit(Date.parse(doc.date), doc._id) \
                    } \
                  } \
                }'
        }
      }
    }
  }

module.exports = show

The view

The view will be quite simple, as it will rely on merlin's rich defaults and scaffolding mechanisms.

function showView() { this.fullConstruct() }

  showView.parents = ['merlinObjectView']

  showView.construct = function() { return this }
  showView.bootstrap = function() {
    showView.bootstrap.super('merlinObjectView').call(this)
    return this
  }

module.exports = showView

The controller

And finally, the controller, binding together the view and the model.

function showController() { this.fullConstruct() }

  showController.parents = ['merlinController', 'merlinObjectController', 'merlinViewableController']

  showController.construct = function() { return this }
  showController.bootstrap = function() {
    // these do some scaffolding work for us
    showController.bootstrap.super('merlinObjectController').call(this)
    showController.bootstrap.super('merlinViewableController').call(this)
    return this
  }

  showController.methods = {
    route: function(context, callback) {
      var myself = this
      var couchParams = {
        descending: false,
        include_docs: true
      }

      this.merlin().db().view('shows/activeByDate', couchParams, function (err, result) {
        if (err)
          callback(err, null)
        else
          myself.invokeRenderer(result, context.renderContext, callback)
      })
    }
  }

module.exports = showController

Calling overridden parent methods

To call a parent method from the child method that overrides it (which you'll notice above in show.bootstrap()), you'll use this pattern:

<theFunction>.super(<optionalParentClassname>).call(this)
  • theFunction: This is the path to the function, which can come in two forms:

    1. The bootstrap() and construct() methods live directly on the constructor rather than in constructor.prototype, so when calling super() from these methods, you'll do something like:

      myClass.bootstrap.super('parentClass').call(this)
      
    2. most other methods will require something like:

      myClass.prototype.someFunction.super('anotherParentClass').call(this)
      
  • optionalParentClassname: This is only optional when your class inherits the given method from a single parent. Any function inherited from multiple parents will need to specify which parent's implementation to use.

  • You can also simply call this.myFunction.super('myParentClass').call(this) in both contexts, though this is less precise and may have unexpected results.

Creating a front page (i.e., using the staticPage object)

Of course, you'll probably want a front page for your site as well, since most casual users will guess the paths to your hidden endpoints with suboptimal efficiency. merlin comes with a built-in staticPage model and the associated view and controller classes to interact with it. To utilize these, start by simply creating a staticPage document in your CouchDB database. A staticPage document ought to look something like this:

{ _id: 'staticPage-theBestPage',
  path: 'pathTo/my/page',
  title: 'Teh best paeg on my wabsite.',
  body: 'Thx u visiting!!!11~',
  stylesheets: [
    '/styles/somewhereOnMy/server.css',
    '/styles/another.css'
  ]
}

A few things to keep in mind:

  1. Leave off the preceding slash in the path.
  2. You can't use javascript variables or regions inside the body field (at least not yet). If you want to add dynamic content within the body of your staticPage, you can do so by creating a page-pathTo-my-page.haml template and inserting those variables/regions manually.
  3. The stylesheet paths are what will appear in the <link> tags in your page's header, so make sure that they're public URLs rather than filesystem paths.

You can create a different page template for as many different pages/paths as you want. The staticPageView class has a getTemplateNames() method that specifies the different filenames that staticPage templates can have. Those are:

['page', 'page-:id:', 'page-:path:']

As you might've guessed, the ":blahblahblah:" construction specifies a variable, so that merlin will know to look for "page-theBestPage.haml" and "page-pathTo-my-page.haml" without you having to explicitly tell him to do so.

All of that is good and well, but what path will we use for the front page? After all, the front page appears when no path is specified. Why, the magic path "<front>", of course. It's called 'merlin' for a reason, after all...

Available middleware

Router

You have to use the merlinRouter middleware. Otherwise, Express will have no idea that it needs to feed requests into your layoutController. You'll probably want this middleware last on the list (or nearly last, anyway). express.createServer( merlin.layoutControllerRouter() )

Something went wrong with that request. Please try again.