Emberjs 1.0.0-RC3: Is there a better way to set model property for #render - ember.js

The code works and here is the jsfiddle for the code. But, I don't like the approach I am using to get {[render}} to work. For now to be able to use {{render events}} in application-template, I need to set it in the application-route using setupController and then calling controllerFor('events'), otherwise it won't work. But I have already defined an EventsRoute with a model hook for setting up the controller content and will prefer for {{render helper}} to use the model set in the EventsRoute.
I was wondering if there is a better or more idiomatic way to do this in ember besides my present approach of going to ApplicationRoute.
Relevant code*
App.Router.map(function(){
this.resource('events');
});
The {{render 'events'}} in application template only works when i set the model via application route.
App.ApplicationRoute = Ember.Route.extend({
setupController: function(){
this.controllerFor('events').set('model', App.Event.find());
}
});
I would prefer to {{render 'events'}} to work with the content set here but it doesn't. But I am keeping for now to use in places where it might make sense to use {{#linkTo}}.
App.EventsRoute = Ember.Route.extend({
model: function(){
return App.Event.find();
},
setupController: function(controller, model){
controller.set('content', model);
}
});
The templates
<script type="text/x-handlebars" data-template-name="application">
{{render 'events'}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="events">
<button {{action 'yEmpty'}}> log event content is empty</button>
{{#each controller}}
<h3>{{title}}</h3>
<p>{{start}} - {{end}}</p>
{{/each}}
{{outlet}}
</script>

I'm unsure of what you're trying to do. Do you want events and appointments to be rendered at the same time on one page? If yes, then, I think, you need to use render (much like you do in your jsfiddle). If not, you could remove {{render 'events'}} and setupController hook on ApplicationRoute and redirect to events route.
I found this issue with Fixture Adapter and looks like it doesn't work with hasMany (therefore jsfiddle example won't work) but I assume you are using RESTAdapter.
UPDATE:
Another place to set the model is in init hook like so:
App.EventsController = Ember.ArrayController.extend({
init: function () {
this.set('model', App.Event.find());
}
});
Personally, I would left the code in setupController hook.

Related

observer fire action to early. before action is initialize observes fired the action

Template
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{input type="text" value=model.name}}
</ul>
<p {{action 'test'}}>test</p>
</script>
ember code
App = Ember.Application.create();
App.Router.map(function () {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return Ember.Object.create({name:'dilip'});
},
actions:{
test:function(){
alert('route test');
console.log('test')
}
}
});
App.IndexController = Ember.Controller.extend({
handleChange:function()
{
console.log('handle')
this.send('test')
}.observes('model.name')
})
action 'test' is already defined in route but its showing below error.This error comes only when i am using observes.
Error while loading route: Error: Nothing handled the action 'test'.
The observer does fires before the route is ready to handle the action. This kinda, sorta makes sense because the controller is "built" as part of the route. I've solved this by setting a property within the routes setupController hook to let the controller know that it's ready for action!
// Route
setupController: function(controller, model) {
this._super(controller, model);
controller.set('routeIsReadyForAction', true);
}
// Controller
routeIsReadyForAction: false,
someObserver: function() {
if (!this.get('routeIsReadyForAction')) {
return;
}
this.send('someRouteAction');
}.observes('someProperty'),
You need to handle the action in the controller, not in the route. Check out this jsbin: http://emberjs.jsbin.com/yubowi/edit?html,js,output
You should define your action within controller.
this.send('test') Looks for actions in this extended class (in this case controller). If u make this.sendAction('test') It goes out and looks for actions but these have to be predefined like so `actionName="actionName". This is mostly used when you have a component that needs to interact with a controller
{{my-component actionName="actionName"}} < sendAction now looks for controllers action

Ember.js Multiple Models for a Modal Dialog Box

I am new to Ember.js, and I am building a web application that is using Ember.js and Ember-Data for its front-end technology. I am trying to understand what would be the best practice for when you might have multiple ember-data bound components on a page that use an independent model.
Here is kind of what I'm trying to do:
https://gist.github.com/redrobot5050/6e775f4c5be221cd3c42
(There's a link on the page to editing it within jsbin this gist. For some reason, I can't get a 'Share' URL off the vanity URL.)
I have a template like so:
<script type="text/x-handlebars" data-template-name="index">
<p>Options for graphics quality: </p>
<ul>
{{#each item in model}}
<li>{{item.setting}}</li>
{{/each}}
</ul>
<p>Currently Selected: {{model.selectedValue}}</p>
<p>Draw Distance Options:</p>
<ul>
{{#each item in dropdown}}
<li>{{item.distance}}
{{/each}}
</ul>
<p>Currently Selected Distance: {{selectedDistance}}
</p>
{{outlet}}
<button {{action 'openModal' 'modal' model}}>Change Settings</button>
</script>
In this template, all the data binds correctly and appears in scope. However, when I attempt to modify it within its modal dialog box, only Quality is bound to its Ember.Select view. I have attempted to force the binding in the IndexController with a controller.set but it does not appear to be working.
My IndexController looks like this:
App.IndexRoute = Ember.Route.extend({
model: function() {
var qualityList = this.store.find('quality');
console.log('qualityList=' + qualityList);
return qualityList;
//return App.Quality.FIXTURES;
},
setupController : function(controller, model) {
controller.set('model', model);
var drawDistanceList = this.store.find('drawDistance');
console.log('distanceList=' + drawDistanceList );
controller.set('dropdown', drawDistanceList);
controller.set('selectedDistance', 1);
//this.controllerFor('modal').set('dropdown', drawDistanceList );
}
});
The JSBin really shows off what I am attempting to do: I want to load/bind each of the drop downs independently from the same controller. The JSBin does not work correctly because I'm not really sure how to do this, just yet. I am posting to stackExchange to see if someone can modify this JSBin and show me what I'm doing wrong, or if someone can point me in the right direction, design-wise on how to solve this problem?
(For example, I think a possible solution could be to create the dropdowns as components, and load the data through their controller or pass it in as properties from the parent controller, but I really want to know what is the "The Ember Way").
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
qualityList: this.store.find('quality'),
drawDistanceList: this.store.find('drawDistance')
});
},
setupController: function(controller, model) {
controller.set('model', model.qualityList);
controller.set('dropdown', model.drawDistanceList);
}
});
Documentation for Ember.RSVP.hash used to be here: http://emberjs.com/api/classes/Ember.RSVP.html#method_hash. I'm not sure why it has disappeared.
For the moment, you can find it at: http://web.archive.org/web/20140718075313/http://emberjs.com/api/classes/Ember.RSVP.html#method_hash

Inheriting singular controller with render helper

I am trying to render a set of tabs for a set of objects (conversations) using the render helper for each. This is not part of a route as it is a persistent part of the interface. I have run into a problem where only the view with the same name as the model gets the intended controller (i.e. the panel contents and not the tab headers).
I have a Chat model, object controller and array controller (deliberately simplified here):
App.Chat = DS.Model.extend({ });
App.ChatsController = Ember.ArrayController.extend({
needs: 'application',
content: Ember.computed.alias('controllers.application.currentChats'),
});
App.ChatController = Ember.ObjectController.extend({ });
The ArrayController needed the needs/content properties because the chats are loaded in the application controller. I used the currentChats name as other routes may load non-current chats.
App.ApplicationController = Ember.Controller.extend({
init: function(){
this.store.find('chat', {"current": true});
this.set('currentChats', this.store.all('chat'));
}
});
I have no difficulty rendering the chat contents with the appropriate controller (into the 'chat' template). However, the chat tabs are given the default ObjectController, and therefore can't fire actions.
<script type="text/x-handlebars" id="application">
<!--application template-->
{{outlet chats}}
</script>
<script type="text/x-handlebars" id="chats">
<div id="chats">
<ul id="chat-tabs">
{{#each}}
{{render 'chatTab' this}}
{{/each}}
</ul>
{{#each}}
{{render 'chat' this}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" id="chatTab">
<!--tab template-->
</script>
<script type="text/x-handlebars" id="chat">
<!--chat template-->
</script>
The application router is as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function(){ },
renderTemplate: function(){
this.render('application', { });
this.render('chats', {
into: 'application',
outlet: 'chats',
controller: 'chats'
});
}
});
This seems to come solely down to naming of the templates. The template called 'chat' inherits the correct controller, but chatTab doesn't despite receiving a chat as the model. Is there any way to force the view to inherit the correct controller? Or am I going about this in an idiosyncratic way.
Many thanks for your help to this Ember novice.
Andrew
It goes solely off the name provided to the render. The easiest way is to just create the other controller and extend the chat controller.
App.ChatTabController = App.ChatController.extend();

Evaluating controller property everytime the template is rendered in Ember

I have a template called sample and I am changing the property show in the controller based on a button press. Now I want the controller to reset the property every time the template is rendered. Currently even if I go to 'next' template and come back, the property show remains true if the button on sample was pressed. I want to change this behaviour and the property show should be false by default. I know I can do this by defining a view and using the didInsertElement hook but is that the only way to do this?? Ember.js website says that Views in Ember.js are typically only created for the following reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
and I am doing none of the above. Here is some sample code:
<script type="text/x-handlebars" data-template-name="sample">
{{#if show}}
Showing stuff
{{/if}}
<button {{action changeShow}}>Change</button>
{{#link-to 'next'}} Next {{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="next">
Hello
{{#link-to 'sample'}}Back{{/link-to}}
</script>
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
you can use didTransition action which will trigger automatically once the transition happened. didTransition action
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
didTransition:function(){
this.controllerFor('sample').set('show',false);
},
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
You can use the renderTemplate hook for the route you're doing this in, and change the controller variable in there.
http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate
I'd do something like this:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
var favController = this.controllerFor('favoritePost');
favController.set("toggle", false)
this._super()
}
});

Ember, Ember-data, and jquery-ui.dialog, "Oh my!"

The task:
Open a form in a lightbox to create a new "event"; the opened form should be bookmarkable.
The road blocks:
There are examples of opening a lightbox using {{action}} tags, but could not find one that opened in its own route.
There are many examples using older versions of ember.js.
There is not a lot of documentation related to ember-data and REST (I know, I know...it isn't "production ready").
The problem:
The fields in the form were not being tied to a backing model so "null" was being posted to my servlet (a Spring controller).
My very first iteration was not too far off from the final outcome (jsfiddle). The thing that finally made it works swapping this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
controller.set("model", model);
},
...
});
...for this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
this.controllerFor("events-new").set("model", model);
},
...
});
The question:
Why does the setupController function need to call controllerFor in order to properly set up the model?
And finally, since I struggled to find a fully-functional example, I wanted to make this accessible (and hopefully discover improvements).
Here's the fiddle: http://jsfiddle.net/6thJ4/1/
Here are a few snippets.
HTML:
<script type="text/x-handlebars">
<div>
<ul>
{{#linkTo "events.new" tagName="li"}}
Add Event
{{/linkTo}}
</ul>
</div>
{{outlet events-new}}
</script>
<script type="text/x-handlebars" data-template-name="events-new">
<form>
<div>
<label>Event Name:</label>
{{view Ember.TextField valueBinding="name"}}
</div>
<div>
<label>Host Name:</label>
{{view Ember.TextField valueBinding="hostName"}}
</div>
</form>
</script>
JavaScript:
...
EP.Router.map(function() {
this.resource("events", function() {
this.route("new");
});
});
EP.EventsNewRoute = Ember.Route.extend({
model : function() {
return EP.Event.createRecord();
},
setupController : function(controller, model) {
//controller.set("model", model); // Doesn't work? Why not?
this.controllerFor("events-new").set("model", model); // What does this do differently?
},
...
});
EP.EventsNewController = Ember.ObjectController.extend({
save : function() {
this.get("content.transaction").commit(); // "content.store" would commit _everything modified_, we only have one element changed, so only "content.transaction" is necessary.
}
});
EP.EventsNewView = Ember.View.extend({
...
});
EP.Event = DS.Model.extend({
name : DS.attr("string"),
hostName : DS.attr("string")
});
Resources:
http://emberjs.com/guides/routing/setting-up-a-controller/
http://emberjs.com/guides/getting-started/toggle-all-todos/ (trying to mimic what I learned, but morph the add-new to a new route)
Writing a LightboxView causes problems / Integrating DOM Manipulating jQuery-Plugins makes actions unusable (lightbox "example")
Dependable views in Ember (another lightbox "example" but doesn't have routes for the lightbox opening)
Why does the setupController function need to call controllerFor in order to properly set up the model?
Ember makes URLs a very integral part of its conventions. This means that the state of your application is represented by the route it is on. You've grokked most of this correctly. But there are couple of subtle nuances, that I will clarify below.
First consider an app with the following URLs,
/posts - shows a list of blog posts.
/posts/1 - shows a single blog post.
And say clicking on a post in the list at /posts takes you to /posts/1.
Given this scenario, there 2 ways a user will get to see the post at /posts/1.
By going to /posts and clicking on the 1st post.
By typing in /posts/1, via bookmarks etc.
In both these cases, the PostRoute for /posts/1 will need the model corresponding to Post id 1.
Consider the direct typing scenario first. To provide a way to lookup the id=1 post model, you would use,
model: function(params) {
return App.Post.find(params.post_id);
}
Your template for post will get the model and it can render using it's properties.
Now consider the second scenario. Clicking on post with id=1 takes you to /posts/1. To do this your template would use linkTo like this.
{{#linkTo 'post' post}} {{post.title}} {{/linkTo}}
Here you are passing in the post model to the linkTo helper. It then serializes the data for the post into a URL, ie:- '/posts/1'. When you click on this link Ember realizes that it needs to render the PostRoute but it already has the post model. So it skips the model hook and directly calls setupController.
The default setupController is setup to simply assign the model on the controller. It's implemented to do something like,
setupController: function(controller, model) {
controller.set('model', model);
}
If you do not need to set custom properties on your controller, you don't need to override it. Note: if you are augmenting it with additional properties you still need to call _super to ensure that the default setupController behaviour executes.
setupController: function(controller, model) {
this._super.apply(this, arguments);
controller.set('customProp', 'foo');
}
One final caveat, If you are using linkTo and the route does not have dynamic segments, then the model hook is still called. This exception makes sense if you consider that you were linking to the /posts route. Then the model hook has to fire else Ember has no data to display the route.
Which brings us to the crux of your question. Nearly there, I promise!
In your example you are using linkTo to get to the EventsNewRoute. Further your EventsNewRoute does not have dynamic segments so Ember does call the model hook. And controller.set("model", model); does work in so much as setting the model on the controller.
The issue is to do with your use of renderTemplate. When you use render or {{render}} helper inside a template, you are effectively getting a different controller to the one you are using. This controller is different from the one you set the model on, hence the bug.
A workaround is to pass the controller in the options, which is why renderTemplate gets this controller as an argument.
renderTemplate: function(controller) {
this.render("events-new", {
outlet : "events-new", controller: controller
});
}
Here's an updated jsfiddle.
Final Note: Unrelated to this question, you are getting the warning,
WARNING: The immediate parent route ('application') did not render into the main outlet and the default 'into' option ('events') may not be expected
For that you need to read this answer. Warning, it's another wall of text! :)