Buy Handlebars swag on DevSwag!

Block helpers make it possible to define custom iterators and other helpers that can invoke the passed block with a new context.

Basic Blocks

Let's define a simple block helper that simply invokes the block as though no helper existed.
<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{#noop}}{{body}}{{/noop}}
  </div>
</div>
The noop helper will receive an options hash. This options hash contains a function (options.fn) that behaves like a normal compiled Handlebars template. Specifically, the function will take a context and return a String.
Handlebars.registerHelper('noop', function(options) {
  return options.fn(this);
});
Handlebars always invokes helpers with the current context as this, so you can simply invoke the block with this to evaluate the block in the current context.

The with helper

Based on the description of the noop helper, it should be obvious how to implement a with helper. Instead of invoking the block with the current context, we will invoke it with whatever context the template passed in.
<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>
You might find a helper like this useful if a section of your JSON object contains a lot of important properties, and you don't want to constantly need to repeat the parent name. The above template could be useful with a JSON like:
{
  title: "First Post",
  story: {
    intro: "Before the jump",
    body: "After the jump"
  }
}
Implementing a helper like this is a lot like implementing the noop helper. Note that helpers take parameters, and parameters are evaluated just like expressions used directly inside {{mustache}} blocks.
Handlebars.registerHelper('with', function(context, options) {
  return options.fn(context);
});
Parameters are passed to helpers in the order that they are passed, followed by the block function.

Simple Iterators

A very common use-case for block helpers is using them to define custom iterators. In fact, all Handlebars built-in helpers are defined as regular Handlebars block helpers. Let's take a look at how the built-in each helper works.
<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>
<div class="comments">
  {{#each comments}}
    <div class="comment">
      <h2>{{subject}}</h2>
      {{{body}}}
    </div>
  {{/each}}
</div>
In this case, we want to invoke the block passed to each once for each element in the comments Array.
Handlebars.registerHelper('each', function(context, options) {
  var ret = "";

  for(var i=0, j=context.length; i<j; i++) {
    ret = ret + options.fn(context[i]);
  }

  return ret;
});
In this case, we iterate over the items in the passed parameter, invoking the block once with each item. As we iterate, we build up a String result, and then return it.
It is easy to see how you could use this to implement more advanced iterators. For instance, let's create an iterator that creates a <ul> wrapper, and wraps each resulting element in an <li>.
{{#list nav}}
  <a href="{{url}}">{{title}}</a>
{{/list}}
You would evaluate this template using something like this as the context:
{
  nav: [
    { url: "http://www.yehudakatz.com", title: "Katz Got Your Tongue" },
    { url: "http://www.sproutcore.com/block", title: "SproutCore Blog" },
  ]
}
The helper would not be that different from the original each helper.
Handlebars.registerHelper('list', function(context, block) {
  var ret = "<ul>";

  for(var i=0, j=context.length; i<j; i++) {
    ret = ret + "<li>" + block(context[i]) + "</li>";
  }

  return ret + "</ul>";
});
You could, of course, use a library like underscore.js or SproutCore's runtime library to make this look a bit prettier. Using SproutCore's runtime:
Handlebars.registerHelper('list', function(context, block) {
  return "<ul>" + context.map(function(item) {
    return "<li>" + block(item) + "</li>";
  }).join("\n") + "</ul>";
});

Conditionals

Another relatively common use-case for block helpers is to implement conditionals. Again, Handlebars' built-in if and unless control structures are implemented as regular Handlebars helpers.
{{#if isActive}}
  <img src="star.gif" alt="Active">
{{/if}}
Control structures typically do not change the current context, but decide whether or not to invoke the block based upon some variable.
Handlebars.registerHelper('if', function(conditional, options) {
  if(conditional) {
    return options.fn(this);
  }
});
When writing a conditional, you will often want to make it possible for templates to provide a block of HTML that your helper should insert if the conditional evaluates to false. Handlebars handles this problem by providing generic else functionality to block helpers.
{{#if isActive}}
  <img src="star.gif" alt="Active">
{{else}}
  <img src="cry.gif" alt="Inactive">
{{/if}}
Handlebars provides the block for the else fragment as options.inverse. If the template provides no inverse section, Handlebars will automatically create a noop function, so you don't need to check for the existance of the inverse.
Handlebars.registerHelper('if', function(conditional, options) {
  if(conditional) {
    return options.fn(this);
  } else {
    return options.inverse(this);
  }
});
Handlebars provides additional metadata to block helpers by attaching them as properties of the options hash. Keep reading for more examples.

Hash Arguments

Like regular helpers, block helpers can accept an optional Hash as its final argument. Let's revisit the list helper from earlier, and make it possible for us to add any number of optional attributes to the <ul> element we will create.
{{#list nav id="nav-bar" class="top"}}
  <a href="{{url}}">{{title}}</a>
{{/list}}
Handlebars provides the final hash as options.hash. This makes it easier to accept a variable number of parameters, while also accepting an optional Hash. If the template provides no hash arguments, Handlebars will automatically pass an empty object ({}), so you don't need to check for the existance of hash arguments.
Handlebars.registerHelper('list', function(context, options) {
  var attrs = SC.keys(options.hash).map(function(key) {
    key + '="' + options.hash[key] + '"';
  }).join(" ");

  return "<ul " + attrs + ">" + context.map(function(item) {
    return "<li>" + options.fn(item) + "</li>";
  }).join("\n") + "</ul>";
});
Hash arguments provide a powerful way to offer a number of optional parameters to a block helper without the complexity caused by positional arguments.

Block helpers can also inject private variables into their child templates. This can be useful to add extra information that is not in the original context data.

For example, when iterating over a list, you may provide the current index as a private variable.

{{#list array}}
  {{@index}}. {{title}}
{{/list}}
Handlebars.registerHelper('list', function(context, options) {
  var out = "<ul>", data;

  for (var i=0; i<context.length; i++) {
    if (options.data) { data = Handlebars.createFrame(options.data); }
    out += "<li>" + options.fn(context[i], { data: data }) + "</li>";
  }

  out += "</ul>";
  return out;
});
Private variables provided via the data option are available in all descendent scopes.
Make sure you create a new data frame each time you invoke a block with data. Otherwise, downstream helpers might unexpectedly mutate upstream variables.
Fork me on GitHub