Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

A plugin for handlebars in require.js (both in dev and build)

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 Handlebars
Octocat-spinner-32 demo
Octocat-spinner-32 .gitignore
Octocat-spinner-32 Handlebars.js
Octocat-spinner-32 README.md
Octocat-spinner-32 demo-build.html
Octocat-spinner-32 demo.html
Octocat-spinner-32 hbs.js
Octocat-spinner-32 r.js
README.md

Require.js Handlebars Plugin

Version

Handlebars : v1.0.beta.4

hbs.js : v0.2.1

Requirements

Should work in both the java and node build environments.

Require.js >= 0.27.0 (I recommend 1.0.2+)

Usage

Write a template ( path: App/Template/One.handlebars ):

<div class="best plugin ever">
  This is my {{ adjective }} template.

  {{! To include a partial: }}
  {{! Use underscores instead of slashes in your path, }}
  {{! and leave off the extension. }}

  {{> App_Template_CoolPartial }}
</div>

Here's the partial (optional) ( path : App/Template/CoolPartial.handlebars )

<div>
  {{! This can obviously have it's own partials, etc, etc }}
  I am a partial
</div>

Include the hbs.js plugin and the Handlebars.js file in the same directory as your require.js script is. Usually, this is similar to the following.

~/Code/scripts/require.js
~/Code/scripts/hbs.js
~/Code/scripts/Handlebars.js
~/Code/scripts/App/Template/One.handlebars
~/Code/scripts/App/Template/CoolPartial.handlebars

Then require your templates like so:

require(['hbs!App/Template/One'], function ( tmplOne ) {
  // Use whatever you would to render the template function
  document.body.innerHTML = tmplOne({adjective: "favorite"});
});

And then the output into your body would be as follows:

<div class="best plugin ever">
  This is my favorite template.

  <div>
    I am a partial
  </div>
</div>

YAY!

I18n

I added a build-time/run-time helper for internationalization. The best way to see how this works is the demo.

Right now, the syntax for this is the same as handlebars helper syntax, with a helper named $ (for brevity).

{{$ "i18nkey"}}

This key should map to your locale json file.

{
  "i18nkey" : "This is a localized string."
}

This 'helper' works differently than actual handlebars templates. It actually modifies the AST that is generated by handlebars at build time. It takes the 'helper' node and converts it into a simple content node with the correct localized content.

The benefit of this is not having to send your entire localization object to the browser in production apps. Instead the localized strings are added directly into the compiled templates. This is faster in every case. :D

The locale defaults to the en_us.json file, but you can set the locale in your require.config (often needs to happen in both your app.build.js and your actual app code) and the locale will change along with that property.

Helpers

Just put your helpers in template/helpers/* and they'll automagically get pulled in as long as you write them as modules.

I find that many helpers are good helpers in regular code as well, so the following is a good practice:

define('template/helpers/roundNumber', ['Handlebars'], function ( Handlebars ) {
  function roundNumber ( context, options ) {
    // Simple function for example
    return Math.round( context );
  }
  Handlebars.registerHelper( 'roundNumber', roundNumber );
  return roundNumber;
});

Then in your templates, you can just do:

{{roundNumber Data.ThreeFourths}}

The system will make sure these modules are pulled in automatically from that directory. But if in your app, you need a rounding module (perhaps in a view/datanormalization place), you could do this:

require(['template/helpers/roundNumber'], function ( roundNumber ){
  var threeFourths = (3/4);
  alert( roundNumber( threeFourths ));
});

It's just a module that happens to register itself.

You can specify a helper path callback in the config. The callback should be a function that gets a name of a helper as the only argument and returns the full path to be require()-d, e.g., the following callback allows for automatic loading of helper modules written in CoffeeScript (via the require-cs plugin) under a non-standard location:

require({
  hbs : {
    helperPathCallback: function(name) {return 'cs!/helpers/' + name;}
  }
}, ['main'])

Meta Data

Any template that begins with a comment, with only a valid json object in it will be read in as meta data for the template.

I encourage you to list the name of the template and give a description, though these aren't strictly necessary.

Styles

If you want to build stylesheets that are comprised of only styles needed by the templates that your app uses, I encourage you to add a styles property to the meta info:

{{!
{
  "name" : "template1",
  "description" : "A nice template.",
  "styles" : ["templatecss"]
}
}}

This will inject a link tag in dev mode to load in this style dynamically. At build time, a screen.build.css is created. At this time it is just a list of import statements. These can be inlined by many existing tools. Eventually I'd love it to just happen.

De-duping happens automatically, so don't worry if multiple templates require the same styles. The styles are injected in the order that they are read in, so usually from least specific to most specific. This is usually what you want, but know that if you do weird things, it could break.

Introspection

In dev mode a few properties are added to your function (an object in javascript) as a helper with debugging and as a testing plug-point.

Those variables look like the following:

require(['hbs!template/one'], function ( tmplOne ) {
  console.log(
    'Variables referenced in this template: ',                     tmplOne.vars,
    'Partials/templates that this file directly depends on: ',     tmplOne.deps,
    'Helpers that this template directly depends on: ',            tmplOne.helpers,
    'The metadata object at the top of the file (if it exists): ', tmplOne.meta
  );
});

Note: All of these go away after a build, as they just take up space with data that is known at build time, which is the ideal time to get stuff figured out (speed-wise).

Builds

As long as all of your paths match up, this should precompile all of your templates and include them in the build.

Before Build

Before Build

After Build

After Build

So many dependencies in the hbs plugin!

I use them for coding happiness. It shouldn't bother you tooooo much, because it all gets built out in production. The hbs.js file essentially gets written to the main.js file as a noop (a few empty definitions), and none of it's dependencies are included into the build.

Demo

To run the demo, go into the root directory of this project and run the following command.

node r.js -o demo/app.build.js

This requires that node.js is installed. To see these in your browser, I'd suggest serving them quickly with the python simple server. (Linux/OSX assumed here, but there is a java implementation of the require.js build that should work just as well as the node version. I have not tried it though.)

cd ~/require-handlebars-plugin
python -m SimpleHTTPServer

Then visit http://127.0.0.1:8000/demo.html for the dev version.

And visit http://127.0.0.1:8000/demo-build.html for the production build version.

You should be able to see all of the templates and individual files in your network panel in dev mode, and just 2 minified files in build mode.

Notes/QA

Partial Collision

This plugin registers every single template as a partial with it's modified module name (Slashes replaced with underscores, and no file extension).

App/Template/One.handlebars is registered as App_Template_One

I'd encourage you to not call registerPartials in your code, and just use the automatic module registering, that way you definitely won't hit any collisions. You could also just be careful. We're all adults here.

Templates not loading cross-domain

In dev mode, loading the templates requires that you are on the same domain as your templates. This is standard same origin policy stuff. Once you build, though, it won't matter since there are no additional requests. Usually a few cleverly placed host overrides get you through the dev mode hurdles.

My helper isn't working

Unfortunately my logic forces a circular dependency right now. The work-around is to add your helper to template/helpers/all.js much like the file that is in the demo. When a work-around is found, I'll update that and get it out. This could also be a 'watched' folder and a generated 'all.js' file. Note:: the all.js goes away in the build, so no worries on production size and unneeded helpers.

Doesn't work with my version of Handlebars

This is a barely modified version of handlebars 1.0.beta.4 (which still went out to the world with a non-updated version tag 1.0.2beta, whoops). Some of the functionality in here is new, but none of it should be specific exactly to what makes this work. Though, I did take out the code that tries to identify node.js and act differently, since we want it to be picked up by require.js and not the built-in node.js require keyword. I also turned it into a proper amd module, which makes it "require-able". There's nothing too crazy, though, so I'd suggest just using it to save yourself time. Or don't.

Other Templating Languages

Very little of this is specific to handlebars, but things are just a tiny bit too specific about how everything works to properly generalize this.

If you'd like to implement this for your templating language of choice, you'll need:

  • Has a pre-compile type functionality (unless you don't care about builds)
  • If it has some concept of partials, that you can register them externally
  • It eventually returns a function that takes data context and outputs something you can deal with.
  • For any of the meta-data, you'll need some fancy regex or an AST to walk through.

I'd just turn your template language into a module first (just the old global name, or whatever), then look through the references to Handlebars in hbs.js and see if your templating language does something similar. It's not a terribly complicated process.

License

Most of the code in this is from James Burke and Yehuda Katz in require.js and handlebars.js (respectively). Those projects are under their own license. Any other code added by me is released under the WTFPL license.

Something went wrong with that request. Please try again.