Execute jquery script when reactive element changes

4,357 views
Skip to first unread message

Aaron Judd

unread,
Mar 30, 2014, 3:25:52 PM3/30/14
to meteo...@googlegroups.com
Others have asked similar questions, but I haven't seen any answers that work for me just yet..

I have a script that runs after a template is rendered and adds some dom elements, in the old pre-blaze fashion it would rewrite it's elements whenever the template was re-rendered, and that's the behaviour I need.  Is there an event, some kind of trigger I can hook into to execute when the dom has changed, even though the template itself isn't re-rendered.  

Specifically, I use owl-carousel like such:
<template name="dashboard">
      <div class="dashboard">
        {{#with Package}}
          <div class="dashboard-widget">
            {{>packagePanel}}
          </div>
            {{>widget name=name}}
        {{/with}}
      </div>
</template>


Template.dashboard.rendered = ->
  $(".dashboard").owlCarousel

Where the contents of package change, and widget is dynamically rendered template list of multiple <div class="dashboard-widget">.  When the package changes I need to either 
either execute $(".dashboard").data('owlCarousel').reinit()  or reapply  $(".dashboard").owlCarousel

Owl inserts it's own elements wrapping the dashboard-widget, and if it isn't reinitialized the change/new list elements are written by meteor outside of the owl-carousel widgets.

This is killing me, seems like it should be simple (as it was before).  I'm on the verge of writing my own meteor carousel.. something I was hoping to avoid for now.

So basically - how do I force a re-render of a template, or hook into the change of the elements? 


Gadi Cohen

unread,
Mar 30, 2014, 3:40:01 PM3/30/14
to meteo...@googlegroups.com
As per the examples link to from Using Blaze, can't you just put in a dummy helper that runs the code you need?

{{rerendered}}

Template.dashboard.rerendered = function() {
  $(".dashboard").owlCarousel();
}

or whatever?

Aaron Judd

unread,
Mar 30, 2014, 4:25:05 PM3/30/14
to meteo...@googlegroups.com
Hmmm.. Sounded good so I created a rerendered helper, and even made it reactive so that it would know something has changed... (otherwise the rerender doesn't re-render ;-) )

Template.dashboard.rerendered = ->
  console.log "here re-render" + Session.get "currentPackage"
  $(".dashboard").data('owlCarousel')?.reinit()
  return Session.get "currentPackage"

And it executes (log msg displays) but the carousel doesn't destroy or reinit. If you do it in the console everything works fine. Does blaze know about something that has been added in the render? maybe the owl code doesn't re-render because it's not reactive.

Also took the {{rerender}} out of the dashboard template, and put later on the page, thinking that perhaps the template needed to be fully loaded before I could access the dom elements..

No bueno...

Gadi Cohen

unread,
Mar 30, 2014, 4:59:23 PM3/30/14
to meteo...@googlegroups.com
Haha ok wow it's late and when I read all the "rerenders" my head started spinning :>

Might have to look at this again tomorrow (if no one else steps in?), but a few thoughts:

In general, I was thinking of a pattern where something was already rendered and you just had to change it.  Is Pattern sometimes undefined?  Then maybe put a seperate template inside the {{#with}}, init owl in that template's rendered() func, and then try use the helper trick.  Note, the helper really should be rerunning anytime there's a change, I've used this pattern before, but can't remember the exact circumstances.

Other thoughts (again assuming the element is already on the DOM):

If you're relying on Session.get() for reactivity, maybe just drop the helper and put that code in a Deps.autorun().

If your changes are coming straight from the database, you could work with cursor.observeChanges().

Either way, I believe we'll get a better method in the future.  The goal for 0.8.0 was just to keep the same API, but back in the day, there was talks of "animation hooks" etc on the roadmap for Meteor UI (as it was called then).  Now that the Blaze base is stable, I guess MDG can start working on all the other cool stuff, but I expect a Component API will be higher priority.  And 0.8.0 was only just released, so... I guess we should be patient :)

swese44

unread,
Mar 31, 2014, 2:08:30 AM3/31/14
to meteo...@googlegroups.com
I would hope a new rerendered/updated event is prioritized first, the 0.8.0 update (while great for performance) took away an event a lot of us depend on. Right now our only options for running code on subsequent template updates are hacky -- either adding a nested Deps.autorun() or adding custom code in a setTimeout() call from a template helper.

Aaron Judd

unread,
Mar 31, 2014, 4:17:55 AM3/31/14
to meteo...@googlegroups.com
The Deps.autorun solution, seems to me to be the best work around here

Deps.autorun () ->
  console.log "deps autorun " + Session.get "currentPackage"
  if $(".dashboard")?
    if $(".dashboard").data('owlCarousel')?
      $(".dashboard").data('owlCarousel').reinit()
      console.log "re-initing"
    else
      console.log "initilizing owl"
      console.log $(".dashboard")
      $(".dashboard").owlCarousel()

The events fire when I need them to, however, the actual reinit and owlCarousel() don't actually write their updates to the dom. Maybe a scoping issue?

gerard sychay

unread,
Mar 31, 2014, 10:17:08 AM3/31/14
to meteo...@googlegroups.com
Hi Aaron,

Like you and others, I've had this same problem. I think you're right to use Deps.autorun. But try putting the actual work in a template (haven't actually run this):

Deps.autorun () ->
  cnt = Package.find().count() # Or some other way to know Package has changed
  Session.set "cnt", cnt

Template.dashboard.Package = ->
  console.log "I depend on: " + Session.get('cnt')

  if $(".dashboard")?
    if $(".dashboard").data('owlCarousel')?
      $(".dashboard").data('owlCarousel').reinit()
      console.log "re-initing"
    else
      console.log "initilizing owl"
      console.log $(".dashboard")
      $(".dashboard").owlCarousel()

This idea came from my Voltage package. I had the same problem: pre-0.8.0, I just stuffed everything in 'rendered' and it re-ran every time. With 0.8.0, it wouldn't catch re-run when someone logged in, which meant the editing buttons never appeared. I had to refactor everything to be more fine-grained. For you case, look at the 'isVoltageAuthorized' variable, which trigger a custom Jquery plugin to re-render (Template.voltagePage.helpers). The CS file doesn't translate directly to your case, but it may help.

HTH,
Gerard

gerard sychay

unread,
Mar 31, 2014, 10:24:31 AM3/31/14
to meteo...@googlegroups.com

Aaron Judd

unread,
Mar 31, 2014, 12:04:09 PM3/31/14
to meteo...@googlegroups.com
Well this is just gross, gross with a more than a fair share of kludge thrown in:

Deps.autorun () ->
  Session.get "currentPackage"
  if $(".dashboard")?
    if $(".dashboard").data('owlCarousel')?
      console.log "reinit dashboard"
      setTimeout (->
        $(".dashboard").data('owlCarousel').reinit()
      ), 1
    else
      setTimeout (->
        console.log "recreating owl dashboard"
        $(".dashboard").owlCarousel
          navigation: false
          pagination: false
          autoHeight: true
      ), 1

Deps.autorun () ->
  Session.get("dashboard")
  setTimeout (->
    $(".dashboard").owlCarousel
      navigation: false
      pagination: false
      autoHeight: true
    ), 1


Template.dashboard.rendered = ->
  console.log "dashboard.rendered"
  $(".dashboard").owlCarousel
    navigation: false
    pagination: false
    autoHeight: true


The setTimeouts were the key, but really, um gross. There are two different events to monitor (dashboard comes down, and has different states within, hence the two autoruns).  Thanks to all the ideas guys, I am sure I will be refactoring this once a week, but until then I am just happy to be able to think about fixing something else ;-)

Brent Abrahams

unread,
Apr 1, 2014, 6:00:44 AM4/1/14
to meteo...@googlegroups.com
I ran into the same sort of trouble when moving over to blaze.  I made a sub-template of content that I knew was going to be wholly re-rendered and put a rendered callback on that template instead of the parent template. That seems to have solved the problem. (Same approach that Gadi suggested: extracting everything inside the {{#with}} block helper into a separate template and putting the rendered callback on that -- although I'd go a step further and put the rendered callback on the packagePanel template or the widget template -- whichever one is getting rerendered.)

Jan Hendrik Mangold

unread,
Apr 1, 2014, 12:07:19 PM4/1/14
to meteo...@googlegroups.com
I played with owlCarousel a little bit and the best thing I was able to come up with is to manipulate the carousel in observe callbacks. It works well. I create the carousel in the Template.rendered, but it is empty

  Template.owl_demo.rendered = function() {
    console.log('owl_demo rendered');
    $('#owl-demo').owlCarousel({
      items : 2,
      itemsDesktop : [1199,2],
      itemsDesktopSmall : [979,2]
    });
    owl = $('#owl-demo').data('owlCarousel');
    owl.itembyid = function(id)
    {
      var p = null;
      for(n=0;n<owl.itemsAmount && p==null;n++)
      {
        if($(owl.$owlItems[n].innerHTML).attr('id') == id) p = n;
      }
      return p;
    };
  };

When I subscribe to the data reactivity is achieved

  Deps.autorun(function(){
    Meteor.subscribe('images', Session.get('number_of_owls'), {
      onError: function(){
        console.log('subscription error');
      },
      onReady: function() {
        Images.find().observe({
          added: function(item, olditem) {
            if(owl)
            {
              if(owl.itembyid(item._id) == null)
              {
                var content = '<div class="item" id="'+item._id+'"><img src="'+item.url+'" title="'+item._id+'"></div>';
                owl.addItem(content);
                console.log('added owl '+item._id+' '+n);
              }
            }
            else
              console.log('owlCarousel null');
          },
          removed: function(item) {
            var n = owl.itembyid(item._id);
            if(n != null)
            {
              owl.removeItem(n);
              console.log('removed owl '+item._id);
            }
          }
        });
      }
    });
  });

Aaron Judd

unread,
Apr 1, 2014, 12:17:44 PM4/1/14
to meteo...@googlegroups.com
Jan,
 This looks like the proper way to me, (using the carousel -add/removeItem) and I'm going to try this now, because my earlier solution while it "works" causes ugly refreshes, and doesn't work for at all for another implementation I have. In the back of my mind I knew this was what I'd probably have to do, but was trying to avoid because, well - it's a lot of code for something so simple (that worked).  One problem is that I don't know the content is so I will need to render that and insert into content. Also my instances of carousel are not permanently visible - they are in reactive parent templates, and then with multiple carousels within that drawer. Just a whole lot of pain for something that shouldn't be painful. Gritting my teeth now and getting to it. Sometimes avoiding the hard solution is just harder. Part of me was starting to think I should just code the carousel from scratch, since I'm going to have to do all this.

Andrew Mao

unread,
Apr 1, 2014, 1:31:30 PM4/1/14
to meteo...@googlegroups.com
Yikes, this is crazy. I am also having a hard time wrapping my head around the proper way to handle a template that expects to get completely redrawn but in fact now only has a single thing changed in Blaze.

Andrew Mao

unread,
Apr 1, 2014, 1:50:48 PM4/1/14
to meteo...@googlegroups.com
Are there any tricks to force a template off the screen and then back on again?

Jan Hendrik Mangold

unread,
Apr 1, 2014, 1:52:06 PM4/1/14
to meteo...@googlegroups.com

On 1 Apr 2014, at 10:31, Andrew Mao <miz...@gmail.com> wrote:

> Yikes, this is crazy. I am also having a hard time wrapping my head around the proper way to handle a template that expects to get completely redrawn but in fact now only has a single thing changed in Blaze.


there are different ways to accomplish this, and the usual pattern that works very well for me is something like the following. In this specific case the requirements for owlCarousel were different. It actually worked for owlCarousel as well, except when you try to remove items from the carousel, that’s where it broke down (because of owlCarousel), thus the more elaborate observe route

Template.foo.data = function() {
return Collection.find();
}

<template name=foo>
<ul>
{{#each data}}
{{>bar}}
{{/each}}
</ul>
</template>

<template name=“bar”>
<li> {{_id}} </li>
</template>

Greg Dhuse

unread,
Apr 2, 2014, 12:33:50 PM4/2/14
to meteo...@googlegroups.com
Yikes, this is crazy. I am also having a hard time wrapping my head around the proper way to handle a template that expects to get completely redrawn but in fact now only has a single thing changed in Blaze.

I encountered a similar problem redrawing a DataTables instance based on the template context which used to trigger a call to .rendered() when the context changed.  I found a work-around that seems reasonable:

<template name="bar">
    <table id="myTable">
        {{#with this}}
            {{renderTable}}
        {{/with}}
    </table>
</template>

Template.bar.renderTable = function() { 
    var data = this;

    // It's important to check that the element exists, because on the first call it will not
    var table = $("#myTable").dataTable();
    if(!table.length) {
        return;
    }

    // Render table...
}

Template.bar.rendered = function() {
    // It's also necessary to call renderTable() from rendered(), because the first call to renderTable() is made before the DOM is populated.  This will trigger the first successful render
    Template.bar.renderTable.call(this.data);
}

Gabriel Pugliese

unread,
Apr 2, 2014, 1:08:37 PM4/2/14
to meteo...@googlegroups.com
I'm having hard time with knob plugin. After updating the value attr of input, I have to trigger the change event.
How would I do that after returning the reactive data????

Template.analytics.helpers({
  onlineUsersCount: function () {
    return Session.get('onlineUsersCount');
  }
});

<input type="text" class="knob" data-readOnly="true" data-min="0" data-max="1000" value="{{onlineUsersCount}}">

Where should I trigger the event? $('.knob).trigger('change');



Gabriel Pugliese
CodersTV.com
@coderstv


--
You received this message because you are subscribed to the Google Groups "meteor-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to meteor-talk...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Gabriel Pugliese

unread,
Apr 2, 2014, 1:13:48 PM4/2/14
to meteo...@googlegroups.com
I did a hack, but it's not what I expect to do at all in every helper:

Template.analytics.helpers({
  onlineUsersCount: function () {
    Meteor.setTimeout(function () {
      this.$('.knob').trigger('change');
    }, 0);
    return Session.get('onlineUsersCount');
  }
});




Gabriel Pugliese
CodersTV.com
@coderstv

Abigail Watson

unread,
Apr 2, 2014, 3:59:52 PM4/2/14
to meteo...@googlegroups.com
A modified resize() pattern may work for opting into an updated() rendering pattern, without resorting to Deps.autorun().  I use the resize() pattern in nearly all my apps to smoothly rerender a page full of objects and templates on the window resize event.  Opting into the rerender by setting a reactive session variable in each helper that you want to trigger a rerender seems pretty clean.

Create a hidden div in your template, and add a field called updated (or whatever).

<template name="fooBar">
  <div id="fooBar" class="panel">
    <h2>{{ getTitle}}</h2>
    <p>{{ getText}}</p>
    <div id="carousel"></div>
  <div> 
  <div class="hidden">{{ updated }}</div>
</template>

On each field that you want to trigger a rerender, opt into the update function by setting the 'updated' session variable with the current date. Then return the 'updated' session variable to the hidden field in the template.

Session.setDefault("updated", null); 

Template.fooBar.getTitle = function(){
  Session.set("updated", new Date());
  return this.title;
};
Template.fooBar.getText = function(){
  Session.set("updated", new Date());
  return this.text;
};

Template.fooBar.updated = function(){
  // add stuff you want to happen on each rerender here
  console.log('Page title was ' + this.title + 'when updated at ' + Session.get("updated"));

  // for instance, 
  $('#carousel').owlCarousel();

  // this is where the rerendering magic happens  
  return Session.get('updated');
};

// generalize the pattern with the following
UI.registerHelper('updated', function(){
  return Session.get('updated');
});

Andrew Mao

unread,
Apr 4, 2014, 12:16:47 PM4/4/14
to meteo...@googlegroups.com
Please write your ideas in https://github.com/meteor/meteor/issues/2001 and https://github.com/meteor/meteor/issues/2010 if you would like to see better ways to handle this in future releases of Meteor.

Aaron Judd

unread,
Apr 4, 2014, 12:53:48 PM4/4/14
to meteo...@googlegroups.com


One last comment on the overall issue here, as commented in 2001.  I see lots of other little things with this general issue, particular rendered functions that check user status in rendered (display or not display) aren't refreshed when a user logs out. Just one more thing to refactor. 
Reply all
Reply to author
Forward
Message has been deleted
0 new messages