Emberjs Controller initialisation - ember.js

I'm playing with emberjs for a few days now and thought I understand the basics. Now I'm trying to build a real world application and already got stuck with a strange problem.
I want to have an application template that uses the 'view' helper to include some other views. I also want to have some 'global' models that contain information about the environment (e.g. the logged in users account information etc.) that I want to be able to use in different views.
I tried this: http://jsfiddle.net/UzgMd/7/
<script type="text/x-handlebars" data-template-name="header">
<div>Header {{fullName}}</div>
</script>
<script type="text/x-handlebars" data-template-name="sidebar">
<div>Sidebar</div>
</script>
<script type="text/x-handlebars" data-template-name="application">
<div>{{view App.HeaderView}}</div>
<div>{{view App.SidebarView}}</div>
</script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="https://raw.github.com/wycats/handlebars.js/1.0.0-rc.3/dist/handlebars.js"></script>
<script type="text/javascript" src="https://raw.github.com/emberjs/ember.js/release-builds/ember-1.0.0-rc.2.js"></script>
<script type="text/javascript" src="https://raw.github.com/MilkyWayJoe/cdnjs/master/ajax/libs/ember-data.js/0.12.00-latest20130328/ember-data-latest.js"></script>
with the following JS:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
App.AccountInfo = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
App.AccountInfo.FIXTURES = [{
id: 1,
firstName: "John",
lastName: "Doe"
}];
//App.headerController = Ember.ObjectController.extend({}).create(); // works
App.HeaderController = Ember.ObjectController.extend({}); // doesnt work
App.HeaderView = Ember.View.extend({
templateName: 'header',
//controller: App.headerController // works
controller: App.HeaderController.create() // doesnt work
});
App.SidebarView = Ember.View.extend({
templateName: 'sidebar'
});
App.ApplicationController = Ember.Controller.extend({});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return App.AccountInfo.find(1);
},
setupController: function(controller, model) {
this._super(controller, model);
//App.headerController.set('model', model); // works
this.controllerFor('header').set('content', model); // doesnt work
}
});
but It doesn't work because the controllerFor()-method in line 47 returns a new instance of the HeaderController instead of the instance already created in line 30. If I call controllerFor() again, no new instance is created but the instance created by the call in line 47 is returned - what I find a bit confusing.
However, if I do it like this: http://jsfiddle.net/UzgMd/8/
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
App.AccountInfo = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
App.AccountInfo.FIXTURES = [{
id: 1,
firstName: "John",
lastName: "Doe"
}];
App.headerController = Ember.ObjectController.extend({}).create(); // works
//App.HeaderController = Ember.ObjectController.extend({}); // doesnt work
App.HeaderView = Ember.View.extend({
templateName: 'header',
controller: App.headerController // works
//controller: App.HeaderController.create() // doesnt work
});
App.SidebarView = Ember.View.extend({
templateName: 'sidebar'
});
App.ApplicationController = Ember.Controller.extend({});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return App.AccountInfo.find(1);
},
setupController: function(controller, model) {
this._super(controller, model);
App.headerController.set('model', model); // works
//this.controllerFor('header').set('content', model); // doesnt work
}
});
using a singleton controller instance in that I inject the model, everything works fine.
Now my questions:
Is the controllerFor() creating a new instance a bug? If not, why does it behave like this or what am I doing wrong?
Is the way I worked around in my second fiddle a proper way to do it, or is there a better way?

I want to have an application template that uses the 'view' helper to include some other views.
The {{view}} helper is appropriate if all you want is a simple Ember.View with no controller. For example something like Ember.InputField. For an application's header and sidebar you'll typically want a view backed by a controller. In that case use the {{render}} helper instead. Have a look at this blog post for more details.
I also want to have some 'global' models that contain information about the environment (e.g. the logged in users account information etc.) that I want to be able to use in different views.
Makes sense. The right place to store that information is in a controller. That might be a property of ApplicationController or in a separate controller like currentUserController. Since a view should only access properties of it's own controller, you can use use controller's needs array to make it accessible. See managing dependencies between controllers
Is the controllerFor() creating a new instance a bug?
No, it is not a bug.
If not, why does it behave like this or what am I doing wrong?
Issue is that you are trying to manually create instances of headerController. In general if you are calling create() on a controller then something is wrong. Ember expects to manage controller instances itself, and will register them on the container so they can be found later. Since your instance was not registered, ember created a new one when you called controllerFor().
Is the way I worked around in my second fiddle a proper way to do it, or is there a better way?
Definitely not the proper way. In this fiddle you are using a global variable to accomplish what ember should be doing for you automatically. That means there is a lot of code that is not necessary, and also there is a lot of coupling between objects which makes things hard to test. If you go with the {{render}} approach it would look something like this:
<script type="text/x-handlebars" id="application">
{{render header}}
{{render sidebar}}
{{outlet}}
</script>
<script type="text/x-handlebars" id="header">HEADER {{content}}<hr/></script>
<script type="text/x-handlebars" id="sidebar">SIDEBAR {{content}}<hr/></script>
App = Ember.Application.create({});
App.HeaderController = Ember.ObjectController.extend();
App.SidebarController = Ember.ObjectController.extend();
App.IndexRoute = Ember.Route.extend({
setupController: function() {
this.controllerFor('header').set('content', 'hi');
this.controllerFor('sidebar').set('content', 'bye');
}
});
Using {{render}} we don't need to define HeaderView or SidebarView since ember will generate them automatically. Each will be bound to a singleton instance of their controller, and those controllers can be accessed from your routes via controllerFor.
JSFiddle here: http://jsfiddle.net/NQKvy/1/

