Ember.js and API pagination - ember.js

I'm using Ember.js (v1.2.0) with an API which returns paginated JSON data like this:
{
"count": 5,
"next": "http://127.0.0.1:8000/some/resource/?page=2",
"previous": null,
"results": [
{
"id": 37,
"title": "Some title",
"description": "Some description",
},
{
"id": 35,
"title": "Sdflskdf",
"description": "sdfkdsjf",
},
{
"id": 34,
"title": "Some other title",
"description": "Dsdlfksdf",
},
]
}
I'm not using ember-data, so I'm using a plain ember object as my model and loading the data like this:
App.SomeResource = Ember.Object.extend({});
App.SomeResource.reopenClass({
find: function () {
return $.getJSON('/some/resource/').then(function (response) {
return response.results.map(function (data) {
return App.SomeResource.create(data);
});
});
},
});
The find method on my model class returns a promise which resolves to an array of objects. While creates SomeResource objects, all the pagination data is lost.
Is there a way to store count, next and previous page urls somewhere when the promise resolves?

I am assigning them to global object but you should do better.
App.SomeResource = Ember.Object.extend({});
App.SomeResource.reopenClass({
find: function () {
return $.getJSON('/some/resource/').then(function (response) {
return RSVP.all(response.results.map(function (data) {
return App.SomeResource.create(data);
})).then(function(createdResources) {
window.count = response.count;
window.next = response.next;
window.previous = response.previous;
return createdResources;
});
});
}
});

Rather than storing this metadata on the global window object, I came up with this:
App.SomeResource.reopenClass({
find: function () {
var url = '/some/resource/';
return Ember.$.getJSON(url).then(function (response) {
response.results = response.results.map(function (resource) {
return App.SomeResource.create(resource);
});
return response;
});
},
});
SomeResource.find() just instantiates Ember objects from the results array and then returns the response with the rest of the data untouched. The route then receives the response and sets up the pagination data and the model in the setupController function:
App.SomeResourceRoute = Ember.Route.extend({
model: function () {
return App.SomeResource.find();
},
setupController: function(controller, model) {
controller.setProperties({count: model.count,
pageCount: model.page_count,
currentPage: model.current_page,
pageRange: model.page_range,
previous: model.previous,
next: model.next,
model: model.results,});
},
});
It works, but maybe there is a better way.

Related

Angular NgRessource with paginated results from django rest framework

My api basically returns something like this:
GET /api/projects/
{
"count": 26,
"next": "http://127.0.0.1:8000/api/projects/?page=2",
"previous": null,
"results": [
{
"id": 21,
"name": "Project A",
...
},
{
"id": 19,
"name": "Project B",
...
},
...
]
}
Using NgResource, I am able to query the api and get the data like this:
var PROJECT = $resource('/api/projects/:id/', {id:'#id'},{
query : {
method : 'GET',
isArray : false
}
});
factory.project_list = function(callback) {
PROJECT.query({},function(project_list){
factory.project_list = project_list.results;
callback();
});
};
My different projects are now available in factory.project_list. The issue here is that each item in factory.project_list are not ngResource items. So I can't call methods such as .$save(), .$update()...
I saw a transformResponse() function but I'm not able to get it working easily...
Do you have any idea what could be the best approach here ?
This is what worked for me:
app.config(['$resourceProvider', function($resourceProvider) {
$resourceProvider.defaults.stripTrailingSlashes = false;
}]);
services.factory('Project', ['$resource',
function($resource) {
return $resource('api/project/:id/', {}, {
query: {
method: 'GET',
url: 'api/projects/',
isArray: true,
transformResponse: function(data, headers) {
return angular.fromJson(data).results;
},
},
});
}
]);

Ember-data return a single json object with store.find

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.

Ember get hasMany

I have Model
EmberApp.Card = DS.Model.extend({
balance: DS.attr('number'),
operations: DS.hasMany('operation', { async: true })
});
Than I make
self.store.find('card', 'me')
response
{
"card": {
"id": "53620486168e3e581cb5851a",
"balance": 20
}
}
getting card Model but without operations and setting it to controller prop "currentCard"
Then I want to find operations throw url /cards/me/operations
response
{"operation": [{ "id": 1, "type": 1 }] }
How can I do it from this.controllerFor('*').get('currentCard')... ?
You actually need to return your operations as a list of ID's in your card response like this:
{
"card": {
"id": "53620486168e3e581cb5851a",
"balance": 20,
"operations": [1, 2, 3]
}
}
This will enable you to do this in another route:
this.modelFor('card').get('operations');
or this in your cards controller:
this.get('content.operations');
This will execute the following call to your API:
/api/operations?ids[]=1&ids[]=2&ids[]=3
First of all you should add links to response from cards/me
EmberApp.ApplicationSerializer = DS.RESTSerializer.extend({
normalizePayload: function(type, payload) {
if (type.toString() === 'EmberApp.Card') {
payload.links = { 'operations': '/cards/me/operations' };
return { card: payload };
} else if (type.toString() === 'EmberApp.Operation') {
return { operations: payload };
}
}
});
Then in route
EmberApp.CardOperationsRoute = Ember.Route.extend({
model: function() {
return this.controllerFor('sessions.new')
.get('currentCard')
.get('operations');
}
});

How can I see my response from server in Ember.js

