Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit 70a0e78818b2611a9e0d4166bfa5411008158f78 @brikis98 committed Apr 10, 2011
@@ -0,0 +1 @@
+.DS_Store
@@ -0,0 +1,9 @@
+# Overview
+
+This project shows an example of how to use [node.js](http://nodejs.org/) to:
+
+* Share [backbone.js](http://documentcloud.github.com/backbone/) models between server and client.
+* Share [dust](http://akdubya.github.com/dustjs/) templates between server and client.
+* Split up rendering between server and client. In particular, data will render *as soon as it is available*, whether that is while still processing on the server or after the page has already reached the client's browser.
+
+Currently, this is more of a code sample than a full fledged library, but I'll work on turning it into an npm module soon.
@@ -0,0 +1,83 @@
+var dust = require('dust');
+var nowjs = require('now');
+var _ = require('underscore');
+
+var everyone = null;
+var collectionCache = null;
+
+var base = dust.makeBase({
+ renderIfReady: function(chunk, context, bodies, params) {
+ var model = context.current();
+ return chunk.map(function(chunk) {
+ renderChunk(chunk, model, context);
+ });
+ }
+});
+
+exports.initialize = function(app) {
+ everyone = nowjs.initialize(app);
+ collectionCache = {};
+ everyone.now.getModelsToRender = getModelsToRender;
+}
+
+exports.render = function(req, res, collection) {
+ var collectionId = exports.cacheCollection(req, res, collection);
+ res.cookie('collection.id', collectionId, {httpOnly: false});
+ collection.fetch();
+ render('index', base.push({collection: collection.models, noScript: noScript(req)}), res);
+}
+
+exports.cacheCollection = function(req, res, collection) {
+ var collectionId = _.size(collectionCache);
+ collectionCache[collectionId] = collection;
+ return collectionId;
+}
+
+exports.getCollectionFromCache = function(collectionId) {
+ return collectionCache[collectionId];
+}
+
+function renderChunk(chunk, model, context) {
+ if (model.get('isFetched')) {
+ renderFetchedModel(chunk, model);
+ } else if (model.get('serverOnly') || context.get('noScript')) {
+ model.bind('change', _.once(function() { renderFetchedModel(chunk, model); }));
+ } else {
+ chunk.end(0);
+ }
+}
+
+function renderFetchedModel(chunk, model) {
+ render(model.get('template'), model.attributes, chunk);
+ model.set({isRendered: true, renderedServerSide: true}, {silent: true});
+}
+
+function getModelsToRender(collectionId, callback) {
+ var collection = exports.getCollectionFromCache(collectionId);
+ if (collection) {
+ collection.each(function(model) { sendModelToClient(model, callback); });
+ } else {
+ callback(null);
+ }
+}
+
+function sendModelToClient(model, callback) {
+ if (model.get('isRendered')) {
+ return;
+ } else if (model.get('isFetched')) {
+ callback(model);
+ } else {
+ model.bind('change', _.once(function(model) { callback(model); }));
+ }
+}
+
+function noScript(req) {
+ return typeof req.query.noScript !== 'undefined' && req.query.noScript == 'true';
+}
+
+function render(template, context, res) {
+ var stream = dust.stream(template, context);
+ stream.on('data', function(data) { res.write(data); });
+ stream.on('end', function() { res.end(); });
+ stream.on('error', function(err) { res.end(err); });
+}
@@ -0,0 +1,26 @@
+var fs = require('fs');
+var path = require('path');
+
+var TEMPLATE_DIR = './templates';
+var PUBLIC_DIR = './public/templates';
+
+exports.watch = function(dust) {
+ // Compile all templates at start-up
+ var templates = fs.readdirSync(TEMPLATE_DIR);
+ _.each(templates, function(template) { compileTemplate(path.join(TEMPLATE_DIR, template)); });
+
+ // Watch the templates directory and recompile them if a file changes or is created
+ var watcher = require('watch-tree').watchTree(TEMPLATE_DIR, {'sample-rate': 500});
+ watcher.on('fileModified', function(path) { compileTemplate(path); });
+ watcher.on('fileCreated', function(path) { compileTemplate(path); });
+
+ function compileTemplate(file) {
+ if (file) {
+ console.log('Recompiling: ' + file);
+ var templateName = path.basename(file, '.jst');
+ var compiled = dust.compile(fs.readFileSync(file, 'UTF-8'), templateName);
+ dust.loadSource(compiled);
+ fs.writeFileSync(path.join(PUBLIC_DIR, templateName + '.js'), compiled);
+ }
+ }
+}
@@ -0,0 +1,24 @@
+var Collections = {};
+if (typeof exports !== 'undefined') {
+ Collections = exports;
+
+ _ = require('underscore')._;
+ Backbone = require('backbone');
+}
+
+Collections.DelayedCollection = Backbone.Collection.extend({
+ fetch: function() {
+ var self = this;
+ this.each(function(model) {
+ if (model.get('delay')) {
+ setTimeout(function() { self.updateModel(model); }, model.get('delay'));
+ } else {
+ self.updateModel(model);
+ }
+ });
+ },
+
+ updateModel: function(model) {
+ model.set({message: 'fetched after ' + this.get('delay') + ' ms', isFetched: true});
+ }
+});
@@ -0,0 +1,19 @@
+var Models = {};
+if (typeof exports !== 'undefined') {
+ Models = exports;
+
+ _ = require('underscore')._;
+ Backbone = require('backbone');
+}
+
+Models.DelayedModel = Backbone.Model.extend({
+ defaults: {
+ isFetched: false,
+ isRendered: false,
+ template: 'delayedModel',
+ delay: 0,
+ serverOnly: false,
+ renderedServerSide: false
+ }
+});
+
Oops, something went wrong.

0 comments on commit 70a0e78

Please sign in to comment.