Skip to content

Instantly share code, notes, and snippets.

@alanrubin
Created January 16, 2013 15:46
Show Gist options
  • Save alanrubin/4548113 to your computer and use it in GitHub Desktop.
Save alanrubin/4548113 to your computer and use it in GitHub Desktop.
Extending backbone-pageable with methods similar to backbone.paginator - filtering, sorting. Also changing some of the behaviour to avoid errors.
define(['underscore', 'backbone', 'config', 'backbone-pageable'], function(_, Backbone, Config, PageableCollection) {
var extendedPageableCollection = PageableCollection.extend({
state: {
pageSize: Config.itemsPerPage
},
mode: 'client',
full: true,
/**
* Set sort configuration and execute the sort for the full collection
**/
setSort: function(column, direction) {
// Storing sort column and direction for future reference
this.state.sortKey = this.sortColumn = column;
this.sortDirection = direction;
this.state.order = (direction === 'asc' ? -1 : 1);
// Creating the comparator and setting in the full collection
// It will read the sortKey and order from the state
this.fullCollection.comparator = this.makeComparator();
// Sorting the collection
this.fullCollection.sort();
},
/**
* Override getPage so that we check index is in range before executing the original method
* If it is out of range, it should be set as the first page
*/
getPage: function (index, options) {
if(_.isNumber(index*1)) {
if (this.state.firstPage === 0) {
if (index < this.state.firstPage || index >= this.state.totalPages) {
// Out of range - set as first to avoid exception at backbone-pageable
index = 'first';
}
}
else if (this.state.firstPage === 1) {
if (index < this.state.firstPage || index > this.state.totalPages) {
// Out of range - set as first to avoid exception at backbone-pageable
index = 'first';
}
} // else firstPage is not valid - do nothing
}
// Call original method
PageableCollection.prototype.getPage.call(this, index, options);
},
/**
* Override fetch function so that collection is reset before retrieving new elements
**/
fetch: function(options) {
// Remove original collection
delete this.originalCollection;
// Execute original fetch function
PageableCollection.prototype.fetch.call(this, options);
},
/**
* Override add function so that original collection is reset before adding new elements
**/
add: function() {
// Remove original collection if model is being added not silently
if(arguments[1] && !arguments[1].silent) {
delete this.originalCollection;
}
// Execute original fetch function
PageableCollection.prototype.add.apply(this, arguments);
},
remove: function() {
// Delete original collection
delete this.originalCollection;
// Execute original fetch function
PageableCollection.prototype.remove.apply(this, arguments);
},
// Retrieves original size of the collection
originalSize: function() {
if(_.isUndefined(this.originalCollection)) {
return this.fullCollection.size();
} else {
return this.originalCollection.size();
}
},
/**
* Set filter configuration and execute filtering for the full collection
**/
setFilter: function(columnArray, value) {
this.filterFields = columnArray;
this.filterExpression = value;
// Set to return to first page to avoid issues
this.state.currentPage = 1;
if(_.isUndefined(this.originalCollection)) {
// Full collection is currently the collection with all elements
// Storing the originalCollection
this.originalCollection = new Backbone.Collection(this.fullCollection.models);
} // else original collection is already stored
// Filter the originalCollection
var filteredModels = this._filter(this.originalCollection.models, this.filterFields, this.filterExpression);
// Reset the fullCollection with filtered models
// TODO: Filtering is now only supported in fullCollection, not only in page
this.fullCollection.reset(filteredModels);
},
/**
* The filtering method - copied from Backbone Paginator library
**/
_filter: function ( models, fields, filter ) {
// For example, if you had a data model containing cars like { color: '', description: '', hp: '' },
// your fields was set to ['color', 'description', 'hp'] and your filter was set
// to "Black Mustang 300", the word "Black" will match all the cars that have black color, then
// "Mustang" in the description and then the HP in the 'hp' field.
// NOTE: "Black Musta 300" will return the same as "Black Mustang 300"
// We accept fields to be a string, an array or an object
// but if string or array is passed we need to convert it
// to an object.
var self = this;
var obj_fields = {};
if( _.isString( fields ) ) {
obj_fields[fields] = {cmp_method: 'regexp'};
}else if( _.isArray( fields ) ) {
_.each(fields, function(field){
obj_fields[field] = {cmp_method: 'regexp'};
});
}else{
_.each(fields, function( cmp_opts, field ) {
obj_fields[field] = _.defaults(cmp_opts, { cmp_method: 'regexp' });
});
}
fields = obj_fields;
//Remove diacritic characters if diacritic plugin is loaded
// if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
// filter = Backbone.Paginator.removeDiacritics(filter);
// }
// 'filter' can be only a string.
// If 'filter' is string we need to convert it to
// a regular expression.
// For example, if 'filter' is 'black dog' we need
// to find every single word, remove duplicated ones (if any)
// and transform the result to '(black|dog)'
if( filter === '' || !_.isString(filter) ) {
return models;
} else {
var words = _.map(filter.match(/\w+/ig), function(element) { return element.toLowerCase(); });
var pattern = "(" + _.uniq(words).join("|") + ")";
var regexp = new RegExp(pattern, "igm");
}
var filteredModels = [];
// We need to iterate over each model
_.each( models, function( model ) {
var matchesPerModel = [];
// and over each field of each model
_.each( fields, function( cmp_opts, field ) {
var value = model.get( field );
if( value ) {
// The regular expression we created earlier let's us detect if a
// given string contains each and all of the words in the regular expression
// or not, but in both cases match() will return an array containing all
// the words it matched.
var matchesPerField = [];
// if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
// value = Backbone.Paginator.removeDiacritics(value.toString());
// }else{
value = value.toString();
// }
// Levenshtein cmp
if( cmp_opts.cmp_method === 'levenshtein' && _.has(Backbone.Paginator, 'levenshtein') && self.useLevenshteinPlugin ) {
var distance = Backbone.Paginator.levenshtein(value, filter);
_.defaults(cmp_opts, { max_distance: 0 });
if( distance <= cmp_opts.max_distance ) {
matchesPerField = _.uniq(words);
}
// Default (RegExp) cmp
}else{
matchesPerField = value.match( regexp );
}
matchesPerField = _.map(matchesPerField, function(match) {
return match.toString().toLowerCase();
});
_.each(matchesPerField, function(match){
matchesPerModel.push(match);
});
}
});
// We just need to check if the returned array contains all the words in our
// regex, and if it does, it means that we have a match, so we should save it.
matchesPerModel = _.uniq( _.without(matchesPerModel, "") );
if( _.isEmpty( _.difference(words, matchesPerModel) ) ) {
filteredModels.push(model);
}
});
return filteredModels;
}
});
return extendedPageableCollection;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment