Sharing an Ember Object between controllers with ember-cli - ember.js

I'm trying to share a Ember object between different controllers in order to 'decorate' it with more information as you go through the app.
In a simple Ember app I would just do:
App.UserDetails = Ember.Object.extend({name: 'Darryl', age: 26 });
App.DetailsRoute = Ember.Route.extend({
model: function () {
return App.UserDetails;
}
});
App.AccountRoute = Ember.Route.extend({
model: function () {
return App.UserDetails;
}
});
But I can't see I would organise this in Ember CLI
Thanks

You should use data persistence library like Ember Data, because it was designed for things like storing user details and there are tons of information how to do that, but you ask about sharing an object so:
You can use Ember.Service:
Details service:
import Ember from 'ember';
export default Ember.Service.extend({
name: 'Darryl',
age: 26
});
Rest:
App.DetailsRoute = Ember.Route.extend({
details: Ember.inject.service(),
model: function () {
return this.get('details');
}
});
App.AccountRoute = Ember.Route.extend({
details: Ember.inject.service(),
model: function () {
return this.get('details');
}
});

The same thing can be done like this,
//userdetails.js
export default Ember.Object.create({
name: 'Darryl',
age: 26
});
In your Routes,
//details.js
import obj from 'path/userdetails';
export default Ember.Route.extend({
model : function(){
return obj;
}
});
//account.js
import obj from 'path/userdetails';
export default Ember.Route.extend({
model : function(){
return obj;
}
});

Related

Ember nested global routes (Wildcard)

I'm trying to do something like this in my routes:
this.route('products', { path: "/products/*choises"}, function() {
this.route('promotion', {path: "/promotion/*offers"});
});
product route:
offerPath: function(params){
this.transitionTo('product.promotion', params);
}
The problem is that it doesn't matter the promotion that I visit, the app thinks is part of the products route.
How can I do this? I need them to be nested.
Update:
You can use beforeModel(transition) hook in router to check what's in the url.
http://example.com/products/manufacturer-209/series-881/tag-17143/none/494822/f‌​lawless
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel(transition) {
console.log(transition.params.products.choises)
// if you use this url: http://example.com/products/manufacturer-209/series-881/tag-17143/none/494822/f‌​lawless
// console log would be: "manufacturer-209/series-881/tag-17143/none/494822/f‌​lawless"
}
});
At least you have the rest of the url so, you can filter out the important information and redirect with this.transitionTo() to the exact place.
You could have the following route:
http://example.com/products/123/promotions/456
or
http://example.com/products/awesome_souce/promotions/monday_deal
In the first case, your route would look like this:
this.route('product', { path: "/products/:product_id"}, function() {
this.route('promotion', {path: "/promotions/:promotion_id"});
});
In the second case, maybe like this:
this.route('product', { path: "/products/:product_name"}, function() {
this.route('promotion', {path: "/promotions/:promotion_name"});
});
Finally, your route handlers can download the proper models (example for the first case):
// app/routes/product.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('product', params.product_id);
}
});
---
// app/routes/product/promotion.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// you can get access to the parent route model if you need for the api query
const product = this.modelFor('product');
return this.store.findRecord('promotion', params.promotion_id);
}
});
If you need only the param from the product route, instead of returning a whole record, for example you can just return params.product_name, so you will have access to a string with this.modelFor('product') in a subroute level.

Access model from another route in EmberJS

