How to pass Model route name item to linkTo in template in ember.js - ember.js

How do I pass a route name to a {{linkTo}} dynamically?
For example, given this code:
App.Router.map(function() {
this.resource('anon', {path: '/main'},
function() {
this.route('home', {path:'/home'});
this.route('about', { path: '/about' });
this.route('contact', { path: '/contact' });
});
});
App.NavController = Ember.ArrayController.extend({
selectedNav:'',
setNav:function(value){
var nav = App.Nav.find(value);
var items = nav.get('navItems');
this.set('content', items);
}
});
these templates:
<script type="text/x-handlebars" data-template-name="nav">
<ul class="nav">
{{#each in controller}}
{{ partial "basicNav"}}
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="_basicNav">
<li>{{#linkTo navItemPath}}{{navItemName}}{{/linkTo}}</li>
</script>
and these models with the following fixture data:
App.Nav = DS.Model.extend({
navItems:DS.hasMany('App.NavItem'),
name:DS.attr('string')
});
App.NavItem = DS.Model.extend({
nav:DS.belongsTo('App.Nav'),
navItemName:DS.attr('string'),
navItemPath:DS.attr('string')
});
App.Nav.FIXTURES = [
{
id: 10,
name: 'Anon',
navItems: [100,200,300]
}
];
App.NavItem.FIXTURES = [
{
id:100,
nav:10,
navItemName:'Home',
navItemPath:'anon.home'
},
{
id:200,
nav:10,
navItemName:'Contact',
navItemPath:'anon.contact'
},
{
id:300,
nav:10,
navItemName:'About',
navItemPath:'anon.about'
}
];
How do I pass navItemPath to the {{linkTo}} helper? In this code snippet:
{{#linkTo navItemPath}}{{navItemName}}{{/linkTo}}
ember complains that it can't find the "navItemPath" route, like it's looking for it literally. If I replace that with a valid literal route like:
{{#linkTo 'anon.home'}}{{navItemName}}{{/linkTo}}
ember will render the linkTo with the navItemName as expected, so I know the controller is passing it the right data, but of course all the routes are goofy. Am I missing something obvious?

You can't do that with LinkTo helper, you need to bind the href of your link to navItemPath using bindAttr
<a {{bindAttr href="navItemPath"}}>{{navItemName}}</a>
Make sure the the logic rending navItemPath's value takes into account the location API

Related

How to dynamically generate clickable links in ember using handlebars

I am trying to generate click able links using emberjs framework. I have the model setup correctly and I have the following handlebar template:
<script type="text/x-handlebars" data-template-name="index" >
{{#each name in model.mymodules }}
{{#link-to name 'home' }}{{name}}{{/link-to}}
{{/each
</script>
The idea is to call modulename/home on each link.
For ex: say I have 3 modules: "abc", "xyz", "123"
I want three links:
abc <a href="/abc/home">, xyz <a href="/xyz/home">, 123 <a href="/123/home">
What controller/route do I need to define for this to work.
jsfiddle:
http://jsfiddle.net/spkRa/2/
You need to make use of ember resources for dealing with this problem
Read http://emberjs.com/guides/routing/defining-your-routes/
Example of application code should be something like this. JSfidle http://jsfiddle.net/NQKvy/291/
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true,
LOG_VIEW_LOOKUPS: true
});
App.Router.map(function() {
this.resource('modules', { path: '/modules' }, function() {
this.route('home', {path: ':module_name/home'});
});
});
App.IndexRoute = Ember.Route.extend({
model:function(){
return App.Modules;
}
});
App.ModulesHomeRoute = Ember.Route.extend({
model: function(params) {
//returns an object from an ember array based on the property value
return App.Module.findProperty('name',params.module_name);
},
serialize: function(model, params) {
//updates the url with the param value
return { module_name: model.get('name') };
}
});
App.Modules = Ember.A([
Ember.Object.create({name:'aaa'}),
Ember.Object.create({name:'bbb'}),
Ember.Object.create({name:'ccc'})
]);
And hadlebars code
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each}}
<li>{{name}}</li>
<li>{{#link-to 'modules.home' this}}{{name}}{{/link-to}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="modules/home">
This is the home of the module {{name}}
</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.

createRecord called w/o params does not add object to collection

Using:
ember-1.0.0-pre.4.js
ember-data.js REVISION:11
handlebars-1.0.rc.2.js
Please have a look at this jsFiddle illustrating the described problem.
I have a list of items that are displayed in a template. The template contain a linkTo helper that let's the controller add an item to the collection and is shown as a text input on the page.
Adding the item to the collection is done by the controller:
App.TodoItem = DS.Model.extend({
title: DS.attr('string', { defaultValue: "unknown" })
});
App.Router.map(function () {
this.resource('todo_items')
});
App.TodoItemsRoute = Em.Route.extend({
model: function () {
return App.TodoItem.find();
}
});
App.TodoItemsController = Em.ArrayController.extend({
addTodoItem: function () {
App.TodoItem.createRecord();
}
});
If I want the new item to be shown is the list, I have to pass params to createRecord, otherwise the item is not visible. The same behaviour can be reproduced by using Chrome's inspector and then the item can be made visible as follows:
// Open the jsFiddle http://jsfiddle.net/bazzel/BkFYd/ and select 'result(fiddle.jshell.net) in the inspector, then:
var item = App.TodoItem.createRecord();
// Nothing visible yet.
item.set('title', 'Whatever');
// Now the text input appear with the title as its value.
Is this expected behaviour and if so, what am I missing here?
I took time to redo your example the way i feel things should be done properly with Emberjs. You should rather make sure of transaction and properly define your views and then all your issues get taken care of. So here's how i think you should do this
Define a view for the textfield to capture the value being entered or
just bind it to the model property.
Listing items and adding a new item to the list should be done in two different views and should not be mixed together
<script type="text/x-handlebars">
{{outlet}}
<div>
{{outlet 'addItem'}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="todo_items">
{{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
<ul>
{{#each item in controller}}
<li>
{{#unless item.isNew}}
{{item.title}}
{{/unless}}
</li>
{{/each}}
</ul>
</script>
Define different states for listing items and adding a new one
To benefit from automatic binding of your text field value to the
model property, you need to associate an ObjectController to the TodoItemsNew route
Finally, make use of transaction to create and commit records to the store
window.App = Em.Application.create();
App.TodoItem = DS.Model.extend({
title: DS.attr('string')
});
App.TodoItem.FIXTURES = [{
id: 1,
title: 'Lorem'
}, {
id: 2,
title: 'Ipsum'
}];
App.store = DS.Store.create({
revision: 11,
adapter: DS.FixtureAdapter.create()
});
App.Router.map(function () {
this.resource('todo_items',function(){
this.route('new');
})
});
App.IndexRoute = Em.Route.extend({
redirect: function () {
this.transitionTo('todo_items');
}
});
App.TodoItemsRoute = Em.Route.extend({
model: function () {
return App.TodoItem.find();
}
});
App.TodoItemsNewRoute = Em.Route.extend({
transaction: App.store.transaction(),
setupController:function(controller) {
console.info(controller.toString());
controller.set('content',this.transaction.createRecord(App.TodoItem));
},
renderTemplate: function() {
this.render('addItem',{
into:'application',
outlet:'addItem',
})
},
events: {
addItem: function() {
this.transaction.commit();
this.transitionTo('todo_items');
}
}
});
App.TodoItemsController = Em.ArrayController.extend();
App.TodoItemsNewController = Em.ObjectController.extend();
App.TextField = Ember.TextField.extend({
insertNewline: function () {
this.get('controller').send('addItem')
}
});
Here' is a working version of the example on jsfiddle. Hopefully, i helped with this example clarify some of your issues.
Thank you Ken for answering my question. It indeed feels like a more proper of way of doing this in Ember. However, I still think it's difficult to get the hang of which objects are accessible from where...
Your example inspired me to do a rewrite of my code. I also made some changes to your approach:
I'm not sure if it's the best practice, my I don't create a store instance. Instead I define a Store class.
The content for the TodoItemsNewController is set by calling the model property on the corresponding route.
renderTemplate in the TodoItemsNewRoute only needs the outlet key.
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="todo_items">
{{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
<ul>
{{outlet "addItem"}}
{{#each controller}}
<li>
{{#unless isNew}}
{{title}}
{{/unless}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="todo_items/new">
{{view Ember.TextField valueBinding="title" placeholder="Enter title"}}
window.App = Em.Application.create();
App.TodoItem = DS.Model.extend({
title: DS.attr('string', {
defaultValue: "unknown"
})
});
App.TodoItem.FIXTURES = [{
id: 1,
title: 'Lorem'
}, {
id: 2,
title: 'Ipsum'
}];
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.FixtureAdapter.create()
});
App.Router.map(function() {
this.resource('todo_items', function() {
this.route('new');
});
});
App.IndexRoute = Em.Route.extend({
redirect: function() {
this.transitionTo('todo_items');
}
});
App.TodoItemsRoute = Em.Route.extend({
model: function() {
return App.TodoItem.find();
}
});
App.TodoItemsNewRoute = Em.Route.extend({
model: function() {
return App.TodoItem.createRecord();
},
renderTemplate: function() {
this.render({
outlet: 'addItem'
});
}
});
App.TodoItemsNewView = Em.View.extend({
tagName: 'li'
});
The updated example is on jsFiddle.
Any reviews are welcome.

How to render a stream view like in twitter of facebook with emberjs arraycontroller?

To render a content of an array with emberjs we usually do the following
<ul>
{{#each controller}}
<li>{{name}} by {{artist}}</li>
{{/each}}
</ul>
How to make a live stream view like we have with twitter (of facebook) where a new stream is added on the top of the streams list ?
On the controller you can set the sortProperties see here to specify on which property the array should be sorted and you can set sortAscending (which is a boolean) to specify which direction the array should be sorted.
When you change the array the view will automatically update.
see this fiddle: http://jsfiddle.net/ZnMFK/2/
or this fiddle: http://jsfiddle.net/KfzFE/ to show the DOM gets updated when the array is changed.
HTML:
<script type="text/x-handlebars" data-template-name="index">
<div class="patient-view extended">
{{#each controller}}
<p>Name: {{name}}</p>
{{/each}}
</div>
</script>
App:
window.App = Em.Application.create();
App.Patient = Em.Object.extend({
order: undefined,
name: undefined
});
App.IndexView = Em.View.extend({
click: function() {
this.get('controller')
.set('sortAscending', !this.get('controller').get('sortAscending'));
}
});
App.IndexController = Em.ArrayController.extend({
sortProperties: ['order'],
sortAscending: false
});
App.IndexRoute = Em.Route.extend({
model: function() {
return Em.A([App.Patient.create({
name: "Bert",
order: 2
}), App.Patient.create({
name: "Ernie",
order: 1
})]);
}
});

{{#each loop}} not working. What would be the right way to get it going

I am following an example at "emberjs.com" which isn't going too well. I have a "GuestController" and "GuestView" within my application. I would like to use the "{{#view}} & {{#each}} to output an object called "guests" from the "GuestView". I am following this online example:
http://emberjs.com/documentation/#toc_displaying-a-list-of-items
fiddle: http://jsfiddle.net/exciter/MjA5A/8/
Here is the code:
APP CODE:
$(function(){
App = Ember.Application.create({
ready: function(){
//alert("APP INIT");
}
});
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName: "application",
classNames: ['']
});
App.GuestController = Ember.Controller.extend();
App.GuestView = Ember.View.extend({
guests: [{name:"The Doctor" },
{name:"The Scientist" },
{name:"The Maestro"}]
});
App.initialize();
});
HTML:
<script type="text/x-handlebars" data-template-name="application">
{{#each App.GuestController}}
{{#view App.GuestView}}
{{guests}}
{{/view}}
{{/each}}
</script>
First of all, we use {{each}} block helper to iterate over an array of items, now when you say {{#each GuestController}} the controller should be of type Ember.ArrayController, and the {{#each GuestController}} looks for the content property inside the GuestController which will be used to iterate over, As per the example I think this is what you are trying to implement...Instead if you want to iterate over an Array inside a view check this