Can't access jQuery objects within onRendered

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()
}
}
})

Read my reply:

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.

Sorry about that. I read the code not the full post xD

If I don’t have subscriptions.
So it still don’t work for all above comments.

Template.productDetail.onRendered(function () {
       // don't have sub
        console.log(this.$('.prod-detail-name'));
});

Please help me. have any solution?

Just for everyone in the future still having issues with accessing reactive elements.

My use case:

  • Generate a list
  • Make a checkbox to a bootstrap toggle box

When i tried to get the reactive element after the onRendered or the subscriptionsReady hook i could create bootstrap toggle boxes just for the first time accesing the site. when i changed the routes it did not show the bootstrap toggle boxes anymore. Only on the first time i visited that page.
The problem is that the toggle boxes may get rerendered via the subscription but the hooks do not get called again.

Never the less i solved my problem by displaying the subscription data with blaze and after the block where it is inserted i make a little tag in which i create the toggle box. that way i ensure that even if it gets reloaded a toggle box will be recreated.

{{#each elements}}
    <tr>
        <td>{{this._id}}</td>
        <td><input type="text" id="{{this._id}}" value="{{this.name}}"></td>
        <td><input
                    data-on="public"
                    data-off="private"
                    class="form-control publicPrivateToggle"
                    type="checkbox"
                    data-toggle="toggle"
                    data-class="fast"
                    id="publicPrivateToggle_{{this._id}}"
                {{setChecked this.public}}
            ></td>
        <script>
            this.$('#publicPrivateToggle_{{this._id}}').bootstrapToggle({
                width: '100%'
            });
        </script>
    </tr>
{{/each}}

So revisiting this…

This aspect of Blaze has always flummoxed me! It turns out it was my use of Template.subscriptionsReady() that was my real problem, <template>.onRendered works as advertised.

@jamgold came up with a hacky workaround that I did not really grok on first read:

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.

So I will be very pedantic. What he is saying is this:

@jamgold’s Hacky workaround

foo.html:

<template name="parent"> 
{{#if Template.subscriptionsReady}}
  <div id="firstDiv"></div>
  {{> childHack}}
{{/if}}
</template>

<!-- childHack template can be empty -->
<template name="childHack"></template>

foo.js:

Template.parent.onRendered(function parentOnRendered() {
  console.log($('#firstDiv').length);  // What?? it is 0, nothing found!!
  // Nothing is found because Template.subscriptionsReady() is false,
  // parent template is successfully rendered with no HTML.
});

Template.childHack.onRendered(function childHackOnRendered() {
  console.log($('#firstDiv').length);  // Hey it is 1, gotcha!
  // this template will only be rendered when Template.subscriptionsReady()
  // is true & the parent template completed rendering its HTML with data
});

Even if this is hacky, it is relatively simple & elegant for handling the cases when you use Template.subscriptionsReady().

THANKS @jamgold!

========== EDIT ==========

Right after posting this I had another thought. And fell upon @mrzafod’s solution. Without the childHack template you can do this:

@mrzafod’s elegant solution

foo.js:

Template.parent.onRendered(function parentOnRendered() {
  console.log('found1?: ', $('#firstDiv').length);  // 0, nothing found
  // Nothing is found because Template.subscriptionsReady() is false,
  // parent template is successfully rendered with no HTML.
  let initialized = false;
  template.autorun( () => {
    if(!initialized && template.subscriptionsReady()) {
      // Subscriptions are now ready, the template is rendering
      console.log('found2?: ', $('#userprofile-modal').length);  // 0, nothing found
      // Nothing is found, because we are still rendering templates
      initialized = true;
      Tracker.afterFlush(() => {
        // Reactivity computations have finished
        console.log('found3?: ', $('#userprofile-modal').length);  // 1
        //  Now the DOM is populated with the template HTML
      });
  });
});

Thanks @mrzafod !!