I have this router.js:
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('analyses', function() {
this.route('new', { path: 'new'});
this.route('show', { path: ':analysis_id' });
this.route('edit', { path: ':analysis_id/edit'});
this.route('dataFunctions', { path: ':analysis_id/dataFunctions', resetNamespace: true }, function() {
this.route('new', { path: 'new'});
});
});
export default Router;
and these 2 models
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
dataFunctions: DS.hasMany('dataFunction', {async: true}),
});
and
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
analysis: DS.belongsTo('analysis', {async: true})
});
The contents of routes/data-functions/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
console.log(this.store.findRecord("analysis", id).get("dataFunctions"));
}
});
The contents of routes/analyses/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll("analysis");
},
setupController(controller, model) {
controller.set("analyses", model);
}
});
The contents of routes/analyses/show.js:
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('analysis', params.analysis_id);
},
setupController(controller, model) {
controller.set("analysis", model);
}
});
When I navigate to /analyses/1/dataFunctions my analysis model is loaded (it is show in ember inspector) but I can’t seem to access it in my data-functions/index.js route. How do I go about this? I need the analysis model to extend findAll in my data-function adapter to change the url for a rails-api nested resource.
I tried using this.store.modelFor("analysis").get("id") but it errors saying get is not a funcion.
I am using Ember 2.0.1 and Ember Data 2.0.0. I am lost here, any help would be greatly appreciated.
It's returning no mode found because you're returning a log statement in the dataFunctions route. Give this a try.
export default Ember.Route.extend({
model(params) {
return this.store.findRecord("analysis", params.analysis_id)
.then( (analysis) => {
return analysis.get('dataFuncitons');
})
}
});
Ok, so went through the code there was a few issues. There was a typo in analysis, and the resetNamespace is making things act weird. Also removed some of the redundant path names.
Router.map(function() {
this.route('analysis', function() {
this.route('new');
this.route('show', { path: ':analysis_id' });
this.route('edit', { path: ':analysis_id/edit'});
this.route('dataFunctions', { path: ':analysis_id/dataFunctions'}, function() {
this.route('new');
});
});
});
Rename the dataFunctions model to data-function to reflect proper conventions, e.g. using singular and dasherizing.
The analysis model
export default DS.Model.extend({
name: DS.attr('string'),
dataFunctions: DS.hasMany('data-function', {async: true}),
});
The data-function model
export default DS.Model.extend({
name: DS.attr('string'),
analysis: DS.belongsTo('analysis', {async: true})
});

Using this.store.filter with the ID of the belongs_to model

I'm trying to retrieve all the layouts for a given account.
/app/models/account.js
import DS from 'ember-data';
export default DS.Model.extend({
companyName: DS.attr('string'),
layouts: DS.hasMany('layout')
});
/app/models/layout.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
account: DS.belongsTo('account', { async: true })
});
/app/routes/layouts.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
layouts: this.store.filter('layout', { account_id: 1 }, function(layout) {
console.log(layout.get('account').content.id);
return layout.get('account').content.id === 1;
})
});
}
});
The console.log line is outputting the ID that I'm expecting (1). In Ember inspector I can see 5 layout models and under 'Belongs To' I can see: account : <DS.PromiseObject:ember960>. Clicking that brings up content : <batmics#model:account::ember600:1> and clicking that brings up the properties, including the correct ID.
But in my templates layouts is empty... and I've no idea why.
Incidentally, layouts: this.store.find('layout', { account_id: 1 }) works, but I need it to use the filter so that it's an active array.
Ember Data works with all its IDs as strings.
Changing your check to === '1' should get this going for you.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
layouts: this.store.filter('layout', { account_id: 1 }, function(layout) {
console.log(layout.get('account').content.id);
return layout.get('account').content.id === '1';
})
});
}
});

ember js - Uncaught Error: Assertion Failed: Cannot delegate set

i face this error when saving data to api
Uncaught Error: Assertion Failed: Cannot delegate set('firstName', a) to the 'content' property of object proxy <>: its 'content' is undefined
below is my code
import Ember from 'ember';
export default Ember.ObjectController.extend({
isValid: Ember.computed(
'email',
'firstName',
'lastName',
'twitter',
function() {
return !Ember.isEmpty(this.get('email')) &&
!Ember.isEmpty(this.get('firstName')) &&
!Ember.isEmpty(this.get('lastName')) &&
!Ember.isEmpty(this.get('twitter'));
}
),
actions:{
save: function() {
if (this.get('isValid')) {
var _this = this;
this.get('model').save().then(function(friend) {
_this.transitionToRoute('friends.show', friend);
});
} else {
this.set('errorMessage', 'You have to fill all the fields');
}
},
cancel: function() {
this.transitionToRoute('friends');
}
}
});
Don't use ObjectController. Use simply Ember.Controller.extend.
I see this on the ember-cli-101 book. I encountered the same issue myself. It's likely that you are not properly setting the model attribute in your route. Based on the book, the error either occurs in the edit or new route.
if your router.js looks like this:
...
Router.map(function() {
this.resource('friends', function() {
this.route('new');
this.route('show', { path: ':friend_id' });
this.route('edit', { path: ':friend_id/edit' });
});
});
...
the friends/index route needs to set the model attribute:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('friend');
},
});
and the friends/new route needs to set the model in a different way:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('friend');
},
});
For anyone not familiar with the book (mentioned above) the question is from code that is in the book, which is why I referenced it. In most cases, if you get this issue it is likely because you forgot to or did not set up the model attribute correctly in the appropriate route.

