I have prepared this jsfiddle. This is the code:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 13,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function() {
// put your routes here
});
App.User = DS.Model.extend({
name : DS.attr('string'),
email : DS.attr('string'),
});
App.User.FIXTURES = [{ id: 'me', name: 'Max Smith', email: 'max.smith#email.com' }];
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
And this is the template:
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Id:</dt> <dd>{{id}}</dd>
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
{{/each}}
</div>
</script>
What I want to do is to display the data of a single user. The opbject has no id (it represents the logged in user, which is session related, and has no id visible for ember). Instead, I am forced to create a list of users (with one user), and give it a fake id (me). This makes no sense in my application.
I would like to use this template, but I have no idea how to configure ember for this:
<script type="text/x-handlebars" data-template-name="index">
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
</div>
</script>
With this fixture:
App.User.FIXTURES = { name: 'Max Smith', email: 'max.smith#email.com' };
Note that this is a single element, and that I am not looping through the models with #each, because this should not be a list: there is only a single element.
Ember refuses to accept this. What can I do to get this to work?
You can returning an array of records from the model hook, as a result ember is generating an ArrayController for it, which expects it's content to be an array.
Change the model hook to return the single record. For instance using me as the id.
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find('me');
}
});
Then your template works. See the updated jsfiddle.
Related
Heyy!!
I'm having trouble passing a model to a route in ember cli. I'm making a simple app where posts have and author and a title. When you click the title you go to the post details and when you click the author you go to the author's profile. My problem is that I go to the respective user but when I refresh the page I get a n error in the author route. I have no idea why, I'm guessing it has to do with the model not being fetched again when I refresh since it passes the model using link-to helper
My code (client):
app/models/author.js
import DS from 'ember-data';
export default DS.Model.extend({
posts: DS.hasMany('post', {async: true}),
name: DS.attr('string'),
url: DS.attr('string')
});
app/models/post.js
import DS from 'ember-data';
var attr= DS.attr;
export default DS.Model.extend({
author: DS.belongsTo('author'),
title: attr('string'),
description: attr('string'),
date: attr('date'),
url:attr('string'),
});
app/routes/author.js
import Ember from 'ember';
export default Ember.Route.extend({
setupController: function(controller, model) {
model.reload();
controller.set('model', model);}
});
app/templates/posts.hbs
<div class="container" style="width:70%">
{{#each model as |post|}}
<div class="well">
<div class="media">
<a class="pull-left" >
<img class="media-object" src={{post.url}} style="width:200px;height:200px">
</a>
<div class="media-body">
<h1>{{#link-to 'post' post}}{{post.title}}{{/link-to}}</h1>
<h4>Posted by: {{#link-to 'author' post.author.id}} {{post.author.name}}{{/link-to}} </h4>
<p>{{post.description}}</p>
</div>
</div>
</div>
{{/each}}
</div>
My Code (server):
var authors=[];//list of authors
var profileRouter= express.Router();
profileRouter.get('/', function(req, res) {
res.send({
'authors':authors
});
});
profileRouter.get('/:id', function(req, res) {
res.send({
'author': authors.find(function(user){
return author.id==req.params.id
// id: req.params.id,
})
});
});
app.use('/api/author', profileRouter);
You are correct that link-to is passing the model, which is not happening when the page is refreshed. You need to define the model hook on the author route (which is not called when the model is passed) -
model: function(params) {
return this.store.find('author', params.id);
}
I'm building the mandatory TODO app to learn ember.
I have tasks and tags in a belongsTo/hasMany relationship (each tag hasMany tasks). When showing tasks, I want to show a computed property on each available tag.
Models:
App.Tag = DS.Model.extend({
tasks: DS.hasMany('task', {async: true}),
..
});
App.Task = DS.Model.extend({
tag: DS.belongsTo('tag', {async: true}),
..
});
Route:
App.TasksRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
tasks: this.store.find('task'),
tags: this.store.find('tag')
});
},
setupController: function(controller, model) {
this.controllerFor('tasks').set('content', model.tasks);
this.controllerFor('tags').set('content', model.tags);
}
});
Tags controller:
App.TagsController = Ember.ArrayController.extend({
needs: ["tag"]
})
Tag controller:
App.TagController = Ember.ObjectController.extend({
taskCount: function() {
// FOLLOWING DOES NOT WORK
return this.get('tasks.length')
}.property('tasks')
});
Tag partial:
<ul>
{{#each tag in model}}
<li>
{{tag.name}} ({{controllers.tag.taskCount}} tasks)
</li>
{{/each}}
</ul>
The computed property 'taskCount' does not work. There is something wrong with 'this'.
Is this canonical way of doing it? And if so, what is wrong? Thanks
EDIT: FIXED
I had missed out
App.ApplicationSerializer = DS.ActiveModelSerializer.extend();
And I've used render to get the controller decoration:
{{render 'tag' tag}}
which calls the controller before rendering
I'm new to Ember.js, this is my first app which has a notifications area, always visible. As this notification area is included in the application template and there is no specific route for it, I added the number using the {{render}} Helper and setting the model from the ApplicationRoute:
<script type="text/x-handlebars">
...
{{#link-to "notifications"}}
{{ render "notification-totals" }}
{{/link-to}}
<div class="dropdown">
{{outlet notifications}}
</div>
...
</script>
<script type="text/x-handlebars" data-template-name="notification-totals">
{{this.firstObject.total}}
</script>
The ApplicationRoute sets the model to the controller:
App.ApplicationRoute = Ember.Route.extend({
setupController: function() {
this.controllerFor('notification-totals').set('model',this.store.find('notification.total'));
}
});
The Model:
App.NotificationTotal = DS.Model.extend({
total: DS.attr('number')
});
How can I access the model within the Controller? Nothing I try seems to work, for example:
App.NotificationTotalsController = Ember.ObjectController.extend({
total: function() {
var model = this.get('model');
.....
}.property()
});
Your code should work fine. You should be getting an array of your model objects, because that's what the route is setting as model.
For example:
<script type="text/x-handlebars">
<div><h3>Notifications:</h3>
{{ render "notification-totals" }}</div>
</script>
<script type="text/x-handlebars" data-template-name="notification-totals">
{{total}}
</script>
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.ApplicationRoute = Ember.Route.extend({
setupController: function() {
var data = [
{
total: 7
}, {
total: 8
}, {
total: 12
}
];
this.controllerFor('notification-totals').set('model', data);
}
});
App.NotificationTotalsController = Ember.ObjectController.extend({
total: function() {
var model = this.get('model');
return model
.mapBy("total")
.reduce(function (prev, cur) {
return prev + cur;
}, 0);
console.log(model);
}.property("model.#each.total")
});
This controller will access all the model objects and generate the sum ({{this.firstObject.total}} will get you totals only from the first object). Working demo here.
If you're still getting nothing, check if your data source is getting anything (this demo uses hardcoded values instead of ember data).
I have a fiddle (http://jsfiddle.net/kitsunde/3FKg4/) with a simple edit-save application:
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="profile/edit">
Edit.
<form {{action 'save' on="submit"}}>
<div>
<input type="email" {{bind-attr value="email"}}>
</div>
<button>Save</button>
</form>
</script>
<script type="text/x-handlebars" id="profile/index">
{{#link-to 'profile.edit'}}Edit{{/link-to}}
{{email}}
</script>
And my application:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Router.map(function () {
this.resource('profile', {path: '/'}, function(){
this.route('edit');
});
});
App.ProfileRoute = Ember.Route.extend({
model: function() {
return this.store.find('user').then(function(users){
return users.get('firstObject');
});
}
});
App.ProfileEditRoute = App.ProfileRoute;
App.ProfileEditController = Ember.ObjectController.extend({
actions: {
save: function(){
var profile = this.get('model');
profile.setProperties({email: this.get('email')});
profile.save();
this.transitionTo('profile');
}
}
});
App.User = DS.Model.extend({
email: DS.attr('string')
});
App.User.FIXTURES = [
{
id: 1,
email: 'herpyderp#gmail.com'
}
];
When I hit save and it goes back to profile/index it doesn't have an updated model and when I go back to profile/edit the edit isn't there. I realize I could use {{input value=email}} which does seem to remember the model changes, but that seems to persist the changes to the model as I type which isn't what I want.
What am I missing?
The save method returns a promise, you could transition when the promise is resolved as:
var route = this;
profile.save().then(function() {
route.transitionTo('profile');
}, function() {
// TODO: implement error logic
});
In that case, your method will be updated when the application goes back to the index state.
I fixed it with 2 changes. First since I was grabbing this.get('email') I was getting the models email address and not the one from input field, so it was actually never updating the data.
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="profile/edit">
Edit.
<form {{action 'save' on="submit"}}>
<div>
{{input value=email}}
</div>
<button>Save</button>
</form>
{{#link-to 'profile'}}Back{{/link-to}}
</script>
<script type="text/x-handlebars" id="profile/index">
{{#link-to 'profile.edit'}}Edit{{/link-to}}
{{email}}
</script>
Second to deal with only updating the model on save I used ember-data transaction handling to rollback the commit when navigating away from the current route, unless it had been saved. I also moved my logic into the router.
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Router.map(function () {
this.resource('profile', {path: '/'}, function(){
this.route('edit');
});
});
App.ProfileRoute = Ember.Route.extend({
model: function() {
return this.store.find('user').then(function(users){
return users.get('firstObject');
});
}
});
App.ProfileEditRoute = App.ProfileRoute.extend({
deactivate: function(){
var model = this.modelFor('profile');
if(model.get('isDirty') && !model.get('isSaving')){
model.rollback();
}
},
actions: {
save: function(){
this.modelFor('profile').save();
this.transitionTo('profile');
}
}
});
App.User = DS.Model.extend({
email: DS.attr('string')
});
App.User.FIXTURES = [
{
id: 1,
email: 'herpyderp#gmail.com'
}
];
Updated fiddle: http://jsfiddle.net/kitsunde/3FKg4/2/
App.Router.map(function() {
this.resource('products', function() {
this.resource('product', { path: ':product_id' }, function() {
this.route('general');
});
})
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
App.ProductRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.product_id);
}
});
Templates:
<script type="text/x-handlebars" data-template-name="product">
Showing {{ name }}
<p>{{ outlet }}</p>
</script>
<script type="text/x-handlebars" data-template-name="product/general">
General template for {{ name }}
</script>
In the /products/3 view the name shows up as it should, but not in the /products/3/general view. Anybody know why?
I have tried to copy the App.ProductRoute and rename it to App.ProductGeneralRoute to find the correct model, but then the params does not exist.
In Ember, nested routes don't have access to their parent routes model. There are two ways to access parent models in a child route.
App.ProductGeneralRoute = Ember.Route.extend({
model: function() {
return this.modelFor('product');
}
});
This sets the model on the ProductGeneral route by getting the model for the ProductRoute. Or you can use needs:
App.ProductGeneralController = Ember.ObjectController.extend({
needs: ['product']
});
In the latter example, you will have access to controllers.product, which will allow you to call controllers.product.model in the template.
See this article for more info on needs.