Can't access jQuery objects within onRendered

For some reason, this isn’t working:

Template.productDetail.onRendered(function () {
  console.log(this.$('.prod-detail-name'));
});

The object returned has a length of 0. Could a package be causing this to happen? This should work.

when onRendered is being called doesn’t mean the content you expect has been added to the DOM yet, especially when dealing with reactivity of collections.

Consider the following example

<template name="products">
 <ul>
 {{#each products}}
  <li><span class="prod-detail-name">{{name}}</span></li>
 {{/each}}
 </ul>
</template>

Template.products.onRendered will be called before all your products have been added to the DOM, this the onRendered callback will not be able to attach whatever behavior your want to each product.

I figured it might be something like that. There is a block of HTML that only shows once the subscription is ready. So I tried this:

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      console.log(this.$('.prod-detail-name'));
    }
  });
});

And that still doesn’t work (returns an empty jQuery object result).

The serendipity is uncanny. I had this exact same problem today. Personally, I solved it with Session, Template Helpers, and Spacebars.

Nevertheless, off the top of my head, I suggest

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      console.log(this.$(this.findAll('.prod-detail-name')[0]));
    }
  });
});

I have no idea if this will work.

Try this

<template name="products">
 <ul>
 {{#each products}}
  {{>productDetail}}
 {{/each}}
 </ul>
</template>

<template name="productDetail">
  <li><span class="prod-detail-name">{{name}}</span></li>
</template>

and then assign onRendered as you originally did

Template.productDetail.onRendered(function () {
  console.log(this.$('.prod-detail-name'));
});

I can’t quite structure it like that. This template is for a product detail page (for a single product):

<template name="productDetail">
  <div class="container prod-detail">
    {{#unless Template.subscriptionsReady}}
      {{> loading item="Product"}}
    {{else}}
      ...display product...
Router.route('/product/:slug', {
  name: 'productDetail',
  data: function () {
    return Products.findOne({slug: this.params.slug});
  }
});

Same problem. Your Template.productDetail.onRendered fires when the template gets rendered, which is most likely before Template.subscriptionsReady

In the part where you have …display product… you could just call another template and attach the onRendered to that.

1 Like

But in the Template.productDetail.onRendered, I have an autorun checking for when the subscriptions are ready. Why does that not work?

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      Tracker.afterFlush(() => {
        console.log(this.$('.prod-detail-name'));
      }))
    }
  });
});
1 Like

Thanks, I found a workaround but I’ll try this next time I run into this issue!

Can you please post the work around?

Well, I needed to use a scroll event, so by the time the scroll handler fires off, everything is fully rendered.

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      $(window).scroll(function () {
        ...

I see. Interesting. Well, duly noted then.

How about a simple $(document).ready() ? I’m using this in my app.

Template.man_game.onRendered(function() {
	$(document).ready(function() {
		var items = $('.items');
		// do something
	});
});
1 Like

Hi, all!

So is there no “official” way to access the DOM from an onRendered callback?

I can use the several suggestions, but none of truly addresses the the confusion caused by the “onRendered” function name. ;D

And it doesn’t seem like we have a “clean” solution to access the DOM immediately after they are rendered.

In my current task, all I’m doing is setting a button to “disabled” or not at onRendered based on a Session variable.

Are there any “official” solutions to this? What method does the Meteor dev team use. Does anyone know?

PS: (document).ready () didn’t work for me. Possibly because the button element may not trigger the ready event. I dunno.

The common way to address this specific use case is by using a helper. In the example below I’ve used a Session variable, as this is what you say you are using, although in practice a template ReactiveVar would probably be a better choice.

<template name="thing">
  <button {{state}}>click me</button>
</template>

Template.thing.helpers({
  state: function() {
    return Session.get('state') || 'disabled';
  }
});
1 Like

DOM is available for any jQuery selector inside onRendered.
If you want to scope to current template in onRendered, use
this.$('.something') for example to match DOM element with class something

but still, it is better to keep state inside meteor JS than querying DOM.

Thank you! So simple - but elegant.

1 Like

You could do this

<template name="productDetail">
 {{#unless Template.subscriptionsReady}}
      {{> loading item="Product"}}
    {{else}}
    {{> product product=product}}
 {{/unless}}
</template>

Template.product.onRendered(function () {
console.log(this.$('.pro-detail-name'))
})

<template name="product">
{{#with product}}
   <p class="pro-detail-name">{{name}}</p>
{{/with}}
</template>

You should try and use ReactiveVarables

Template.name.onCreated(function () {
this.state = new ReactiveVar('disabled');
});

Template.name.events({
'eventType selector' : function (event, template) {
template.state.set('newValue');
}
});

Template.name.helpers({
state : function () {
var instance = Template.instance();

if(instance) {
return instance.state.get()
}
}
})