Bian Jiaping
Bian Jiaping

Reputation: 966

Meteor 0.8.0 - Failed to operate DOM in rendered callback

Before 0.8.0, the following code works perfectly.

<template name="carousel">
    <!--Here I used the carousel of Bootstrap-->
    <div id="myCarousel" class="carousel">
        <ol class="carousel-indicators">
        {{#each counter}}
            <li data-target="#myCarousel" data-slide-to="{{this}}"></li>
        {{/each}}
        </ol>
        <div class="carousel-inner">
            {{#each carousels}}
                <div class="item"><a href="{{link}}"><img src="{{src}}" ></a></div>
            {{/each}}
        </div>
        <a class="carousel-control left" href="#myCarousel" data-slide="prev">&lsaquo;</a>
        <a class="carousel-control right" href="#myCarousel" data-slide="next">&rsaquo;</a>
    </div>
</template>

Template.carousel.helpers({
    carousels: function() {
        return Carousels.find();
    },
    counter: function() {
        return _.range(0, Carousels.find().count());
    }
});

Template.carousel.rendered = function() {
    Meteor.defer(function() {
        $('#myCarousel .carousel-indicators li:first').addClass('active');
        $('#myCarousel .item:first').addClass('active');
    });
}

But after updating to 0.8.0, adding 'active' class doesn't work any more. Using Meteor.setTimeout instead of Meteor.defer, I finally find that it works only when the delay is long enough (sometimes longer than 150ms).

Template.carousel.rendered = function() {
    Meteor.setTimeout(function() {
        $('#myCarousel .carousel-indicators li:first').addClass('active');
        $('#myCarousel .item:first').addClass('active');
    }, 150);
}

Why does this happen, and is there any better solution?

[Updated] Now I use a simple isFirst helper to realize it. I think this is a better solution.

{{#each carousels}}
    <div class="item {{isFirst _id}}"><a href="{{link}}"><img src="{{src}}" ></a></div>
{{/each}}

Template.carousel.isFirst = function(id) {
    return Carousels.find().fetch()[0]._id == id ? 'active' : '';
}

As for the counter, I just make "counter" begin from 1, and the HTML is as follows:

<ol class="carousel-indicators">
    <li class="active" data-target="#myCarousel" data-slide-to="0"></li>
    {{#each counter}}
        <li data-target="#myCarousel" data-slide-to="{{this}}"></li>
    {{/each}}
</ol>

Upvotes: 3

Views: 422

Answers (1)

0x6A75616E
0x6A75616E

Reputation: 4716

Template.carousel.rendered only runs once in Blaze (Meteor's new rendering engine), but it used to run multiple times in Spark (previous versions). This old behaviour worked because it ran before and after your {{#each}} block was rendered.

The problem here is that Template.carousel.rendered runs before your {{#each}} block is rendered, so those items don't exist yet.

The recommended pattern is to put the contents of your {{#each}} in a separate template and tap into the rendered event of that template.

By the way, Meteor.defer is no longer required.

Upvotes: 2

Related Questions