Morph fails to remove in IE 8/9 - ember.js

I'm working on an Ember app (using RC1) and am having a problem with IE (surprise). We have a series of nested views that can show either a trip's summary, details, or edit. The trip view looks (roughly) like this:
{{#if tripController.showSummary}}
{{template "view/TripSummaryView"}}
{{/if}}
{{#if tripController.showDetails}}
{{template "view/TripDetailView"}}
{{/if}}
{{#if tripController.showEdit}}
{{template "view/TripEditView"}}
{{/if}}
On the div surrounding the trip summary view, I have an action:
<div {{bindAttr id="tripController.tripId"}}
class="card single-line"
{{action "toggleDetails" tripController on="click"}}
style="cursor:pointer">
And the controller handles the action:
toggleDetails: function(tripController) {
if (this.get('showDetails')) {
this.set('showDetails', false);
this.set('showSummary', true);
} else {
this.set('showDetails', true);
this.set('showSummary', false);
}
},
All pretty vanilla, and it works perfectly in Chrome, FF, and Safari. But when I run it in IE, it bombs in the metamorph's realNode() function (line 17264 in ember.js):
var realNode = function(start) {
while (start.parentNode.tagName === "") {
start = start.parentNode;
}
return start;
};
with this error:
SCRIPT5007: Unable to get value of the property 'parentNode': object is null or undefined
When I put a breakpoint in the code, it appears that Ember is trying to remove 2 metamorphs, but that the second is inside the first and so start is null the second time around. I put a breakpoint in that function in Chrome, but it doesn't ever appear to get called.
I've tried reducing this down to a JSFiddle, but I can't seem to get it to break - so there must be something in the way we've got our views set up that's causing a problem. Any suggestions on how to go about debugging this would be most appreciated!
Thanks,
Scott

You can achieve the same using ContainerView but I am also facing similar problem where childviews are not receiving model/controller. Here is a [similar post] (An example of ContainerView required where childViews are binded with their respective controllers "containerview's childviews controller binding")

Related

Ember.js - Animate Handlebars Change

I am using an {{if}} statement in a handlebars template (in Ember.js) and currently have it setup so if foo is true, a form is displayed.
I have a button which toggles the value of foo and hides/shows the form. This is done with an {{action}} attribute.
What I would like to do is animate this change. If possible, how can I do this using the current setup?
Note: I would be fine with fadeIn/fadeOut (jQuery).
Take a look at Liquid Fire (see: https://github.com/ef4/liquid-fire). I personally didn't work with it yet, but the examples look promising. Especially this form example looks similar to what you want: http://ef4.github.io/ember-animation-demo/#/animated-if-demo
Source is on the next slide: http://ef4.github.io/ember-animation-demo/#/animated-if-source
You can do this way:
in the handlebars file:
<script type="text/x-handlebars" data-template-name="newPost">
<p> Template for New Post </p>
<div class="errorMsg">
<p>Error Message</p>
</div>
</script>
in the view you can set a trigger with an action, and hide the div with the message error
App.NewPostView = Ember.View.extend({
showError: function() {
$('.errorMsg').fadeToggle("slow");
},
didInsertElement : function(){
$('.errorMsg').hide();
this.get('controller').on('showError', this, this.showError);
},
willClearRender: function()
{
this.get('controller').off('showError', this, this.showError);
}
});
And finally in the controller, this must extend to Ember.Evented and when you want to show the message error, you must call a this.trigger error.
App.NewPostController = Ember.ObjectController.extend(Ember.Evented,{
actions:{
// This an example the method that verify and show error after validating
addNewPost: function(post){
if(post.get('name') === 'valid'){
//do your business
}else{
//this will show the message error
this.trigger('showError');
}
}
}
})
when you want to hide the message error, you must call this.trigger('showError') again and this hide the message, you can use this with other effects.

EmberJS calling controller function within #each

I'm trying to call a controller function inside a #each template to check if the model is selected. This needs to receive the model for each loop iteration.
jsbin is here http://emberjs.jsbin.com/bemos/4/edit
I've tried using the following template, but adding a parameter to {{controller.selected}} results in ember throwing an exception, or not doing anything (the result seems to be different in jsbin).
{{#each model}}
<li><a hres="#" {{action "clickItem" this}}>{{label}} -- selected is {{controller.selected name}} </a></li>
{{/each}}
My function in the controller looks as follows
selected: function(id) {
var list = this.get("selectedList");
console.log("Checking sel for", id);
if(list.contains(id))
return true;
return false;
}.property("selectedList")
In the jsbin, I expect to be able to click on the "Hello 1"/"Hello 2" elements, and this should change the part of the text that says "selected is false" to true. It should also update the number of items selected in the bottom. Currently, this shows 1 item is selected (since the default is selectedList:["2"]) however "Hello 2" says "selected is false" - this should say "true".
(I should add I'm pretty new to EmberJS as have been working with ExtJS for a few years!).
Thanks
Do you really have a problem in code ? I guess no, since your bin is working fine and even clickItem is working as well. In your local environment where you are facing this issue, I think you are not using same version of handlebars and ember. Please check if you are using below,
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.1/ember.js"></script>
Update 1 -
Here is the bin I tried to update - http://emberjs.jsbin.com/veqoqocu/1/edit
Its not fully functioning but fixes some issue.
There are multiple problems,
You should use ArrayController for lists, redesign your example otherwise selected property will never work the way you want. Here is a good example - http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
push & pop needs to be pushObject and popObject
computer property on array works like - }.property('selectedList.[]')

Appending child view to body / other element

I'm building a popover/dropdown with Ember which essentially boils down to:
<div class="popover">
<span {{action showPopover}}>Click</span>
{{#if popoverShowing}}
<div class="popover-body">The popover body</div>
{{/if}}
</div>
All works fine but I have other elements on the page which are absolutely positioned and due to them forming a new stacking context there's no way I can make the popover be displayed above them.
If this were plain old Javascript, I'd append the popover to the body much like how Bootstrap does with the container option but we don't have that level of control in Ember AFAICT.
The only solution I can think of is to use an {{outlet}} in the application template and render to that, but that means for every popover/dropdown I have to split the contents out to a different view/template/controller and have an action in the router which seems rather over-complicated!
Can anyone think of a better option?
One approach that seems to work is to detach the body element on didInsertElement and then manually destroy on willDestroyElement:
didInsertElement: function() {
Ember.$("body").append(this.$())
},
willDestroyElement: function() {
this.$().remove()
}
This appears to work fine, but there are probably bugs lurking!

Ember.js - Using a Handlebars helper to detect that a subview has rendered

There are numerous questions that ask in one way or another: "How do I do something after some part of a view is rendered?" (here, here, and here just to give a few). The answer is usually:
use didInsertElement to run code when a view is initially rendered.
use Ember.run.next(...) to run your code after the view changes are flushed, if you need to access the DOM elements that are created.
use an observer on isLoaded or a similar property to do something after the data you need is loaded.
What's irritating about this is, it leads to some very clumsy looking things like this:
didInsertElement: function(){
content.on('didLoad', function(){
Ember.run.next(function(){
// now finally do my stuff
});
});
}
And that doesn't really even necessarily work when you're using ember-data because isLoaded may already be true (if the record has already been loaded before and is not requested again from the server). So getting the sequencing right is hard.
On top of that, you're probably already watching isLoaded in your view template like so:
{{#if content.isLoaded}}
<input type="text" id="myTypeahead" data-provide="typeahead">
{{else}}
<div>Loading data...</div>
{{/if}}
and doing it again in your controller seems like duplication.
I came up with a slightly novel solution, but it either needs work or is actually a bad idea...either case could be true:
I wrote a small Handlebars helper called {{fire}} that will fire an event with a custom name when the containing handlebars template is executed (i.e. that should be every time the subview is re-rendered, right?).
Here is my very early attempt:
Ember.Handlebars.registerHelper('fire', function (evtName, options) {
if (typeof this[evtName] == 'function') {
var context = this;
Ember.run.next(function () {
context[evtName].apply(context, options);
});
}
});
which is used like so:
{{#if content.isLoaded}}
{{fire typeaheadHostDidRender}}
<input type="text" id="myTypeahead" data-provide="typeahead">
{{else}}
<div>Loading data...</div>
{{/if}}
This essentially works as is, but it has a couple of flaws I know of already:
It calls the method on the controller...it would probably be better to at least be able to send the "event" to the ancestor view object instead, perhaps even to make that the default behavior. I tried {{fire typeaheadHostDidRender target="view"}} and that didn't work. I can't see yet how to get the "current" view from what gets passed into the helper, but obviously the {{view}} helper can do it.
I'm guessing there is a more formal way to trigger a custom event than what I'm doing here, but I haven't learned that yet. jQuery's .trigger() doesn't seem to work on controller objects, though it may work on views. Is there an "Ember" way to do this?
There could be things I don't understand, like a case where this event would be triggered but the view wasn't in fact going to be added to the DOM...?
As you might be able to guess, I'm using Bootstrap's Typeahead control, and I need to wire it after the <input> is rendered, which actually only happens after several nested {{#if}} blocks evaluate to true in my template. I also use jqPlot, so I run into the need for this pattern a lot. This seems like a viable and useful tool, but it could be I'm missing something big picture that makes this approach dumb. Or maybe there's another way to do this that hasn't shown up in my searches?
Can someone either improve this approach for me or tell me why it's a bad idea?
UPDATE
I've figured a few of the bits out:
I can get the first "real" containing view with options.data.view.get('parentView')...obvious perhaps, but I didn't think it would be that simple.
You actually can do a jQuery-style obj.trigger(evtName) on any arbitrary object...but the object must extend the Ember.Evented mixin! So that I suppose is the correct way to do this kind of event sending in Ember. Just make sure the intended target extends Ember.Evented (views already do).
Here's the improved version so far:
Ember.Handlebars.registerHelper('fire', function (evtName, options) {
var view = options.data.view;
if (view.get('parentView')) view = view.get('parentView');
var context = this;
var target = null;
if (typeof view[evtName] == 'function') {
target = view;
} else if (typeof context[evtName] == 'function') {
target = context;
} else if (view.get('controller') && typeof view.get('controller')[evtName] == 'function') {
target = view.get('controller');
}
if (target) {
Ember.run.next(function () {
target.trigger(evtName);
});
}
});
Now just about all I'm missing is figuring out how to pass in the intended target (e.g. the controller or view--the above code tries to guess). Or, figuring out if there's some unexpected behavior that breaks the whole concept.
Any other input?
UPDATED
Updated for Ember 1.0 final, I'm currently using this code on Ember 1.3.1.
Okay, I think I got it all figured out. Here's the "complete" handlebars 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 Ember.Evented/jQuery convention. This updated code is based on the built-in Ember {{action}} helper, and should be able to accept any target="..." argument in your template, just as {{action}} does. Where it differs from {{action}} is (besides firing automatically when the template section is rendered):
Sends the event to the view by default. Sending to the route or controller by default wouldn't make as much sense, as this should probably primarily be used for view-centric actions (though I often use it to send events to a controller).
Uses Ember.Evented style events, so for sending an event to an arbitrary non-view object (including a controller) the object must extend Ember.Evented, and must have a listener registered. (To be clear, it does not call something in the actions: {…} hash!)
Note that if you send an event to an instance of Ember.View, all you have to do is implement a method by the same name (see docs, code). But if your target is not a view (e.g. a controller) you must register a listener on the object with obj.on('evtName', function(evt){...}) or the Function.prototype.on extension.
So here's a real-world example. I have a view with the following template, 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">
Cancel
Add
</div>
</div>
</script>
I needed to know when this element was available in the DOM, so I could attach a typeahead to it:
<input id="subjectPicker" type="text" data-provide="typeahead">
So, I put a {{trigger}} helper in the same 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 implemented didRenderSubjectPicker 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! Now the typeahead gets wired when (and only when) the sub-section of the template is finally rendered. Note the difference in utility, didInsertElement is used when the main (or perhaps "concrete" is the proper term) view is rendered, while didRenderSubjectPicker is run when the sub-section of the view is rendered.
If I wanted to send the event directly to the controller instead, I'd just change the template to read:
{{trigger didRenderSubjectPicker target=controller}}
and do this in my controller:
App.ReportPickerController = Ember.ArrayController.extend({
wireTypeahead: function(){
// I can access the rendered DOM elements here
}.on("didRenderSubjectPicker")
});
Done!
The one caveat is that this may happen again when the view sub-section is already on screen (for example if a parent view is re-rendered). But in my case, running the typeahead initialization again is fine anyway, and it would be pretty easy to detect and code around if need be. And this behavior may be desired in some cases.
I'm releasing this code as public domain, no warranty given or liability accepted whatsoever. If you want to use this, or the Ember folks want to include it in the baseline, go right ahead! (Personally I think that would be a great idea, but that's not surprising.)

Ember.js - Using {{#if}} around {{bindAttr}} with shared condition?

I'm on Ember-1.0.0-pre2 and I seem to be having trouble using an {{#if}} statement around an element which has a {{bindAttr class="..."}} and the binding conditions are the same. I.E. the if statment and class binding are to the same controller attribute. See code:
<button {{action "toggleShow" target="controller"}}>Toggle Visibility</button>
{{#if show}}
<div {{bindAttr class="show:red:green"}}>test</div>
{{/if}}
http://jsfiddle.net/y49ch/10/
If you click the "Toggle Visibility" button several times you'll notice you get a the common error that says: "Something you did caused a view to re-render after it rendered but before it was inserted into the DOM. Because this is avoidable and the cause of significant performance issues in applications, this behavior is deprecated ..."
At first look that seems stupid, but that's a very primitive example of my problem. In my case, there is a computed property on the end of both bindings (if and class attribute). In both cases the computed properties share a common dependent key. When that common dependent key changes it causes both helpers to be update and thus the error.
Is this a bug? I can guess what's happening here, but it seems like I should be able to do this safely.
EDIT: The above is a primitive example of the problem I'm having. It's meant to show it in a very simple way. Below is a more complex example.
Template:
<button {{action "toggleValue" target="controller"}}>Toggle Value</button><br>
{{#if greaterThanTen}}
<div {{bindAttr class="isOdd:red:green"}}>test</div>
{{/if}}
Javascript:
App.myController = Ember.Controller.create({
value: 10,
greaterThanTen: function() {
return this.get('value') > 10;
}.property('value'),
isOdd: function() {
return this.get('value') % 2 === 1;
}.property('value'),
toggleValue: function() {
this.set('value', (this.get('isOdd') ? 10 : 11));
}
});
http://jsfiddle.net/y49ch/16/
I see it now. Your original code had both points watching the same property which got me a little confused, but now it makes more sense. I can't really get what's going on, but I suspect it might have something to do with the runloop.
I've changed your code a little (see this jsfiddle) so that div is now a child view. Some of your properties were moved from the controller to the view (does your spec allow these guys to be at the view or does it have to be at the controller? unless I missed something only the view should be concerned about isOdd and toggleValue at this point) and the css is bound through classNameBindings watching for the value property that is bound to the parent view.
App.myController = Ember.Controller.create({
value: 10,
greaterThanTen: function() {
return this.get('value') > 10;
}.property('value')
});
App.MyView = Ember.View.extend({
templateName: 'my-view',
valueBinding: 'controller.value',
toggleValue: function() {
this.set('value', (this.get('isOdd') ? 10 : 11));
},
isOdd: function() {
return this.get('value') % 2 === 1;
}.property('value'),
ChildView: Em.View.extend({
classNameBindings: 'parentView.isOdd:red:green'
})
});
Now, the template looks like this:
<script type="text/x-handlebars" data-template-name="my-view">
<button {{action "toggleValue"}}>Toggle Value</button><br>
{{#if greaterThanTen}}
{{#view view.ChildView}}
test
{{/view}}
{{/if}}
</script>
Since the default tag for the View is div, it renders the same html, and it totally acts as a different view and prevents unecessary re-render.
Edit: Just as proof of concept, I've added a button to add to the value instead of just toggle so you can actually see the color changing after it gets visible. Here's the fiddle
Let me know if this is good for you