How to load belongsTo/hasMany relationships in route with EmberJS

In my EmberJS application I am displaying a list of Appointments. In an action in the AppointmentController I need to get the appointments owner, but the owner always returns "undefined".
My files:
models/appointment.js
import DS from 'ember-data';
export default DS.Model.extend({
appointmentStatus: DS.attr('number'),
owner: DS.hasMany('person'),
date: DS.attr('Date')
});
models/person.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string')
});
templates/appointmentlist.js
{{#each appointment in controller}}
<div>
{{appointment.date}} <button type="button" {{action 'doIt'}}>Do something!</button>
</div>
{{/each }}
controllers/appointmentlist.js
export default Ember.ArrayController.extend({
itemController: 'appointment'
});
controllers/appointment.js
export default Ember.ObjectController.extend({
actions:{
doIt: function(){
var appointment = this.get('model');
var owner = appointment.get('owner'); //returns undefined
//Do something with owner
}
}
});
Now, I know I can change the owner-property to owner: DS.hasMany('person', {async: true}), and then handle the promise returned from appointment.get('owner');, but that is not what I want.
I have discovered that if I do this {{appointment.owner}} or this {{appointment.owner.name}} in the appointmentlist template, the owner record is fetched from the server. So I guess Ember does not load relationships unless they are used in the template.
I think that the solution to my problem is to use the appointmentlists route to fetch the record in the belongsTo relationship. But I can't figure out how.
Maybe something like this?
routes/appointmentlist.js
export default Ember.Route.extend({
model: function() {
return this.store.find('appointment');
},
afterModel: function(appointments){
//what to do
}
});
EDIT
I did this:
routes/appointmentlist.js
export default Ember.Route.extend({
model: function() {
return this.store.find('appointment');
},
afterModel: function(appointments){
$.each(appointments.content, function(i, appointment){
var owner= appointment.get('owner')
});
}
});
and it works, but I do not like the solution...
You are still asynchronously loading those records, so if you are fast enough you could still get undefined. It'd be better to return a promise from the afterModel hook, or just modify the model hook to do it all.
model: function() {
return this.store.find('appointment').then(function(appointments){
return Ember.RSVP.all(appointments.getEach('owner')).then(function(){
return appointments;
});
});
}
or
model: function() {
return this.store.find('appointment');
},
afterModel: function(model, transition){
return Ember.RSVP.all(model.getEach('owner'));
}
Another way to go is:
export default Ember.Controller.extend({
modelChanged: function(){
this.set('loadingRelations',true);
Ember.RSVP.all(this.get('model').getEach('owner')).then(()=>{
this.set('loadingRelations',false);
});
}.observes('model')
});
This way the transition finishes faster and the relations are loaded afterwards. The loading-state can be observed through loadingRelations.
When there are a lot of relations to load I think this gives a better UX.
You want to load all the assocations in the route, because you want to use Fastboot for search engines and better first time site opened experience.
Holding your assocation loading after primary models are loaded, might not be the best decision.
I am using a syntax to load all assocations in the route:
let store = this.store;
let pagePromise = store.findRecord('page', params.page_id);
let pageItemsPromise = pagePromise.then(function(page) {
return page.get('pageItems');
});
return this.hashPromises({
page: pagePromise,
pageItems: pageItemsPromise
});
And for this.hashPromises I got a mixin:
import Ember from 'ember';
export default Ember.Mixin.create({
hashPromises: function(hash) {
let keys = Object.keys(hash);
return Ember.RSVP.hashSettled(hash).then(function(vals) {
let returnedHash = {};
keys.forEach(function(key) {
returnedHash[key] = vals[key].value;
});
return returnedHash;
});
}
});