eyijta
Last Updated: February 25, 2016
·
959
· drgorb

Accessing Template data in helpers

How it all started

I wanted to create a component as a template in a meteor package. As an example I chose a date picker.

It is pretty straight forward but for a few things I had to lookup.

In order to get all the parts needed, here is the package.js

Package.onUse(function (api) {
    api.versionsFrom('1.1.0.2');
    api.use(["templating", "underscore", "jquery", "twbs:bootstrap", "reactive-var"]);
    api.addFiles(['moment/moment.js', 'moment/fr.js', 'date-pick.html', 'date-pick.js', 'date-pick.css'], 'client');
});

I had an issue with the moment.js package. It is difficult to add locales. This is why it is added as files explicitly.

The whole thing is available on github

The tricky thing was to access the template variables in the helper functions. This being a Date Picker, two pieces of information are required for the whole template

  1. The month to be displayed
  2. The selected date

In the first version, I simply defined them as global variables at the top of the js file.

var now = new ReactiveVar();
var selectedDate = new ReactiveVar();

This proved to be a bad idea as these are not part of the template instance and hence if the template is used twice, the first instance does not work.

So I added the variables to the data attribute of the template in the created callback: the this object is the template instance.

Template.datePick.created = function () {
    this.data.now = new ReactiveVar(this.data.initial ? moment(this.data.initial) : moment());
    this.data.selectedDate = new ReactiveVar(this.data.now.get().clone());
    console.log("template created");
}

Accessing template data in helpers

But then the issue arose of accessing these variables in the helpers.

It turns out to be fairly easy, but you need to do something special in the template.

When you use a helper outside of any with or each block, this is equal to the templateInstance.data. But if you provide a data context for your helper, there is no way to access the template's data property.

example 1: helper with template data context

The first element of the template is the selected date, which, when clicked, will trigger the date-picker drop down

<span class="dropdown-toggle" type="button" id="datePicker" data-toggle="dropdown"
      aria-haspopup="true" aria-expanded="true">
    {{selectedFormatedDate}}
</span>

The helper is defined like so:

selectedFormatedDate: function(){
    if(this.selectedDate.get())
        return this.selectedDate.get().format(this.format || "DD.MM.YYYY");
    else
        return "Date Picker";
}

As you can see, the template data property selectedDate can be accessed in the this

example 2: helper with own data context

But this does not work inside an {{#each...

When listing the days, the selectedDate and the currently displayed date need to be compared in order to know which cell needs highlighting.

This is done like so:

<tbody>
{{#each weeks}}
    <tr>
        {{#each days}}
            <td class="{{class}} {{isSelected ../..}}">{{day}}</td>
        {{/each}}
    </tr>
{{/each}}
</tbody>

There are two loops:

  1. go through all the weeks
  2. inside each week go through the days

When displaying the days, a highlighting class is added to the one day which is selected. But as the data context is the day object from the week, we do not have access to the template's data property.

The trick is to pass the template's data property as a parameter. This is done with the ../.. which tells Blaze to pass as parameter the data context of two levels above.

The helper can then be defined like so:

isSelected: function(template){
    if(this.moment){
        return this.moment.isSame(template.selectedDate.get()) ? "selected-date" :  "";
    }
}

and the current data context in this can be compared to the template data context in template.

Conclusion

It is all documented of course but I still needed some time to figure it out.