Created
December 6, 2011 20:44
-
-
Save polotek/1439922 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The Article domain object, this can be completely custom or | |
* part of some ORM. It doesn't really matter. It's an object you | |
* can call methods on. As long as you return some compatible | |
* promise-like thing to the data/render cycle | |
*/ | |
var util = require('util') | |
, Q = require('q') // https://github.com/kriskowal/q | |
, db = require('./data-layer') | |
, Domain = require('./domain'); | |
var Article = function() { | |
} | |
util.inherits(Article, Domain.Record); | |
// Some instance methods | |
Article.prototype.foo = function(value) { | |
return this.bar + value; | |
} | |
// Static methods | |
// Some logic here that fleshes out db calls | |
// into domain objects. | |
Article.ranked = function(limit) { | |
var articles; | |
// Could be your fancy ORM. This returns a promise and | |
// could be totally lazy | |
articles = Article.all.sortBy('rank').limit(limit || 100); | |
// OR | |
// Could be totally custom using promises | |
var defer = Q.defer(); | |
articles = defer.promise; | |
// I'm butchering some couchdb api here, but you get the point | |
db.view('ranked_articles', function(err, view) { | |
if(err) { return defer.reject(err); } | |
var objects = Article.create(view.rows); | |
defer.resolve(objects); | |
}); | |
return articles; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SomeController.route("GET", "/", [ | |
getUserAndSession, | |
checkAccess, | |
function (req, res, params, next) { | |
// decide which template to show. Let's assume the framework | |
// has already done the current user lookup | |
var user = req.currentUser | |
, template = 'frontindex'; | |
if(user && user.loggedIn) { | |
template = 'dashboard'; | |
} | |
// You have a main domain object that you want | |
// to render. So load it. | |
Home.get(function(home) { | |
// Build up your data. This object will be a combination | |
// of values and promises that represent async calls. | |
var data = { | |
someValue: "Some data value here" | |
, tasks: Task.indexList() | |
, articles: Article.ranked(10); | |
} | |
// The render method does more than just execute | |
// the template. It also fully resolves the data | |
// object by composing any nested promises and | |
// waiting until they're all resolved | |
home.render(template, data) | |
.on('error', next) | |
// So render return a stream. The stream can | |
// emit data that is ready to be pushed down the | |
// pipe. We need an event for when the stream is | |
// ready in case any of the setup fails. | |
.on('ready', function(templateStream) { | |
templateStream.on('error', next).pipe(res); | |
}); | |
}); | |
}]), |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>{this.title}</title> | |
<link rel="stylesheet" href="style.css"/> | |
</head> | |
<body> | |
{this.topbar(data.links)} | |
<div class="container_16"> | |
<div class="grid_11"> | |
<div class="that_value">Here's your value: {data.someValue}</div> | |
<div class="section"> | |
{this.summary(data.articles)}</div> | |
</div> | |
<div class="grid_5"> | |
<div class="section"> | |
<h3>Sidebar</h3> | |
{render('task_list', data.tasks)} | |
{if req.currentUser && req.currentUser.hasRole('admin') } | |
{ render('admin_links', req.currentUser.adminLinks()) } | |
{/if} | |
</div> | |
</div> | |
</div> | |
<ul> | |
{forEach this.footerLinks() link idx} | |
<li class="{link.class}">{link.render()}</li> | |
{/forEach} | |
</ul> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a totally made up, blue sky, vision of what a request cycle might look like. None of the ideas here are fully baked.
There are several interesting things happening here though. Domain objects are just js objects with properties and methods on them. They can be backed by whatever datastore you want and implemented however you prefer. There are just a few important areas where they need to conform to a compatible api. Here the only constraint is that if you're passing data to the render api, it could be a value or a promise-like thing to be resolved asynchronously.
The controller handles a few things.
The controller leverages eventing and pipes where it can. The nice thing is that the template rendering is a stream. It doesn't have to be, but this is the way you would do immediate rendering by flushing to the response as soon as data is available.
The view also has several things happening. This has a lot of stuff even I'm not sure about. But interesting anyway. I actually don't mind a little logic in my templates. It can certainly get out of hand, but it's there responsibility of the dev/designer to manage that. There is the similar idea to my last gist where a template is like a function execution. It's passed some data and it can also execute in some context.
By the time the template logic executes, all promises included in the initial data have been resolved. Doesn't matter if they're lazy or they fired immediately. The resolved value is available here. On line 20 you can see the other side of rendering. The
render
function is available in templates (the version on an object is the same, it just sets the context). Render doesn't do anything different when called in a template. But we can grab the stream from this sub template and send it out the maintemplateStream
. So even conditional partials are supported with streaming.The last thing is
this.footerLinks()
on line 26. Ignore the pseudo-js forEach crap, it's not important. Like the previous data methods, footerLinks is going to return either a value or a promise. Ideally the main template rendering would be paused and some construct would make sure this gets resolved before continuing by looping over the returned value. This could also be in a conditional.As you can see, this assumes that we're embracing both streams and promises extensively in order to minimize callbacks.