Thursday, November 15, 2012

expressjs with ... no layout ?!

Yesterday, I was working on a backoffice web application I will be releasing soon for one of my iOS projects. This web application is powered by node.js. I love node.js.
When it comes to creating web apps with nodejs I first had tried to managed all the routing aspects and stuff myself but lately I crossed expressjs.
expressjs is not as overbloated as most web frameworks in my opinion. I like simple stuff because when it comes to debug or extend the thing it's always easier. And it happened, I had to get my hands into expressjs because it appears that the layout abilities were removed from expressjs since 3.0.

The solution they (the developers) give is to have two separate files which you will include in any view before and after your custom content. Something like (with ejs engine):

<%- include 'top.ejs' %>
my custom content
<%- include 'bottom.ejs' %>

As a former ruby on rails developer, I am used to partials and layouts, so this workaround is rather repelling to me.



To find or build a decent workaround I would have to hunt the res.render() method. So I dove in express.js and found that the 'res' of any middleware or route handler is setup in the file response.js of the framework, then I found that the response is in fact an object with the prototype of http.ServerResponse

var res = module.exports = {
  __proto__: http.ServerResponse.prototype
};

Interesting!

Now if you scroll to line 695 (of response.js) you'll see that res.render() calls app.render()

app.render(view, options, fn);

Now if you switch to the application.js file, around line 454 there is the app.render() method. One thing is worth to note there, the template 'name' to render is expended to it's path by the use of the View object which is not accessible from outside. What this means is that we should use app.render whenever  possible to render a template and not try to do it by hand because we would loose all the path resolution bound to the express object.

Let's do it!

http.ServerResponse.prototype.renderInLayout = function (template, options, cb) {
  /* do stuff */
}

Now we need to access the express instance from our function. It's easy since express stores 'req' in the 'res' and 'req' stores 'app' if you follow me ;)

var app = this.req.app;

And so, the whole function becomes:

http.ServerResponse.prototype.renderInLayout = function (template, options, cb) {
  var
    self = this
  , app = this.req.app
  ;
  app.render(template, options || {}, function (error, result) {
    if (error) { /* handle error */ }
    app.render(layout, { yield: result }, cb);
  });
}

http.ServerResponse.prototype.renderInLayout = render;

Now I can write a simple layout like:

<html>
<body>
  <%- yield %>
</body>
</html>

And use my renderInLayout like this:

res.renderInLayout('devices', {
  title: 'Devices list',
  devices: result
});


You may find the full code with a bit of sugar on github: https://github.com/Orion98MC/express_layout

The beauty of this is that it should work for any engine. Of course, it's a very simplistic implementation, one could add contentFor and stuff ... But that's what I was looking for at the moment. Feel free to fork!

Cheers.


No comments:

Post a Comment