UPDATED
Updated for Ember 1.0 final, I am currently using this code on Ember 1.3.1.
OK, I think I get it. Here's the "full" helper helper:
Ember.Handlebars.registerHelper('trigger', function (evtName, options) { // See http://stackoverflow.com/questions/13760733/ember-js-using-a-handlebars-helper-to-detect-that-a-subview-has-rendered // for known flaws with this approach var options = arguments[arguments.length - 1], hash = options.hash, hbview = options.data.view, concreteView, target, controller, link; concreteView = hbview.get('concreteView'); if (hash.target) { target = Ember.Handlebars.get(this, hash.target, options); } else { target = concreteView; } Ember.run.next(function () { var newElements; if(hbview.morph){ newElements = $('#' + hbview.morph.start).nextUntil('#' + hbview.morph.end) } else { newElements = $('#' + hbview.get('elementId')).children(); } target.trigger(evtName, concreteView, newElements); }); });
I changed the name from {{fire}} to {{trigger}} to more closely match the Ember.Evented / jQuery convention. This updated code is based on the built-in Ember {{action}} helper and should accept any target="..." argument in your template, as {{action}} does. Where it differs from {{action}} (except for automatic triggering when rendering a section of a template):
- By default, an event is sent to the view. Sending to a route or controller by default will not make much sense, since it should probably be primarily used for view-oriented actions (although I often use it to send events to the controller).
- It uses Ember.Evented style events, therefore, to send an event to an arbitrary object without viewing (including the controller), the object must extend Ember.Evented and must have a registered listener. (To be clear, it does not call something in the
actions: {…} hash actions: {…} !)
Note: if you send an event to an Ember.View instance, all you have to do is implement the method with the same name (see docs , code ). But if your goal is not a view (for example, a controller), you must register the listener on an object with the extension obj.on('evtName', function(evt){...}) or Function.prototype.on .
So here is an example of the real world. I have a view with the following pattern using Ember and Bootstrap:
<script data-template-name="reportPicker" type="text/x-handlebars"> <div id="reportPickerModal" class="modal show fade"> <div class="modal-header"> <button type="button" class="close" data-dissmis="modal" aria-hidden="true">×</button> <h3>Add Metric</h3> </div> <div class="modal-body"> <div class="modal-body"> <form> <label>Report Type</label> {{view Ember.Select viewName="selectReport" contentBinding="reportTypes" selectionBinding="reportType" prompt="Select" }} {{#if reportType}} <label>Subject Type</label> {{#unless subjectType}} {{view Ember.Select viewName="selectSubjectType" contentBinding="subjectTypes" selectionBinding="subjectType" prompt="Select" }} {{else}} <button class="btn btn-small" {{action clearSubjectType target="controller"}}>{{subjectType}} <i class="icon-remove"></i></button> <label>{{subjectType}}</label> {{#if subjects.isUpdating}} <div class="progress progress-striped active"> <div class="bar" style="width: 100%;">Loading subjects...</div> </div> {{else}} {{#if subject}} <button class="btn btn-small" {{action clearSubject target="controller"}}>{{subject.label}} <i class="icon-remove"></i></button> {{else}} {{trigger didRenderSubjectPicker}} <input id="subjectPicker" type="text" data-provide="typeahead"> {{/if}} {{/if}} {{/unless}} {{/if}} </form> </div> </div> <div class="modal-footer"> <a href="#" class="btn" data-dissmis="modal">Cancel</a> <a href="#" {{action didSelectReport target="controller"}} class="btn btn-primary">Add</a> </div> </div> </script>
I needed to know when this element was available in the DOM, so I could attach a file like:
<input id="subjectPicker" type="text" data-provide="typeahead">
So, I put the {{trigger}} helper in one block:
{{#if subject}} <button class="btn btn-small" {{action clearSubject target="controller"}}>{{subject.label}} <i class="icon-remove"></i></button> {{else}} {{trigger didRenderSubjectPicker}} <input id="subjectPicker" type="text" data-provide="typeahead"> {{/if}}
And then didRenderSubjectPicker implemented in my view class:
App.ReportPickerView = Ember.View.extend({ templateName: 'reportPicker', didInsertElement: function () { this.get('controller').viewDidLoad(this); } , didRenderSubjectPicker: function () { this.get('controller').wireTypeahead(); $('#subjectPicker').focus(); } });
Done! Typeahead now connects when (and only when) the template sub-channel is finally displayed. Note the difference in utility, didInsertElement used when the main (or perhaps "specific" is the right term) is rendered, and didRenderSubjectPicker started when the submode of the view is displayed.
If I wanted to send the event directly to the controller instead, I would just modify the template to read:
{{trigger didRenderSubjectPicker target=controller}}
and do it in my controller:
App.ReportPickerController = Ember.ArrayController.extend({ wireTypeahead: function(){
Done!
The only caveat is that this may happen again when the view section is already on the screen (for example, if the parent view is re-displayed). But in my case, starting typeahead initialization again is fine anyway, and it would be pretty easy to detect and code if needed. And this may be desirable in some cases.
I release this code as a public domain, no guarantees or responsibilities were accepted. If you want to use this, or Ember people want to include it in the baseline, go straight ahead! (Personally, I think it would be a great idea, but this is not surprising.)