Implement search effectively in Backbone.js - javascript

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/

Related

pass a collection instead of array to fetch().then() callback

Module which fetches data from the server and returns a Promise
MedicineManager.module("Entities", function (Entities, MedicineManager, Backbone, Marionette, $, _) {
Entities.Suggestion = Backbone.Model.extend({
default: {
suggestion: ""
}
});
Entities.SuggestionCollection = Backbone.Collection.extend({
model: Entities.Suggestion,
comparator: "suggestion"
});
var API = {
getSuggestions: function (medicine) {
var suggestions = new Entities.SuggestionCollection();
suggestions.url="/medicine_suggestions/?id=" + medicine;
return suggestions.fetch();
}
};
MedicineManager.reqres.setHandler("suggestion:entities", function (medicine) {
return API.getSuggestions(medicine);
});
});
Module which calls the above module for getting fetched data
MedicineManager.module("MedicinesApp.Suggest", function (Suggest, MedicineManager,
Backbone, Marionette, $, _) {
MedicineManager.request("suggestion:entities", "paracetamol").then(function (medicines) {
});
});
How do I get collection instead of an array as the parameter to the callback function in then() because the medicines parameter passed to the callback is giving an array after fetching the values.
Pass the success callback along with options, which will be invoked with the collection as first argument by backbone:
Entities.SuggestionCollection = Backbone.Collection.extend({
model: Entities.Suggestion,
comparator: "suggestion"
});
var API = {
getSuggestions: function(medicine, options) {
var suggestions = new Entities.SuggestionCollection([],{
url : "/medicine_suggestions/?id=" + medicine
});
return suggestions.fetch(options);
}
};
MedicineManager.reqrest.setHandler("suggestion:entities",API.getSuggestions);
Which you can use like:
MedicineManager.request("suggestion:entities", "paracetamol", {
success: function(medicines) {}
});
Instead of directly returning the xhr promise, filter it via .then to set your collection as the promised value :
var API = {
getSuggestions: function (medicine) {
var suggestions = new Entities.SuggestionCollection();
suggestions.url="/medicine_suggestions/?id=" + medicine;
return suggestions.fetch().then(function() {
return suggestions;
});
}
};
The first argument (medicines) in MedicineManager.request(...).then(function (medicines) { }); will be your 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 - cannot loop through a collection

I am having trouble looping through a collection that helps populate a view with data. When I try to loop through the collection and get the following error,
Uncaught TypeError: Object # has no method 'each'
I have absolutley no idea why I am getting this error, other than the object obviosly doesn't have that method, however I only get this error when I run the drop function see code below.
Here is by backbone code,
GroupListView.prototype.keyup = function() {
this.filtered = this.collection.searchName(this.$el.find("#search-query").val());
return this.renderList(this.filtered);
};
GroupListView.prototype.renderList = function(collection) {
var $collection, $this;
console.log(this.$el);
console.log(collection);
if (!collection) {
collection = this.collection;
console.log(collection);
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
$collection = collection;
if ($collection.length < 1) {
this.$el.find("#people-groups-list").hide();
return this.$el.find("#people-groups-list").after('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');
} else {
this.$el.find("#people-groups-list").show();
collection.each(function(item) {
//console.log(item);
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
return $this.append(displayView.render());
});
return this;
}
};
GroupListDisplayView.prototype.render = function() {
var $body;
//console.log(this.model.toJSON());
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
console.log(this.person);
personTile = new app.PersonTileView({
model: this.person
});
return $body.html(personTile.render());
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListDisplayView.prototype.drop = function(e) {
var $collection, $model, person_id, request;
e.preventDefault();
$collection = this.collection;
person_id = e.originalEvent.dataTransfer.getData('Text');
request = new app.PersonGroupAdd;
$model = this.model;
return request.save({
async: true,
wait: true,
person_id: person_id,
group_id: this.model.get("id")
}, {
success: function(d) {
return $model.fetch({
async: true,
wait: true
});
}
});
};
GroupListView.prototype.initialize = function() {
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};
Try this instead, place this function with in your collection
parse: function (data) {
data.forEach(function (item) {
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
});
}
Hope this helps.
I'm not sure what the drop function has to do with it, but I see renderList being passed the results of searchName during keyup. What's likely happening is that searchName is returning a regular array, instead of a wrapped object that would have the each method (i.e. a Backbone.Collection, jQuery collection or Underscore wrapped object).
Instead of calling collection.each, use jQuery or Underscore:
$.each(collection, function(i, item) { ... });
or
_.each(collection, function(item, i, list) { ... });

Why are my Backbone Models nested strangely within a Collection, requiring drilling down to access methods/properties?

I've got a Collection and a Model, both using attributes/options to augment them with additional capabilities. Here's the Model (LoadRouteGroup):
return Backbone.Model.extend({
initialize: function () {
console.log(this);
},
fetchf: function () {
console.log("FETCH");
}
});
And the Collection (LoadRouteGroups):
return Backbone.Collection.extend({
constructUrl: function(options) {
if (options.groupingType === "facility") {
// TODO: new endpoint: /api/v1/loadroutes?grouping=facility
this.url = clawConfig.endpoints.webApiRootUrl + "/api/loads/facilities";
}
else {
this.url = clawConfig.endpoints.webApiRootUrl + "/api/v1/loadroutes";
}
},
initialize: function (models, options) {
options || (options = {});
this.constructUrl(options);
console.log(this);
}
});
They're instantiated as such:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
// WORKS
loadRouteGroup.fetchf();
// assign groupingType option to collection to denote which URL to use
var loadRouteGroups = new LoadRouteGroups({
model: loadRouteGroup
}, {
groupingType: "facility"
});
var firstGroup = loadRouteGroups.at(0);
// DOESN'T WORK
firstGroup.fetchf();
// WORKS
firstGroup.attributes.model.fetchf();
I would expect that call to firstGroup.fetchf() to work... but it doesn't. Instead, I have to weirdly drill down and use firstGroup.attributes.model.fetchf() in order to access the method.
What's going on here? This would seem straightforward to me, but I can't for the life of me figure out what's wrong with the relationship between my Collection and Model.
The collection definition should include the model type:
return Backbone.Collection.extend({
// ....
model: LoadRouteGroup
});
When initializing the collection, pass in an array of models:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
var loadRouteGroups = new LoadRouteGroups([loadRouteGroup], {
groupingType: "facility"
});
Specify the model when you extend the collection instead of when you instantiate.

Categories

Resources