ember model rendering strange behaviuor - ember.js

While experimenting with ember and ember-localforage-adapter I noticed a strange behaviour.
ember.debug.js:4888DEBUG: Ember : 1.12.0
ember.debug.js:4888DEBUG: Ember Data : 1.0.0-beta.18
ember.debug.js:4888DEBUG: jQuery : 1.11.3
I have three models:
/app/models/ledger.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
purchases: DS.hasMany('purchase', {async: true}),
players: DS.hasMany('player', {async: true}),
});
/app/models/purchase.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
ledger: DS.belongsTo('ledger', {async: true}),
player: DS.belongsTo('player', {async: true})
});
/app/models/player.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
balance: DS.attr('number'),
ledger: DS.belongsTo('ledger',{ async: true }),
purchases: DS.hasMany('purchase', {async: true}),
});
and a simple route:
/app/routes/purchase.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('purchase', params.purchase_id);
}
});
My experimental template is
/app/templates/purchase.hbs
name: {{model.name}}
<br>
amount: {{model.amount}}
<br>
player: {{model.player.name}}
and it works showing me all invoked attributes. But if I add another line like
name: {{model.name}}
<br>
amount: {{model.amount}}
<br>
player: {{model.player.name}}
<br>
ledger: {{model.player.ledger.title}}
it shows the name of the player for just an instant and never shows the ledger title. How should load in the store the requested record to have them available in my template?

