Ember.js :: Cannot render ember.js models in view - ember.js

If anyone can put me out of my misery with this I would greatly appreciate it, drving me mad and I know it's gonna be something stupidly simple.
I have an array:
Data
var test = [{"name":"Kober Ltd","town":"Bradford","type":"Z1CA","number":3650645629},
{"name":"Branston Ltd.","town":"Lincoln","type":"Z1CA","number":3650645630}]
and I want to render this info as child elements inside a collectionView:
collectionView
App.ThreeView = Ember.CollectionView.extend({
itemViewClass: Ember.View.extend({
click: function(){
alert('hello')
},
classNames: ['element','foobar'],
templateName: 'foo'
})
})
and here is my controller:
controller
App.ThreeController = Ember.ArrayController.extend({
content: [],
init: function(){
var me = this;
$.each(test,function(k,v){
var t = App.ThreeModel.create({
name : v.name,
town: v.town,
type: v.type,
number: v.number
})
me.addObject( t )
})
console.log( me.content )
}
})
Templates:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="three">
</script>
<script type="text/x-handlebars" data-template-name="foo">
<div class="symbol"> {{ view.content.type }} </div>
<div class="number"> {{ view.content.number }} </div>
<div class="name"> {{ view.content.name }} </div>
<div class="town"> {{ view.content.town }} </div>
</script>
I am using the latest Ember so...V2 router that syncs up all the parts with the 'Three' name. Every will work if I put the array directly into the view:
App.ThreeView = Ember.CollectionView.extend({
content: test, // manually added a pure array into view content
itemViewClass: Ember.View.extend({
click: function(){
alert('hello')
},
classNames: ['element','foobar'],
templateName: 'foo'
})
})
But when I try and do this 'properly', using Ember.js Objects, I get no rendered views ( aside from an empty application view ).
I have tried work arounds, like adding a 'contentBinding' from the view to the controller just to see if I can force a connection but still nothing. It is important that I render the view through the container as I am using Three.js to pick up on the rendered content and manipulate further.
So, to summarise: I can render pure arrays in view, but nothing passed from controller. Incidentally, the controller is definitely being instituted as I can console log its contents on init. If i change the view name, the controller is not instantiated so I know the namespacing is working.
thanks in advance!

I'm not sure to embrace the whole problem, but for now, when you define your controller, in the init() function, first don't forget to call this._super() (it will go through the class hierarchy and call the constructors). Maybe that's just the missing thing.
Edit: it seems like with the new router, defining a view as a CollectionView does not work.
so I replaced it with a normal Ember.View, and use an {each} helper in the template.
<script type="text/x-handlebars" data-template-name="three">
{{each controller itemViewClass="App.FooView" tagName="ul"}}
</script>
here is a minimal working fiddle: http://jsfiddle.net/Sly7/qCdAY/14/
EDIT 2:
By re-reading the question, and seeing you try to bind the CollectionView content's property to controller, I tried it, because it just work fine :)
http://jsfiddle.net/Sly7/qCdAY/15/

Related

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();

Ember - how to display a list of checkbox related to an array using CollectionView?

