Skip to content
This repository has been archived by the owner on Apr 30, 2021. It is now read-only.

Commit

Permalink
introduction of promises when using beforeRender and afterRender
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel Alabor committed May 21, 2013
1 parent 68681ef commit 67d02ac
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 41 deletions.
4 changes: 4 additions & 0 deletions 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
Expand Down
46 changes: 27 additions & 19 deletions lib/server/router-mixin.js
Expand Up @@ -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
Expand All @@ -128,51 +126,61 @@ function navigate(routeUri) {
* Parameters:
* (Barefoot.View) view - The view which should be rendered
*
* Returns:
*
*
* See also:
* * <route>
*/
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 =
'<script>function deserializeDataStore(){' +
'return ' + serializiedDataStore + ';}</script>';
$('body').append(javaScriptElement);
}
}

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
Expand Down
96 changes: 79 additions & 17 deletions lib/view.js
Expand Up @@ -4,7 +4,7 @@
*
* For further information, please refer to the regarding environment specific
* mixins.
*
*
* Environment Specific Mixins:
* - <Barefoot.View.Client>
* - <Barefoot.View.Server>
Expand Down Expand Up @@ -35,29 +35,64 @@
* Working with <Barefoot.View>, 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 <Barefoot.View.Shared.render>) to accomplish hassle free view
* (see <Barefoot.View.Shared.render>) 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 <addSubview> and <removeSubview>
* 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
* <APIAdapter> into <Model>s. Since calls to the <APIAdapter> 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 <Q at https://github.com/kriskowal/q> implementation of the
* <Promises/A+ at http://promises-aplus.github.io/promises-spec/> spec.
*
*
* Attention:
* Please be aware you are not overwriting the <renderSubviews> and <render>
* 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
Expand Down Expand Up @@ -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. <Barefoot.View.Shared.render>
* *Do never* call this method on your own. <Barefoot.View.Shared.render>
* 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
Expand All @@ -133,19 +168,46 @@ function renderSubviews() {
* * <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
Expand Down
2 changes: 1 addition & 1 deletion 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"
Expand Down
14 changes: 10 additions & 4 deletions test/specs/view.js
Expand Up @@ -70,7 +70,10 @@ describe('View', function() {
})

it('should call beforeRender', function(done) {
view.beforeRender = done;
view.beforeRender = function(resolve) {
resolve();
done();
};
view.render();
})

Expand All @@ -80,7 +83,10 @@ describe('View', function() {
})

it('should call afterRender', function(done) {
view.afterRender = done;
view.afterRender = function(resolve) {
resolve();
done();
}
view.render();
})

Expand All @@ -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);
Expand All @@ -115,5 +121,5 @@ describe('View', function() {
view.renderSubviews();
})
})

})

0 comments on commit 67d02ac

Please sign in to comment.