Related

Ember How can i render a child route into his parent?

I'm new with Ember and trying to render the content into the category template. So, if I clicked on the category, it will show me details and list content in the category template. I have tested something, but it didn't work. I have searched for this problem, but I can't solve it. I hope you can help me.
best regards
app.js
TableNotices.Router.map(function() {
this.resource('tableNotices', { path: '/' }, function(){
this.resource('category', {path: ':id'}, function(){
this.route('contents');
this.resource('content', {path: '/content/:id'});
});
});
});
TableNotices.ContentsRoute = Ember.Route.extend({
model: function() {
return this.modelFor('category').get('contents');
}
});
TableNotices.Content = DS.Model.extend({
content: DS.attr('string'),
contentType: DS.attr('string'),
orderPos: DS.attr('number'),
category: DS.belongsTo('category')
});
TableNotices.Category = DS.Model.extend({
name: DS.attr('string'),
parent: DS.attr('number'),
picture: DS.attr('string'),
contents: DS.hasMany('content', {async:true})
});
index.html:
<script type="text/x-handlebars" data-template-name="category">
{{name}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="contents">
foobar
</script>
jsbin
In the route you can specify how to render the templates with renderTemplate.
TableNotices.ContentsRoute = Ember.Route.extend({
model: function() {
return this.modelFor('category').get('contents');
}
renderTemplate: function() {
this.render('contents', { // the template to render
into: 'category', // the template to render into
outlet: 'category', // the name of the outlet in that template
controller: 'contents' // the controller to use for the template
});
}
});
<script type="text/x-handlebars" data-template-name="category">
{{name}}
{{outlet "contents"}}
</script>
Although Aaron Renoir's answer will work, the best way to think of this is that because your routes are nested, your templates need to be nested as well. Ember will render each of the nested templates into the parent template when these nested routes are activated. This gives you a nice way to delegate different parts of your page to various templates which helps to keep the content organized.
Each of your "parent" templates will need an {{outlet}} for the child templates to be rendered into.

itemControllers and custom Views

I am working on a small app that animates different iframes in and out of view. Right now I am just trying to start simple with two iframes for my data.
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{current: true, url:'http://www.flickr.com'},
{url:'http://bing.com'}
];
}
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'iframe',
now: function() {
return this.filterBy('isCurrent').get('firstObject');
}.property('#each.isCurrent')
});
App.IframeController = Ember.ObjectController.extend({
isCurrent: Ember.computed.alias('current')
});
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'isCurrent'],
templateName: 'iframe'
});
And my templates:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "next"}}>Next</button>
{{#each}}
{{view "iframe"}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="iframe">
<iframe {{bind-attr src=url}}></iframe>
</script>
Why can't my IframeView access my isCurrent property of my itemController? I am also unsure if this is the right way to do this, or if there is an easier way to have my each use my IframeView
Here is a jsbin: http://emberjs.jsbin.com/vagewavu/4/edit
isCurrent lives on the controller. The controller property will be in scope in the view, but the properties under the controller aren't in scope of the view. You just need to reference controller first.
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'controller.isCurrent'],
templateName: 'iframe'
});
Additionally your next action isn't doing anything, just creating some local variables, maybe you weren't finished implementing it. Either way I tossed together an implementation.
next: function() {
var now = this.get('now'),
nowIdx = this.indexOf(now),
nextIdx = (nowIdx + 1) % this.get('length'),
next = this.objectAt(nextIdx);
now.toggleProperty('current');
next.toggleProperty('current');
}
http://emberjs.jsbin.com/vagewavu/10/edit

Filling Ember selection with data received from server