After reading this I added the property payer to the purchase model:
payer: function(){
return DS.PromiseObject.create({
promise: this.get( 'player' )
});
}.property('')
to be used in the template instead of player:
<h3>show purchase</h3>
model.name: <strong>{{model.name}}</strong>
<br>
model.amount: <strong>{{model.amount}}</strong>
<br>
<br>
model.payer.name: <strong>{{model.payer.name}}</strong>
<br>
model.payer.ledger.title: <strong>{{model.payer.ledger.title}}</strong>
<br>
<br>
model.player.name: <strong>{{model.player.name}}</strong>
<br>
model.player.ledger.title: <strong>{{model.player.ledger.title}}</strong>
This approach is very similar to the one suggested by Artych but, maybe because of my Rails background, I prefer to access an associated record from anywhere by the model and not by the controller associated to the route
The following gif (download it if don't see the animation) show the behaviour of both:
Can someone explain what happen? Why model.player.name disappear at the time when model.plyer.ledger.title appear? What's the difference from the template point of view between payer and player considering that both return a DS.PromiseObject?

You could try to resolve model.player.ledger in afterModel hook:
afterModel: function(model) {
var controller = this.controllerFor('purchase');
model.get('player').then(function(player) {
controller.set('player', player);
return player.get('ledger');
}).then(function(ledger){
controller.set('playerLedger', ledger);
}, function(error) {
console.log("promise is not resolved", error);
});
}
In template
{{player.name}} | {{playerLedger.title}}
I think you could see why model.player.ledger is not resolved or it could be empty.
You have typo in player model. Should be:
//app/models/player.js
import DS from 'ember-data';
export default DS.Model.extend({
// ...
ledger: DS.belongsTo('ledger', { async: true }),
//not ledger: DS.belongsTo({ async: true }),
});

Related

Ember - Displaying API response data on Template.hbs

I'm very new to Ember - highly appreciated if anyone can assist. I need to display the network request response:
This is my port route:
import Route from '#ember/routing/route';
export default Route.extend({
model(params) {
this.store.findRecord('node', params.node_id).then((res) => {
var port = res.ports.arrangedContent.currentState;
console.log(port);
return port;
});
}
});
Here is the port model:
import DS from 'ember-data';
var ports = {
label: DS.attr('string'),
mode: DS.attr('string'),
node_name: DS.attr('string'),
port_csid: DS.attr('string'),
proxied_ssh_url: DS.attr('string'),
web_terminal_url: DS.attr('string'),
runtime_status: DS.belongsTo('nodeRuntimeStatus', {async: false}),
parent: DS.belongsTo('node', {async: false}),
};
export default DS.Model.extend(ports);
below is the template:
{{#each item in model}}
<span>
{{item.label}}
</span>
{{/each}}
API call happens and I can console.log the ports (array) but no sure why I get nothing to display.
So I found what was wrong with this.
Firstly, as Lux mentioned, I was missing a return in my model().
Second problem was with each helper.
I changed the template from {{#each item in model}} to {{#each model as |item|}} and resolved the issue
you're missing a return in your model() hook. So you should do:
model(params) {
return this.store.findRecord('node', params.node_id).then((res) => {
var port = res.ports.arrangedContent.currentState;
console.log(port);
return port;
});
}

How to load nested records (master-detail records) using Ember Data?

The master record, app/models/account:
import DS from 'ember-data';
export default DS.Model.extend({
username: DS.attr('string'),
emailaddress: DS.hasMany('emailaddresses'),
birthdate: DS.attr('date'),
gender: DS.attr('boolean')
});
The detail record, app/models/emailaddress:
import DS from 'ember-data';
export default DS.Model.extend({
account: DS.belongsTo('account'),
emailaddress: DS.attr('string'),
verificationcode: DS.attr('string'),
isverified: DS.attr('string')
});
The dummy JSON string from the server:
{"id":0,"username":"ikevin","birthdate":"Jan 30, 2017 1:34:38
PM","gender":true,"emailaddresses":[{"id":0,"emailaddress":"aaa#bbb.com","verificationcode":"AAAAAA","isverified":false}]}
The adapter /app/adapters/account.js
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
urlForQueryRecord(query) {
if (query.profile) {
delete query.profile;
return `${this._super(...arguments)}/profile`;
}
return this._super(...arguments);
}
});
The route app/route/index.js:
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return RSVP.hash({
walletbalance: this.get('store').queryRecord('wallet', {balance: true}),
instagramfriendscount: this.get('store').queryRecord('instagram', {friendscount: true}),
accountprofile: this.get('store').queryRecord('account', {profile: true})
});
}
});
And the app/templates/components/account-profile.hbs:
<div class="box">
<div class="title">Your Profile</div>
<div>Username: {{account.accountprofile.username}}</div>
<div>Email Address: {{account.accountprofile.emailaddess}}</div>
<div>Birthdate: {{account.accountprofile.birthdate}}</div>
<div>Gender: {{account.accountprofile.gender}}</div>
</div>
I think there are 2 problems here:
In the Chrome Ember plugin, the data for model type "emailaddress" is always 0. So, that means it is not loaded.
In the app/templates/components/account-profile.hbs, {{account.accountprofile.emailaddess}}is not referring to the correct field. Note: for now it is expected to display only 1 email address.
How do I resolve these problems to load and display nested records?
Thanks!
Yes, I resolved it myself:
I changed it to Nested Arrays (as specified here: http://thejsguy.com/2016/01/29/working-with-nested-data-in-ember-data-models.html)
So the string returned from the server becomes:
{"id":0,"username":"ikevin","birthdate":"Jan 30, 2017 2:01:14
PM","gender":true,"emailaddresses":[{"emailaddress":"aaa#bbb.com","verificationcode":"AAAAAA","isverified":false}]}
And in the .hbs:
<div>Email Address: {{account.accountprofile.emailaddresses.0.emailaddress}}</div>
It displays the email address!
Thanks!

EmberFire Adding relationship from different model

-------------------------------
Ember : 1.13.11
Ember Data : 1.13.15
Firebase : 2.3.2
EmberFire : 1.6.3
jQuery : 1.11.3
-------------------------------
I've got two endpoints in my firebase app. /employees and /subjects. In my ember app I want to add subjects to an employee (employees/$id/subjects). The problem is, I don't know how to load all my subjects from /subjects so I can add them to my array.
This is my routes:
Router.map(function() {
this.route('dashboard');
this.route('employees', function() {
this.route('employee', { path: '/:id'});
});
});
And this is my model
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('employee', params.id);
}
});
I've tried various things to get this to work, creating a subroute this.route(employee, function(){ this.route('subjects') }, loading a second model in my employee model, none of which has worked. I'm new to ember so I might have gotten some things mixed up. Any helps is appreciated.
EDIT
Employee model
export default DS.Model.extend({
name: DS.attr('string'),
position: DS.attr('string'),
accessoryPosition: DS.attr('string'),
education: DS.attr('string'),
experience: DS.attr('string'),
imgUrl: DS.attr('string'),
subjects: DS.hasMany('subject', {async: true})
});
Subject Model
export default DS.Model.extend({
name: DS.attr('string')
});
To maybe describe my intentions a bit better, here is the flow I want:
User selects an employee, employees info is shown along with a list of subjects assigned to that employee. But I also need the full list of subjects available, if I want to assign a new subject to an employee. Hence my question. Hope this makes sense
Okay, then you can do this in your Route:
export default Ember.Route.extend({
model(params) {
return Ember.RSVP.hash({
employee: this.store.findRecord('employee', params.id),
subjects: this.store.findAll('subject')
});
}
});
then in your template:
{{#each employee.subjects as |subject|}}
{{!these are your employee subjects}}
{{subject.name}}
{{/each}}
{{#each subjects as |subject|}}
{{!these are all your subjects}}
{{subject.name}}
{{/each}}

ArrayProxy expects an Array or Ember.ArrayProxy, but you passed object

I am getting this error:
ArrayProxy expects an Array or Ember.ArrayProxy, but you passed object
I am getting my data from a rails application using active-model-serializers. The data is showing in mhy ember inspector but my template is not rendering properly with this error in the console.
Router.map(function() {
this.resource('brands', function() {
this.resource('brand', { path: '/:brand_id' });
});
this.resource('campaigns', function() {
this.resource('campaign', { path: '/:campaign_id' },
this.resource('index'), { path: 'brands/:brand_id' });
});
});
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
brand: this.store.find('brand'),
campaign: this.store.find('campaign')
});
}
});
export default DS.Model.extend({
name: DS.attr('string'),
facebook_page_id: DS.attr('string'),
active: DS.attr('boolean'),
facebook_uid: DS.attr('string'),
facebook_token: DS.attr('string'),
facebook_token_expires: DS.attr('string'),
website_url: DS.attr('string'),
privacy_policy_link: DS.attr('string'),
terms_link: DS.attr('string'),
instagram_account: DS.attr('string'),
instagram_url: DS.attr('string'),
twitter_account: DS.attr('string'),
twitter_url: DS.attr('string'),
avatar_url: DS.attr('string'),
youtube_account: DS.attr('string'),
youtube_url: DS.attr('string'),
favicon_url: DS.attr('string'),
open_graph_url: DS.attr('string'),
campaigns: DS.hasMany('campaign', {async: true})
});
export default DS.Model.extend({
name: DS.attr('string'),
brand_id: DS.attr('string'),
brand: DS.belongsTo('brand', {async: true})
});
{{#each brand in controller}}
<a>
{{#link-to 'brand' this}}
{{brand.name}}
{{/link-to}}
</a>
{{else}}
<a>No brands found.</a>
{{/each}}
No errors in the server logs.
Embers default index controllers are ArrayControllers that expect their model to be an array.
In the model hook of - what appears to be - your BrandsIndexRoute you specified the model as
Ember.RSVP.hash({
brand: this.store.find('brand'),
campaign: this.store.find('campaign')
});
Which returns a single object, not an array of brands as is expected.
What you should do instead is something like:
//brands route
export default Ember.Route.extend({
model: function() {
return this.store.find('brand');
}
});
//campaigns route
export default Ember.Route.extend({
model: function() {
return this.store.find('campaign');
}
});
You're trying to iterate over the controller, but your controller is decorating a object with two properties on it, not an array. Your object looks like this:
{
brand: [...],
campaign: [...]
}
Additionally if you have a controller defined as an array controller it will throw this error (which is what I'm guessing is really happening). Because you're passing an object to your controller, instead of an array.

Ember App Deployed to Heroku Isn't Loading Data from REST API

Thanks in advance for any help.
I'm having a strange problem with a simple Ember app where, after deploying to Heroku, my models only make the REST call after the index route for the model is hit.
For example, I have two models: Resort and Forecast. Each have a belongsTo relationship, so every Resort has a Forecast and vice versa. In the resort template, there's a link to the corresponding forecast. When clicked, it properly routes to the forecast, however all the attributes in the forecast are undefined because it never made the API call to retrieve the forecasts JSON blob. I can watch the network tab in Chrome tools to verify this. When I navigate to /forecasts, the REST call is made, and the data is populated.
For whatever reason, all the API calls are made as I would expect. Once deployed to Heroku, this isn't the case.
This app is using ember-cli, and the relevant code follows:
/adapters/application.js
import DS from "ember-data";
var ApplicationAdapter = DS.ActiveModelAdapter.extend({
host: 'http://api.firstchair.io',
buildURL: function() {
var base;
base = this._super.apply(this, arguments);
return "" + base + ".json";
}
});
export default ApplicationAdapter;
/models/resort.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
state: DS.attr('string'),
latitude: DS.attr('string'),
longitude: DS.attr('string'),
region: DS.attr('string'),
token: DS.attr('string'),
weather: DS.attr('string'),
temperature: DS.attr('string'),
last_24_hours_snowfall: DS.attr('string'),
last_48_hours_snowfall: DS.attr('string'),
last_72_hours_snowfall: DS.attr('string'),
wind: DS.attr('string'),
conditions: DS.attr('string'),
baseDepth: DS.attr('string'),
humanReadableWeather: DS.attr('string'),
forecast: DS.belongsTo('forecast'),
fullDescription: function() {
return this.get('name') + ', ' + this.get('state');
}.property('name', 'state'),
currentSnowfall: function() {
return (this.get('last_24_hours_snowfall') || 0) + '"';
}.property('last_24_hours_snowfall'),
hasWind: function() {
return this.get('wind') > 0;
}.property('wind')
});
/models/forecast.js
import DS from 'ember-data';
export default DS.Model.extend({
startsAt: DS.attr('string'),
endsAt: DS.attr('string'),
weather: DS.attr('array'),
highs: DS.attr('array'),
lows: DS.attr('array'),
resort: DS.belongsTo('resort'),
days: function() {
var weather = this.get('weather');
var highs = this.get('highs');
var lows = this.get('lows');
if (!weather) { return []; }
return weather.map(function(currWeather, index) {
return {
weather: currWeather,
high: highs[index],
low: lows[index],
daysSince: index
};
});
}.property('weather', 'highs', 'lows')
});
/routes/resort.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
var resort = this.store.find('resort', params.resort_id);
console.log(resort);
console.log(resort.forecast);
return resort;
}
});
/routes/resorts.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('resort');
}
});
/routes/forecast.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
console.log('hello');
return this.store.find('forecast', params.forecast_id);
}
});
/routes/forecasts.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('forecast');
}
});
Is there something I should be doing to ensure that the data is loaded eagerly?
You can look at the code in its entirety at: https://github.com/firstchair-io/webapp
Any insight into what might be going wrong would be greatly appreciated. Thank you.
It sounds like the relationships are not being sideloaded from the backend server. So the records contain an array of ID's in the hasMany fields, but the data itself is not sent automatically. To cause Ember Data to load the associated records, set {async: true} on the relation.