Thursday, June 12, 2014

CanJS is not calling Mustache function when value is updated

Mustache can map not only specific values to template, but also results of the functions, which is sometimes handy. There is example.

But recently, when I was using this feature inside of CanJS template, it only rendered results for the first time and did nothing when values were changing. This was not working properly:

  can.Component.extend({
    tag: "sum",
    scope: {
      model: new can.Model({x: 2, y:3}),
      result: function() {
        return this.model.x * this.model.y;
      }
    },
    template: '<input can-value="model.x">*<input can-value="model.y">={{result}}'
  });

As I found out, problem was that it was missing proper value invocation and simply calling values via attr method fixes the problem.
This works fine:

  can.Component.extend({
    tag: "sum",
    scope: {
      model: new can.Model({x: 2, y:3}),
      result: function() {
        return this.model.attr('x') * this.model.attr('y');
      }
    },
    template: '<input can-value="model.x">*<input can-value="model.y">={{result}}'
  });

Wednesday, June 4, 2014

Accessing multiple objects from CanJS events in components

CanJS components have nice and easy way to access context object from event, just as parameter:

{{#books}}
  <input type="button" can-click="add" />
{{/books}}

add: function(book) {
  books.add(book);
}

But what if you need also parent object, or object from some other hierarchy. To do it, CanJS can bind DOM objects to any data, and in components this is usually context. Just add special property {{data '...'}} to DOM element in your Mustache template, for example:

{{#shelves}}
  <h1>{{name}}</h1>
  <div {{data 'shelf'}}>
    {{#books}}
      <input type="button" can-click="add" {{data 'book'}} />
    {{/books}}
  </div>
{{/shelves}}

And you can access all objects like:

add: function(context, el) {
  var book = el.data('book');
  var shelf = el.parent().data('shelf');
  shelf.add(book);
}