Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fill out README and add the TemplateEngine

  • Loading branch information...
commit e17fda162bc4846391c045cc4d13bcd8bd4ff22d 1 parent 75c6989
@steveukx authored
Showing with 183 additions and 1 deletion.
  1. +5 −0 .gitignore
  2. +52 −1 README.md
  3. +15 −0 package.json
  4. +111 −0 src/template-engine.js
View
5 .gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+.idea/
+*.iml
+*.log
+*.swp
View
53 README.md
@@ -1,4 +1,55 @@
hogan-middleware
================
-Middleware component to use Hogan.js mustache templates as views in an Express server
+Middleware component to use Hogan.js mustache templates as views in an Express server
+
+Usage
+=====
+
+ var app = express();
+
+ app.configure(function () {
+ app.set('views', __dirname + '/views'); // tell express which directory your views are in
+ app.set('view engine', 'mustache'); // name your templates
+ app.engine('mustache', require('hogan-middleware').__express); // register the engine
+ });
+
+Once registered, your routing in express can use a mustache file name as the view to be rendered:
+
+ app.get('/', req, res, next) {
+ res.render('home', { SiteName: 'My Website' });
+ }
+
+In this case there is a file named `home.mustache` in the `views` directory that may have content as:
+
+ <!doctype html>
+ <html>
+ <head><title>Hello World</title></head>
+ <body>
+ <h1>{{SiteName}}</h1>
+ </body>
+ </html>
+
+Partial Templates
+=================
+
+Mustache allows the use of partial templates, this is supported by the middleware component by making all templates
+available as partial templates when rendering a template.
+
+When `home.mustache` is being used as the name of the template to be rendered, that can include `a.mustache` from the
+views directory by adding `{{>a}}`. As `a.mustache` is rendered as a partial, that also has all templates available
+to it for use as partials, so could in turn have `{{>b}}` to include a nested partial.
+
+To allow for a tidy source tree, templates can be in any number of sub-directories under the main views directory,
+they are all made available for use as partials without any path identifier.
+
+Note - multiple templates with the same name but in different directories will overwrite each other.
+
+Note - don't include the same template as a partial inside itself.
+
+Live Updating
+=============
+
+As express uses the the render engine for the first time, a series of watches are added to any sub-directory of the
+views directory so that any changes are automatically reloaded for you while the server is still running.
+
View
15 package.json
@@ -0,0 +1,15 @@
+{
+ "name": "hogan-middleware",
+ "version": "0.0.0",
+ "description": "Middleware component to use Hogan.js mustache templates as views in an Express server",
+ "author": "Steve King <steve@mydev.co>",
+ "contributors": [ { "name": "Steve King", "email": "steve@mydev.co" } ],
+ "keywords": ["mustache", "middleware", "template"],
+ "repository": "git://github.com/steveukx/hogan-middleware",
+ "main":"./src/template-engine.js",
+ "engines": { "node": ">= 0.8.0" },
+ "dependencies":{
+ "hogan.js":"",
+ "readdir":"*"
+ }
+}
View
111 src/template-engine.js
@@ -0,0 +1,111 @@
+
+var Hogan = require('hogan.js');
+var ReadDir = require('readdir');
+var Path = require('path');
+var FS = require('fs');
+
+function TemplateEngine() {
+}
+
+/**
+ * All active directory file system watches
+ * @type {fs.FSWatcher[]}
+ * @ignore
+ */
+TemplateEngine._watches = [];
+
+/**
+ * Called by the express server to get the content for a given template at the templatePath supplied. The templateData
+ * can contain any content from a configured route, and will be made available to the templates.
+ *
+ * Templates can include partials by name for any template also in the views directory, note that if sub-directories are
+ * used to create included partials, express will not necessarily recognise that file as a valid view path... you've been
+ * warned.
+ *
+ * @param {String} templatePath Path to the template
+ * @param {Object} templateData Data to give to the template
+ * @param {Function} next Callback to receive two arguments, an error object and the template result.
+ */
+TemplateEngine.__express = function(templatePath, templateData, next) {
+ var templateName = Path.basename(templatePath, Path.extname(templatePath));
+ var templates = TemplateEngine._getTemplates(templateData.settings.views);
+ var output = null, error = null;
+
+ try {
+ output = templates[templateName].render(templateData, templates);
+ }
+ catch (e) {
+ error = e;
+ }
+ finally {
+ next(error, output);
+ }
+};
+
+/**
+ * Stores an individual template based on the supplied path, the name of the template is the file's basename without
+ * the extension.
+ *
+ * @param {String} templatePath
+ */
+TemplateEngine._storeTemplate = function(templatePath) {
+ var templateName = Path.basename(templatePath, Path.extname(templatePath));
+ TemplateEngine.__templates[templateName] = Hogan.compile(FS.readFileSync(templatePath, 'utf-8'));
+
+ console.log('Stored template', templateName);
+};
+
+/**
+ * Gets all templates, when the template path hasn't yet been scanned it will be read synchronously to ensure there are
+ * always templates available, the template directory is then watched to allow templates to be changed while the server
+ * is still running.
+ *
+ * @param {String} templatesPath
+ */
+TemplateEngine._getTemplates = function(templatesPath) {
+ if(!TemplateEngine.__templates) {
+ TemplateEngine._refreshTemplates(templatesPath);
+ FS.watch(templatesPath, {persistent: false}, TemplateEngine._refreshTemplates.bind(TemplateEngine, templatesPath));
+ }
+ return TemplateEngine.__templates;
+};
+
+/**
+ * Refresh all directory watches
+ * @param {String} templatesPath
+ */
+TemplateEngine._refreshWatches = function(templatesPath) {
+ console.log('Refreshing watched directories');
+
+ // Remove any existing watches
+ TemplateEngine._watches.splice(0).forEach(function(watch) {
+ watch.close();
+ });
+
+ ReadDir.readSync(templatesPath, ['**/'], ReadDir.ABSOLUTE_PATHS + ReadDir.INCLUDE_DIRECTORIES)
+ .forEach(function (path) {
+ console.log(' [WATCH] ', path);
+ TemplateEngine._watches.push(
+ FS.watch(path, {persistent:false}, TemplateEngine._refreshTemplates.bind(TemplateEngine, templatesPath))
+ );
+ });
+};
+
+/**
+ * Reads all templates in the supplied path (synchronously). Can be called at any time, and is used as the handler for
+ * the file system watch of the templates directory.
+ *
+ * @param {String} templatesPath
+ */
+TemplateEngine._refreshTemplates = function(templatesPath) {
+ console.log('Refreshing templates for', templatesPath);
+
+ TemplateEngine._refreshWatches(templatesPath);
+
+ TemplateEngine.__templates = {};
+ ReadDir.readSync(templatesPath, ['**.mustache'], ReadDir.ABSOLUTE_PATHS)
+ .forEach(TemplateEngine._storeTemplate, TemplateEngine);
+ console.log('Refreshing templates complete');
+};
+
+module.exports = TemplateEngine;
Please sign in to comment.
Something went wrong with that request. Please try again.