How to toggle Jade layout in Express for Node.js
One of the awesome things I did in the ASP.NET MVC version of CodeTunnel was add the ability to toggle the view layout file on or off based on whether or not the incoming request was an ajax request. It ended up being as simple as adding a single line to the _ViewStart.cshtml file.
While redesigning CodeTunnel in Node.js I wanted to accomplish the same functionality. Unfortunately the Jade view engine for express is not as accommodating as the Razor view engine in ASP.NET was. In Jade you signify that a view extends another view by including an extends
keyword at the top of the view, followed by a path to the view you wish to extend.
Here is an example of a layout view and a child view that extends the layout view.
// layout.jade
doctype 5
html
head
title Jade is awesome!
body
block content
Block's in Jade are like placeholders. When a view extends another view it can define the content for those placeholders.
// home.jade
extends layout
block content
h1 Home page!
Jade is a great view engine but unfortunately there is no way to dynamically modify the extends
keyword at runtime. Once you say that a view extends another view then it does so forever. So right out the gate we know we cannot accomplish what we want in as simple a manner as we did in ASP.NET.
Lucky for us Jade also allows you to include "partial views", meaning you can stuff another view inside the current view you're working in using the include
keyword. Using this we can create two views.
// home_full.jade
extends layout
block content
include home
Now we simply put our main content in home.jade.
// home.jade
h1 Home page!
If we render the home_full.jade view then it will automatically extend the layout view as well as include our child view. So what can we do with this? We definitely don't want to split every render function with an ugly ternary statement.
res.render('home' + req.xhr ? null : '_full', viewModel);
It's not that much code, but to repeat it over and over again in every action would be a profound waste. Indeed there is a better way and when I came up with it I truly began to see the power of Javascript on the server. If you have dealt with Express or Connect then you are familiar with the concept of middleware. When a request comes through express a request object is created. This request object is then passed through a pipeline of middleware functions. Each middleware is ultimately just a function that accepts a request object, a response object, and a callback function.
Middleware in express are able to sit in the middle of the pipeline and do work with each request that comes in. Once it has finished its work it passes the request along to the next middleware via the callback function.
app.use(function (req, res, next) {
console.log('This is a custom middleware function.');
next();
});
Eventually the request makes it all the way to a controller function where a view is typically rendered to the response and sent to the client. You saw earlier that Express exposes a function on the response object called render()
that allows us to send a view to the client by passing in the view name. Well what I decided to do was write my own piece of middleware that would help with rendering our views based on whether or not the current request is an ajax request.
app.use(function (req, res, next) {
res.renderView = function (viewName, viewModel) {
res.render(viewName + req.xhr ? null : '_full', viewModel);
next();
};
});
Do you see what I did here? I actually added a function to the response object called renderView
. In this function I check if the request is an ajax request and I render the appropriate view. Oh the joys of programming in a dynamic language! Now it's as simple as calling renderView()
instead of render()
from within my controller functions.
The client side wasn't very hard either. With just a little JQuery I was able to grab all hyperlinks with a special "hijax" class and attach a click event to them that would perform an ajax call.
$('a.hijax').click(function (e) {
e.preventDefault(); // Stop browser from loading the URL.
$.ajax({
url: $(this).attr('href'),
}).done(function (markup) {
$('#content').html(markup);
});
});
Now as you click links with the "hijax" class an ajax call is made to the same URL the link was pointing to. The difference is that we will only get back the partial markup that we're after so we can stick it on the page dynamically. If you navigate to that same URL in your browser you would get the view complete with layout file.
Thanks to this fancy little middleware I can be confident that web-crawlers and people who insist on disabling Javascript will still be able to see all of the content on the site. That's what I call DRY progressive enhancement!