I am getting a basic feel for Ember, and running into some weird issues at the moment. The app is pretty basic (and ugly). On a specific route, (/stack) , I list out a bunch of cards that belong to this stack. StackController is an array controller since it retrieves a stack of cards that belong to it, essentially
StackController = Ember.ArrayController.extend();
and
StackRoute = Ember.Route.extend({
model: function(){
return App.Card.find();
}
});
Then in my templates, I simply loop through it using the #each helper to display a property of the card ('front'), like this:
<ul>
{{#each}}
<li>{{front}}</li> <button {{action 'backside' this}}>View</button>
{{/each}}
</ul>
Up until here, everything is working the way it should. However, when I click on the 'View' button to trigger the 'backside' event, it yields a :
"Nothing handled the event 'backside' " error. Kind of bummed because it seems pretty basic that it should do so, oh yes, I do explicitly specify the 'backside' event in my Controller, like this:
StackController = Ember.ArrayController.extend({
actions:{
backside:function(){
alert("backside event handled");
}
}
});
For a more detailed look, here is the jsBin:
http://jsbin.com/AHiTicU/5/edit
What am I doing wrong?
The code in the JSBIN is accurate, there maybe some typos here however (unlikely, but a heads up).
You are using the ember-1.0.0-rc.6, in that version to use the action you need to put you action handler inside of the route, in a object with a key called events, like the following:
App.StackRoute = Ember.Route.extend({
model: function(){
return App.Card.find(); // must substitute this with this.store.find('card');
},
events:{
backside:function(){
alert('working');
}
}
});
Updated jsbin http://jsbin.com/AHiTicU/6/edit
I recommend you to use the lastest version, at the moment 1.2.0, you can get it in the home page of emberjs website http://emberjs.com/.
So your current code will work http://jsbin.com/AHiTicU/9/edit
Related
Since I'm not totally sure on which level my issue actually is to be solved best, I'd like to summarise the path I went and the things I tried first:
It's more or less about $el (I think).
As most basic backbone examples state, I started with having the $el defined within its view, like
Invoice.InvoiceView = Backbone.View.extend({
el: $('#container'),
template: ..,
..
});
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
Following this article's advice, I switched to passing $el over to the view while calling the render()-method. Example:
$('#container').html( new WineListView({model: app.wineList}).render().el );
So far so good - but now render() gets called, while it maybe shouldn't (yet).
For example the View asynchronously fetches a model in its initialize()-routine. Adding a binding to reset or sync (e.g. like this.model.bind('sync', this.render, this)) makes sure, render() gets definitely called once the model is fetched, however above stated way, render() still might get called while the model isn't fetched yet.
Not nice, but working(TM), I solved that by checking for the model's existence of its primary key:
render: function() {
if(this.model.get('id')) {
...
}
However, what I didn't expect - and if it really isn't documented (at least I didn't find anything about it) I think it really should be - the fetch operation doesn't seem to be atomic. While the primary key ('id') might be already part of the model, the rest might not, yet. So there's no guarantee the model is fetched completely that way. But that whole checking seemed wrong anyway, so I did some research and got pointed to the deferred.done-callback which sounded exactly what I was looking for, so my code morphed into this:
render: render() {
var self = this;
this.model.deferred.done(function() {
self.model.get('..')
};
return this;
}
..
$('#container').html( new WineListView({model: app.wineList}).render().el);
It works! Nice, hu? Ehrm.. not really. It might be nice from the runtime-flow's point of view, but that code is quite cumbersome (to put it mildly..). But I'd even bite that bullet, if there wouldn't be that little, tiny detail, that this code sets (=replaces) the view instantly, but populates it later (due to the deferred).
Imagine you have two (full-page) views, a show and an edit one - and you'd like to instantly switch between the two (e.g. after hitting save in the edit-view it morphs into the show-view. But using above code it sets (=resets) the view immediately and then renders its content, once the deferred fires (as in, once fetching the model is completed).
This could be a short flickering or a long blank transition page. Either way, not cool.
So, I guess my question is: How to implement views, which don't know about their container, involve models which need to be fetched, views which should be rendered on demand (but only once the model is fetched completely), having no need to accept UI/UX trade-offs and - the cherry on the cake - having maintainable code in the end.
First of all, the first method you found is terrible (hard coding selector in view's constructor)
The second: new WineListView({model: app.wineList}).render().el is very common and ok. This requires you to return the reference to view from render method, and everyone seems to follow this, which is unnecessary.
The best method (imo) is to simply attach the views element to the container, like this
$('#container').html(new WineListView({model: app.wineList}).el);
The WineListView doesn't need to know about where it's going to be used, and whatever is initializing WineListView doesn't need to worry about when to render the WineListView view instance:
because the el is a live reference to an HTML Element, the view instance can modify it anytime it wants to, and the changes will reflect wherever it is attached in DOM/ when it gets attached in DOM.
For example,
WineListView = Backbone.View.extend({
initialize: function(){
this.render(); // maybe call it here
this.model.fetch({
success: _.bind(this,function(){
this.render(); // or maybe here
})
});
}
});
Regarding flickering: this hardly has to do anything with rendering or backbone, it's just that you're replacing one element with another and there will be an emptiness for a tiny bit of time even if your new view renders instantly. You should handle this using general techniques like transitions, loaders etc, or avoid having to switch elements (For example convert labels into inputs in the same view, without switching view)
First off, the linked example is outdated. It's using version 0.9.2,
whereas the current version (2016-04-19) is 1.3.3. I recommend
you have look at the change log and note the differences, there are many.
Using the el property is fine. Like everything though, there's a time and place.
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
I wouldn't define an el property on every view, but sometimes it makes sense, such as your example. Which is why, I'm assuming, Backbone allows the use of the el property. If you know container is already in the DOM, why not use it?
You have a few options:
The approach outlined in my original answer, a work-around.
fetch the model, and in the success callback, insert the view element into the DOM:
model.fetch({
success:function() {
$('#container').html(new View({model:model}).render().el);
}
});
Another work-around.
Define an el property on the view and fetch the model in the view initialize function. The new content will be rendered in the container element (also the view), when the content/model data is ready, by ready, I mean when the model has finished fetching from the server.
In short,
If you don't want to define an el property, go with number 1.
If you don't want to let the view fetch the model, go with number 2.
If you want to use the el property, go with number 3.
So, I guess my question is: How to implement views, which don't know about their container
In your example, I would use the el property, it's simple a solution with the least amount of code. Not using the el property here, turns into hacky work-arounds that involve more code (complexity) without adding any value (power).
Here's what the code looks like using el:
var Model = Backbone.Model.extend({url:'/model_url'});
var model = new Model();
// set-up a view
var View = Backbone.View.extend({
el:'#container',
template:'model_template',
initialize:function() {
this.model.fetch();
this.listenTo(this.model,'sync',this.render);
},
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var view = new View({model:model});
Check out the documentation for el.
Here is an updated working example.
If there is an obvious flicker because, your model takes a noticeable amount of time
to be fetched from the server...maybe you should think about displaying a loading bar/variation thereof
while fetching the model. Otherwise instead of seeing the flicker, it will appear the
application is slow, delayed, or hanging..but in reality - it's waiting to render the next view,
waiting for the model to finish fetching from the server. Sitting on old content, just waiting for
the model to load new data to show new content.
i have an ember view with a bunch of jquery code to manipulate the dom, the thing is when i use:
this.transitionTo("forecast.workpage", id);
it only loads the view once but when i try click it again the view doesn't execute again, i try several ways for instance adding this.rerender() in the end of my didInsertElement like that:
didInsertElement : function(){
//all my js code
this.rerender()
}
but it trows an error
Uncaught TypeError: Cannot read property '0' of undefined
, try adding this._super() in the begin, same result, is there a way to force the view to refresh from the view itself or the route? or controller ?
edit: try this approach with no results...
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent: function(){
//$(document).foundation();
$(".sortables li:even").find('div.row').addClass("gray");
}
Same thing, executes onces but where i click on the action doesn't execute the code.
To re-render the whole route (page), you can use the route's refresh method. Or, as a possible solution to this.rerender() failing, you could call that after it renders the first time.
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
this.rerender();
});
}
Personally, I wouldn't recommend either one. Your view should update automatically when the data it depends on changes. It if it's not updating, you probably have your observers set up wrong. (I don't really know your use case though, so use your best judgement.)
Apologies for such a novice question but I'm really stuck here.
I'm working on someone's code and trying to understand it.
I have got this template 'temp.tmpl':
<li class="icon mu status-{{state}} type-{{type}}" style="background-color:{{colour}};">
<a href="#">
<span class="top-stat"><span>{{topStatNumber}}</span><span>{{topStatModifier}}</span></span>
<span class="display-text">{{#if promotionName}}{{promotionName}}{{else}}{{name}}{{/if}}</span>
</a>
</li>
and following view:
define([
'views/toolkitView',
'text!templates/components/temp.tmpl'
], function(ToolkitView, MUItem) {
return ToolkitView.extend({
template:MUItem,
events: {
"click a": "showActiveMU"
},
showActiveMU: function() {
this.trigger("active:mu:selected", this.model.get("code"));
return false;
}
});
});
I'm not able to figure out how variables are getting rendered in template or what template
is compiling against?
It's hard to give a complete answer without seeing the contents of ToolkitView, but I believe this is what is going on:
Your new view extendsToolkitView, and inherits the methods from that view. There is probably a render method that takes whatever template view that renders your template with data from whatever model is assigned to the view, (which in this case is the MUItem template that you are loading through your define statement). Using ToolKitView as a "base" allows you to share common methods among your views, and tweak or extend them as need be.
In response to your comment regarding showActiveMu: when you create a new instance of this view, and assign a model to it, the view is able to access the model through this.model. In your case, the view's showActiveMU method will trigger an event, get the "code" attribute from the model, and pass that as an argument to any function listening for that event. More on backbone events here.
var Model = new FooModel();
var muItem = new MUView({model: fooModel});
// listen for event triggered by the view's showActiveMU event
muitem.on('active:mu:selected', function (code) {
console.log(code); // the code from the model assigned to muItem view
});
I'm trying to wrap my head around Ember at the moment, but all the magic is making this difficult.
I've set LOG_TRANSITIONS: true and Ember.LOG_BINDINGS = true; which gives me some minimal logging to the console, but I really need more than that.
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
For example, I have the following routes set up:
App.Router.map(function() {
this.route("example_items", {path: "/"});
});
with:
App.ExampleItemsRoute = Ember.Route.extend({
model: function() {
return App.ExampleItem.find();
}
});
Ember renders my ApplicationController and its application.handlebars template:
<header class="page-header">
<h1>Application Template</h1>
</header>
{{outlet}}
But fails to render my example_items.handlebars template. I get no exception or warning, and if I check the DOM, I can see ember has created a generic view in its place.
The bindings logging shows me that Ember has transitioned to example_items, but it seems it hasn't used either my ExampleItemsController, ExampleItemsView or template.
How can I debug a situation like this if I receive no errors or messages?
Edit:
App.ExampleItems View:
App.ExampleItemsView = Ember.CollectionView.extend({
templateName: 'example_items'
});
And App.ExampleItemsController:
App.ExampleItemsController = Ember.ArrayController.extend({
});
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
Yes. With the latest ember you can now LOG_ACTIVE_GENERATION to see console.log output whenever ember generates something for you.
Another new setting that might be helpful is LOG_VIEW_LOOKUPS
Here's your problem: CollectionView won't use your template. It takes an array as its content property (usually set up as a binding to the controller) and creates childViews manually. Without a content set it'll appear as a blank view.
If you add classNames: ['my-view'] to your view definition, you should see that the view it's instantiating and inserting is actually your view class, just empty. Add contentBinding: 'controller' and it should render itemViews for each item in the array, as well.
Is there any reason why setupController would not get called when using {{linkTo}}? I have two instances in my app where linkTo is being used, and in the second case. It doesn't work. The only difference that I can see is that in the first case linkTo is being used in a loop, and in the second it's not. Below is relevant code for the non-working one:
App.Router.map(function() {
this.resource("search", { path: "/search/:args" });
});
App.SearchCriteria = Ember.Object.extend({ });
App.SearchRoute = Ember.Route.extend({
serialize: function(model, params) {
// .. some code that converts model to a string called args
return {'args': args}
},
model: function(params) {
// convert args, which is query string-formatted, to an object
// and then make a App.SearchCriteria object out of it.
return App.SearchCriteria.create($.deparam(params.args));
},
setupController: function(controller, model) {
controller.set("searchCriteria", model);
}
});
In the search template:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
The last thing I see in the logs is:
Transitioned into 'search'
Normally, I'd see the setupController being called at some point, but it's not happening or some reason. I even tried using the {{action}} method to call a handler and then use transtionTo, but that had the same results.
UPDATE 1: Adding more details
The only difference between the working and non-working cases is that in the working case, the {{linkTo}} is being called from the same template as that of the controller and router (i.e., the linkTo is in the search template and it's invoking the SearchRoute). In the working case, the linkTo is being called on the SearchRoute but from a different template belonging to a different router).
After some Chrome debugging of Ember code, I found out that the router isn't being called is because partitioned.entered is empty. In the working case, it is non-empty.
var aborted = false;
eachHandler(partition.entered, function(handler, context) {
if (aborted) { return; }
if (handler.enter) { handler.enter(); }
setContext(handler, context);
if (handler.setup) {
if (false === handler.setup(context)) {
aborted = true;
}
}
});
UPDATE 2: Root issue found - bug?
I think I understand the root cause of why the handler isn't being triggered, and I think it's because the partitionHandlers(oldHandlers, newHandlers) method doesn't think that the model has changed, thus doesn't fire the handler.
To be specific, this is the relevant part of the view:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
Although the user checks off the checkbox (thus changing the state of searchCriteria), Ember doesn't think that searchCriteria is any different, thus doesn't do anything.
Is this a bug?
I'm not sure what your problem is, but this may help.
setupController is called every time the route is entered. But model hook may not be called every time.
See Ember guide: http://emberjs.com/guides/routing/specifying-a-routes-model/
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
Genrally speaking, if you click the link generated by link-to to enter the route, Ember will not call model hook for that route. Instead it passes the model (link-to parameter) to that route.
The philosophy here is since the client already has the model context, Ember think there is no need to get it again from server (that's model hook's job).