Waiting for conditionally rendered DOM elements


#1

I’m trying to get up and rolling with semantic-ui, and in order to do so I need to reliably be able to call their initialization functions on DOM elements that use their classes. For example, checkboxes have the class “ui checkbox”, and I need to call $(’.ui.checkbox’).checkbox() in order to initialize them.

The problem is when the DOM elements I need to init are being conditionally rendered, such as when they’re wrapped in a #if:

{{#if currentUser}}
  <input type="checkbox" class="ui checkbox">
{{/if}}

The docs recommend using the template’s onRendered callback:

Template.foo.onRendered(() => {
  $('.ui.checkbox').checkbox();
});

But alas, half the time the DOM element in question doesn’t exist when my jQuery function gets called in onRendered.

Has anyone else bumped into this issue? Is there a solution? Any help would be greatly appreciated. Thanks!


#2

I’m having a similar issue right now – except my $ doesn’t contain any elements at all, according to Meteor:

Exception in defer callback: Error: Can't use $ on template instance with no DOM
    at Blaze.TemplateInstance.$ (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3516:11)
    at Blaze.TemplateInstance.findAll (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3527:42)
    at .<anonymous> (http://localhost:3000/app/app.js?hash=d005183c715ba9a6da246205dab30282c2c42e1c:445:8)
    at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3341:22
    at Function.Template._withTemplateInstanceFunc (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3687:12)
    at fireCallbacks (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3337:12)
    at .<anonymous> (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3417:5)
    at fireCallbacks (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:1955:26)
    at Object.Tracker.nonreactive (http://localhost:3000/packages/tracker.js?hash=6f5d0f5486aaa54b0abe636174eeb06dcc2a736b:617:12)
    at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:1952:13

I created a thread on the topic. Please let me know if you find the answer! :3


#3

The best (for me) workaround I’ve found so far is to rewrite my templates using ViewModel and use the if binding from that package instead of spacebars’ #if. For example, instead of this:

{{#if currentUser}}
  <div>Only show when logged in</div>
{{/if}}

I can, using ViewModel, write:

<div {{b "if: loggedIn"}}>Only show when logged in</div>

With this JavaScript code:

Template.foo.viewmodel({
  loggedIn: false,
  autorun() {
    this.loggedIn(!!Meteor.user());
  }
});

This works because ViewModel doesn’t prevent the element from being rendered to the DOM, it just sets “display: none” on it if the condition is false. Other approaches that accomplish the same would likely work just as well, but I must say I highly recommend the ViewModel package. It’s excellent.

That being said, I’ve run into issues with Blaze’s conditional directives several times, and it’s been very frustrating. It would be great if Blaze provided some sort of hook that got called each time a condition got evaluated, or perhaps each time the result of a conditional changed. Perhaps such a thing already exists. I don’t have anything approaching comprehensive knowledge. I’m open to other options, but for now, I’m please that ViewModel makes what I’m trying to do possible (and pretty easy too). Thanks Manuel! :grinning:


#4

The way I solve that problem is a combination of

  1. divide the initial template into multiple templates so the onRendered callback corresponds to the part that that is conditional
  2. autorun
  3. Meteor.defer(() => this.$(...))
  4. [when data is involved] Template.subscriptionsReady

That said, sometimes I have layer over layer (in an onRendered, an autorun that checks subscription.ready() and then runs a defer)


#5

@diegoolivier Thanks! Definitely, I agree, the initial template can be divided so that my onRendered callback corresponds to the elements which I care about. Not familiar with Meteor.defer(). Will have to read the docs. Thanks.