3

My question is how to get 1 event or rendered callback when a group of elements are updated in the DOM? If I follow the link in the Blaze wiki https://github.com/avital/meteor-ui-new-rendered-callback this is not what I want. If I follow the second recommendation, I will get as many rendered calls as I have elements. And the parent element will only get 1 rendered callback on page load.

In my case, I'm using the Footable Jquery plugin to format a table. The initial load works fine, but if I change a filter variable in the Collection find, the DOM updates and no rendered is called again since Blaze only calls rendered once. I do not want to put the into another template, because that just means multiple calls to rendered and thus multiple calls to Footable when it only needs one for the entire table.

Any help is appreciated.

<template name="customerData">
  <table class="table">
    {{#each dataRows}}
    <tr>
      <td>{{first_name}}</td>
      <td>{{last_name}}</td>
      <td>{{email}}</td>
     {{#each phones}}
        <td>{{phone}}</td>
     {{/each}}
    </tr>
    {{/each}}
  </table>
</template>

Template.customerData.rendered = function(){
  $(".table").footable();
}

Template.customerData.phones = function(){
    var result = [];

    _.each(this.phoneNumbers, function(element, index, list){
       result.push({ phone: element});
    });

return result;
}
richStorm
  • 33
  • 6
  • have a look at [_.debounce()](http://underscorejs.org/#debounce). – user728291 Apr 01 '14 at 22:04
  • @user728291 this is a great suggestion. Thank you. I ended up using _.debounce() in a rendered callback for the row and moved the row html into its own template. – richStorm Apr 08 '14 at 10:37

1 Answers1

7

The best solution would be to write a custom block helper. Lemme do it for you :)

Implementation

UI.registerHelper('footableBody', function () {

  var dependency = new Deps.Dependency(),
      dataSource = this,
      handle, footable;

  return UI.Component.extend({
    render: function () {
      var self = this;
      return UI.Each(function () {
        return dataSource;
      }, UI.block(function () {
        dependency.changed();
        return self.__content;
      }));
    },
    rendered: function () {
      var $node = $(self.firstNode).closest('table');
      handle = Deps.autorun(function () {
        if (!footable) {
          $node.footable();
          footable = $node.data('footable');
        } else {
          footable.redraw();
        }
        dependency.depend();
      });
    },
    destroyed: function () {
      handle && handle.stop();
    },
  });
});

Usage

Now, in your templates you can do something like:

<table class="table">
  <thead>
    ...
  </thead>
  <tbody>
  {{#footableBody dataRows}}
    <tr>
      <td>{{first_name}}</td>
      <td>{{last_name}}</td>
      <td>{{email}}</td>
      <td>{{phone}}</td>
    </tr>
  {{/footableBody}}
  </tbody>
</table>

Of course you should customize the behavior of the helper to your own needs.

Reflections

There is also another - more generic - solution that follows the pattern of how markdown helper is implemented here. However, I would not encourage to apply this strategy to your specific usecase.

Tomasz Lenarcik
  • 4,762
  • 17
  • 30
  • Would you mind pointing to some documentation about how to write custom components, or at least some examples? – Andrew Mao Apr 02 '14 at 03:56
  • 1
    @AndrewMao I don't know if it is in the documentation, but the great source of examples is the source code of the built in `ui` and `showdown` packages (take a look at `UI.Each`, `UI.If` and `UI.With` and `markdown` implementations). You can also take a look at `blaze-layout` package by **EventedMind** - it's deprecated but it is a good example anyways. I also did a several of my own, so if you looked at `mathjax`, `highlight` or `tags` packages (currently `devel` or `blaze-` branches) you may also find something that may interest you. – Tomasz Lenarcik Apr 02 '14 at 07:46
  • @apendua Great solution! Thank you. However, I'll throw a curve ball in the mix. If I'm calling a "_.each" on let's say multiple phone numbers where I don't know the how many there are. This solution returns the first 3 columns just fine with Footable, but the phone numbers columns are rendered after your "dependency.changed()" – richStorm Apr 03 '14 at 11:31
  • Accepted the solution. I updated the question with the additional info in my situation. Sorry I didn't include it to begin with. Looks like the rendered is firing when the data returns from the server, but in my case I need rendered to fire when the final is rendered. – richStorm Apr 03 '14 at 11:42
  • @richStorm I never claimed this solution would be good enough for all purposes :) It only presents a general scheme, which you of course should "tailor" to your specific needs. – Tomasz Lenarcik Apr 03 '14 at 11:43
  • @richStorm Unfortunately it seems there is no way to alter this behavior. That's why we use `dependency`. In fact, the `Deps.autorun` will rerun for the first time exactly after the last `td` is rendered. To me it seems reasonable ... – Tomasz Lenarcik Apr 03 '14 at 11:48
  • @apendua Yes, Deps.autorun will run after the "first name", "last name", and "email" td's are rendered. However, the behavior of Blaze seems to process a render coming from a cursor first and then stitch in the helper (referring to the helper "Template.customerData.phones"). So do I put the Deps.changed() inside this helper? – richStorm Apr 03 '14 at 12:00
  • @richStorm You can try it. However, from what I have seen in `footable` source code, they require the columns number to be more or less constant. More specifically, when the table is initialized for the first time, they look for columns defined within `` (you can probably alter this behavior by providing a custom selector, but I am not sure), and from that moment adding data with more columns just won't work. If I were you I wouldn't mess with the number of columns but rather put all the phone numbers within one cell. – Tomasz Lenarcik Apr 03 '14 at 12:11