How to manage 2 nested dynamic segments with ember? - ember.js

EDIT: I found this very interesting question and answer here, but unfortunately it does not work with Ember 1.0. It would be great if someone could edit it for ember 1.0. The JsFiddle is here.
I want to articulate my app around (a) first language choice, then (b) a page choice, so I have the following routes
App.Router.map(function () {
this.resource('lang', { path: '/:lang_id' }, function() {
this.resource('page', { path: '/:page_id' });
});
});
Implemented by
App.ApplicationRoute = Em.Route.extend({
model: function() {
return this.store.find('lang');
}
});
App.LangIndexRoute = Em.Route.extend({
model: function(params) {
return this.store.find('lang', params.lang_id);
}
});
App.PageRoute = Em.Route.extend({
model: function(params) {
return this.store.find('page', params.page_id);
}
});
model is :
App.Lang = DS.Model.extend({
name: DS.attr('string'),
pages: DS.hasMany('page')
});
App.Page = DS.Model.extend({
lang: DS.belongsTo('lang'),
name: DS.attr('string'),
title: DS.attr('string'),
extra_title: DS.attr('string'),
});
App.Lang.FIXTURES = [
{
id: 'en',
name: 'EN',
pages: [1, 2]
},
{
id: 'fr',
name: 'FR',
pages: [3]
},
];
App.Page.FIXTURES = [
{
id: 'page-item1',
name: 'ITEM1',
title: 'ITEM1',
extra_title: 'item1',
},
{
id: 'page-item2',
name: 'ITEM2',
title: 'ITEM2',
extra_title: 'item2',
},
{
id: 'page-item3',
name: 'ITEM3',
title: 'ITEM3',
extra_title: 'item3',
},
];
And the following markup :
<script type="text/x-handlebars" >
<nav>
<ul>
{{#each}}
<li>{{#linkTo 'lang' this currentWhen="lang" activeClass="selected" }}{{name}}{{/linkTo}}</li>
{{/each}}
</ul>
</nav>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="lang/index">
<nav>
<ul class="projects">
{{#each pages}}
<li>{{#link-to "page" activeClass="selected"}}{{name}}{{/link-to}}</li>
{{else}}
<li>Loading...</li>
{{/each}}
</ul>
</nav>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="page">
</script>
I cannot get the pages listed for the given language to be enumerated when loading the lang.index template.
How could I do that, or would there be a better way to manage language with ember ?
I think my mistake (or misunderstanding of the framework) is either in the model hook or in the iterator of #each...

I unblock the situation by modifying the route, model and template as below.
One of the problem was due to the need of declaring the hasmany relationship as async. (using FIXTURES), the other were related to the linkTo in the template.
However I still don't have exactly the behavior I want in the pages/index template. All pages are rendered, instead of only the one of the given language.
App.Router.map(function () {
this.resource('pages', { path: '/:lang_id' }, function() {
this.route('page', { path: '/:page_id' });
});
App.ApplicationRoute = Em.Route.extend({
model: function() {
return this.store.find('lang');
}
});
App.PagesIndexRoute = Em.Route.extend({
model: function(params) {
console.log(params.lang_id);
return this.store.find('page', params.lang_id);
}
});
App.Lang = DS.Model.extend({
name: DS.attr('string'),
pages: DS.hasMany('page',{async:true})
});
<script type="text/x-handlebars" >
<nav>
<ul>
{{#each}}
<li>{{#linkTo 'pages' this currentWhen="pages" activeClass="selected" }}{{name}}{{/linkTo}}</li>
{{/each}}
</ul>
</nav>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="lang/index">
<nav>
<ul class="projects">
{{#each pages}}
<li>{{#link-to "pages.page" this activeClass="selected"}}{{name}}{{/link-to}}</li>
{{else}}
<li>Loading...</li>
{{/each}}
</ul>
</nav>
{{outlet}}
</script>

Related

How to pass computed properties from route to component

Taking the following as an example:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each model as |item|}}
<li>{{item}}</li>
{{/each}}
{{magnus-component name=firstName}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="components/magnus-component">
<!-- Doesn't appear -->
{{name}}
</script>
...and
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
},
name: "Magnus",
surname: "Womble",
fullName: Ember.computed('name', 'surname', function(){
return this.get('name') + ' '+this.get('surname');
})
});
Which can be found in the following jsbin:
http://emberjs.jsbin.com/higako/edit?html,js,output
How do you correctly pass the fullName property to the component template?
You should move your logic from IndexRoute to IndexController:
App.IndexController = Ember.Controller.extend({
name: "Magnus",
surname: "Womble",
fullName: Ember.computed('name', 'surname', function(){
return this.get('name') + ' '+this.get('surname');
})
});
And then, if you use:
{{magnus-component name=fullName}}
instead of (firstName isn't defined anywhere):
{{magnus-component name=firstName}}
It will work.
Working demo.

Both automatic and conditional routing Emberjs

If the user access the site directly www.mysite.com the routes should transition to first product page of first category.
If the user copy paste url of a particular product. user should be navigate to that particular product page.
I need some directions how to go about it as when I copy a product url www.mysite.com/Men/shirts it navigate to default first category first product as per my transitionTo
My router.js looks like this
MyApp.Router.map(function () {
this.resource('categories', {path: '/'}, function () {
this.resource('category', { path: '/:category_id'}, function () {
this.resource('product', { path: '/:product_id' });
});
});
});
my categories route's after model look like this
afterModel: function(categories,transition){
this.transitionTo('category',categories.get('firstObject'));
},
my category route after model looks like this
afterModel: function (category) {
var self = this;
self.transitionTo('product', category.get('products').get('firstObject'));
}
You should probably do the default transition from the Index Routes.
MyApp.CategoriesIndexRoute = Em.Route.extend({
afterModel: function(model) {
this.transitionTo('category',model.get('firstObject'));
}
});
MyApp.CategoryIndexRoute = Em.Route.extend({
afterModel: function(model) {
this.transitionTo('product', Em.get(model, 'products.firstObject'));
}
});
Child resources/routes inherits parent's model since this PR. You can use this.modelFor if you are using older version
Working Demo
This is an example following your routing structure,
http://emberjs.jsbin.com/viyagaga/1/edit
specific category: http://emberjs.jsbin.com/viyagaga/1#/category/2
specific product: http://emberjs.jsbin.com/viyagaga/1#/category/2/product/4
hbs
<script type="text/x-handlebars" data-template-name="categories">
this is all categories<br/>
<ul>
{{#each category in model}}
<li>
{{#link-to "category" category.id}}
id:{{category.id}} name:{{category.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="category">
this is category {{model.name}}<br/>
with products:<br/>
<ul>
{{#each product in model.products}}
<li>
{{#link-to "product" product.id}}
id:{{product.id}} name:{{product.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="product">
this is product:<br/>
{{model.name}}<br/>
</script>
js
App = Ember.Application.create();
App.Router.map(function () {
this.resource('categories', {path: '/'}, function () {
this.resource('category', { path: 'category/:category_id'}, function () {
this.resource('product', { path: 'product/:product_id' });
});
});
});
App.IndexRoute = Ember.Route.extend({
beforeModel: function() {
this.transitionTo("categories");
}
});
App.CategoriesRoute = Ember.Route.extend({
model:function(){
return allCategoriesData;
}
});
App.CategoryRoute = Ember.Route.extend({
model: function(params) {
return allCategoriesData.findBy("id",parseInt(params.category_id,10));
}
});
App.ProductRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor("category").products.findBy("id",parseInt(params.product_id,10));
}
});
var allCategoriesData = [
{"id":1,"name":"Category1",
"products":[
{"id":1, "name":"product11"},
{"id":2, "name":"product12"}
]
},
{"id":2,"name":"Category2",
"products":[
{"id":3, "name":"product21"},
{"id":4, "name":"product22"},
{"id":5, "name":"product23"}
]
},
{"id":3,"name":"Category3",
"products":[
{"id":6, "name":"product31"}
]},
{"id":4,"name":"Category4",
"products":[
{"id":7, "name":"product41"},
{"id":8, "name":"product42"},
{"id":9, "name":"product43"},
{"id":10, "name":"product43"}
]}
];
If you need to show each route on its own window without the corresponding master then the routing and hbs templates need to change as follows,
http://emberjs.jsbin.com/cajalosu/1/edit
hbs
<script type="text/x-handlebars" data-template-name="categories">
this is all categories<br/>
<ul>
{{#each category in model}}
<li>
{{#link-to "category" category.id}}
id:{{category.id}} name:{{category.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="category">
this is category {{model.name}}<br/>
with products:<br/>
<ul>
{{#each product in model.products}}
<li>
{{#link-to "product" model.id product.id}}
id:{{product.id}} name:{{product.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="product">
this is product:<br/>
{{model.name}}<br/>
</script>
js
....
App.Router.map(function () {
this.route('categories', {path: '/'});
this.resource('category', { path: 'category/:category_id'});
this.resource('product', { path: 'category/:category_id/product/:product_id' });
});
....
App.ProductRoute = Ember.Route.extend({
model: function(params) {
return allCategoriesData.findBy("id",parseInt(params.category_id,10)).products.findBy("id",parseInt(params.product_id,10));
}
});
....
EDIT - transition to first object of first category
http://jsbin.com/felalizo/1#/category/1/product/1

Ember Data pluralizing url endpoints when using store.find

I got an error with
GET http://localhost:63342/people 404 (Not Found)
and
Assertion failed: Error while loading route:....
Why does this error http://localhost:63342/people 404 appears? I don't have this route.
here is my js code:
window.Models = Ember.Application.create();
Models.Router.map(function () {
//匹配路由后显示person_list模板
this.resource('persons', { path: '/' });
});
Models.PersonsRoute = Ember.Route.extend({
model:function(){
return this.store.find('person');
},
setupController:function(controller, model){
controller.set('content', model)
}
});
Models.Person = DS.Model.extend({
name: DS.attr('string'),
description:DS.attr('string')
});
Models.Person.FEATURES = [{
name:'Kratos Zhang',
description:'a c# coder in 7agree.'
}]
and template
<script type="text/x-handlebars" data-template-name="persons">
<ul>
{{#each item in persons}}
<li>
<h4>{{item.name}}</h4>
<p>{{item.description}}</p>
</li>
{{/each}}
</ul>
why isn't it working?
You can override pathForType in the adapter if that isn't your endpoint, Ember Data by default pluralizes endpoints
App.ApplicationAdapter = DS.RESTAdapter.extend({
pathForType: function(type) {
//return Ember.String.pluralize(type);
return type;
},
});
This isn't necessary, you can delete it
setupController:function(controller, model){
controller.set('content', model)
}
persons doesn't exist in the template
{{#each item in model}}
<li>
<h4>{{item.name}}</h4>
<p>{{item.description}}</p>
</li>
{{/each}}
or
{{#each item in controller}}
<li>
<h4>{{item.name}}</h4>
<p>{{item.description}}</p>
</li>
{{/each}}

Assertion failed: Your server returned a hash with the key category but you have no mapping for it

Note really sure what to do. I'm trying to use a slug instead of ID but when I try to go directly to a link (/categories/source), I get the error. I've tried adding mappings in the RESTadapter but it hasn't worked. I ha
I've a feeling that my problem is in my CategoryRoute/model code but I can't see to get the right combo. Any help is appreciated.
To be honest, I don't know why it has to make two REST calls when it's already got the model info available. Any help is appreciated, thanks!
Json responses
{"categories":[{"id":1,"name":"Misc","slug":"misc"},{"id":2,"name":"Technology","slug":"technology"},{"id":3,"name":"Ecommerce","slug":"ecommerce"},{"id":4,"name":"Visitor","slug":"visitor"},{"id":5,"name":"Content","slug":"content"},{"id":6,"name":"evars","slug":"evars"},{"id":7,"name":"Marketing","slug":"marketing"},{"id":8,"name":"Props","slug":"props"},{"id":9,"name":"Source","slug":"source"}]}
{"category":{"id":2,"name":"Technology","slug":"technology"}}
Html file
<script type="text/x-handlebars">
<div class="container">
<div class="navbar navbar-inverse">
<a class="navbar-brand" href="#">LayerSpark</a>
<ul class="nav navbar-nav">
<li>{{#linkTo 'categories'}}Categories{{/linkTo}}</li>
<li>{{#linkTo 'help'}}Help{{/linkTo}}</li>
<li>{{#linkTo 'account'}}Account{{/linkTo}}</li>
</ul>
</div>
</div>
<div class="container">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="categories">
<div {{bindAttr class=":row"}}>
<div {{bindAttr class=":col-lg-4"}}>
<ul>
{{#each model}}
<li>{{#linkTo 'category' this}}{{name}}{{/linkTo}}</li>
{{/each}}
</ul>
</div>
<div {{bindAttr class=":col-lg-8"}}>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="category">
Post:<br>
My name is :: {{ name }}<br>
My id is :: {{id}}<br>
My slug is :: {{slug}}<br>
</script>
<script type="text/x-handlebars" id="categories/index">
<p class="text-warning">Please select a Category</p>
</script>
App.js
App = Ember.Application.create({
LOG_TRANSITIONS:true
});
var attr = DS.attr;
App.Category = DS.Model.extend({
name: attr('string'),
slug: attr('string')
});
DS.RESTAdapter.configure("plurals", {
category: "categories"
});
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.create({
url: '/api',
mappings:{
category:'App.Category'
}
})
});
App.Router.map(function() {
this.resource('about');
this.resource('categories', function() {
this.resource('category',{path:':category_slug'});
});
this.resource('help');
this.resource('account');
});
App.CategoriesRoute = Ember.Route.extend({
model: function() {
return App.Category.find();
}
});
App.CategoryRoute = Ember.Route.extend({
model: function(route,model) {
console.log(route);window.mod = model;
return App.Category.find({slug:route.category_slug});
},
serialize: function(model) {
return {
category_slug: model.get('slug')
};
}
});
Ember.Handlebars.registerBoundHelper('markdown', function(input) {
return new Handlebars.SafeString(showdown.makeHtml(input));
});
Ember.Handlebars.registerBoundHelper('date', function(date) {
return moment(date).fromNow();
});
I used the following router setup so that I could have a resource of Post corresponding to a route of /posts/:id. Also gives comments belonging to posts.
this.resource('posts', function() {
this.route('new');
});
this.resource('post', { path: '/posts/:post_id' }, function() {
this.resource('comments', function() {
this.route('new');
this.route('create');
});
this.route('comment', { path: 'comments/:comment_id'});
});
I had to define the categories resource separately and then explicitly state that post was under the /posts route in the definition of the resource. Maybe you need a router like:
this.resource('categories', function() {});
this.resource('category',{path:'/categories/:category_slug'});

How to render hasMany relationship data?

How can I get the following example code to render not just the person data but also the phone numbers of that person when rendering the URL /#/persons/1. I try to loop though the phone numbers with {{#each phoneNumber in phoneNumbers}} but there has to be a fundamental understanding error on my side.
Right now I only get:
app.js
App = Ember.Application.create({
LOG_TRANSITIONS: true,
rootElement: '#container'
});
// Router
App.Router.map(function() {
this.resource('persons', function() {
this.resource('person', { path: ':person_id' });
});
this.resource('phoneNumbers', function() {
this.resource('phoneNumber', { path: ':phone_number_id' });
});
});
App.PersonsRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
});
App.PhoneNumbersRoute = Ember.Route.extend({
model: function() {
return App.PhoneNumber.find();
}
});
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
phoneNumbers: DS.hasMany('App.PhoneNumber')
});
App.PhoneNumber = DS.Model.extend({
number: DS.attr('string'),
person: DS.belongsTo('App.Person')
});
App.Person.FIXTURES = [{
id: 1,
firstName: 'X',
lastName: 'Smith'
}, {
id: 2,
firstName: 'Y',
lastName: 'Smith'
}];
App.PhoneNumber.FIXTURES = [{
id: 1,
person_id: 1,
number: '20'
}, {
id: 2,
person_id: 1,
number: '23'
}, {
id: 3,
person_id: 2,
number: '42'
}];
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Phone book</title>
</head>
<body>
<div id='container'></div>
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="persons">
<p>All people:</p>
<ul>
{{#each controller}}
<li>{{firstName}} {{lastName}}</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="person">
<h1>{{firstName}} {{lastName}}</h1>
{{#if phoneNumbers.length}}
<ul>
{{#each phoneNumber in phoneNumbers}}
<li>{{phoneNumber.number}}</li>
{{/each}}
</ul>
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="phone_numbers">
<ul>
{{#each controller}}
<li>{{number}}</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="phone_number">
<h1>{{number}}</h1>
</script>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="js/handlebars.js"></script>
<script type="text/javascript" src="js/ember.js"></script>
<script type="text/javascript" src="js/ember-data.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</html>
Ember-data doesn't act like ActiveRecord in this regard: when you use has_many you have to have a property that is an array of ids for the records. It isn't enough to have a foreign key for the owner on the reciprocal model.
try adding phoneNumbers: [1, 2] to your first Person fixture, and phoneNumbers: [3] to the second.
If you were using the RESTAdapter, those key names would be phone_number_ids instead. If you use active_model_serializers you can use the embed feature to automatically include an array of ids.