Probably you are loading data via ajax, so initially the {{each}} helper is empty, because the data isn’t fetched yet. After the view is inserted, doesn’t have any item, this.$('li').length == 0, because the ajax is pending. So no events are added. The ajax finish, the {{each}} update because of data binding, but the didInsertElement doesn’t trigger again.
You can use the afterRender queue and schedule a function to be triggered when the rendering is finished:
# hbs
{{#each}}
<li>{{name}}</li>
{{/each}}
<li>this one works</li>
I believe the didInsertElement function runs synchronously, so if your data is async, it’s possible for the code in the didInsertElement function to run before the li elements within the each block even exist. Scheduling the code to run after the render ensures it runs after the data has been loaded and rendered.
I found a more simple approach (no run loops), instead of attach the event in each li, just create a custom class for each element using the itemViewClass option. And in that custom view, listen the click event overriding the click method. Something like this:
Yes this is a good pratice, because ember doesn’t know about events added by this.$().on(...), so it need to be removed manually, when the view is destroyed.
The typical setup to integrate jquery events in ember, is the following :
App.MyCustomView = Ember.View.extend({
myEventHandler: function() {
// events trigereds out of the ember context, like jquery plugins,
// will run out of the runloop, so the Ember.run is needed
Ember.run(this, function() {
// do someting
})
},
// the view is ready, and the template is rendered
didInsertElement: function() {
// atach the custom event
this.$().on('someEvent', this.myEventHandler);
},
// view will be removed
willClearRender: function() {
// remove the custom event
this.$().off('someEvent');
}
})
(Code review request) … Related to the above pattern for incorporating jquery plugins, here’s what I’m doing to attach a plugin on domready. Is it a best practice to attach domready handlers in didInsertElement like this? Also, I’m running the event handler code in Ember.run, although in this case, I’m not sure it’s needed. Any thoughts on better ways to do this?
App.ApplicationView = Ember.View.extend({
idleTarget: Ember.$(document),
addIdleTimer: function() {
// events triggered out of the ember context, like jquery plugins,
// will run out of the runloop, so the Ember.run is needed
Ember.run(this, function() {
var doc = this.idleTarget,
self = this;
doc.idleTimer(App.SETTINGS.idleTimeout);
doc.on("idle.idleTimer", function logOff(){
// function you want to fire when the user goes idle
App.Util.logger.debug("timed out");
self.get("controller.target").transitionTo("logout");
doc.idleTimer("destroy");
});
});
},
// the view is ready, and the template is rendered
didInsertElement: function() {
// attach the custom event
var self = this;
Ember.$(function attachIdleTimer() {
self.addIdleTimer();
});
},
// view will be removed
willClearRender: function() {
// remove the custom event
// Prob not needed here b/c the setup also destroys
// but just good housekeeping
this.idleTarget.idleTimer("destroy");
}
});
There is no need to use jquery ready on didInsertElement because ApplicationView and all others views will just start to render and call didInsertElement after the document load event.
The place where you need to manually create a run loop (put an Ember.run) is doc.on("idle.idleTimer" like the following:
App.ApplicationView = Ember.View.extend({
idleTarget: Ember.$(document),
addIdleTimer: function() {
var doc = this.idleTarget, self = this;
doc.idleTimer(App.SETTINGS.idleTimeout);
doc.on("idle.idleTimer", function logOff() {
// the Ember.run is needed here because ember doesn't
// know about events manually attached with elem.on(eventName, func)
Ember.run(function() {
App.Util.logger.debug("timed out");
self.get("controller.target").transitionTo("logout");
doc.idleTimer("destroy");
})
});
},
didInsertElement: function() {
this.addIdleTimer();
},
willClearRender: function() {
this.idleTarget.idleTimer("destroy");
}
});
Because the on method attaches an event handler in the document object, and ember doesn’t know about it.
@marcioj Thanks for the review. So do we need the outer Ember.run or just the one you added?
The Ember runloop still eludes me, except for the general notion that it keeps the bound data in sync with the views. Can you recommend a good resource online to explain the inner workings?
Hi @marcioj - just checking if you’ve had time to review (again). I think I almost got it but am wondering if Ember.run should be outside or within the doc.on event binding. Here, we have it in both places - any benefit or need for that?
Just remember that the ember runloop was extracted to a project called backburner (link above), so maybe you could find resources about it too.
There is some situations where you need to deal with the runloop:
When you need to interact with the generated dom of a view, in these cases you use Ember.scheduleOnce('afterRender', ...). this post gives further info.
Integrating with events from custom plugins (your case). Because ember doesn’t know about events attached via view.$(selector).on(evtName, func).
?
About the first one, I think we could hide this from the user, creating a new view callback like didRenderTemplate. So ember views would handle in that way:
if (typeof view.didRenderTemplate == "function") {
Ember.run.scheduleOnce('afterRender', view, view.didRenderTemplate);
}
Thoughts …?
In the second, I don’t see a way to abstract from the user, but since this commit, now we can do it in a better way: