Format JSON ojects for Ember / drill-down / serialize / adapter - javascript

I'm pulling some JSON with ajax (because I can't get past CORS with my limited understanding/lack of useful examples/ of the RESTAdapter...) - and I'm getting these nested objects. I need an array of the 'book' - but I'm unsure of how to format this JSON so that it's readable.
routes/books.js
import Ember from 'ember';
import ajax from 'ic-ajax';
var libraryThingApi = "http://www.librarything.com/api_getdata.php?";
export default Ember.Route.extend({
model: function() {
var libraryData = ajax({
url: libraryThingApi + "userid=F.L.O.W.",
type: 'GET',
dataType: 'jsonp'
});
console.log(libraryData);
return libraryData;
}
});
what is returned
Promise
_id: 47
_label: "ic-ajax: unwrap raw ajax response"
...
_result: Object
books: Object
111333266: Object
title: "a book title"
111730480: Object
title: "a book title"
settings: Object
theuser: "F.L.O.W."
more_things: "etc"
{
books: {
111601539: {
book_id: "111601539",
title: "Rosie Revere, Engineer",
author_fl: "Andrea Beaty",
copies: 1
},
121604536: {
book_id: "121604536",
title: "Ember for people who aren't core.",
author_fl: "No author",
copies: "This book does not exist"
}
},
settings: {
theuser: "sheriffderek"
}
}

Since you're just grabbing raw json, you can use it really however you'd like. It then just becomes a matter of how you want to access the data in your route's template.
Ember's model hook is promise aware so you are correct to simply return the promise. The fulfilled value of that promise is then what you'll be interested in for your template. Thus if the fulfilled value is something like this:
{
books: [
{id: 1, title: "some title", nested_data: {author: "some author"}},
{id: 2, title: "another title", nested_data: {author: "another author"}},
]
}
Then inside the template for your route you can access the fulfilled value from your returned promise inside the model hook. Pre Ember 1.10 (deprecated later in the 1.x series) you can each through your books in your route's template like this:
<ul>
{{#each book in model.books}}
<li>{{book.title}} by {{book.nested_data.author}}</li>
{{/each}}
</ul>
Ember 1.10 and beyond introduces block params for each statements:
<ul>
{{#each model.books as |book|}}
<li>{{book.title}} by {{book.nested_data.author}}</li>
{{/each}}
</ul>
Read the information about Ember 1.10 release here.

Related

How to properly parse XML API content into EmberJS?

I am attempting to build a front end web system that will run off of a third party sites database and administration console. The API seems to be entirely reliant upon GET calls, either requesting or altering information by targeting specific URLs.
The API returns XML, example:
<responseITEMs xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ITEMs>
<ITEM libraryid="e3712df592253fcb4" featured="false" releasedate="2017-24-01 00:00:00" code="ABC001" detail="Some text" name="Dummy One" displaytitle="Dummy One" keywords="" id="1fef760bc1d61c8c" status="active" lastupdated="2016-24-01 04:53:28"/>
<ITEM libraryid="e3712df592253fcb4" featured="false" releasedate="2017-24-01 00:00:00" code="ABC003" detail="Some text" name="Dummy Three" displaytitle="Dummy Three" keywords="" id="3e35wba1d9b32a45" status="active" lastupdated="2016-24-01 04:53:15"/>
<ITEM libraryid="e3712df592253fcb4" featured="false" releasedate="2017-24-01 00:00:00" code="ABC002" detail="Some text" name="Dummy Two" displaytitle="Dummy Two" keywords="" id="cca6f0cab9defe80" status="active" lastupdated="2017-24-01 01:57:37"/>
</ITEMs>
</responseITEMs>
I have not used EmberJs before, but it was suggested to me. I'm not sure it's possible to use with XML, so I currently have a PHP script running on a different local server that's calling a fixed API URL endpoint and converting the response to JSON:
$Json = json_encode(simplexml_load_string($data));
echo $Json;
The JSON I end up with looks like this:
ITEMs: {
ITEM: [
{
#attributes: {
libraryid: "e3712df592253fcb4",
featured: "false",
releasedate: "2017-24-01 00:00:00",
code: "ABC001",
detail: "Some text",
name: "Dummy One",
displaytitle: "Dummy One",
keywords: "",
id: "1fef760bc1d61c8c",
status: "active",
trackcount: "0",
lastupdated: "2016-24-01 04:53:28"
}
},
{
#attributes: {..... etc
I am trying to write an Ember normalizer that will mean that I can run a simple loop through the items (real term is not ITEM) on an Ember template. Currently it is:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
payload = {
ITEM: {
id: payload.ITEMs.ITEM[0]["#attributes"].id,
type: requestType.modelName,
name: payload.ITEMs.ITEM[0]["#attributes"].name
}
};
return this._super(store, primaryModelClass, payload, id, requestType);
}
});
At this point Ember inspector shows that I am getting the ID and name under the Data tab, but I can't get them onto my template, or obviously retrieve more than the first item due to the hardcoded ITEM[0].
Route:
export default Ember.Route.extend({
model() {
return this.store.findAll('ITEM');
}
});
Model:
export default DS.Model.extend({
name: DS.attr('string'),
});
Template:
<strong>{{ model.name }}</strong>
<ul>
{{#each model.ITEM as |one|}}
<li>{{one.name}}</li>
{{/each}}
</ul>
Obviously I am not that far along and do not know Ember well at all. I am open to and would appreciate better solutions to tackling this as well as technical input towards my current code.
If you are using php-script, you can convert XML to correct response on PHP side, why use serializer for this?
Other solution would be creating new adapter and serializer to work with XML-responses without php-convertion.
You may find this link useful: https://bendyworks.com/blog/old-new-soap-ember-js

Define the ember data model for nested rest url

I am trying to do something that sounds simple but I can't find the solution.
My application needs to edit documents which contains pages.
Here is my model :
MyApplication.Document = DS.Model.extend({
title: DS.attr('string'),
pages: DS.hasMany('page', {async: true})
});
MyApplication.Page = DS.Model.extend({
document: DS.belongsTo('document', {async: true}),
title: DS.attr('string'),
params: DS.attr(),
objects: DS.attr()
});
And the routes :
MyApplication.Router.map(function () {
this.resource('document', {path: '/document/:document_id'});
});
MyApplication.Document = Ember.Route.extend({
model: function (params) {
return this.store.find('document', params.document_id);
}
});
When I load the document 1, the application call http://www.myserver.com/api/document/1.
The problem is that when I want to find a page of the document, it calls
http://www.myserver.com/api/pages/ID
instead of
http://www.myserver.com/api/document/1/pages/ID
Theses nested URL are important in my application.
I found different things on the subject like adding links in the JSON response :
{
"document": {
"id": "1",
"title": "Titre du document",
"pages": ["1", "2", "3"],
"links": {"pages" : "pages"}
},
But when I call for the pages, it requests http://www.myserver.com/api/document/1/pages without the id.
I also try specify the document when I ask for the page :
this.store.find("page", 1, {document:1});
Can't find a complete documentation on this subject, so if someone can explain me what's wrong, I'll be happy.
Thank.
Depends : EMBER DATA >= V1.0.0-BETA.9
The way to handle nested routes is hidden under release notes
Need to send back the links with response like this
{
"document": {
"id": 1,
"title": "Titre du document",
"links": {"pages" : "/documents/1/pages"}
}
You'll need to customize the adapter:page's buldUrl method like
MyApplication.PageAdapter = DS.RestAdapter.extend({
// NOTE: this is just a simple example, but you might actually need more customization when necessary
buildURL: function(type, id, snapshot) {
return '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
#code-jaff answer adapted to Ember 2.1.0:
// app/adapters/page.js
import DS from './application'; // I suppose you have your adapter/application.js
export default DS.RESTAdapter.extend({
buildURL: function(type, id, snapshot) {
return this.host + '/' + this.namespace + '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
Your problem likely stems from the quotes that are surrounding the IDs in your JSON. If you modify your serializer so that there are no quotes for the the IDs both around the document ID and the pages IDs, you should get the behavior that you expect. Also, you need to modify the formatting of your links to point to the relative path:
The resulting JSON should look like:
{
"document": {
"id": 1,
"title": "Titre du document",
"pages": [1, 2, 3],
"links": {"pages" : "/documents/1/pages"}
}
Please see this answer for a description of why adherence to Ember's expectations with regard to the JSON format is important and for an overview of the expected format.

Ember sets only one element instead of many

I've got two async models:
App.Posts = DS.Model.extend({
'content': attr('string'),
'comments': DS.hasMany('comments', {async: true}),
});
App.Comments = DS.Model.extend({
'body': DS.attr('string'),
'postId': DS.belongsTo('posts', {async: true})
});
Via the PostController I try to load the Comments onClick through an action:
App.PostController = Ember.ArrayController.extend({
loadComments: function(post_id) {
this.store.find('comments', post_id);
}
});
(perhaps there is a better way to do this??)
Request and API Response are correct (see API Response below), but only one comment is rendered, then Ember throws an error:
TypeError: Cannot read property 'postId' of undefined
In Embers Console > Data Tab there is one comment in the comment Model, but there is also a post element in the comment model with the comment properties set to undefined. This would explain, why Ember can't read the property postId, because it's not a comment. Why does Ember push the post into the comment model and only pushes one instead of 3 comments into the model?
API Response
{
"comments": [
{
"id": 2,
"postId": 31152,
"body": "Lorem ipsum dolor sit amet, consetetur",
},
{
"id": 2,
"postId": 31152,
"body": "asdasd",
},
{
"id": 2,
"postId": 31152,
"body": "asd asd sd",
}
]
}
This is a slight shot in the dark, and I'd generally put it as a comment, but it's a tad big. Can you try changing all of the model references to singular. That's the correct pattern for Ember Data models.
App.Post = DS.Model.extend({
'content': attr('string'),
'comments': DS.hasMany('comment', {async: true}),
});
App.Comment = DS.Model.extend({
'body': DS.attr('string'),
'postId': DS.belongsTo('post', {async: true})
});
this.store.find('comment', post_id);
And now that I write this, I might see another issue. If you are querying for comments by post_id (pretend it's 7) then Ember Data is expecting a single record back, not a collection of records. So it's likely looking at the comments collection and thinking it's a single record, and that just blows up it's logic.

Ember Data: Allow Embedded ID for Record

While using the RESTAdapter, I have an Organization model which is to be embedded in the response. It appears that the default implementation of the Ember.RESTAddapter sends the id, using the same model name, but not as an object (this currently 'works'):
POST/PUT api/v1/item/{id}
{
"item" {
id: "1029383829"
...
organization: "26044097612186763401268824297"
}
}
I have consulted the documentation, and found that the mixin DS.EmbeddedRecordsMixin should do this, coupled with declaring embedded: "always" on the attrs or the Serializer:
models/item.js
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {embedded: "always"})
});
serializers/item.js:
var ItemSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
organisation: {serialize: "id", embedded: "always"}
}
}
);
However, when deserializing records, Ember Data complains, saying that it expected an object, but just got a string:
Assertion Failed: Expected an object as data in a call to push for
app#model:organization: , but was 26044097612186763401268824297
Ultimately, I would prefer a system, likened to sideloading, wherein an additional attribute, post-fixed "_id", describes the corresponding id of an embedded record:
{
"item": {
id: 1,
name: "name",
organization_id: "26044097612186763401268824297"
...
}
}
How can I allow serializing and deserializing embedded id sideloading for my Organization model?
You aren't actually embedding the record, you're just specifying the id, in that case you should mark it as async.
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {async: true})
});
And remove your embedded records implementation.

Make ember to resolve hasMany relationship when loading

I'm currently facing a big problems for days. I'm using ember simple-auth plugin which provide me a session object accessible through the code or the templates. That session object store the account information such as username, id and rights.
My models are like this :
App.Right = DS.Model.extend({
label: DS.attr('string', { defaultValue: undefined })
});
App.Right.FIXTURES = [
{
id: 1,
label: 'Admin'
}, {
id: 2,
label: 'Manager'
}, {
id: 3,
label: 'User'
}
];
App.User = DS.Model.extend({
username: DS.attr('string'),
rights: DS.hasMany('right', {async: true})
});
App.User.FIXTURES = [
{
id: 1,
username: "Someone",
rights: [1]
}
];
Then I have (as specified on the simple-auth documentation) this setup :
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.Session.reopen({
account: function() {
var userId = this.get('userId');
if (!Ember.isEmpty(userId)) {
return container.lookup('store:main').find('user', userId);
}
}.property('userId')
});
...
}
});
Inside one of my view I'm doing this:
this.get('context.session.account.rights').toArray()
but it gives me an empty array. That piece of code is executed inside an Ember.computed property.
The question is how can I resolve the childrens of account before rendering the view ?
Since async: true this.get('context.session.account.rights') will return a promise object so you will have to use this.get('context.session.account.rights').then(... see: http://emberjs.com/api/classes/Ember.RSVP.Promise.html#method_then
Okay so I finally got it to work. It doesn't solve the original question because the original question was completely stupid. It's just IMPOSSIBLE to resolve relationships synchronously when you use the async: true. Trying to resolve it in advance is NOT the solution because you will still not know when it has actually resolved.
So here is the solution:
$.each(this.get('cellContent.buttonList'), function(i, button) {
button.set('hasAccess', false);
this.get('context.session.account').then(function(res) {
res.get('rights').then(function(result) {
button.set('hasAccess', Utils.hasAccess(result.toArray(), button.rights));
});
});
});
Using the following cellContent.buttonList definition:
buttonList: [
Ember.Object.create({
route: 'order',
label: 'Consult',
rights: 'all'
}), Ember.Object.create({
route: 'order.edit',
label: 'Edit',
rights: [1, 2]
})
]
Explanation
We have to use Ember.Object in order to have access to the set method. Using an Ember object is very handy. It allows us to change the value of properties after the render process making the view to update according to the new value you just set.
Because it updates the view, you don't have to care anymore whether your model has resolved or not.
I hope this will help people as much as it helps me.

Categories

Resources