0

When I try to use this.findAll on a template where the selector is in a sub-template, findAll returns nothing.

Here's the HTML:

<template name="products">
    {{#each productList}}
        {{> product }}
    {{/each}}
</template>
<template name="product">
    <div class="box">{{name}}</div>
</template>

Here's the JS:

Template.products.helpers({
    productList: function() {
        var all = Products.find({}).fetch();
        return all;
    }
});
Template.products.rendered = function(){
    var boxes = this.findAll('.box');
    console.log(boxes.length);
}

Output of boxes.length is 0. Any ideas how I could get the "box" elements?

ian
  • 1,505
  • 2
  • 17
  • 24

3 Answers3

1

According to the docs for findAll:

Only elements inside the template and its sub-templates can match parts of the selector.

So it should work for sub-templates. I tried this with a fixed array of products and it worked, which implies that you are just seeing a delay between the call to rendered and the products being fetched. For example if you do:

Template.products.events({
  'click .box': function (e, t) {
    var boxes = t.findAll('.box');
    console.log(boxes.length);
  }
});

Then if you click on one of the boxes, you should see the correct number logged to the console. In short, I think the test may just be invalid. If you are using iron-router, you could try adding a waitOn for the products - that may ensure they arrive before the rendered call.

David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • Looking at the actual page, I do observe that there is some kind of delay. The console.log has already shown the output before the products appears in the page. I do use iron-router and I use waitOn for the products. I need to run a script when all products have been loaded. Any other ideas? – ian Jun 05 '14 at 04:51
  • This isn't easy because meteor doesn't really have the notion of "done" when it comes to loading collection data. What happens when another product is added? If the answer is to run your function again, then I'd recommend thinking of a way to use `product.rendered` instead of `products.rendered`. Side note - it's also worth mentioning that you can always use `$('.box')` in either function to get at all the boxes on the page, although that may not make sense if you have multiple instances of the `products` template. – David Weldon Jun 05 '14 at 05:42
  • Thanks David! You gave me the idea how to solve it. I'll post the answer in a while. – ian Jun 05 '14 at 06:04
0

Here's what I did to run a script after all products have been loaded.

I've added last_product property in all the products.

Template.products.helpers({
    productList: function() {
        var all = Products.find({}).fetch();
        var total = all.length;
        var ctr = 0;
        all.forEach(function(doc){
            doc.last_product = false;

            ctr++;
            if(ctr == total)
            {
                doc.last_product = true;
            }
            return doc;
        });
        return all;
    }
});

Then instead of "Template.products", I used "Template.product" to detect if the last product is rendered. When the last product is rendered, run the script.

Template.product.rendered = function(){
    if(this.data.last_product){
        var boxes = $('.pbox');
        console.log(boxes.length);
    }
}

boxes.length now has the correct length.

Thanks to David for the idea!

ian
  • 1,505
  • 2
  • 17
  • 24
0

Here's the correct answer. I've added this to my iron-router route:

action : function () {
    if (this.ready()) {
        this.render();
    }
}

Found the answer from https://stackoverflow.com/a/23576039/130237 while I was trying to solve a different problem.

Community
  • 1
  • 1
ian
  • 1,505
  • 2
  • 17
  • 24