Get Multiple pages of data at once with Backbone Collection - javascript

I have this Collection mixin that I'm using to attempt to continue to pull multiple pages of data into the collection together, but I assume it resets itself when it detects new options passed into fetch as I'm only ending up with the last page of data.
var Pageable = {
fetch: function(options) {
var originalSuccess = options.success;
options.data.page = 1;
var doFetch = function(options) {
var beforeLength = this.length;
options.data = _.extend({page: options.data.page, per_page:100}, options.data);
options.success = function(col, res, opts) {
if (this.length === beforeLength) return originalSuccess(col, res, opts);
options.data.page++;
doFetch(options);
}.bind(this);
return Collection.prototype.fetch.call(this, options);
}.bind(this);
return doFetch(options);
}
};
I think from this you get the gist of what I'm trying to do, any ideas as to how to go about this?

Figured it out, you need to add options to tell the collection to not remove, but only add/update...
var Pageable = {
fetch: function(options) {
var originalSuccess = options.success;
_.extend(options, {
add: true, // <----
remove: false, // <----
update: true, // <----
data: {
page: 1,
per_page:100
}
});
var doFetch = function(options) {
var beforeLength = this.length;
options.success = function(col, res, opts) {
console.log(this.length, beforeLength);
if (this.length === beforeLength) return originalSuccess(col, res, opts);
options.data.page++;
doFetch(options);
}.bind(this);
return Collection.prototype.fetch.call(this, options);
}.bind(this);
return doFetch(options);
}
};

Related

Implement search effectively in Backbone.js

I am trying to perform a search on my current collection and if the results aren't retrieved i am trying to query my search api
Collection:
var Backbone = require('backbone'),
_ = require('underscore'),
Urls = require('../../libs/urls'),
services = require('../../libs/services'),
skuListModel = require('../../models/sku/SkuListModel');
var SkuListCollection= Backbone.Collection.extend({
model: skuListModel,
sync: function (method, model, options) {
options = _.defaults({}, options, {
readUrl: Urls.sku.list
});
return services.sync.call(model, method, model, options);
}
});
View
searchData: function (e) {
var self = this;
var models = this.skuCollection.filter(function (item) {
return item.get("sku_code").indexOf(e.target.value) > -1
});
console.log(models);
if (models != null) {
self.skuCollection.set(models);
}
else {
self.skuCollection.fetch({
data: {
search_string: e.target.value
}
}).then(function (response) {
console.log(response);
//self.skuCollection.add(self.skuSearchCollection.toJSON(), { silent: true });
});
}
}
My question effectively is how do i modify my current collection to store the retrieved results and if my solution seems effective.
Move your filtering logic to the collection
Use promises to unify your response : an immediately resolved deferred if you find models, the xhr object if you have to fetch the data
Customize the behavior of fetch via the set options, e.g {remove: false} to keep the existing models
These points lead to a collection definition :
var SkuListCollection = Backbone.Collection.extend({
skus: function(code) {
var self = this;
var filtered = function() {
return self.filter(function (item) {
return item.get("sku_code").indexOf(code) !== -1;
});
};
var models = filtered();
if (models.length) {
// models found : define a promise and resolve it
var dfd = $.Deferred();
dfd.resolve(models);
return dfd.promise();
} else {
// models missing: fetch and add them
return this.fetch({
remove: false,
data: {
search_string: code
}
}).then(filtered);
}
}
});
Your view would then be rewired as :
searchData: function (e) {
this.skuCollection.skus(e.target.value).then(function(models) {
// do what you have to do with the filtered models
});
}
And a demo http://jsfiddle.net/nikoshr/84342xer/1/

Backbone Collection fetch() method override previous models on repeat call

