Ember - Displaying API response data on Template.hbs - ember.js

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;
});
}

Related

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!

ember model rendering strange behaviuor

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 }),
});

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.

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;
});
}
});