Created
January 16, 2013 15:46
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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