I would like to receive json data from server and then generate html selection tag in Ember. This is my handlebars template:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="rankings">
{{view Ember.Select contentBinding="countries" optionLabelPath="countries.name" optionValuePath="countries.path"}}
</script>
and my Ember trunk
App.ApplicationAdapter = DS.RESTAdapter;
App.Router.map(function() {
this.route('rankings')
});
App.RankingsRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('countries', this.store.findAll('country'));
}
});
App.RankingsController = Ember.Controller.extend({
countries: []
});
App.Country = DS.Model.extend({
path: DS.attr('string'),
name: DS.attr('string'),
}
It is not working fine. I am not having any errors in console and template is not rendering with this route and controller. What can be wrong?
Ember.Select looks for data on your controller's model. You have the countries array set up outside of your content, so it will not be able to view the data.
Personally, I would do it in the following way, though there are a couple of different approaches you can take to get similar results:
App.ApplicationAdapter = DS.RESTAdapter;
App.Router.map(function() {
this.route('rankings')
});
App.RankingsRoute = Ember.Route.extend({
model: function() {
return {countries : this.store.findAll('country')};
}
});
App.RankingsController = Ember.Controller.extend({
});
App.Country = DS.Model.extend({
path: DS.attr('string'),
name: DS.attr('string'),
}

Binding model to template with emberjs

I am going to bind model to template with emberjs
<script type="text/x-handlebars" id="dashboard">
<div>
<span>this is user list</span>
<div>
{{render userinfo userinfo}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="_userinfo">
{{#each model}}
<span>{{user}}
{{/each}}
</script>
App.Userinfo= DS.Model.extend({
user: DS.attr("string")
});
App.Userinfo.FIXTURES = [
{user:"user1"},
{user:"user2"},
{user:"user3"}
];
App.UserinfoView= Ember.View.extend({
});
App.UserinfoController = Ember.ObjectController.extend({
});
App.Router.map(function() {
this.resource('dashboard', {path: '/dashboard'}, function() {
});
});
App.DashboardRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('dashboard', { // the template to render
controller: 'dashboard' // the controller to use for the template
});
}
});
App.DashboardController = Ember.ObjectController.extend({
});
When i go to /#/dashboard, Dashboard template is loaded.
In here, I have rendered userinfo.
I'd like to bind Userinfo Model to usersinfo template so that I display all users.
Help me, please.
The short: here a working jsbin.
The long: You hade slightly to much unnecessary going on in your code, basically this does the job:
First of all you had no redirect to your dashboard route, since it's your only route (at least as far I can see from your code) we redirect directly to it after entering the index route
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('dashboard');
}
});
I've removed the DashboardController since there is nothing to be done.
Then your DashboardRoute was missing the model hook to provide actually data for your dashboard template
App.DashboardRoute = Ember.Route.extend({
model: function(){
return App.Userinfo.find();
}
});
In the router map you don't need to define a path if the URL is the same name as your template name in your case it is the same dashboard
App.Router.map(function() {
this.resource('dashboard');
});
The userinfo model was correct
App.Userinfo= DS.Model.extend({
user: DS.attr("string")
});
But your fixtures where missing the id
App.Userinfo.FIXTURES = [
{id:1, user:"user1"},
{id:2, user:"user2"},
{id:3, user:"user3"}
];
Moreover you where using the render helper with a partial template _userinfo the correct way to render a partial is this
{{partial userinfo}}
As you can see we don't pass any additional parameters to it because the data will be made available trough your model hook. The partial helper uses the context and the data provided in the template it is rendered into, in your case the dashboard template, therefore the model hook is necessary.
Hope it helps.

Query server for data on bind/observe

I apologize if this has a painfully obvious answer but I am both a JS and Ember noob and I am having trouble finding a solution to what I think is a common scenario. Essentially, I have a multi-page app with html/css/js front end and a java back end with an exposed REST api. I have 1 app.js file that I include in all screens and multiple controllers, some of which only apply to individual screens.
EDIT: Forgot my question. My question is how do I delay the query to my server for my user data until my controller has an observer. Since the controller is present on multiple screens (which dont all need it) I do not want to blindly query on creation of the object since it would be wasteful. For now i have a hacky way of doing it where at the end of my inline script tag of a page I call the populate method. Below is what my code currently looks like.
Section of app.js:
App = Ember.Application.create();
User = Ember.Object.extend({
username: 'empty',
fullname: 'empty user'
});
App.UserDataSource = Ember.Object.extend({
fetchMyUser: function(callback) {
$.get('ncaa/user', function(data) {
callback(User.create({
username: data.username,
fullname: data.fullname}));
});
}
});
App.userDataSource = App.UserDataSource.create();
App.UserController = Ember.Object.extend({
content: null,
populate: function() {
var controller = this;
this.get('dataSource').fetchMyUser(function(data) {
controller.set('content', data);
});
}
});
App.userController = App.UserController.create({
dataSourceBinding: Ember.Binding.oneWay('App.userDataSource')
});
Ember.run.sync();
Section of index.html
<script type="text/x-handlebars">
Welcome, {{App.userController.content.fullname}}
</script>
....other code....
<script type="text/javascript">
$(document).ready(function() {
....other code....
App.userController.populate();
});
</script>
I am pretty sure my first steps will be modifying that handlebars template to extend Ember.View but would like to know what the community believes is the best practice. Also, is it wrong for me to try and put all of this in one app.js file? It would be ok to query on creation of my controller if it was only imported on screens that required the user to display.
The answer for my question did end up being in the Ember.View. Essentially what I do is override the init function of my view which adds the call to populate the necessary controller with data. The view is that instantiated via the handlebars template so no more unnecessary calls or hacky work around. Important changes below.
Index.html:
<script type="text/x-handlebars">
{{#view App.UserNameView }}
Welcome, {{fullName}}
{{/view}}
</script>
App.js:
App.UserNameView = Em.View.extend({
init: function() {
this._super();
App.userController.populate();
},
fullNameBinding: 'App.userController.content.fullname',
userNameBinding: 'App.userController.content.username'
});