I'm using backbone.js Collection fetch method to send request to return data based on offset and limit. First collection.fetch() method returns 5 models because LIMIT 0 and LIMIT 5 , and while calling again Backbone.Collection.prototype.fetch.call(this, options) its returns same data(previous models) but server response with next OFFSET=5 and LIMIT=5, i,e next set of objects.But i want to append in collection object whenever its call fetch method.
define([
'underscore',
'backbone',
'utils',
'core/base/models-and-collections',
'core/constants'
], function(_, Backbone, Utils, CollectionUtils, Constants) {
var Collection = Backbone.Collection.extend({
initialize: function(models, options) {
this.options = options;
_.bindAll(this, 'parse', 'url', 'pageInfo', 'nextPage');
typeof(options) != 'undefined' || (options = {});
typeof(this.limit) != 'undefined' || (this.limit = 5);
typeof(this.offset) != 'undefined' || (this.offset = 0);
typeof(this.size) != 'undefined' || (this.size = 5);
console.log("photo-collection-intitalize");
},
url: function() {
console.log("photo-collection-url-hit");
// if (this.size === this.limit) {
return Utils.keywordFormat("/api/student/{studentId}?limit={limit}&offset={offset}", {
studentId: this.options.studentId,
limit: this.limit,
offset: this.offset
});
},
nextFetch: function(options) {
console.log("next fetch method");
typeof(options) != 'undefined' || (options = {});
var self = this;
var success = options.success;
options.success = function(resp) {
if(success) {
success(self, resp);
console.info("-collection response on success");
console.info(resp);
}
};
return Backbone.Collection.prototype.fetch.call(this, options);
},
pageInfo: function(){
},
nextPage: function(){
},
parse: function(resp) {
console.info(resp);
if(!resp.items) {
console.error("items not specified", resp);
}
console.log("resp:limit "+resp.limit+ "resp: offset "+ resp.offset);
this.size= resp.limit;
return resp.items;
},
});
return Collection;
});
this.collection= new Collection();
this.collection.once("sync",this.GetFirstSetOfData,this);
this.collection.fetch();
GetFirstSetOfData: function(collection){
//set view
}
//set next data offset
this.collection.offset=this.collection.offset+this.collection.limit;
//call again fetch method
var newCollection=this.collection.nextFetch();
//new Collection is same data as previous this.collection have but server response next set of data
// i,e row 5-9
I would do something like this, when fetch is successful it triggers a 'sync' event. So each time it is successful you can increment the offset counter:
offsetStep: 5,
initialize: function() {
this.on('sync', this.incrementOffset);
}
incrementOffset: function() {
this.offset += this.offsetStep;
}
Then instead of having nextFetch method you just need to pass {add: true} when fetching - this will merge in new models from the server as long as they have unique ids:
myCollection.fetch({ add: true });

Ember inFlight Error on delete

Im getting this error and am having trouble debugging it:
Uncaught Error: Attempted to handle event `deleteRecord` on <Todos.Todo:ember309:29> while in state root.loaded.updated.inFlight.
All transactions between ember and my JSON API seem to be working fine, but if alter the state of the todo, by either renaming it or checking it as complete, and then do a delete, I get the error above.
the changes they are persisting, and if I delete a todo without making any changes, that works as well.
Here is some code:
Route
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return this.store.find('todo');
}
});
Controller
Todos.TodoController = Ember.ObjectController.extend({
actions: {
editTodo: function () {
this.set('isEditing', true);
},
acceptChanges: function () {
this.set('isEditing', false);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
} else {
this.get('model').save();
}
},
removeTodo: function () {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
},
},
isEditing: false,
isCompleted: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
return model.get('isCompleted');
} else {
// property being used as a setter
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
Adapter
Todos.ApplicationAdapter = DS.RESTAdapter.extend({
defaultSerializer: "Todos/todosREST",
serialize: function(record, options) {
var tmp = get(record, 'store').serializerFor(record.constructor.typeKey).serialize(record, options);
console.log('ser2:', tmp)
return tmp
},
createRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: false });
var tmp = this.ajax(this.buildURL(type.typeKey), "POST", { data: data });
// console.log('createRecord:', tmp, data, record);
return tmp;
},
updateRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record);
var id = get(record, 'id');
var tmp = this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data })
console.log(this.buildURL(type.typeKey, id), "PUT", { data: data })
return tmp;
},
namespace: 'api'
});
Serializer
Todos.TodosRESTSerializer = DS.RESTSerializer.extend({
typeForRoot: function(root) {
var camelized = Ember.String.camelize(root);
var tmp = Ember.String.singularize(camelized);
console.log('Camelized:'. tmp)
return tmp;
},
keyForAttribute: function(attr) {
return Ember.String.underscore(attr);
},
serialize: function(record, options) {
// client to server
var json = {};
record.eachAttribute(function(name) {
json[serverAttributeName(name)] = record.get(name);
})
record.eachRelationship(function(name, relationship) {
if (relationship.kind === 'hasMany') {
json[serverHasManyName(name)] = record.get(name).mapBy('id');
}
});
console.log('options: ', options)
if (options && options.includeId) {
json.id = record.get('id');
}
return json;
},
serializeIntoHash: function(hash, type, record, options) {
// hash should be an empty object, so just extend it
Ember.merge(hash, this.serialize(record, options));
},
extractSingle: function(store, type, payload, id, requestType) {
// server to client
console.log('in serializer')
return this._super(store, type, post_payload, id, requestType);
},
extractArray: function(store, type, payload, id, requestType) {
// server to client
console.log(payload, type)
var todos = payload.objects;
payload = {todos: todos}
return this._super(store, type, payload, id, requestType);
},
});
Anyone have any ideas?

