How to dynamically generate clickable links in ember using handlebars - ember.js

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>

Related

How do I add Projects parent to Ember-CLI TodoMVC?

I am working on a todo type project using Ember-CLI. I used as a starting point the nifty todoMVC project, but built with Ember-CLI using this guide:
http://blaketv.com/2014/10/03/ember-cli-todo-mvc-tutorial-0-0-47//
My question is, how would I go about adding projects at the parent level. So we would have a master-detail type interface and in the sidebar we would have projects and you could CRUD project names, and then when you click on a project name, you see the todos in the detail pane.
I have gotten far enough defining the hasMany relationships to the models, but I cannot figure out if I need multiple {{outlets}} It is very difficult to get everything on the same page and working.
Here is my model for project:
export default DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean'),
description: DS.attr('string'),
todos: DS.hasMany('todo', {async: true})
});
and model for todos:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
and the main Router:
Router.map(function() {
this.resource('projects', function () {
this.route('new');
this.resource('project', { path: ':id' }, function () {
this.route('todos');
});
});
});
Project Route:
export default Ember.Route.extend({
model: function(params) {
return this.store.find('project', params.id);
}
});
Index Route:
export default Ember.Route.extend({
model: function() {
return this.store.find('project');
}
});
Todos Route:
export default Ember.Route.extend({
model: function() {
return this.modelFor('todos');
}
});
So for project.hbs this is where it gets tricky. I create the sidebar with bootsrap and then this outlet shows the todos....
<div class="projects-column col-md-3">
<div id="inbox-header"><span class="glyphicon glyphicon-inbox"></span> Inbox <span class="badge">42</span></div>
<div id="projects-header"><span class="glyphicon glyphicon-list-alt"></span> Projects</div>
<div id="forecast-header"><span class="glyphicon glyphicon-calendar"></span> Forecast</div>
<div id="log-header"><span class="glyphicon glyphicon-book"></span> Sessions Log</div>
</div>
<div>{{outlet}}</div>
Index.hbs:
<ul>
{{#each model}}
<li>{{link-to title "project.todos" this}}</li>
{{/each}}
So this above when you click on the project title link, it shows the associated todos.... but it renders in the left pane... it's probably just something about the CSS layout...but something tells me there is a very Ember-ish way to do this that I am missing.
Then in /project/todo.hbs we have the iteration
{{#each model.todos}}
<li>{{title}}</li>
{{/each}}
I haven't even really addressed making the CRUD for controllers or anything. Most likely this above is laughable and there is a much more elegant way to approach this...
Basically I want a projects parent route, that I do CRUD with... and then when you render a list of project links in the sidebard and click on one, you get in the right pane the rendered ToDoMVC working app.
Of course this is just a starting point for my application. Most likely if someone comes up with a elegant way to do this, we can turn it into an open source project on github for others to learn from.
I think a bunch of burgeoning ember developers are having a hard time with this type of thing because of the multiple ways to do it (outlets, partials, render, render into other templates, views, components, etc)
Don't really know how to get any further.
Not sure if you're still stuck, but I'd try it without bootstrap as a side bar, and just put an {{#each}} [full code here]
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_BINDINGS: true,
LOG_VIEW_LOOKUPS: true,
LOG_ACTIVE_GENERATION: true,
debugMode: true
});
App.Router.map(function() {
this.resource('projects', {
path: '/'
});
this.resource('project', {
path: '/projects/:project_id'
}, function() {
// URL = '/projects/:id/todos'
this.resource('project.todos', {
path: '/todos'
}, function() {
// URL = '/project/:id/todos/new'
this.route("new");
});
});
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
//App.Store = DS.Store.extend({adapter : DS.FixtureAdapter});
App.ProjectsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('project');
},
actions: {
addproject: function() {
var newproject = this.store.createRecord('project', {
name: "My New project"
});
},
removeproject: function(project) {
console.log(project);
console.log(this.controller.get("model"));
this.controller.get("model").removeObject(project);
}
}
});
App.ProjectRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('project', params.project_id).then(function(project) {
return project;
});
}
});
App.ProjectsIndexRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('project');
}
});
App.ProjectTodosRoute = Em.Route.extend({
model: function(params) {
return this.modelFor('project');
},
actions: {
addtodo: function() {
this.transitionTo("project.todos.new");
}
}
});
App.projecttodosNewRoute = Em.Route.extend({
model: function(params) {
parentprojectId = this.modelFor('project').get("id");
newtodo = this.store.createRecord('todo', {
id: "5",
name: "John Doe",
//project : parentprojectId
project: this.store.getById('project', parentprojectId)
});
console.log("new todo = " + newtodo);
return newtodo;
},
actions: {
save: function() {
//console.log(this.controllerFor('projecttodosNew').content);
//console.log('save of newtodo = '+this.controllerFor('projecttodosNew').get('newtodo'));
console.log('newtodo~ ' + newtodo.get('name') + ', ' +
newtodo.id + ', ' + newtodo);
newtodo.save()
//this.controllerFor('projecttodosNew').content.save()
.then(function() {
this.transitionTo("project.todos");
});
},
cancel: function() {
console.log("rollback for " + this.get("controller.model"));
this.get("controller.model").rollback();
this.set("controller.model", null);
this.transitionTo("project.todos");
}
}
});
//App.projecttodosNewController = Ember.ObjectController
// .extend({
// needs : [ 'application', 'project'],
// newtodo : null
// });
App.Project = DS.Model.extend({
name: DS.attr(),
todos: DS.hasMany('todo', {
async: true
})
});
App.Project.FIXTURES = Em.A([{
id: 1,
name: 'Monday',
todos: ['2']
}, {
id: 2,
name: 'Tuesday',
todos: ['1', '2']
}, {
id: 3,
name: 'Wednesday',
todos: ['4']
}]);
App.Todo = DS.Model.extend({
name: DS.attr('string'),
//project : DS.belongsTo('project')
});
App.Todo.FIXTURES = [{
id: 1,
name: 'shop',
project: 1
}, {
id: 2,
name: 'sell things',
project: 2
}, {
id: 4,
name: 'dance',
project: 3
}];
/* Put your CSS here */
html,
body {
margin: 20px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.1/ember.js"></script>
<script src="http://builds.emberjs.com/tags/v1.0.0-beta.10/ember-data.prod.js"></script>
</head>
<body>
<script type="text/x-handlebars">
<h2>Welcome to "The Project/TODO Demo"</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="projects">
<ul>
{{#each item in model}}
<li>{{#link-to 'project.todos' item }}{{item.name}}, List of todos{{/link-to}} ,
<button {{action "removeproject" item}}>X</button>
</li>
{{/each}}
</ul>
<button type="button" {{action "addproject" this.id}}>Add a project</button>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/index">
<br><b>Name of project:</b> {{name}}
</script>
<script type="text/x-handlebars" data-template-name="project">
{{#link-to "projects"}}Home{{/link-to}} {{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/todos/index">
<h1></h1>
<b>todos</b>
<br>
<ul>
{{#each todo in todos}}
<li>{{todo.name}}</li>
{{/each}}
</ul>
<button type="button" {{action "addtodo"}}>Add a todo</button>
<br>{{#link-to 'project' this}}project details page{{/link-to}} {{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/todos/new">
<h1></h1>
<b>New todos</b>
<br>
<ul>
<li>Name: {{input type='text' value=model.name}}</li>
<li>todo Id: {{input type='text' value=id}}</li>
<li>Parent project Id: {{project}}</li>
</ul>
<button type="button" {{action "save"}}>Save todo</button>
<button type="button" {{action "cancel"}}>cancel</button>
<br>{{outlet}}
</script>
</body>
</html>
on the top of your homepage / index. After you get that working you can start playing with the layout with bootstrap, and getting it looking cool.
similar to links doc. Hope that gets you passed your hurdle.

Ember.js Redirect to Template

I have a list of matches, and when I click one, I want to display the match. I know that I can do a Master-Detail style page, where when I click one, I can see the outlet somewhere on the same page, but that is not what I want.
I want it so that when I click on a link, it goes to an entirely new page for the match. I'm not really sure how to go about doing that.
Here is my route for #/matches (in coffeescript)
App.MatchesRoute = Ember.Route.extend(
model: ->
App.Match.find()
)
Here is my matches.handlebars
<div id="matches">
{{#each match in controller}}
{{#linkTo "match" match class="panel six columns"}}
Match between {{match.player.name}} and {{match.opponent.name}}
{{/linkTo}}
<br />
{{/each}}
</div>
// I know that if I have this outlet, it will render `match.handlebars`
// right here, but I want it to be it's own page.
// {{outlet}}
I've only been working with Ember for a few days, and all of the examples I've found use Master-Detail views.
Please let me know of any other information I can provide from my code.
<Edit date="March 11th 2013">
I've pushed a this repository in GitHub. This is a conceptual app that uses renderTemplate somewhat the way you're describing.
</Edit>
In your child route, use the renderTemplate hook in order to tell your application to render a specific template in a specific {{outlet}}. Example:
Source Fiddle
App.Router.map(function() {
this.resource('matches', { path: 'matches' }, function() {
this.route('match', { path: 'match/:match_id' });
});
});
App.MatchesRoute = Em.Route.extend({
model: function() {
return App.Match.find();
},
setupController: function(controller, model) {
model = App.Match.find();
controller.set('content', model);
},
renderTemplate: function() {
this.render('matches', {
into: 'application'
})
}
});
App.MatchesMatchRoute = Em.Route.extend({
model: function(params) {
return App.Match.find(params.match_id);
},
setupController: function(controller, model) {
controller.set('content', model);
},
renderTemplate: function() {
this.render('match', {
into: 'application'
})
}
});
This MatchesMatchRoute is setup to render its template (matches/match) into the application template. And since there is only one {{outelet}} this template (see handlebars below), we don't have to specify anything:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="matches">
<h2>Matches</h2>
<ul>
{{#each match in controller}}
<li>
{{#linkTo matches.match match}}
{{match.title}}
{{/linkTo}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="match">
<h3>{{title}}</h3>
<p>{{description}}</p>
</script>
If you have a scenario with multiple outlets, you have to hame them, like in the handlebars below:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet main}}<br />
{{outlet nested}}
</script>
Then your routes will have to specify the outlet as well. Example:
Source Fiddle
[...route code...]
renderTemplate: function() {
this.render('content', {
into: 'application',
outlet: 'main'
});
this.render('buttons', {
into: 'application',
outlet: 'nested'
});
}
[...route code...]
You can cause a template to render into a different template's outlet by using the renderTemplate hook when defining the route (see the guide: http://emberjs.com/guides/routing/rendering-a-template/)
For your example it might look like this:
App.MatchRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ into: 'matches' });
}
});

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.

How to pass Model route name item to linkTo in template in 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

{{#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