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

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

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 model when created already has attributes

In my my application I do something like this to create a new model,
this.model = new App.Models.Organisation;
The code for the model looks like this,
'use strict'
App.Models.Organisation = Backbone.Model.extend({
urlRoot: "http://" + App.API_ROOT + "/organisations",
defaults: {
//members : new App.Collections.Users
},
initialize: function() {
//Gets
var members = this.get('users');
var projects = this.get('projects');
var teams = this.get('teams');
var clients = this.get('clients');
console.log(members);
console.log(projects);
console.log(teams);
console.log(clients);
//Sets
if(members != undefined) {
this.set('members', App App.Collections.Users(members));
} else {
this.set('members', App App.Collections.Users);
}
if(projects != undefined) {
this.set('projects', new App.Collections.Projects(projects));
} else {
this.set('projects', new App.Collections.Projects);
}
if(teams != undefined) {
this.set('teams', new App.Collections.Teams(teams));
} else {
this.set('teams', new App.Collections.Teams);
}
if(clients != undefined) {
this.set('clients', new App.Collections.Clients(clients));
} else {
this.set('clients', new App.Collections.Clients);
}
},
validate: function() {
}
});
However when log the new model where I expect to see empty attributes I get the following:
Why would teams and projects have a value when the model is newly created?
The teams collections looks like this,
'use strict'
App.Collections.Teams = Backbone.Collection.extend({
url: 'http://' + Pops.API_ROOT + '/teams',
model: Pops.Models.Team,
initialize: function() {
var members = this.get('members');
this.set('members', new App.Collections.Users(members));
},
search: function(filterValue) {
var matcher = new RegExp(filterValue);
var found_models = this.filter(function(model) {
return matcher.test(model.get('name'));
});
return found_models;
},
});
and the projects collection like this,
App.Collections.Projects = Backbone.Collection.extend({
url: 'http://' + App.API_ROOT + '/project',
model: App.Models.Project,
sort_key: "name",
sort_order: 1,
parent_filter: false,
filters: [1,2,3],
initialize:function() {
var pm = this.get('projectmanager');
this.set('project_manager', new App.Models.User(pm));
var sp = this.get('salesperson');
this.set('sales_person', new App.Models.User(sp));
this.sortByField('created_at', 'desc');
},
comparator: function (item1, item2) {
var val1 = item1.get(this.sort_key);
var val2 = item2.get(this.sort_key);
if (typeof (val1) === "string") {
val1 = val1.toLowerCase();
val2 = val2.toString().toLowerCase();
}
var sortValue = val1 > val2 ? 1 : -1;
return sortValue * this.sort_order;
},
sortByField: function(fieldName, orderType) {
this.sort_key = fieldName;
this.sort_order = orderType == "desc" ? -1 : 1;
console.log(this.sort_order);
this.sort();
},
sortStatus: function( filters ) {
this.filters = filters;
this.each(function(project){
project.set('visible', _.contains(filters, parseInt(project.get('status'))));
});
},
myProjects: function() {
this.each(function(project){
if(project.get('user_id') == '1' && project.get('organisation_id') == null) {
project.set('visible', true);
} else {
project.set('visible', false);
}
}, this);
},
status: function( status ) {
if(this.parent_filter == false) {
//Filter all projects on the dashboard
this.each(function(project){
project.get('visible', true);
project.set('visible', project.get('status') == String(status) );
});
} else {
//Filter only projects that are currently visible
this.each(function(project) {
if(project.get('visible')) {
project.set('visible', project.get('status') == String(status) );
}
});
}
},
otherProjects: function() {
this.each(function(project){
if(project.get('organisation_id') != null) {
project.set('visible', true);
} else {
project.set('visible', false);
}
}, this);
},
sortBy: function(filterBy, orderBy) {
this.sortByField(filterBy, orderBy);
this.sort();
},
search: function(filterValue) {
var matcher = new RegExp(filterValue);
var found_models = this.filter(function(model) {
return matcher.test(model.get('name'));
});
return found_models;
},
});
I see what's going on now, in your teams collection initialize method you have this line:
this.set('members', new App.Collections.Users(members));`
So this is calling set on a collection which is different from calling set on an individual model.
On a collection set treats the first element as an array of models. You are passing 'members' as the first parameter and this adding a model to the collection with every character in the string as one attribute of that model
On a model, set expects either an attributes hash to be passed or 2 parameters attribute name and value to be passed, and will set the model attributes accordingly.
Basically you cannot treat the collection as an individual model.
If you want to keep a reference to the members from the teams collection, why not keeping a reference like this.members = new App.Collections.Users(members) that you can access from other places in the teams collection?

Get Multiple pages of data at once with Backbone Collection

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

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.

Backbone model not instantiating in collection

I have a backbone model like so
define([
'underscore',
'backbone'
],function( _,Backbone) {
var Task = Backbone.Model.extend({
//api url
url:'',
methodToURL: {
'read': './api/tasks/index',
'create': './api/tasks/task',
'update': './api/tasks/task',
'delete': './api/tasks/task'
},
sync: function(method, model, options) {
options = options || {};
options.url = this.methodToURL[method.toLowerCase()];
Backbone.sync(method, model, options);
}
});
return Task;
});
And a collection
define(['underscore','backbone','models/task'],function( _,Backbone,Task) {
var TaskCollection = Backbone.Collection.extend({
//Model
model:Task,
//api url
url:'',
methodToURL: {
'read': './api/tasks/index',
'create': './api/tasks/task',
'update': './api/tasks/task',
'delete': './api/tasks/task'
},
sync: function(method, model, options) {
options = options || {};
options.url = this.methodToURL[method.toLowerCase()];
Backbone.sync(method, model, options);
},
//construct
initialize: function() {
this.sort_key = 'end';
this._model = new Task();
this.fetch();
},
comparator: function(a,b) {
a = a.get(this.sort_key);
b = b.get(this.sort_key);
return a > b ? 1
: a < b ? -1
: 0;
},
mark_complete: function(task_id) {
var task_status = 0;
console.log(this.model);
this.model.save({id:task_id,task_status:task_status});
},
mark_incomplete: function(task_id) {
var task_status = 1;
console.log(this.model);
this.model.save({id:task_id,task_status:task_status});
},
sort_by_status: function() {
this.sort_key = 'task_status';
this.sort();
},
sort_by_task_tag: function() {
this.sort_key = 'task_group';
this.sort();
}
});
return TaskCollection;
});
When i the mark_complete method runs the model is logged to the console, but it logs this
"function (){ parent.apply(this, arguments); } " and says "function (){ parent.apply(this, arguments); } has no method 'save'";
Am guessing the model is supposed to be instantiated so the collection can have access it to its methods, so what is wrong?
The model property is just a constructor that Collection uses when you add a model to the collection. It is intended to make your life easier when you try to input data to the collection. Instead of always calling the constructor when adding a Task model to TaskCollection, you'd just input a JavaScript object and it will do the same thing.
So this is how your code would look like when you would want to insert a model without setting the model property to your TaskCollection
taskCollection.add(new Task({
name: "Get Milk",
description: "We're out of milk. There's a sale going on at the local supermarket."
}));
// If you wanted to just input just the JSON object without calling the
// constructor, then you can't.
And this is how your code would look like if you had set the model property
taskCollection.add({
name: "Get Milk",
description: "We're out of milk. There's a sale going on at the local supermarket."
});
As you can see, you don't need to call the Task constructor; the instance of TaskCollection will call it for you.
And this is why instances of TaskCollection will only have the model property set to the actual constructor of Task, not an initialized version.

Categories

Resources