Partial Templates

There will be certain pieces of the pages in our site that are repeated. Things like headers, footers, and certain application-specific widgets will appear on multiple pages, or every page. We could copy the markup for those pieces into the page’s template, but it will be easier to manage a single shared template for each of these common elements. Copying and pasting a reference to a child template is more maintainable than copying and pasting the markup itself. If we don’t want to have to repeat the code that imports our CSS and client-side JavaScript, we can templatize a default version of the pages in our site, storing all that information there, in addition to site-wide headers and footers. We can leave a space for the content of each page and reuse the rest. Let’s create a file with this boilerplate markup called parent.html:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{pageTitle}}</title>
    <link rel="stylesheet" src="css/style.css" />
    {{>stylesheets}}
  </head>
  <body>
    {{>content}}
    <footer>&copy; 2011 Node for Front-End Developers</footer>
  </body>
  {{>scripts}}
</html>

Every template engine handles nested templates differently. While Mustache uses the {{>...}} syntax you see above, templating engines that are otherwise very similar to Mustache may use something different. Try not to get too caught up in the particular syntax of this engine, just know that the greater-than sign here indicates a child template, and we’ll use that to do composition.

To allow this parent template to load individual child templates and create full pages, we’ll have to add some robustness into our application code. Instead of loading and rendering templates inline, we’ll load another third-party module to manage loading, and move rendering to its own function.

The requirejs module works like the client-side Require.js utility, providing a slightly different approach to dependency management than Node’s. The difference we’re taking advantage of below is the text.js extension. We’ll need to download this from the Require.js site and save it in our Node application’s root directory. This will allow Require to load and manage text files, guaranteeing that they’re loaded efficiently and available when they’re needed. Adding the prefix text! to the path of the dependency (our template, in this case) tells Require to load the file as text instead of trying to evaluate it:

var connect = require("connect"),
  fs = require("fs"),
  mustache = require("mustache"),
  requirejs = require("requirejs"),
  parentTmpl;
      
// configure requirejs to fall back to Node's require if a module is not found      
requirejs.config({ nodeRequire: require });
      
connect(
  connect.static(__dirname + "/public"),
  connect.router(function(app) {
    app.get("/show/:tmpl/:firstName/:lastName", function(req, res) {
      var userName = {
          firstName: req.params.firstName,
          lastName: req.params.lastName
        };
      // once the parent template is loaded, render the page
      requirejs(["text!public/parent.html"], function(_parentTmpl) {
        parentTmpl = _parentTmpl;
        render(res, req.params.tmpl + ".html", userName);
      });
    });
  })
).listen(8000);
    
function render(res, filename, data, style, script, callback) {
  // load the template and return control to another function or send the response
  requirejs(["text!public/" + filename], function(tmpl) {
    if (callback) {
      callback(res, tmpl, data, style, script);
    } else {
      // render parent template with page template as a child
      var html = mustache.to_html(
        parentTmpl, 
        {content: data}, 
        {content: tmpl, stylesheets: style || "", scripts: script || ""}
      );
      res.end(html);
    }
  });
}

While we could make our system for rendering templates within a wrapper far more robust, this smaller amount of code will let us do quite a bit (within certain constraints). In addition to the content for each page, our parent template and its rendering function will accept additional CSS or JavaScript imports as string literals. We can allow the render function to send back the rendered HTML without modification, or we can override that behavior by passing in an additional callback to call once the child template loads.

Now that we have files being loaded and requests being made in order, you can begin to see the issues with nesting callbacks that frighten some people who are new to Node. We’ve abstracted out some functionality, but there are still anonymous functions and nested logic. If you’re used to DOM scripting, this probably isn’t as scary, but you’ve probably also recognized it as something that needs to be managed. Although the code above is all in a single file for clarity, in a real application we’d put all of our template rendering code into its own module and move the logic into named functions private to that module.

Get Node for Front-End Developers now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.