From 67d02acb3c3c4a3c89b67362a733d777018e7be7 Mon Sep 17 00:00:00 2001 From: Manuel Alabor Date: Tue, 21 May 2013 12:46:05 +0200 Subject: [PATCH] introduction of promises when using beforeRender and afterRender --- CHANGES.md | 4 ++ lib/server/router-mixin.js | 46 ++++++++++-------- lib/view.js | 96 +++++++++++++++++++++++++++++++------- package.json | 2 +- test/specs/view.js | 14 ++++-- 5 files changed, 121 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8c7cba3..5da4794 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,8 @@ # Changes +## 0.0.7 +* Introduced promises for rendering `Views` using [Q](https://github.com/kriskowal/q) + * The `beforeRender` and `afterRender` hook has a `resolve` and `reject` argument now. These make it possible to wait for asynchronous function calls. Perfect for populating models with data from the `APIAdapter` before proceed with the actual rendering. + ## 0.0.6 * `APIAdapter.Server` uses [winston](https://github.com/flatiron/winston) for logging errors now * `APIAdapter.Server` does not crash anymore if an `error` callback does not supply an error object as argument diff --git a/lib/server/router-mixin.js b/lib/server/router-mixin.js index 32234fe..0743ef7 100644 --- a/lib/server/router-mixin.js +++ b/lib/server/router-mixin.js @@ -105,8 +105,6 @@ function route(routeUri) { */ function navigate(routeUri) { this.res.redirect(routeUri); - //var callback = this[this.routes[routeUri]]; - //return callback.apply(this, _.values(this.req.query)); } /** Function: render @@ -128,35 +126,41 @@ function navigate(routeUri) { * Parameters: * (Barefoot.View) view - The view which should be rendered * - * Returns: - * - * * See also: * * */ function render(view) { - var $ = cheerio.load(this.layoutTemplate); + var self = this + , $; + + function initDOM() { + $ = cheerio.load(self.layoutTemplate); + } + + function renderMainView() { + var promise; - function renderMainView($, mainView) { - if(!_.isUndefined(mainView)) { - var clonedMainView = _.clone(mainView()); + if(!_.isUndefined(self.mainView)) { + var clonedMainView = _.clone(self.mainView()); clonedMainView.$ = $; clonedMainView.$el = clonedMainView.selectDOMElement($); - clonedMainView.render(); + promise = clonedMainView.render(); } + + return promise; } - function renderView($, view) { + function renderView() { view.$ = $; view.$el = view.selectDOMElement($); return view.render(); } - function serializeDataStore($, dataStore) { - if(!_.isUndefined(dataStore) && - _.keys(dataStore.registeredModels).length > 0) { - var serializiedDataStore = JSON.stringify(dataStore.toJSON()) + function serializeDataStore() { + if(!_.isUndefined(self.dataStore) && + _.keys(self.dataStore.registeredModels).length > 0) { + var serializiedDataStore = JSON.stringify(self.dataStore.toJSON()) , javaScriptElement = ''; @@ -164,15 +168,19 @@ function render(view) { } } - function writeResponse() { - this.res.send($.html()); + function writeHTTPResponse() { + self.res.send($.html()); + } + + function writeHTTPError() { + self.res.send(500); } - Q.fcall(loadTemplate) + Q.fcall(initDOM) .then(renderMainView) .then(renderView) .then(serializeDataStore) - .then(writeResponse); + .done(writeHTTPResponse, writeHTTPError); } /** Function: start diff --git a/lib/view.js b/lib/view.js index 62d369f..7374a1c 100644 --- a/lib/view.js +++ b/lib/view.js @@ -4,7 +4,7 @@ * * For further information, please refer to the regarding environment specific * mixins. -* + * * Environment Specific Mixins: * - * - @@ -35,29 +35,64 @@ * Working with , you should never implement a render function. * Instead, do everything you'd do there inside of the *renderView* function. * Barefoot overwrites backbones render() with its own version - * (see ) to accomplish hassle free view + * (see ) to accomplish hassle free view * rendering, both on client and server. * + * * Subviews/Nested Views: - * You may be used to create your views subviews directly in the render + * You may be used to create your views subviews directly in the render * function and call their render function there. * * Barefoot supports you by providing the and * functions. Use these functions inside the initialization function of your - * view. + * view. Managing subviews this way brings a few improvements: * - * Managing subviews this way brings a few improvements: * * Barefoot can render views on its own on the server and the browser client * * You do not take care of destroying your view hiearchy when rendering a new * view. Barefoot will handle this for you. (No more Zombies) * + * + * beforeRender and afterRender hooks: + * Barefoot looks for a beforeRender or afterRender function and executes them + * before/after rendering your view automatically. + * + * If beforeRender or afterRender is invoked, a "resolve" and "rejected" + * argument is passed to them. These two functions are essential when using + * asynchronous calls which prepare the view for rendering or doing async + * cleanup work. + * + * Lets say you use the beforeRender hook to load some data from your + * into s. Since calls to the are async, you + * have to ensure that beforeRender does not finish until the api call is done + * and your data in place. The following example shows such a beforeRender + * implementation: + * + * > function beforeRender(resolve, reject) { + * > contactsCollection.fetch({ + * > success: function() { + * > resolve(); + * > } + * > }); + * > console.log('Loading contacts from API...'); + * > } + * + * This function will not "terminate" as soon as the console.log statement was + * executed. Barefoot will wait until you explicitly call the "resolve" function + * argument (or reject in case something went wrong). + * + * These technique is widley known as "Promises". Barefoot uses the popular + * implementaion implementation of the + * spec. + * + * * Attention: * Please be aware you are not overwriting the and * method. This would break barefoots rendering mechanisms on the server and * client. */ var _ = require('underscore') - , Backbone = require('backbone'); + , Backbone = require('backbone') + , Q = require('q'); /** Function: addSubview @@ -99,20 +134,20 @@ function removeSubview(subview) { * that all events of the subviews are bind to the DOM. * * Attention: - * *Do never* call this method on your own. + * *Do never* call this method on your own. * invoces this method automatically when needed. */ function renderSubviews() { var self = this - ,$ = self.$; + , $ = self.$; - _.each(self.subviews, function(subview) { + return Q.when(_.each(self.subviews, function(subview) { subview.$ = $; subview.$el = subview.selectDOMElement($); subview.render.call(subview); subview.delegateEvents(); - }); + })); } /** Function: render @@ -133,19 +168,46 @@ function renderSubviews() { * * */ function render() { - if(!_.isUndefined(this.beforeRender)) { - this.beforeRender(); + var self = this; + + function invokeBeforeRender() { + var deferBeforeRender = Q.defer() + , resolve = deferBeforeRender.resolve + , reject = deferBeforeRender.reject; + + if(!_.isUndefined(self.beforeRender)) { + self.beforeRender(resolve, reject); + } else { + resolve(); + } + + return deferBeforeRender.promise; } - if(!_.isUndefined(this.renderView)) { - this.renderView(); + function invokeRender() { + if(!_.isUndefined(self.renderView)) { + self.renderView(); + } } - if(!_.isUndefined(this.afterRender)) { - this.afterRender(); + function invokeAfterRender() { + var deferAfterRender = Q.defer() + , resolve = deferAfterRender.resolve + , reject = deferAfterRender.reject; + + if(!_.isUndefined(self.afterRender)) { + self.afterRender(resolve, reject); + } else { + resolve(); + } + + return deferAfterRender.promise; } - this.renderSubviews(); + return invokeBeforeRender() + .then(invokeRender) + .then(invokeAfterRender) + .then(self.renderSubviews.bind(self)); } /** Function: selectDOMElement diff --git a/package.json b/package.json index f862608..b53fa77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-barefoot" - , "version": "0.0.6" + , "version": "0.0.7" , "description": "Barefoot makes code sharing between browser and server reality. Write your application once and run it on both ends of the wire." , "keywords": [ "backbone" diff --git a/test/specs/view.js b/test/specs/view.js index fe86e2b..aaac48e 100644 --- a/test/specs/view.js +++ b/test/specs/view.js @@ -70,7 +70,10 @@ describe('View', function() { }) it('should call beforeRender', function(done) { - view.beforeRender = done; + view.beforeRender = function(resolve) { + resolve(); + done(); + }; view.render(); }) @@ -80,7 +83,10 @@ describe('View', function() { }) it('should call afterRender', function(done) { - view.afterRender = done; + view.afterRender = function(resolve) { + resolve(); + done(); + } view.render(); }) @@ -99,7 +105,7 @@ describe('View', function() { beforeEach(function() { view = new Barefoot.View({ el: 'body' }); subview = new Barefoot.View({ el: 'nav' }); - + view.$ = function() {}; subview.renderView = function() {}; view.addSubview(subview); @@ -115,5 +121,5 @@ describe('View', function() { view.renderSubviews(); }) }) - + }) \ No newline at end of file