Reference var from one model to another returns defaults in Backbone

I have a model which sets the defaults like so:
var CampModel = Backbone.Model.extend({
defaults: {
siteID: $.jStorage.get('currentSiteID'),
active: -1,
pending: -1,
},
url: function () {
//some url.
},
sync: function (method, model, options) {
var method = 'read';
var that = this,
options = options || {};
options.success = function(model, response, options){
if(response.errorMessage != "Session is over")
console.log('Update session');
if(response.success)
if(response.returnValue.length){
that.set('response', response.returnValue);
that.CountActiveAndPending(response.returnValue);
}
else {
that.set('response', []);
}
else console.log('report: bad request, error: '+ response.errorMessage);
}
Backbone.sync(method, model, options);
},
},
//Counts active and pending campaigns for front page.
CountActiveAndPending: function (data) {
var active = 0;
var pending = 0;
//var it = this;
$.each(data, function (index, val) {
if (val.ApprovedOnSite) active++;
else pending++;
});
this.set('active', active);
this.set('pending', pending);
}
});
and in a different model I try and get the models parameters like so:
this.set({
campModel: new CampModel(),
})
});
this.get('campModel').save();
console.log(this.get('campModel').get('active'));
},
Everything seems to run great but when I try to get the "active" param from the CampModel I get the -1 default value and not the value assigned in the model. Any thoughts as to why this happens?
Model#save is asynchronous, when you're doing:
console.log(this.get('campModel').get('active'));
the server hasn't responded yet, so CountActiveAndPending has never been called and active is still -1. Try to log its value in your success callback.

Constructing fuelux datagrid datasource with custom backbone collection

I am trying to build datagrid with sorting, searching and paging enabled. Therefore, I am using fuelux-datagrid.
MY backbone view looks like this:
var app = app || {};
$(function ($) {
'use strict';
// The Players view
// ---------------
app.PlayersView = Backbone.View.extend({
template: _.template( $("#player-template").html() ),
initialize: function () {
if(this.collection){
this.collection.fetch();
}
this.listenTo(this.collection, 'all', this.render);
},
render: function () {
this.$el.html( this.template );
var dataSource = new StaticDataSource({
columns: [
{
property: 'playername',
label: 'Name',
sortable: true
},
{
property: 'age',
label: 'A',
sortable: true
}
],
data: this.collection.toJSON(),
delay: 250
});
$('#MyGrid').datagrid({
dataSource: dataSource,
stretchHeight: true
});
}
});
});
The player template just contain the template as given in fuelux datagrid . My routing code somewhere instantiate app.playerview with collection as
new app.PlayersView({
collection : new app.PlayersCollection
}));
My players collection contains list of player model as below
[{
"id":1,
"playername":"rahu",
"age":13
},
{
"id":2,
"playername":"sahul",
"age":18
},
{
"id":3,
"playername":"ahul",
"age":19
}]
My datasource class/function to construct datasoruce with columns and data method is as given in datasource constructor
However, I get the error the " datasource in not defined ". Can anybody help me?
I just wanted to hack the code so that instead of datasource constructed from local data.js in given example, I want to construct the datasource so that it takes data from playercollection.
Also, how to add the one extra column so that we can put edit tag insdie and its should be able to edit the particular row model on clicking that edit.
I have been stucking around these a lot. It would be great help to figure out the answer.
I was stucking around datasource.
I modified the datasource as follows and then it worked.
var StaticDataSource = function (options) {
this._formatter = options.formatter;
this._columns = options.columns;
this._delay = options.delay || 0;
this._data = options.data;
};
StaticDataSource.prototype = {
columns: function () {
return this._columns;
},
data: function (options, callback) {
var self = this;
setTimeout(function () {
var data = $.extend(true, [], self._data);
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
var match = false;
_.each(item, function (prop) {
if (_.isString(prop) || _.isFinite(prop)) {
if (prop.toString().toLowerCase().indexOf(options.search.toLowerCase()) !== -1) match = true;
}
});
return match;
});
}
// FILTERING
if (options.filter) {
data = _.filter(data, function (item) {
switch(options.filter.value) {
case 'lt5m':
if(item.population < 5000000) return true;
break;
case 'gte5m':
if(item.population >= 5000000) return true;
break;
default:
return true;
break;
}
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
}, this._delay)
}
};
Infact, I just removed following code and its associated braces.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore'], factory);
} else {
root.StaticDataSource = factory();
}
}(this, function () {
I dont know what exactly the above code is doing an what dependdencies they have over.

Categories

Resources