node-barefoot
Version:
Barefoot makes code sharing between browser and server reality. Write your application once and run it on both ends of the wire.
238 lines (200 loc) • 6.31 kB
JavaScript
/** Mixin: Barefoot.Router.Server
* This mixin contains any server side specific code for the <Barefoot.Router>.
*
* See also:
* * <Barefoot.Router>
*/
var _ = require('underscore')
, debug = require('debug')('barefoot:server:router')
, cheerio = require('cheerio')
, winston = require('winston')
, Q = require('q')
, CookieAdapter
, DataStore;
/** Function: toString
* String representation of this module.
*/
function toString() {
return 'Router.Server';
}
/** Function: preInitialize
* This function is called before any initialization or constructor of the
* <Barefoot.Router> is executed. It expects that the passed options object
* contains the following elements:
*
* app - An Express.JS app
* layoutTemplate - The minimal HTML skeleton to render the application into.
* Example: "<html><head></head><body></body></html>"
* startExpressApp - A callback function which initiates the actual startup of
* the Express.JS app from above.
*
* Each of these are copied into this router object directly to access them
* later on.
*
* Parameters:
* (Object) options
*/
function preInitialize(options) {
debug('pre initialize');
this.app = options.app;
this.layoutTemplate = options.layoutTemplate;
this.startExpressApp = options.startExpressApp;
this.apiAdapter = options.apiAdapter;
this.setupRequestContext = options.setupRequestContext;
}
/** Function: route
* This replacements for the route function of Backbone.Router ensures that each
* route defined in the router is added to the Express.JS app.
*
* The route function ensures that you have access to all cookies from the
* request via the cookieAdapter property of this router.
*
* Parameters:
* (String) routeUri - URI
*
* Returns:
* The created Express.JS route
*
* See also:
* * <Barefoot.CookieAdapter.Server>
*/
function route(routeUri) {
debug('route `%s`', routeUri);
var self = this
, Barefoot = require('../')();
// Lazy-Load the barefoot objects CookieAdapter and DataStore. Otherwise
// we'd have some cyclic dependency problems.
CookieAdapter = Barefoot.CookieAdapter;
DataStore = Barefoot.DataStore;
return self.app.get('/' + routeUri, function(req, res) {
debug('request to `/%s`', routeUri);
var callback = self[self.routes[routeUri]]
, cookieAdapter = new CookieAdapter({ cookies: req.cookies })
, dataStore = new DataStore();
// Ensure that request-local stuff is available locally:
self.req = req;
self.res = res;
self.cookieAdapter = cookieAdapter;
self.dataStore = dataStore;
// Ensure the request context is set up locally:
if(!_.isUndefined(self.setupRequestContext)) {
self.setupRequestContext.call(self);
}
// Ensure that the APIAdapter has access to the current request:
if(!_.isUndefined(self.apiAdapter)) {
self.apiAdapter.req = req;
}
// Execute the route:
var routeResult = callback.apply(self, _.values(req.params));
// Ensure cookies are placed inside the response:
res.cookies = self.cookieAdapter.cookies;
return routeResult;
});
}
/** Function: navigate
* Rewrites <Backbone.Router.navigate at http://backbonejs.org/#Router-navigate>
* to replicate its functionality when rendering the application on the server.
*
* Basically the callback for the given routeUri is picked and called.
*
* Parameters:
* (String) routeUri - The URI of the route to navigate to
*
* Returns:
* (Object) the value which the route callbacks returns.
*/
function navigate(routeUri) {
debug('redirecting to `%s`', routeUri);
this.res.redirect(routeUri);
}
/** Function: render
* This function initiates the rendering of the passed view. This is done by
* loading the layout template into a cheerio DOM object.
*
* Afterwards the presence of a main view object is checked. If available, that
* main view gets rendered into the DOM. In any case, the passed view gets
* rendered.
*
* As a final step the presence of registered models in the application wide
* <Barefoot.DataStore> is checked. If present, its content gets serialized into
* a script DOM element so the client can properly deserialize its state
* afterwards. See <Barefoot.Router.Client.start> for the deserialization code.
*
* The resulting HTML code is then sent to the client by using the Express.JS
* response object, buffered from the route callback in the <route> function.
*
* Parameters:
* (Barefoot.View) view - The view which should be rendered
*
* See also:
* * <route>
*/
function render(view) {
debug('render');
var self = this
, $;
function initDOM() {
debug('init DOM');
$ = cheerio.load(self.layoutTemplate);
}
function renderMainView() {
debug('render main view');
var promise;
if(!_.isUndefined(self.mainView)) {
var clonedMainView = _.clone(self.mainView());
clonedMainView.$ = $;
clonedMainView.$el = clonedMainView.selectDOMElement($);
promise = clonedMainView.render();
}
return promise;
}
function renderView() {
debug('render view');
view.$ = $;
view.$el = view.selectDOMElement($);
return view.render();
}
function serializeDataStore() {
debug('serialize data store');
if(!_.isUndefined(self.dataStore) &&
_.keys(self.dataStore.registeredModelsAndCollections).length > 0) {
var serializiedDataStore = JSON.stringify(self.dataStore.toJSON())
, javaScriptElement =
'<script>function deserializeDataStore(){' +
'return ' + serializiedDataStore + ';}</script>';
$('body').append(javaScriptElement);
}
}
function writeHTTPResponse() {
debug('write http response');
self.res.send($.html());
}
function writeHTTPError(err) {
winston.log('error', 'Uncatched HTTP Error', {
source: toString()
, err: err.toString() || err
, stack: err.stack || undefined
});
debug('write http error');
self.res.send(500);
}
Q.fcall(initDOM)
.then(renderMainView)
.then(renderView)
.then(serializeDataStore)
.done(writeHTTPResponse, writeHTTPError);
}
/** Function: start
* Calls the passed starter function, buffered in <preInitialize>.
*/
function start() {
debug('start');
this.startExpressApp(this.app);
}
module.exports = {
preInitialize: preInitialize
, route: route
, navigate: navigate
, render: render
, start: start
};