Skip to content

Commit

Permalink
Merge pull request #28 from tstmedia/beforeafter
Browse files Browse the repository at this point in the history
Adds before, during and after lifecycle events to controllers
  • Loading branch information
mikefrey committed Dec 28, 2012
2 parents 7747cc6 + 4088faf commit 48ae155
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 22 deletions.
57 changes: 35 additions & 22 deletions controller.js → controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ module.exports = Controller

var EventEmitter = require('events').EventEmitter
var _ = require('underscore')
var async = require('async')
var extendable = require('extendable')
var Plugin = require('./plugin')
var lifecycle = require('./lifecycle')
var Plugin = require('../plugin')

function Controller(options) {
this.res = options.res
Expand Down Expand Up @@ -53,12 +55,18 @@ function Controller(options) {
console.log('Running controller\'s initialize function')
this.initialize.apply(this, args)

// Call the `action` if one was matched in the route
if (options.route.action) {
console.log('Calling the controller\'s ' + options.route.action + ' action')
var action = this[options.route.action]
if (action) action.call(this, this._done)
}
this.runBefore(function(err) {
if (err) return this.error(err)
var parallelFuncs = [this.runDuring.bind(this)]
// Call the `action` if one was matched in the route
if (options.route.action) {
console.log('Calling the controller\'s ' + options.route.action + ' action')
var action = this[options.route.action]
if (action) parallelFuncs.push(action.bind(this))
}
// run 'during' methods and the action if there is one
async.parallel(parallelFuncs, this._done)
}.bind(this))
}
}

Expand All @@ -67,7 +75,11 @@ _.extend(Controller.prototype, EventEmitter.prototype, {
// Called when the action method is done populating the
// this.model object with data.
_done: function() {
this._render()
if (this.res.finished) return
this.runAfter(function(err) {
if (err) return this.error(err)
this._render()
}.bind(this))
},

// Determines the best response type based on what the
Expand All @@ -82,37 +94,38 @@ _.extend(Controller.prototype, EventEmitter.prototype, {
return this.render(function(err, html){
if (err) self.error(err)
if (self.html) return self.html(html)
throw 'No `html` method implemented'
throw new Error('No `html` method implemented')
})
}

if (/json$/.test(preferredType)) {
console.log('Rendering JSON')
if (this.json) return this.json(this.serialize())
throw 'No `json` method implemented'
if (this.json) return this.json(this.model)
throw new Error('No `json` method implemented')
}

if (/xml$/.test(preferredType)) {
console.log('Rendering XML')
if (this.xml) return this.xml(this.serialize())
throw 'No `xml` method implemented'
if (this.xml) return this.xml(this.model)
throw new Error('No `xml` method implemented')
}

this.error(415, 'Media type not supported')
},

before: lifecycle.createRegFunction('before'),
runBefore: lifecycle.createExecFunction('before'),
during: lifecycle.createRegFunction('during'),
runDuring: lifecycle.createExecFunction('during'),
after: lifecycle.createRegFunction('after'),
runAfter: lifecycle.createExecFunction('after'),

// Determines the media type to respond with. Override
// with your own content negotiation code.
mediaType: function() {
return this._defaultMediaType
},

// Override this method to update data based on the
// negotiated content type or any other use case
serialize: function() {
return this.data
},

// Initialize is an empty method by default. Override
// it with your own initialization logic.
initialize: function(){},
Expand Down Expand Up @@ -181,13 +194,13 @@ _.extend(Controller.prototype, EventEmitter.prototype, {
this._render = function(){}

var self = this
// use a timeout to let the stack clear before firing the event
// use nextTick to let the stack clear before firing the event
// since it's possible the handler won't be registered yet.
setTimeout(function(){
process.nextTick(function(){
// the app will be listening for a transfer event and
// will create a new controller based on the current one
self.emit('transfer', self, controller, action, data)
}, 1)
})
}

})
Expand Down
70 changes: 70 additions & 0 deletions controller/lifecycle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
var async = require('async')

var rx = {}
function getRegexp(str) {
if (!rx[str]) rx[str] = new RegExp(str+'$', 'i')
return rx[str]
}

var createRegFunction = module.exports.createRegFunction = function(name) {
return function(method, filter) {
var self = this
var items = []
switch (typeof method) {
case 'string':
method = this[method]
case 'function':
items.push({ method: method, filter: filter })
break
case 'object':
Object.keys(method).forEach(function(key) {
items.push({method: self[key], filter:method[key]})
})
}

this['_'+name] = [].concat(items, this['_'+name] || [])
}
}

var createExecFunction = module.exports.createExecFunction = function(name) {
return function(callback) {
var items = this['_'+name]
if (!Array.isArray(items)) return callback()

var concurrent = []
var serial = []
items.forEach(function(item) {
if (!!(item.filter && item.filter.async))
return concurrent.push(item)
serial.push(item)
})

var run = runItem.bind(this)
async.parallel([
function(cb) { async.forEach(concurrent, run, cb) },
function(cb) { async.forEachSeries(serial, run, cb) }
], callback)

}
}

function runItem(item, callback) {
var filter = item.filter
if (filter) {
var format = this.mediaType()
var action = this.route.action
// filter out non matching media type formats
if (filter.format && !getRegexp(filter.format).test(format)) return callback()
// filter out excepted actions
if (filter.except && action) {
if (Array.isArray(filter.except) && ~filter.except.indexOf(action)) return callback()
if (filter.except == action) return callback()
}
// filter out actions not in `only`
if (filter.only && action) {
if (Array.isArray(filter.only) && !~filter.only.indexOf(action)) return callback()
if (!Array.isArray(filter.only) && filter.only != action) return callback()
}
}
item.method.call(this, callback)
}
1 change: 1 addition & 0 deletions templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ _.extend(Templating.prototype, {

render: function(tmpl, data, options, callback) {
var self = this
console.log('Rendering template', tmpl)
this.loadTemplate(tmpl, options, function(err, template) {
if (err) return callback(err)

Expand Down
Loading

0 comments on commit 48ae155

Please sign in to comment.