My code is quite simple (Client Side):
Record.Router.map(function () {
this.resource('main', { path: '/' });
});
Record.MainRoute = Ember.Route.extend({
model: function () {
var response = Record.Rank.find();
console.log(response.get('name'));
console.log(response);
return Record.Rank.find();
}
});
My model:
Record.Rank = DS.Model.extend({
id: DS.attr('integer'),
rank: DS.attr('integer'),
content: DS.attr('string')
});
I use RESTadapter:
Record.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.reopen({
namespace: 'recordApp'
})
});
My Server side code (PHP):
<?php
namespace RecordContainer;
echo '{"rank":
{
"id": "1",
"rank": "2",
"content": "walla"
}
}';
I expect to something after I issue Record.Rank.find() but my console.log(response.get('name')) logs undefined and the second console.log(response) show the following, no information about echo from server inside:
How do I see the response from the server, in Ember?
1st: Calling find on a DS.Model without any parameters, i.e. Record.Rank.find(), is equivalent to sending a findAll() request to your server. In other words, it should fetch all Record.Rank. Therefore ember-data expects an array in the response of the format:
{
"ranks":[
{
"id": "1",
"rank": "2",
"content": "walla"
},
{
"id": "2",
"rank": "5",
"content": "foo"
}
]
}
2nd: Even if the response from the PHP was correct (as described above), console.log(response.get('name')); would probably return undefined since the request is not yet completed and the record(s) are not available. If you really want to access the records loaded into the store you need to place your code into a Promise resolve callback:
Record.MainRoute = Ember.Route.extend({
model: function () {
var response = Record.Rank.find();
response.then(function(ranks) {
console.log(ranks.getEach('name'));
});
return response;
}
});

Getting a sub-collection of a model in a template with Underscore and Backbone

I have a JSon like this that is a collection of issues:
{
"id": "505",
"issuedate": "2013-01-15 06:00:00",
"lastissueupdate": "2013-01-15 13:51:08",
"epub": false,
"pdf": true,
"price": 3,
"description": "Diese Version ist nur als PDF verfügbar.",
"downloadable": true,
"renderings": [
{
"height": 976,
"width": 1024,
"sourcetype": "pdf",
"pagenr": 0,
"purpose": "newsstand",
"relativePath": null,
"size": 0,
"url": "a/url/image.jpg"
},
{
"height": 488,
"width": 512,
"sourcetype": "pdf",
"pagenr": 0,
"purpose": "newsstand",
"relativePath": null,
"size": 0,
"url": "a/url/image.jpg"
}
}
I'm using backbone and I want to access the sub-elements (renderings).
I overwrite the parse method to tell that my 'renderings' is a new collection, this part works fine.
Where I'm getting some problems is when i pass my collection of issues in my template. Then I can make a for each on the issues and accessing his attributes (id, issuedate, lastissueupdate,etc) but how can I access the renderings of my issues ?
My View:
define([
'jquery',
'underscore',
'backbone',
'text!templates/home/homeTemplate.html',
'collections/issues/IssueCollection'
], function($, _, Backbone, homeTemplate, IssueCollection){
var HomeView = Backbone.View.extend({
el: $("#page"),
initialize:function(){
var that = this;
var onDataHandler = function(issues){
that.render();
}
this.issues = new IssueCollection();
this.issues.fetch({
success:onDataHandler,
error:function(){
alert("nicht gut")
}
});
},
render: function(){
$('.menu li').removeClass('active');
$('.menu li a[href="#"]').parent().addClass('active');
var compiledTemplate = _.template(homeTemplate, {_:_, issues: this.issues.models});
this.$el.html(compiledTemplate);
}
});
return HomeView;
});
Then I have a very basic template.
To resume, I have a collection ISSUES of models ISSUE containing a collection of RENDERINGS and I want to access the model RENDER in my template.
edit:
IssueCollection
define([
'underscore',
'backbone',
'models/issue/IssueModel'
], function(_, Backbone, IssueModel){
var IssueCollection = Backbone.Collection.extend({
model: IssueModel,
url: function(){
return '/padpaper-ws/v1/pp/fn/issues';
},
parse: function(response){
return response.issues;
}
//initialize : function(models, options) {},
});
return IssueCollection;
});
IssueModel
define([
'underscore',
'backbone',
'collections/renderings/RenderingsCollection'
], function(_, Backbone, RenderingsCollection) {
var IssueModel = Backbone.Model.extend({
parse: function(response){
response.renderings = new RenderingsCollection(response.renderings);
return response;
},
set: function(attributes, options) {
if (attributes.renderings !== undefined && !(attributes.renderings instanceof RenderingsCollection)) {
attributes.renderings = new RenderingsCollection(attributes.renderings);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
});
return IssueModel;
});
RenderingsCollection
define([
'underscore',
'backbone',
'models/rendering/RenderingModel'
], function(_, Backbone, RenderingModel){
var RenderingsCollection = Backbone.Collection.extend({
model: RenderingModel
//initialize : function(models, options) {},
});
return RenderingsCollection;
});
RenderingModel
define([
'underscore',
'backbone'
], function(_, Backbone) {
var RenderingModel = Backbone.Model.extend({
return RenderingModel;
});
Thank's !!
You could use the Collection#toJSON method to have a complete representation of your whole data. Still, you'd have to use model.attributes.key to access the attributes of your models. So I'd not so much recommend it. Instead, I'd say you should build your own JSON object (using the toJSON method recursively). (I'll update with some code if it's not clear)
Edit:
var data = [], renderings;
for(var i=0; i<issues.length; i++) {
data.push(issues.models[i].toJSON());
data[i].renderings = [];
renderings = issues.models[i].get('renderings');
for(var j=0; j<renderings.length; j++) {
data[i].renderings.push(renderings.models[j].toJSON());
}
}
for example would give you the whole data, and you could access any attribute easily.
Actually I could use a double _.each in my template like this, giving my models from the view.
<% _.each(issues, function(issue) { %>
.... code ...
<% _.each(issue.get('renderings'), function(rendering) { %>
And then to access my renderings attributes we do like this:
<%= rendering.url %>
for example, not "get" like the first time.
Thank's for your participation!