UI hooks for Blaze helpers on render?

I’ve got something that sort of looks like this in my HTML:

{{#each things}}
  <input type="checkbox" class="thing-box"/>
{{/each}}

What I would like to do is attach a jQuery handler after the things are loaded on the page. In the template JS file things is a helper like the following:

things: () => return Session.get("thingsAndStuff").things;

I have tried, to no avail, to subscribe to thingsAndStuff in the template onRendered callback

  let template = this;
  template.subscribe('thingsAndStuff', () => {
    Tracker.afterFlush(() => {
      // attack jquery handler here
    });
  })

Is there a good way to make this work in Meteor/Blaze?

It depends what type of jquery handler you want to add? If it’s simple event handlers, Template.blah.events({...}) should do what you want.

If you’re after something more complicated, Tracker.afterFlush should work, but it needs to be triggered from an autorun that depends on something.

template.autorun(() => {
Session.get("thingsAndStuff");
Tracker.afterFlush(() => {
  // this should fire after the UI updates.
});
});

It’s worth nothing that I’ve had trouble with this pattern in the past, two things to watch out for:

  1. Something other than thingsAndStuff causes a re-render (e.g., a reactive if condition above {{#each things}}).
  2. A delay in the rendering of the actual thing you want (e.g., if you’re rendering another template inside of your {{#each}}.
  3. re-attaching jquery handlers to existing elements can throw exceptions (so destroy before you attach).

Two other options that help with the above:

  1. stick a setTimeout in your afterFlush
  2. create a new helper that performs an action instead returning something (bit yucky):
{{#let myThings=things}}
{{#each thing in myThings}}
...
{{/each}}
{{triggerJquery}}
{{/let}}

Now whenever your things function triggers (even if it isn’t because of the reactivity of Session.get) you’ll call the triggerJquery helper.

You can also change your helper to:

things() {
Tracker.afterFlush(() => {
   // jquery
});
return Session.get("thingsAndStuff").things;
}

In general, it’s better to put your jquery handlers in onRendered and to fully understand the cases where your template is re-rendered such that you need to re-trigger. Relying on setTimeout, or Tracker.afterFlush may work 99% of the time, but it can indicate that you don’t understand all the ways that reactivity may cause your UI to change - which can cause problems down the road.

2 Likes

In my experience it is better to use sub-templates and attach the jQuery stuff in the onRendered callback

{{#each thing in things}}
  {{>thingTemplate thing}}
{{/each}}

and then

Template.thingTemplate.onRendered(function(){
  const instance = this;
  // scoped to the template
  // instance.find('..selector'); -> returns DOM node
  // instance.$('..selector'); -> returns jQuery
});
3 Likes

Is it possible for the parent template to access this sub-template and vice versa?

Parent -> child can be accomplished in a couple of ways:

  1. changing the data context (note that you’ll be back in the same place if changing data re-renders the UI).
  2. Using Blaze.getView with an element rendered by the child template, and then getting the parent of the view until you get the child template instance.
  3. Technically you can call templateInstance.getChild(templateName) - but I’m not sure how that works when you render the same child template multiple times (e.g., thingTemplate).

Child -> Parent:

  1. Pass in a reactive var as part of the data, the child can set state on this var and autorun blocks in the parent that depend on it will run
  2. Pass in a callback function as part of the data to the child, the child can call this function like any other.
  3. event handlers attached to elements rendered by the child.
  4. calling templateInstance.parent()

I don’t love any of these approaches of child -> parent. I’ve never needed parent -> child beyond just changing the data I pass in.

One thing to note on @jamgold’s solution is that if your thing changes, or you need to attach a jquery handler across multiple of those (e.g., a sortable with different lists) you’ll end up in the same situation, where you need to “update” the handler when the data changes. But if this isn’t your situation then it’s for sure a cleaner approach.

1 Like

I would check out the blaze guide

1 Like

What job does the jquery handler do?
And why can’t it be done with events?

I think this might a situation where the framework can help you, if you stop trying to fight it

Do meteor events help with things like jQuery’s sortable? This would be revelationary to me!

Having a quick look, the initialisation would have to be in onRendered but blaze event listeners should receive the sortable events.
jQuery is pretty good about using native events, and even when not, it helps that the backend for blaze (including event handlers) is done through jquery

Hm. That’s probably not super useful unfortunately as it’s the initialization that is a pain. Still pretty interesting though!