How to update hasMany in Ember.js using different controllers?
Hi
I have Ruby on Rails 4.0.3 app and I am using Ember.js
DEBUG: Ember : 1.6.0-beta.3 ember.js?body=1:3917
DEBUG: Ember Data : 1.0.0-beta.7+canary.f482da04 ember.js?body=1:3917
DEBUG: Handlebars : 1.3.0 ember.js?body=1:3917
DEBUG: jQuery : 1.11.0
I want to display hasMany in the same view using different controllers.
I have seen some example on StackOverflow but most (if not all) of them are for displaying records.
Ok talk is cheap, I am showing the code:
Models:
-javascripts/models/task.js
EmTasks.Task = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr("string"),
list: DS.belongsTo('list')
});
-javascripts/models/list.js
EmTasks.List = DS.Model.extend({
name: DS.attr('string'),
tasks: DS.hasMany('task')
});
Router:
-javascripts/router.js
EmTasks.Router.map(function(){
return this.route("lists", {
path: '/'
});
});
EmTasks.ListsRoute = Ember.Route.extend({
model: function() {
return this.store.find('list');
}
});
Controllers:
-javascripts/controllers/lists_controller.js
EmTasks.ListsController = Em.ArrayController.extend({
addList: function() {
this.store.createRecord('list', {
name: this.get('newListName')
}).save();
return this.set('newListName', '');
},
destroyList: function(id) {
if (confirm("Are you sure?")) {
this.get('store').find('list', id).then( function(record) {
record.destroyRecord();
});
}
},
});
-javascripts/controllers/list_controller.js
EmTasks.ListController = Em.ObjectController.extend({
actions: {
editList: function() {
this.set('isEditingList', true);
var model = this.get('model')
},
acceptChanges: function () {
this.set('isEditingList', false);
var name = this.get('model.name');
if (Ember.isEmpty(name)) {
this.send('removeList');
} else {
var list = this.get('model')
list.set('name', name);
list.save()
}
},
removeList: function () {
var list = this.get('model');
list.destroyRecord();
}
},
isEditingList: false
});
-javascripts/controllers/task_controller.js
EmTasks.TaskController = Em.ObjectController.extend({
isEditingTask: false
});
Templates:
-javascripts/templates/lists.handlebars [fragment]
{{#each itemController='list'}}
<div class='col-md-8'>
<h3>
{{#if isEditingList}}
{{edit-input class="form-control" value=name focus-out="acceptChanges" insert-newline="acceptChanges"}}
{{else}}
<div {{action 'editList' on='doubleClick'}}>
{{name}}
</div>
{{/if}}
</h3>
</div>
<div class='col-md-4 down13p'>
<button class="btn btn-danger btn-small pull-right" {{action "destroyList" id}} type="button">
<span class="glyphicon glyphicon-ban-circle"></span>
</button>
</div>
{{#each task in this.tasks }}
<div class="col-md-10">
{{#if task.isEditingTask}}
{{edit-input class="form-control" value=task.name focus-out="acceptChanges" insert-newline="acceptChanges"}}
{{else}}
<div {{action 'editList' on='doubleClick'}}>
{{name}}
</div>
{{/if}}
But is looks like isEditingTask property is not working...
Any idea how to fix that?
OK found a solution, just add itemController to tasks each loop
{{#each task in this.tasks itemController='task' }}
<div class="col-md-10">
{{#if task.isEditingTask}}
{{edit-input class="form-control" value=task.name focus-out="acceptChanges" insert-newline="acceptChanges"}}
{{else}}
<div {{action 'editTask' on='doubleClick'}}>
{{name}}
</div>
{{/if}}
HTH
Related
Here is my dilemma. I'm currently loading my categories using a static variable onto the 'category' page. Everything loads and the links are clickable but when a user clicks on the category. The 'categoryPage' won't load the respective images that belong to that category. Here is my code.
categoryPage HTML:
<template name="categoryPage">
<div class="text-center light-container" id="home-section">
<h1>{{categoryName}}</h1>
<hr/>
<div class="row">
{{#each categoryGifs}}
<a href="{{pathFor 'gif'}}">
<img class="freezeframe gifs" data-id="{{_id}}" src="{{image}}"/>
</a>
{{/each}}
</div>
</div>
</template>
categoryPage JS:
Template.categoryPage.helpers({
// Load 16 most recent ones
// When down arrow is click load another 16 more gifs
'categoryName': function(){
var category = this.params.category;
return category;
},
'categoryGifs': function() {
var category = this.params.category;
console.log(category);
return GifsCollection.find({category: category}, {fields: {_id: 1, image: 1, category: 1} });
}
});
Router JS:
Router.route('/categories', {
name: 'categories',
template: 'categories',
fastRender: true
});
Router.route('/categories/:category', {
name: 'categoryPage',
template: 'categoryPage',
data: function(){
var category = this.params.category;
return GifsCollection.find({category: category});
},
fastRender: true
});
in 'categoryGifs': function(), change
var category = this.params.category;
with:
var category = Router.current().params.category;
here is the working code:
http://meteorpad.com/pad/AdRS8mfyHsZjA2Rvp/Leaderboard
I have the following template
<script type="text/x-handlebars" id="friends/new">
<label>First Name</label>
{{input value=firstName}}<br />
<label>Last Name</label>
{{input value=lastName}}<br />
<label>About</label>
{{textarea value=about}}<br />
<button {{action "create"}} {{bind-attr disabled=isInvalid}}>Create</button>
</script>
I put data into all the fields and click the create button, which goes to the following controllers
App.FriendsNewRoute = Ember.Route.extend({
model: function(){
return { firstName: "", lastName: "", about: ""}
}
});
App.FriendsNewController = Ember.Controller.extend({
needs: "friends",
isInvalid: true,
validForm: function(){
if(this.get('lastName') && this.get('firstName')){
this.set("isInvalid", false);
} else {
this.set("isInvalid", true);
}
}.observes('firstName','lastName'),
actions: {
create: function(){
var newFriend = Ember.copy(this.content);
console.log(newFriend);
}
}
});
When calling this.get('lastName') ect the content that I have entered into the text boxes is correct. But when I log this.content, the value is still the initial value that I set in the FriendsNewRoute. What do I need to do so that this.content correctly update with the current data in my template?
You should change:
Ember.Controller
to
Ember.ObjectController
I'm building a quiz generator as my first Ember project, but I'm struggling. I've been able to create, edit and delete quizzes and save them to localstorage, but I'm having trouble saving/deleting quiz questions for each quiz.
I'm building it in Yeoman-Ember. I tried to add a demo to JSBin but it didn't work, so I've got a demo build here:
http://test.richardwestenra.com/embertest/
and here's a zip containing the current state of the build: http://test.richardwestenra.com/embertest/dev.zip
Here's my combined-scripts.js file:
(function() {
var Quizmaker = window.Quizmaker = Ember.Application.create();
/* Order and include as you please. */
})();
(function() {
/* global $ */
Quizmaker.QuizzesController = Ember.ObjectController.extend({
});
Quizmaker.NewController = Ember.ObjectController.extend({
content: {},
quiztypes: ['Multiple choice', 'List', 'Either/or'],
actions: {
save: function(){
var title = $('#title').val();
var excerpt = $('#excerpt').val();
var quiztype = $('#quiztype').val();
var fullname = $('#fullname').val();
var submittedOn = new Date();
var store = this.get('store');
if (Ember.isEmpty(title)) {
window.alert('Please enter a title');
return false;
} else if (Ember.isEmpty(quiztype)) {
window.alert('Please enter a quiz type');
return false;
}
var quiz = store.createRecord('quiz',{
quiztype : quiztype,
fullname : fullname,
title : title,
excerpt : excerpt,
submittedOn : submittedOn
});
quiz.save();
this.transitionToRoute('index');
},
cancel: function(){
this.transitionToRoute('index');
},
createQuestion: function(){
window.alert('This doesn\'t work for new questions. I don\'t know why. It works for existing questions.');
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var model = this.get('model');
var questions = this.get('questions');
questions.pushObject(question);
model.set('questions', questions);
model.save();
}
}
});
Quizmaker.QuizController = Ember.ObjectController.extend({
quiztypes: ['Multiple choice', 'Checklist', 'Boolean'],
actions: {
edit: function(){
this.transitionToRoute('quiz.edit');
this.set('isEditing', true);
},
doneEditing: function(){
var model = this.get('model');
var title = $('#title').val();
var excerpt = $('#excerpt').val();
var quiztype = $('#quiztype').val();
var fullname = $('#fullname').val();
var questions = this.get('questions');
if (Ember.isEmpty(title)) {
window.alert('Please enter a title');
return false;
} else if (Ember.isEmpty(quiztype)) {
window.alert('Please enter a quiz type');
return false;
} else {
this.set('isEditing', false);
model.set('title', title);
model.set('excerpt', excerpt);
model.set('quiztype', quiztype);
model.set('fullname', fullname);
model.set('questions', questions);
model.save();
this.transitionToRoute('quiz');
}
},
cancel: function(){
if (window.confirm('Are you sure you want to abandon your changes?')){
this.set('isEditing', false);
this.transitionToRoute('quiz');
}
},
remove: function(){
if (window.confirm('Are you sure you want to delete this quiz?')){
var quiz = this.get('model');
quiz.destroyRecord();
this.transitionToRoute('index');
}
},
createQuestion: function(){
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var model = this.get('model');
var questions = this.get('questions');
questions.pushObject(question);
model.set('questions', questions);
model.save();
}
}
});
Quizmaker.QuestionsController = Ember.ArrayController.extend({
needs: 'quiz',
quiz: Ember.computed.alias("controllers.quiz"),
actions: {
createQuestion: function(){
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var quiz = this.get('quiz');
var questions = quiz.get('questions');
questions.pushObject(question);
console.log(question);
},
removeQuestion: function(id){
var question = this.findProperty('id', id);
this.removeObject(question);
}
}
});
})();
(function() {
Quizmaker.Store = DS.Store.extend();
// Quizmaker.ApplicationAdapter = DS.FixtureAdapter;
Quizmaker.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'quizzes'
});
})();
(function() {
/* global Ember */
Quizmaker.Quiz = DS.Model.extend({
title : DS.attr('string'),
excerpt : DS.attr('string'),
fullname : DS.attr('string'),
quiztype : DS.attr('string'),
questions: DS.hasMany('question', {async: true}), // via http://stackoverflow.com/questions/22494140/in-ember-js-how-do-i-create-a-computed-property-that-references-first-item-in-pr
questionsCount: function() {
return this.get('questions.length');
}.property('questions.#each'), // via http://stackoverflow.com/questions/16463958/how-to-use-multiple-models-with-a-single-route-in-emberjs-ember-data
// firstQuestion: function() {
// return this.get('questions.firstObject');
// }.property('questions.firstObject')
submittedOn : DS.attr('date')
});
Quizmaker.Question = DS.Model.extend({
quiz: DS.belongsTo('quiz'),
question: DS.attr('string'),
answers: DS.attr('string')
});
// delete below here if you do not want fixtures
Quizmaker.Quiz.FIXTURES = [
{
id: 0,
title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
excerpt: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.',
quiztype: 'Boolean',
fullname: 'Full Name',
submittedOn: null
}
];
Quizmaker.Question.FIXTURES = [
{
id: 0,
question: 'Test question Lorem ipsum dolor sit amet, consectetur adipiscing elit',
quiz: 0,
answers: [
{ answer: 'alpha', weight: 0 },
{ answer: 'beta', weight: 5 }
]
}
];
})();
(function() {
Quizmaker.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.get('store').findAll('quiz');
}
});
Quizmaker.QuizzesRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('quiz');
}
});
Quizmaker.QuizRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('quiz', params.quiz_id);
}
});
Quizmaker.QuestionsRoute = Ember.Route.extend({
controllerName: 'quiz',
model: function() {
return this.store.findAll('question');
}
});
})();
(function() {
Quizmaker.QuizView = Ember.View.extend({
keyDown: function(e) {
var esc = 27;
if(e.keyCode === esc){
this.get('controller').send('cancel');
}
}
});
// Give the text fields one way value binding so they don't automatically update
Quizmaker.TextField = Ember.TextField.extend({
valueBinding: Ember.Binding.oneWay('source')
});
Quizmaker.TextArea = Ember.TextArea.extend({
valueBinding: Ember.Binding.oneWay('source')
});
Quizmaker.Select = Ember.Select.extend({
valueBinding: Ember.Binding.oneWay('source')
});
})();
(function() {
Quizmaker.QuizzesView = Ember.View.extend({
});
})();
(function() {
Quizmaker.Router.map(function () {
// Add your routes here
this.resource('index',{path : '/'});
this.resource('new' , {path : '/quiz/new'});
this.resource('quizzes' , {path : '/quizzes'});
this.resource('questions' , {path : '/questions'});
this.resource('quiz', { path: '/quiz/:quiz_id' }, function(){
this.route('edit', { path: '/edit' });
});
});
})();
(function() {
/* global moment */
Ember.Handlebars.helper('format-date', function(date){
return moment(date).fromNow();
});
})();
This is quiz.hbs:
{{#if isEditing}}
<h1>Edit quiz</h1>
{{partial "editform"}}
{{else}}
<h1>{{title}}</h1>
<h4>A <em style="text-transform: lowercase;">{{quiztype}}</em> quiz by {{fullname}} <small class="muted">{{format-date submittedOn}}</small></h4>
<hr>
<p class="lead">{{excerpt}}</p>
<button type="submit" class="btn btn-default" {{action 'edit'}}>Edit</button>
<button class="btn btn-danger pull-right" {{action 'remove'}}>Delete Quiz</button>
{{/if}}
This is _editform.hbs:
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">Title</label>
<div class="col-sm-10">
{{!-- {{#view Quizmaker.TextInputView}}
{{/view}} --}}
{{!-- <input type="text" class="form-control" id="title" name="title" placeholder="Title of the quiz" required> --}}
{{view Quizmaker.TextField type="text" class="form-control" id="title" name="title" sourceBinding="title" placeholder="Title of the quiz" required="required" }}
</div>
</div>
<div class="form-group">
<label for="excerpt" class="col-sm-2 control-label">Excerpt</label>
<div class="col-sm-10">
{{!-- <textarea class="form-control" id="excerpt" name="excerpt" placeholder="Short description of the quiz" required></textarea> --}}
{{view Quizmaker.TextArea class="form-control" id="excerpt" name="excerpt" sourceBinding="excerpt" placeholder="Short description of the quiz" rows="3" required="required" }}
</div>
</div>
<div class="form-group">
<label for="fullname" class="col-sm-2 control-label">Author</label>
<div class="col-sm-10">
{{!-- <input type="text" class="form-control" id="fullname" name="fullname" placeholder="Enter your Full Name like Alan Smithee" required> --}}
{{view Quizmaker.TextField type="text" class="form-control" id="fullname" name="fullname" sourceBinding="fullname" placeholder="Enter your full name, e.g. Alan Smithee" required="required" }}
</div>
</div>
<div class="form-group">
<label for="quiztype" class="col-sm-2 control-label">Quiz type</label>
<div class="col-sm-10">
{{view Quizmaker.Select id="quiztype" name="quiztype" class="form-control" viewName="select" content=quiztypes prompt="Pick a type:" sourceBinding="quiztype"}}
</div>
</div>
<div class="form-group">
<label for="quiztype" class="col-sm-2 control-label">Questions ({{questionsCount}})</label>
<div class="col-sm-10">
{{render "questions" questions}}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
{{#if isEditing}}
<button type="submit" class="btn btn-success" {{action 'doneEditing'}}>Save</button>
<button class="btn btn-warning pull-right" {{action 'cancel'}}>Cancel</button>
{{else}}
<button type="submit" class="btn btn-success" {{action 'save'}}>Save</button>
<button class="btn btn-warning pull-right" {{action 'cancel'}}>Cancel</button>
{{/if}}
</div>
</div>
</form>
And this is questions.hbs:
<p><button class="btn btn-info btn-sm" {{action 'createQuestion'}}><span class="glyphicon glyphicon-plus"></span> Add new question</button></p>
<table class="table table-striped">
{{#each model}}
<tr>
<td>{{this.question}}</td>
<td>{{this.id}}</td>
<td><button class="btn btn-danger btn-xs pull-right" {{action 'removeQuestion' id}}>Delete</button></td>
</tr>
{{/each}}
</table>
If anyone can point out ways I can improve it, I'd be very grateful. I've tried implementing every method I can find to get it to save or delete hasMany data but it always throws different errors and I'm not really sure where to proceed from here.
I'm also trying to figure out how to toggle the isEditing state depending on whether the /edit route is displayed (instead of having it activated by an action).
let's break this down into two different items, last to first.
The trick in showing the edit mode versus view mode is to take advantage of the free index route which is rendered at the root of every resource.
You can change your quiz template to be an outlet
{{outlet}}
and create a quiz/index template which originally held your quiz data. This template will only show up when you are at /quiz/123 when you navigate to /quiz/123/edit the index template will be replaced with the edit template. (You'll probably want to return the model from the resource to both routes using modelFor).
App.Router.map(function() {
this.resource('foo', {path:'/'},function(){
this.route('edit');
})
});
App.FooRoute = Em.Route.extend({
model: function() {
return [{color:'red'}, {color:'yellow'},{color: 'blue'}];
}
});
App.FooIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('foo');
}
});
App.FooEditRoute = Ember.Route.extend({
model: function() {
return this.modelFor('foo');
}
});
http://emberjs.jsbin.com/lodeviyu/1/edit
In Ember Data if you have two record types that each have a relation to each other:
App.FooRecord = DS.Record.extend({
bars: DS.hasMany('bar')
});
App.BarRecord = DS.Record.extend({
foo: DS.belongsTo('foo')
});
Let's create and associate two records:
var foo = store.create('foo');
var bar = store.create('bar');
foo.get('bars').pushObject(bar);
bar.set('foo', foo);
Now let's save, and see what Ember Data will do:
foo.save();
// json sent
{
foo: {}
}
// responds with id 1
bar.save();
// json sent
{
bar: {
foo: 1
}
}
Ember Data chooses not to save the hasMany relationship if their is a correlated belongsTo from a different model type.
https://github.com/emberjs/data/commit/7f752ad15eb9b9454e3da3f4e0b8c487cdc70ff0#commitcomment-6078838
I'm creating an ember.js app. The first page is single field, with a button. On button click, I'd like it to go to the path #/deals/:api_key. However, when I click the button, I'm not clear on the best way to go about it.
Here's what i have so far:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
App.Deal = DS.Model.extend({
name: DS.attr('string')
});
App.Router.map(function() {
this.resource('start', { path: '/' });
this.resource('deals', { path: '/deals/:api_key' });
});
App.DealsRoute = Ember.Route.extend({
model: function(params) {
return App.Deal.find();
}
});
App.StartController = Ember.ObjectController.extend({
apiKey: "",
getDeals: function (model) {
this.transitionToRoute('deals');
}
});
App.DealsView = Ember.View.extend({
didInsertElement: function() {
// Add active class to first item
this.$().find('.item').first().addClass('active');
this.$().find('.carousel').carousel({interval: 1000});
}
});
<script type="text/x-handlebars" data-template-name="start">
{{view Em.TextField valueBinding="apiKey" placeholder="API Key"}}
<br />
<button {{action 'getDeals'}} class="btn btn-large">Get Won Deals!</button>
</script>
<script type="text/x-handlebars" data-template-name="deals">
<div id="carousel" class="carousel slide">
<div class="carousel-inner">
{{#each model}}
<div class="item">
{{name}}
</div>
{{/each}}
</div>
</div>
</script>
Any suggestions on the right way to pass data from a text input into the next transition as a query param?
you need to pass the parameter in the view in a linkTo helper, e.g.
{{#linkTo 'deals' api_key}}go to deals{{/linkTo}}
this generates a link with the dynamic section you need.
go to deals
check the docs about linkTo for more info: http://emberjs.com/guides/templates/links/
Given these JSON data models on a RESTful server
/users
{"users":[
{"id":"1","first_name":"John","last_name":"Doe"},
{"id":"2","first_name":"Donald","last_name":"Duck"}
]}
/users/1
{"user":
{"id":"1","first_name":"John","last_name":"Doe","account":"1"}
}
/accounts
{"accounts":[
{"id":"1","owned_by":"1"},{"id":"2","owned_by":"2"}
]}
/accounts/1
{"account":
{"id":"1","owned_by":"1","transactions":[1,17]}
}
and these Ember data models
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
url: 'http://api.mydomain.ca'
})
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
account: DS.belongsTo('App.Account')
});
App.Account = DS.Model.extend({
ownedBy: DS.belongsTo('App.User'),
transactions: DS.hasMany('App.Transaction')
});
what other ember code do I have to write to load the data into the models and then write a template that outputs a user's name, account id, and the number of transactions in the account?
I was able to solve this so I will post my code in case it helps someone else. The trick is to make sure the JSON data is formatted exactly how Ember wants it and to create the proper routes.
From what I can tell, Ember expects parent objects to provide a list of child objects. This feels weird to me so if anyone knows a way to do it with child objects referencing their parents with a foreign key please let me know.
I changed the account property on my /user/:user_id JSON object to account_id I also included the account_id on the user objects found at /users and I changed the owned_by property on the account to user_id.
My javascript file
var App = Ember.Application.create();
// Router
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', {path:':user_id'});
}); // '/#/users/:user_id'
this.resource('accounts', function() {
this.resource('account', {path:':account_id'});
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('users');
}
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.AccountsRoute = Ember.Route.extend({
model: function() {
return App.Account.find();
}
});
// Controllers
App.TransactionsController = Ember.ArrayController.extend();
// Adapter
App.Adapter = DS.RESTAdapter.extend({
url: 'http://api.mydomain.ca'
});
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: App.Adapter.create({})
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
account: DS.belongsTo('App.Account')
});
App.Account = DS.Model.extend({
user: DS.belongsTo('App.User'),
transactions: DS.hasMany('App.Transaction'),
balance: function() {
return this.get('transactions').getEach('amount').reduce(function(accum, item) {
return accum + item;
}, 0);
}.property('transactions.#each.amount')
});
App.Transaction = DS.Model.extend({
account: DS.belongsTo('App.Account'),
amount: DS.attr('number'),
description: DS.attr('string'),
timestamp: DS.attr('date')
});
And the handlebars templates
<script type="text/x-handlebars" data-template-name="application">
<div class="row">
<div class="twelve columns">
<h2>Accounts</h2>
<p>{{outlet}}</p>
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="users">
<div class="row">
<div class="three columns" id="users">
{{#each user in controller }}
{{#linkTo "user" user class="panel twelve columns"}}{{user.firstName}} {{user.lastName}}{{/linkTo}}
{{/each}}
</div>
<div class="nine columns" id="user">
{{ outlet }}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h2>{{firstName}} {{lastName}}</h2>
{{#if account}}
{{render "account" account}}
{{else}}
Error: Account not set up!
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="accounts">
<div class="row">
<div class="three columns" id="accounts">
{{#each account in controller }}
{{#linkTo "account" account class="panel twelve columns"}}{{account.id}} {{account.user.firstName}} {{account.user.lastName}}{{/linkTo}}
{{/each}}
</div>
<div class="nine columns" id="account">
{{ outlet }}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="account">
<p>Account Number: {{id}}, Balance: {{balance}}, {{transactions.length}} transactions</p>
{{render "transactions" transactions}}
</script>
<script type="text/x-handlebars" data-template-name="transactions">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Amount</th>
<th>Timestamp</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{{#each transaction in controller}}
<tr>
<td>{{transaction.id}}</td>
<td>{{transaction.amount}}</td>
<td>{{transaction.timestamp}}</td>
<td>{{transaction.description}}</td>
</tr>
{{/each}}
</tbody>
</table>
</script>
Create a Index route that seeds your IndexController with a model and create a related Template that iterates over your relationships.
Here is an example for a simple HasMany-Relationship between post and comments:
var App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create()
});
App.Post = DS.Model.extend({
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
post: DS.belongsTo('App.Post'),
body: DS.attr('string'),
});
App.IndexRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Post.find("1"));
}
});
The HTML-Code should look like this:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<script type="text/x-handlebars" data-template-name="index">
{{#each comment in content.comments}}
{{comment.body}}
{{/each}}
</script>
</body>
And the last but not least the server response /posts/1
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": [1, 2, 3]
},
"comments": [{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}]
}