I have an application with items.
I would like to display checkboxes for a list of values in an item.
I use a Checkbox to do this:
App.DisplayedValueCheckbox = Em.Checkbox.extend({
itemValuesBinding: 'parentView.item.values',
checked: function () {
return true; //Default value is always true
}.property('content', 'itemValues.#each'),
click: function (evt) {
//update graph
}
});
And try to display it into a collection view:
App.DisplayedValuesView = Em.CollectionView.extend({
contentBinding: 'App.Item.values'
,itemBinding: 'App.Item'
, tagName: 'ul'
, itemViewClass: Em.View.extend({
itemBinding: 'parentView.item'
, templatename: 'displayed-values'
})
});
which is part of a parent view:
<script type="text/x-handlebars" id="item">
<h1>{{name}}</h1>
<hr><p>{{desc}}</p>
<div>
<div>
{{#each item in values}}
<li>{{item}}</li>
{{/each}}
</div>
<div>{{view App.DisplayedValuesView}}</div>
</div>
</script>
But the view does not display the checkboxes and there is an error:
Unable to find view at path 'App.DisplayedValuesView'
Edit: this error was corrected thank to #sly7_7's answer
Here is the jsFiddle of this problem
I know I miss something, but what ?
Something in the bindings (I am not sure how they work) ?
Should I write a specific Route (App.ItemRoute = ...) ?
Defining the checkbox view ? The template ?
The mistake is that your declare your App as a local var. Ember/Handlebars helpers needs global when you want to reference a view from the application namespace.
So just remove the 'var' before App :)

Triggering the display of a View from within a template

In my application I display a list of accounts like so:
<script type="text/x-handlebars" data-template-name="accounts">
{{#each account in controller}}
{{#linkTo "account" account class="item-account"}}
<div>
<p>{{account.name}}</p>
<p>#{{account.username}}</p>
<i class="settings" {{ action "openPanel" account }}></i>
</div>
{{/linkTo}}
{{/each}}
</script>
Each account has a button which allows users to open a settings panel containing settings just for that account. as you can see in this quick screencast:
http://screencast.com/t/tDlyMud7Yb7e
I'm currently triggering the opening of the panel from within a method located on the AccountsController:
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
console.log('trigger the panel');
}
});
But I feel that it's more appropriate to open the panel from within a View that I've defined for this purpose. This would give me access to the View so that I can perform manipulations on the DOM contained within it.
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<i class="icon-cancel"></i>close
<h3>Account Settings</h3>
Disconnect Account
</div>
</div>
</script>
The problem I'm encountering is that I don't see how I can trigger a method on the Social.MainPanelView from the context of the AccountsController. Is there a better solution?
UPDATE 1
I've worked up a Fiddle to illustrate what I'm talking about:
http://jsfiddle.net/UCN6m/
You can see that when you click the button it calls the showPanel method found on App.IndexController. But I want to be able to call the showPanel method found on App.SomeView instead.
Update:
Approach One:
Simplest of all
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
/* we can get the instance of a view, given it's id using Ember.View.views Hash
once we get the view instance we can call the required method as follows
*/
Ember.View.views['panel-account-settings'].openPanel();
}
});
Fiddle
Approach Two:(Associating a controller, Much Cleaner)
Using the Handlebars render helper: what this helper does is it associates a controller to the view to be displayed, so that we can handle all our logic related to the view in this controller, The difference is
{{partial "myPartial"}}
just renders the view, while
{{render "myPartial"}}
associates App.MyPartialController for the rendered view besides rendering the view, Fiddle
now you can update your code as follows
application.handlebars(The place you want to render the view)
{{render "mainPanel"}}
accounts controller
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
this.controllerFor("mainPanel").openPanel();
}
});
main panel view
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed']
});
main panel controller
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
Approach Three:
This one is the manual way of accomplishing Approach Two
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
controllerBinding: 'Social.MainPanelController',
classNames: ['panel', 'closed'],
templateName: 'mainPanel'
});
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
use this.controllerFor("mainPanel").openPanel()
You need to use the action helper rather than directly coding the links. The action helper targets the controller by default, but you can change it to target the view instead:
<a {{action openPanel target="view"}}></a>
Your second link should be a linkTo a route, since you are specifying a link to another resource. The whole snippet, revised:
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<a {{action openPanel target="view"} class="button button-close"><i class="icon-cancel"></a></i>
<h3>Account Settings</h3>
{{#linkTo "connections"}}Disconnect Account{{/linkTo}}
</div>
</div>
</script>

Ember.js: replacing simple linkTo helper with a view

I've got an app with basic functionality built out. I'm not going through and adding additional features. In this case I need to convert a simple button, currently using linkTo, to a View. Problem is that I'm not sure how to convert one to the other and still keep the link intact.
How do I do this conversion? Here's the code I have now:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#linkTo "account" account}}
<img {{bindAttr src="account.icon"}} />
{{/linkTo}}
{{/each}}
</script>
and here's the code I'm going to have:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#view "Social.AccountButtonView"}}
<img {{bindAttr src="account.icon"}} />
{{/view}}
{{/each}}
</script>
Social.AccountButtonView = Ember.View.extend({
tagName: 'a',
classNames: ['item-account'],
click: function(){
// do something
}
});
I would assume that I'd be building on top of the click handler in the View, but I'm not sure how to pass the reference to item being iterated over, nor how to reference the correct route within the View.
Assistance please?
Update 1
The first version renders an href attribute with a value of #/accounts/4 based on the Router I have set up:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'});
});
});
When I convert the current code to a view, how do I mimic the functionality that linkTo provides?
You can define a property binding for account in your handlebars template.
This binding works like this:
<script type="text/x-handlebars">
<h1>App</h1>
{{#each item in controller}}
{{#view App.AccountView accountBinding="item"}}
<a {{bindAttr href="view.account.url"}} target="_blank">
{{view.account.name}}
</a>
{{/view}}
{{/each}}
</script>
Note that I added accountBinding, so the general rule is propertyName and Binding as a suffix. And remember that when you add a property to a view, you will not be able to access it directly, instead you will have to access it with view.propertyName as shown above.
Just keep in mind that you must have a View class when using the {{view}} helper:
window.App = Em.Application.create();
App.AccountView = Em.View.extend(); // this must exist
App.ApplicationRoute = Em.Route.extend({
model: function() {
return [
{id: 1, name: 'Ember.js', url: 'http://emberjs.com'},
{id: 2, name: 'Toronto Ember.js', url: 'http://torontoemberjs.com'},
{id: 3, name: 'JS Fiddle', url: 'http://jsfiddle.com'}];
}
})
Working fiddle: http://jsfiddle.net/schawaska/PFxHx/
In Response to Update 1:
I found myself in a similar scenario, and ended up creating a child view to mimic the {{linkTo}} helper. I don't really know/think it's the best implementation tho.
You can see my previous code here: http://jsfiddle.net/schawaska/SqhJB/
At that time I had created a child view within the ApplicationView:
App.ApplicationView = Em.View.extend({
templateName: 'application',
NavbarView: Em.View.extend({
init: function() {
this._super();
this.set('controller', this.get('parentView.controller').controllerFor('navbar'))
},
selectedRouteName: 'home',
gotoRoute: function(e) {
this.set('selectedRouteName', e.routeName);
this.get('controller.target.router').transitionTo(e.routePath);
},
templateName: 'navbar',
MenuItemView: Em.View.extend({
templateName:'menu-item',
tagName: 'li',
classNameBindings: 'IsActive:active'.w(),
IsActive: function() {
return this.get('item.routeName') === this.get('parentView.selectedRouteName');
}.property('item', 'parentView.selectedRouteName')
})
})
});
and my Handlebars looks like this:
<script type="text/x-handlebars" data-template-name="menu-item">
<a {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a>
</script>
<script type="text/x-handlebars" data-template-name="navbar">
<ul class="left">
{{#each item in controller}}
{{view view.MenuItemView itemBinding="item"}}
{{/each}}
</ul>
</script>
I'm sorry I can't give you a better answer. This is what I could come up with at the time and haven't touched it ever since. Like I said, I don't think this is the way to handle it. If you are willing to take a look into the {{linkTo}} helper source code, you'll see a modular and elegant implementation that could be the base of your own implementation. I guess the part you're looking for is the href property which is being defined like so:
var LinkView = Em.View.extend({
...
attributeBindings: ['href', 'title'],
...
href: Ember.computed(function() {
var router = this.get('router');
return router.generate.apply(router, args(this, router));
})
...
});
So I guess, from there you can understand how it works and implement something on your own. Let me know if that helps.

Binding value from textfield when pressing button

I have the following example (see below) to work with Ember.js, and everything works alright as far as I enter something in the textfield and press enter. But how can I have the same result when I press the button? How can I bind the value of the textfield when clicking the button? Do I have work with a view?
Thanks in advance!
<script type="text/x-handlebars">
{{view App.TextField}}
{{#view Ember.Button target="App.peopleController" action="addPerson"}}
Add Person
{{/view}}
<ul id='todo-list'>
{{#each App.peopleController }}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
<script>
App = Em.Application.create();
App.peopleController = Em.ArrayController.create({
content: [{name: "Tom"}, {name: "Mike"}],
addPerson: function(name) {
this.unshiftObject(App.Person.create({name: name}));
}
});
App.Person = Em.Object.extend({
name: null
});
App.TextField = Em.TextField.extend({
insertNewline: function() {
App.peopleController.addPerson(this.get("value"));
this.set("value", "");
}
});
</script>
This is actually a little tricky to accomplish, but I've reworked your example in a jsFiddle: http://jsfiddle.net/ebryn/vdmrA/
I would advise against hardcoding references to controllers in your view subclasses. You can't reuse those view components elsewhere if you do that.
I think in your code for the Ember Button is just going to call the addPerson function without giving it the name parameter that it expects. You might have to write a separate view for that button in order to get the value of the input field to pass to the addPerson function.
App.MyButton = Em.Button.extend({
click: function(){
var value = App.TextField.get('value')
// etc
}