I am working with Ember and Ember-data. But the JSON which i receive is not par with the Ember side-loading standards. The JSON does'nt have a root model. Also the models are embedded and some times haves Ids and sometimes does not have Id.
I have seen couple of links on how to add root model using extract hook and also how to play with embedded model using
App.ColorSerializer = DS.RestSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
foos: {embedded: 'always'}
}
});
This code is taken from this link.
This is the JSON used there
{
colors:[
{
id: 1,
color: "red",
foos:[
{
id:1,
name:'something 1'
},
{
id:2,
name:'something 2'
}
]
},
...
Now the problem that i am facing is that my JSON could also look like below(no root model "color")
{
id: 1,
color: "red",
foos:[
{
id:1,
name:'something 1'
},
{
id:2,
name:'something 2'
}
]
},
...
or even like this(without Ids for foo objects)
{
id: 1,
color: "red",
foos:[
{
name:'something 1'
},
{
name:'something 2'
}
]
},
...
Is there any way i can handle this? How do i add Ids to the embedded model foo? Also is there some solution/plugin which would accept any kind of embedded JSON and convert it into side loaded JSON and added Ids if needed.
I have seen this solution. Does it really work? Because it does not use the latest EmbeddedRecordsMixin
I used a generic transform for arrays:
// /transforms/array.js
import DS from "ember-data";
import Ember from "ember";
export default DS.Transform.extend({
deserialize: function (value) {
if (Ember.isArray(value)) {
return Ember.A(value);
} else {
return Ember.A();
}
},
serialize: function (value) {
if (Ember.isArray(value)) {
return Ember.A(value);
} else {
return Ember.A();
}
}
});
Then in my model, I simply use:
foos: DS.attr("array")
Related
Use case :
I am trying to render a page that compare outputs of 2 performance benchmark runs.
Issue :
Only part of the data is getting deserialized at the time of the page render. On multiple refreshs all of the data gets rendered.
The network tab seems to show that the output is sent correctly by the backend.
Execution:
1) The data is rendered using the api /reports/:reportId.
This api is called twice using 2 reportIds to be compared and the difference is then displayed on the UI.
The /reports/:reportId api returns an output like :
{
"data": {
"id": 87,
"type": "reports",
"attributes": {
"status": "STARTED",
"startdatetime": 1532511531000,
"enddatetime": 1533485380052,
"queries": [
{
"startdatetime": 1532511531000,
"enddatetime": 1533485380056,
"reportId": 87,
"id": "Q1"
}
]
}
}
}
The output intermittently renders everything but the "queries" array while deserializing the array perfectly in some runs.
2) To parse the queries array I used a custom array transform like :
import DS from 'ember-data';
export default DS.Transform.extend({
deserialize: function(serialized) {
return (Ember.typeOf(serialized) == "array")
? serialized
: [];
},
serialize: function(deserialized) {
var type = Ember.typeOf(deserialized);
if (type == 'array') {
return deserialized
} else if (type == 'string') {
return deserialized.split(',').map(function(item) {return jQuery.trim(item);});
} else {
return [];
}
}
});
3) The model for reports looks like:
status: DS.attr('string'),
startdatetime: DS.attr('number'),
enddatetime: DS.attr('number'),
queries: DS.attr('array')
4) My route for compare looks like :
import Route from '#ember/routing/route';
import { hash } from 'rsvp';
import { inject as service } from '#ember/service';
export default Route.extend({
model(params) {
return hash({
leftrun: this.store.findRecord('report', params.id1),
rightrun: this.store.findRecord('report', params.id2)
})
}
});
On running the page on debug mode when the queries array doesn't get deserialized,
1) I can see that the "status" and other attributes is accessible.
2) The network tab shows that the output for reports/:runId is "pending" (Which doesn't make sense as the "status" is set.) Once the page is fully loaded, the network tab shows that the full data including the queries array was received by the webapp.
Is there an issue with the flow or my transform that is causing this issue?
I have a route that displays a list of challenges, when I create a new challenge the record is persisted however the model of list of challenges is not updated when I transition back into the route. Is there something I am missing?
//new.js
var challenge = this.store.createRecord('challenge', {
name_en: this.get('model.name_en'),
name_fr: this.get('model.name_fr'),
description_en: this.get('model.description_en'),
description_fr: this.get('model.description_fr'),
end_date: this.get('model.end_date'),
start_date: this.get('model.start_date'),
points_cap: this.get('model.points_cap'),
points_goal: this.get('model.points_goal'),
challenge_type: 1,
short_description_en: this.get('model.short_description_en'),
short_description_fr: this.get('model.short_description_fr'),
excluded_activities: excluded
});
// Persist record.
challenge.save().then((challenge) => {
this.transitionToRoute('challenges');
}).catch((error) => {
this.handleError(error, 'error.system_error');
});
//router.js
Router.map(function() {
this.route('challenges', function() {
this.route('new');
this.route('challenge', {
path: ':challenge_id'
}, function() {
this.route('delete');
this.route('edit');
});
});
//challenges.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
import UserProfile from '../models/user-profile';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
userProfile: UserProfile.create(),
model: function() {
return this.store.query('challenge', {league_id: this.get('userProfile.league_id')});
}
});
//new challenge payload
{
"activity_exclusion_list":[
],
"challenge_type":1,
"challengeUrl":null,
"end_date":"31-10-2015",
"number_participants":null,
"number_teams":null,
"points_cap":null,
"points_goal":null,
"start_date":"01-10-2015",
"leagueId":"1",
"teams":[
],
"name_lang":{
"en":"New Challenge ",
"fr":null
},
"description_lang":{
"en":"New Challenge",
"fr":null
},
"short_description_lang":{
"en":"New Challenge",
"fr":null
}
}
//response from new challenge
{
"challenge_type":"Individual",
"description":" ",
"description_fr":null,
"description_lang":{
"en":"New Challenge",
"fr":null
},
"challengeUrl":" ",
"start_date":"01-10-2015",
"end_date":"31-10-2015",
"name":" ",
"name_fr":null,
"name_lang":{
"en":"New Challenge ",
"fr":null
},
"points_cap":0,
"points_goal":0,
"short_description":" ",
"short_description_fr":null,
"short_description_lang":{
"en":"New Challenge",
"fr":null
},
"number_participants":0,
"number_teams":0,
"teams":[
],
"challenge_id":265,
"activity_exclusion_list":[
],
"leagueId":1
}
In your challenges route have you tried using a this.store.filter instead? The issue could be that the query function only returns a RecordArray, whereas filter returns a Live RecordArray which will update your templates and everything else when the promise (in this case the successful save) is resolved.
model: function() {
return this.store.filter('challenge', {league_id: this.get('userProfile.league_id')}, function() {
// We're not actually filtering, so just return true for everything
return true;
});
}
I had the same problem and this turned out to be the solution, hope it helps!
Ember Docs reference
I have defined a model(app/models/job.js)
import DS from 'ember-data';
export default DS.Model.extend({
status: DS.attr(),
result: DS.attr()
});
And I am trying to load it from the index controller(app/controllers/index.js)
import Ember from 'ember';
export default Ember.Controller.extend({
productName: "",
customerName: "",
startDate: "",
endDate: "",
actions: {
search: function() {
let data = this.store.find("job", '9e5ce869-89b3-4bfc-a70f-034593c21eae');
return data;
}
}
});
The HTTP response I get is:
{
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}
}
How ever I get following error and warning:
WARNING: Encountered "status" in payload, but no model was found for model name "status" (resolved model name using next-gen-analytics#serializer:job:.modelNameFromPayloadKey("status"))
WARNING: Encountered "result" in payload, but no model was found for model name "result" (resolved model name using next-gen-analytics#serializer:job:.modelNameFromPayloadKey("result"))
TypeError: Cannot read property '_internalModel' of undefined
at finders.js:50
at Object.Backburner.run (ember.debug.js:224)
at ember$data$lib$system$store$$Service.extend._adapterRun (store.js:2043)
at finders.js:45
at tryCatch (ember.debug.js:56151)
at invokeCallback (ember.debug.js:56166)
at publish (ember.debug.js:56134)
at ember.debug.js:32577
at Queue.invoke (ember.debug.js:910)
at Object.Queue.flush (ember.debug.js:974)onerrorDefault # ember.debug.js:32616exports.default.trigger # ember.debug.js:56792Promise._onerror # ember.debug.js:57758publishRejection # ember.debug.js:56065(anonymous function) # ember.debug.js:32577Queue.invoke # ember.debug.js:910Queue.flush # ember.debug.js:974DeferredActionQueues.flush # ember.debug.js:770Backburner.end # ember.debug.js:160Backburner.run # ember.debug.js:228run # ember.debug.js:20238ember$data$lib$system$adapter$$default.extend.ajax.Ember.RSVP.Promise.hash.success # rest-adapter.js:831jQuery.Callbacks.fire # jquery.js:3148jQuery.Callbacks.self.fireWith # jquery.js:3260done # jquery.js:9314jQuery.ajaxTransport.send.callback # jquery.js:9718
Any suggestion will be appreciated
UPDATE
I was thinking this is kind of a bug, so I went ahead to log a bug on ember-data github repo, got the response from #wecc in an hour or so (NICE)
https://github.com/emberjs/data/issues/3683
So to fix this issue, I wrote my own serializer.
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizePayload: function(payload) {
return {
'job': {
id: '9e5ce869-89b3-4bfc-a70f-034593c21eae',
status: payload.status,
result: payload.result
}
};
}
});
And now, it starts working.
OPINION I can think of why they implemented the default RESTSerializer this way, but we probably should give more information to the users on the documentation of the ember-data, otherwise, newbie who is trying to use it will get lost.
1) Your server response should have a root element with the same name, as model's. So, for single object it shuld be:
{
"job": {
"id": "<backend must provide an id, integer preferred>"
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}
}
And for multiple objects:
{
"jobs": [{
"id": "<backend must provide an id, integer preferred>"
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}, {/*next object*/}]
}
I tried to debug it, and I found that the code goes to rest-serializer.js and extractSingle function, and goes to the line "if (!store.modelFactoryFor(modelName)) {" and it returns "false".
extractSingle: function (store, primaryTypeClass, rawPayload, recordId) {
Ember.deprecate("`serializer.normalizePayload` has been deprecated. Please use `serializer.normalizeResponse` with the new Serializer API to modify the payload.", this.normalizePayload === JSONSerializer.prototype.normalizePayload, {
id: "ds.serializer.normalize-payload-deprecated",
until: "2.0.0"
});
var payload = this.normalizePayload(rawPayload);
var primaryRecord;
for (var prop in payload) {
var modelName = this.modelNameFromPayloadKey(prop);
if (!store.modelFactoryFor(modelName)) {
Ember.warn(this.warnMessageNoModelForKey(prop, modelName), false, {
id: "ds.serializer.model-for-key-missing"
});
continue;
}
var isPrimary = this.isPrimaryType(store, modelName, primaryTypeClass);
var value = payload[prop];
Is this possible? I know I can do:
this.store.find('model', 1)
but that's not what I want. I would like to retrieve the json in this format: (working if I retrieve it in the route like this ):
App.OptionsRoute = Ember.Route.extend({
model: function () {
return {
"options":{
"id": "1",
"headline": "A Headline",
"results": [
{
"id": "1",
"title": 'Option 1',
},
{
"id": "2",
"title": "Option 2"
}
]
}
};
}
});
options model:
App.Options = DS.Model.extend({
headline: DS.attr(),
results: DS.attr()
});
options.hbs
<h5>{{options.headline}}</h5>
{{#each item in options.results}}
<h5>{{item.title}}</h5>
{{/each}}
I am using the RESTAdapter. And that is the only model that will be retrieved on that route. I would like to be able to use ember-data, but store.find expects an array.
You're missing a point here. First of all you're using bad format for your response. You need custom serializer. You can also use a bit more dirty workaround like this(but it works). Route:
App.OptionsRoute = Ember.Route.extend({
model: function() {
that = this;
return new Promise(function (resolve, reject) {
url = that.store.adapterFor('option').buildURL('option');
Ember.$.getJSON(url).then(function (json) {
body = json.options;
correct = {
options: [
body
]
};
that.store.pushPayload('option', correct);
resolve(that.store.all('option').get('firstObject'));
});
});
}
});
Template:
<h5>{{model.headline}}</h5>
{{#each item in model.results}}
<h5>{{item.title}}</h5>
{{/each}}
Application outputs:
A Headline
Option 1
Option 2
Working demo - please notice that I'm using $.mockjax to recreate your response from server, but it matches format you provided.
Updated 03/17/2016 to better reflect current best practices for EmberJS v1.13.0 and up.
Problem
I am rendering Highcharts into a component and i almost have it working, but the binding of a property into the component is getting lost somewhere.
This is how I call the component:
//templates/index.hbs
{{pie-chart data=pieData}}
This is what the data property looks like (currently set in a controller):
//controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
init() {
this._super(...arguments);
this.pieData = [
['0 - 30', 2.5],
['31 - 60', 7.5],
['61 - 90', 12.5],
['91 - 120', 77.5]
];
}
});
And here is the component logic:
//components/pie-chart.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['chart'],
renderChart() {
return this.$().highcharts({
chart: {
height: 275
},
title: null,
plotOptions: {
pie: {
dataLabels: {
enabled: false
}
}
},
series: {
type: 'pie',
data: this.get('data')
},
colors: ['#777777', '#888888', '#999999', '#aaaaaa', '#bbbbbb', '#cccccc', '#dddddd', '#eeeeee'],
credits: {
enabled: true
}
});
},
didUpdateAttrs() {
let chart = this.$().highcharts();
let series = this.get('data');
chart.series[0].setData(series);
},
didInsertElement() {
this._super(...arguments);
this.renderChart();
},
willDestroyElement() {
this.$().highcharts().destroy();
}
});
i got the idea from this blog and i am trying to adapt it to make more charts.
the chart renders onto the screen, but it is blank... there are no errors to report... the only thing i can think is the data property is not being handled correctly in order to plot the graph?
I am not sure if this is my wrong use of Ember code, or wrong use of Highcharts code?
Solution
the series property inside the highchart was supposed to be an array of objects.. but I mistakenly defined just an object:
the fix:
series: [{
type: 'pie',
data: this.get('data')
}],
So, that is how you render a Highchart.js into a component :)