2
votes

Here's what I want, a custom block helper that can act like a template, monitoring for it's own events etc. The html would look like this:

{{#expandable}}
    {{#if expanded}}
        Content!!!
        <div id="toggle-area"></div>
    {{else}}
        <div id="toggle-area"></div>
    {{/if}}
{{/expandable}}

And here's some javascript I have put together. This would work if I just declared the above as a template, but I want it to apply to whatever input is given to that expandable block helper.

Template.expandableView.created = function() {
    this.data._isExpanded = false;
    this.data._isExpandedDep = new Deps.Dependency();
}
Template.expandableView.events({
    'click .toggle-area': function(e, t) {
        t.data._isExpanded = !t.data._isExpanded;
        t.data._isExpandedDep.changed();
    }
});
Template.expandableView.expanded = function() {
    this._isExpandedDep.depend();
    return this._isExpanded;
};

I know I can declare block helpers with syntax like this:

Handlebars.registerHelper('expandable', function() {
    var contents = options.fn(this);
    // boring block helper that unconditionally returns the content
    return contents;
});

But that wouldn't have the template behavior.

Thanks in advance! This might not be really possible with the current Meteor implementation.

Update

The implementation given by HubertOG is super cool, but the expanded helper isn't accessible from within the content below:

<template name="expandableView">
    {{expanded}} <!-- this works correctly -->
    {{content}}
</template>

<!-- in an appropriate 'home' template -->
{{#expandable}}
    {{expanded}} <!-- this doesn't work correctly. Expanded is undefined. -->
    <button class="toggle-thing">Toggle</button>
    {{#if expanded}}
        Content is showing!
    {{else}}
        Nope....
    {{/if}}
{{/expandable}}

In the actual block helper, expanded is undefined, since the real thing is a level up in the context. I tried things like {{../expanded}} and {{this.expanded}}, but to no avail.

Strangely, the event handler is correctly wired up.... it fires when I click that button, but the expanded helper is simply never called from within the content, so even console.log() calls are never fired.

2

2 Answers

3
votes

Meteor's new Blaze template engine solves this problem quite nicely.

Here's an excerpt from the Blaze docs showing how they can be used.

Definition:

<template name="ifEven">
  {{#if isEven value}}
    {{> UI.contentBlock}}
  {{else}}
    {{> UI.elseBlock}}
  {{/if}}
</template>

Template.ifEven.isEven = function (value) {
  return (value % 2) === 0;
}

Usage:

{{#ifEven value=2}}
  2 is even
{{else}}
  2 is odd
{{/ifEven}}
1
votes

You can achieve this by making a helper that returns a template, and passing the helper options as a data to that template.

First, make your helper template:

<template name="helperTemplate">
  <div class="this-is-a-helper-box">
    <p>I'm a helper!</p>
    {{helperContents}}
  </div>
</template>

This will work as a typical template, i.e. it can respond to events:

Template.helperTemplate.events({
  'click .click-me': function(e, t) {
    alert('CLICK!');
  },
});

Finally, make a helper that will return this template.

Handlebars.registerHelper('blockHelper', function(options) {
  return new Handlebars.SafeString(Template.helperTemplate({
    helperContents: new Handlebars.SafeString(options.fn(this)),
  }));
});

The helper options are passed as a helperContents param inside the template data. We used that param in the template to display the contents. Notice also that you need to wrap the returned HTML code in Handlebars.SafeString, both in the case of the template helper and its data.

Then you can use it just as intended:

<template name="example">

  {{#blockHelper}}
    Blah blah blah
    <div class="click-me">CLICK</div>
  {{/blockHelper}}

</template>