From 90b572a84821bf51d7502ce47c0aa18c50220557 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 25 Mar 2013 09:50:09 +0100 Subject: [PATCH] Update backbone.paginator --- .../0.7/backbone.paginator.js | 1043 +++++++++++++++++ .../0.7/backbone.paginator.min.js | 4 + ajax/libs/backbone.paginator/package.json | 2 +- 3 files changed, 1048 insertions(+), 1 deletion(-) create mode 100644 ajax/libs/backbone.paginator/0.7/backbone.paginator.js create mode 100644 ajax/libs/backbone.paginator/0.7/backbone.paginator.min.js diff --git a/ajax/libs/backbone.paginator/0.7/backbone.paginator.js b/ajax/libs/backbone.paginator/0.7/backbone.paginator.js new file mode 100644 index 00000000000000..c3193315e69d5e --- /dev/null +++ b/ajax/libs/backbone.paginator/0.7/backbone.paginator.js @@ -0,0 +1,1043 @@ +/*! backbone.paginator - v0.7.0 - 3/25/2013 +* http://github.com/addyosmani/backbone.paginator +* Copyright (c) 2013 Addy Osmani; Licensed MIT */ +/*globals Backbone:true, _:true, jQuery:true*/ +Backbone.Paginator = (function ( Backbone, _, $ ) { + "use strict"; + + var Paginator = {}; + Paginator.version = "0.7.0"; + + // @name: clientPager + // + // @tagline: Paginator for client-side data + // + // @description: + // This paginator is responsible for providing pagination + // and sort capabilities for a single payload of data + // we wish to paginate by the UI for easier browsering. + // + Paginator.clientPager = Backbone.Collection.extend({ + + // DEFAULTS FOR SORTING & FILTERING + useDiacriticsPlugin: true, // use diacritics plugin if available + useLevenshteinPlugin: true, // use levenshtein plugin if available + sortColumn: "", + sortDirection: "desc", + lastSortColumn: "", + fieldFilterRules: [], + lastFieldFilterRules: [], + filterFields: "", + filterExpression: "", + lastFilterExpression: "", + + //DEFAULT PAGINATOR UI VALUES + defaults_ui: { + firstPage: 0, + currentPage: 1, + perPage: 5, + totalPages: 10, + pagesInRange: 4 + }, + + // Default values used when sorting and/or filtering. + initialize: function(){ + //LISTEN FOR ADD & REMOVE EVENTS THEN REMOVE MODELS FROM ORGINAL MODELS + this.on('add', this.addModel, this); + this.on('remove', this.removeModel, this); + + // SET DEFAULT VALUES (ALLOWS YOU TO POPULATE PAGINATOR MAUNALLY) + this.setDefaults(); + }, + + + setDefaults: function() { + // SET DEFAULT UI SETTINGS + var options = _.defaults(this.paginator_ui, this.defaults_ui); + + //UPDATE GLOBAL UI SETTINGS + _.defaults(this, options); + }, + + addModel: function(model) { + this.origModels.push(model); + }, + + removeModel: function(model) { + var index = _.indexOf(this.origModels, model); + + this.origModels.splice(index, 1); + }, + + sync: function ( method, model, options ) { + var self = this; + + // SET DEFAULT VALUES + this.setDefaults(); + + // Some values could be functions, let's make sure + // to change their scope too and run them + var queryAttributes = {}; + _.each(_.result(self, "server_api"), function(value, key){ + if( _.isFunction(value) ) { + value = _.bind(value, self); + value = value(); + } + queryAttributes[key] = value; + }); + + var queryOptions = _.clone(self.paginator_core); + _.each(queryOptions, function(value, key){ + if( _.isFunction(value) ) { + value = _.bind(value, self); + value = value(); + } + queryOptions[key] = value; + }); + + // Create default values if no others are specified + queryOptions = _.defaults(queryOptions, { + timeout: 25000, + cache: false, + type: 'GET', + dataType: 'jsonp' + }); + + queryOptions = _.extend(queryOptions, { + data: decodeURIComponent($.param(queryAttributes)), + processData: false, + url: _.result(queryOptions, 'url') + }, options); + + var bbVer = Backbone.VERSION.split('.'); + var promiseSuccessFormat = !(parseInt(bbVer[0], 10) === 0 && + parseInt(bbVer[1], 10) === 9 && + parseInt(bbVer[2], 10) === 10); + + var success = queryOptions.success; + queryOptions.success = function ( resp, status, xhr ) { + if ( success ) { + // This is to keep compatibility with Backbone 0.9.10 + if (promiseSuccessFormat) { + success( resp, status, xhr ); + } else { + success( model, resp, queryOptions ); + } + } + if ( model && model.trigger ) { + model.trigger( 'sync', model, resp, queryOptions ); + } + }; + + var error = queryOptions.error; + queryOptions.error = function ( xhr ) { + if ( error ) { + error( model, xhr, queryOptions ); + } + if ( model && model.trigger ) { + model.trigger( 'error', model, xhr, queryOptions ); + } + }; + + var xhr = queryOptions.xhr = $.ajax( queryOptions ); + if ( model && model.trigger ) { + model.trigger('request', model, xhr, queryOptions); + } + return xhr; + }, + + nextPage: function (options) { + if(this.currentPage < this.information.totalPages) { + this.currentPage = ++this.currentPage; + this.pager(options); + } + }, + + previousPage: function (options) { + if(this.currentPage > 1) { + this.currentPage = --this.currentPage; + this.pager(options); + } + }, + + goTo: function ( page, options ) { + if(page !== undefined){ + this.currentPage = parseInt(page, 10); + this.pager(options); + } + }, + + howManyPer: function ( perPage ) { + if(perPage !== undefined){ + var lastPerPage = this.perPage; + this.perPage = parseInt(perPage, 10); + this.currentPage = Math.ceil( ( lastPerPage * ( this.currentPage - 1 ) + 1 ) / perPage); + this.pager(); + } + }, + + + // setSort is used to sort the current model. After + // passing 'column', which is the model's field you want + // to filter and 'direction', which is the direction + // desired for the ordering ('asc' or 'desc'), pager() + // and info() will be called automatically. + setSort: function ( column, direction ) { + if(column !== undefined && direction !== undefined){ + this.lastSortColumn = this.sortColumn; + this.sortColumn = column; + this.sortDirection = direction; + this.pager(); + this.info(); + } + }, + + // setFieldFilter is used to filter each value of each model + // according to `rules` that you pass as argument. + // Example: You have a collection of books with 'release year' and 'author'. + // You can filter only the books that were released between 1999 and 2003 + // And then you can add another `rule` that will filter those books only to + // authors who's name start with 'A'. + setFieldFilter: function ( fieldFilterRules ) { + if( !_.isEmpty( fieldFilterRules ) ) { + this.lastFieldFilterRules = this.fieldFilterRules; + this.fieldFilterRules = fieldFilterRules; + this.pager(); + this.info(); + // if all the filters are removed, we should save the last filter + // and then let the list reset to it's original state. + } else { + this.lastFieldFilterRules = this.fieldFilterRules; + this.fieldFilterRules = ''; + this.pager(); + this.info(); + } + }, + + // doFakeFieldFilter can be used to get the number of models that will remain + // after calling setFieldFilter with a filter rule(s) + doFakeFieldFilter: function ( rules ) { + if( !_.isEmpty( rules ) ) { + var testModels = this.origModels; + if (testModels === undefined) { + testModels = this.models; + } + + testModels = this._fieldFilter(testModels, rules); + + // To comply with current behavior, also filter by any previously defined setFilter rules. + if ( this.filterExpression !== "" ) { + testModels = this._filter(testModels, this.filterFields, this.filterExpression); + } + + // Return size + return testModels.length; + } + + }, + + // setFilter is used to filter the current model. After + // passing 'fields', which can be a string referring to + // the model's field, an array of strings representing + // each of the model's fields or an object with the name + // of the model's field(s) and comparing options (see docs) + // you wish to filter by and + // 'filter', which is the word or words you wish to + // filter by, pager() and info() will be called automatically. + setFilter: function ( fields, filter ) { + if( fields !== undefined && filter !== undefined ){ + this.filterFields = fields; + this.lastFilterExpression = this.filterExpression; + this.filterExpression = filter; + this.pager(); + this.info(); + } + }, + + // doFakeFilter can be used to get the number of models that will + // remain after calling setFilter with a `fields` and `filter` args. + doFakeFilter: function ( fields, filter ) { + if( fields !== undefined && filter !== undefined ){ + var testModels = this.origModels; + if (testModels === undefined) { + testModels = this.models; + } + + // To comply with current behavior, first filter by any previously defined setFieldFilter rules. + if ( !_.isEmpty( this.fieldFilterRules ) ) { + testModels = this._fieldFilter(testModels, this.fieldFilterRules); + } + + testModels = this._filter(testModels, fields, filter); + + // Return size + return testModels.length; + } + }, + + + // pager is used to sort, filter and show the data + // you expect the library to display. + pager: function (options) { + var self = this, + disp = this.perPage, + start = (self.currentPage - 1) * disp, + stop = start + disp; + // Saving the original models collection is important + // as we could need to sort or filter, and we don't want + // to loose the data we fetched from the server. + if (self.origModels === undefined) { + self.origModels = self.models; + } + + self.models = self.origModels.slice(); + + // Check if sorting was set using setSort. + if ( this.sortColumn !== "" ) { + self.models = self._sort(self.models, this.sortColumn, this.sortDirection); + } + + // Check if field-filtering was set using setFieldFilter + if ( !_.isEmpty( this.fieldFilterRules ) ) { + self.models = self._fieldFilter(self.models, this.fieldFilterRules); + } + + // Check if filtering was set using setFilter. + if ( this.filterExpression !== "" ) { + self.models = self._filter(self.models, this.filterFields, this.filterExpression); + } + + // If the sorting or the filtering was changed go to the first page + if ( this.lastSortColumn !== this.sortColumn || this.lastFilterExpression !== this.filterExpression || !_.isEqual(this.fieldFilterRules, this.lastFieldFilterRules) ) { + start = 0; + stop = start + disp; + self.currentPage = 1; + + this.lastSortColumn = this.sortColumn; + this.lastFieldFilterRules = this.fieldFilterRules; + this.lastFilterExpression = this.filterExpression; + } + + // We need to save the sorted and filtered models collection + // because we'll use that sorted and filtered collection in info(). + self.sortedAndFilteredModels = self.models.slice(); + self.info(); + self.reset(self.models.slice(start, stop)); + + // This is somewhat of a hack to get all the nextPage, prevPage, and goTo methods + // to work with a success callback (as in the requestPager). Realistically there is no failure case here, + // but maybe we could catch exception and trigger a failure callback? + _.result(options, 'success'); + }, + + // The actual place where the collection is sorted. + // Check setSort for arguments explicacion. + _sort: function ( models, sort, direction ) { + models = models.sort(function (a, b) { + var ac = a.get(sort), + bc = b.get(sort); + + if ( _.isUndefined(ac) || _.isUndefined(bc) || ac === null || bc === null ) { + return 0; + } else { + /* Make sure that both ac and bc are lowercase strings. + * .toString() first so we don't have to worry if ac or bc + * have other String-only methods. + */ + ac = ac.toString().toLowerCase(); + bc = bc.toString().toLowerCase(); + } + + if (direction === 'desc') { + + // We need to know if there aren't any non-number characters + // and that there are numbers-only characters and maybe a dot + // if we have a float. + // Oh, also a '-' for negative numbers! + if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) && + (!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))){ + + if( (ac - 0) < (bc - 0) ) { + return 1; + } + if( (ac - 0) > (bc - 0) ) { + return -1; + } + } else { + if (ac < bc) { + return 1; + } + if (ac > bc) { + return -1; + } + } + + } else { + + //Same as the regexp check in the 'if' part. + if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) && + (!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))){ + if( (ac - 0) < (bc - 0) ) { + return -1; + } + if( (ac - 0) > (bc - 0) ) { + return 1; + } + } else { + if (ac < bc) { + return -1; + } + if (ac > bc) { + return 1; + } + } + + } + + if (a.cid && b.cid){ + var aId = a.cid, + bId = b.cid; + + if (aId < bId) { + return -1; + } + if (aId > bId) { + return 1; + } + } + + return 0; + }); + + return models; + }, + + // The actual place where the collection is field-filtered. + // Check setFieldFilter for arguments explicacion. + _fieldFilter: function( models, rules ) { + + // Check if there are any rules + if ( _.isEmpty(rules) ) { + return models; + } + + var filteredModels = []; + + // Iterate over each rule + _.each(models, function(model){ + + var should_push = true; + + // Apply each rule to each model in the collection + _.each(rules, function(rule){ + + // Don't go inside the switch if we're already sure that the model won't be included in the results + if( !should_push ){ + return false; + } + + should_push = false; + + // The field's value will be passed to a custom function, which should + // return true (if model should be included) or false (model should be ignored) + if(rule.type === "function"){ + var f = _.wrap(rule.value, function(func){ + return func( model.get(rule.field) ); + }); + if( f() ){ + should_push = true; + } + + // The field's value is required to be non-empty + }else if(rule.type === "required"){ + if( !_.isEmpty( model.get(rule.field).toString() ) ) { + should_push = true; + } + + // The field's value is required to be greater tan N (numbers only) + }else if(rule.type === "min"){ + if( !_.isNaN( Number( model.get(rule.field) ) ) && + !_.isNaN( Number( rule.value ) ) && + Number( model.get(rule.field) ) >= Number( rule.value ) ) { + should_push = true; + } + + // The field's value is required to be smaller tan N (numbers only) + }else if(rule.type === "max"){ + if( !_.isNaN( Number( model.get(rule.field) ) ) && + !_.isNaN( Number( rule.value ) ) && + Number( model.get(rule.field) ) <= Number( rule.value ) ) { + should_push = true; + } + + // The field's value is required to be between N and M (numbers only) + }else if(rule.type === "range"){ + if( !_.isNaN( Number( model.get(rule.field) ) ) && + _.isObject( rule.value ) && + !_.isNaN( Number( rule.value.min ) ) && + !_.isNaN( Number( rule.value.max ) ) && + Number( model.get(rule.field) ) >= Number( rule.value.min ) && + Number( model.get(rule.field) ) <= Number( rule.value.max ) ) { + should_push = true; + } + + // The field's value is required to be more than N chars long + }else if(rule.type === "minLength"){ + if( model.get(rule.field).toString().length >= rule.value ) { + should_push = true; + } + + // The field's value is required to be no more than N chars long + }else if(rule.type === "maxLength"){ + if( model.get(rule.field).toString().length <= rule.value ) { + should_push = true; + } + + // The field's value is required to be more than N chars long and no more than M chars long + }else if(rule.type === "rangeLength"){ + if( _.isObject( rule.value ) && + !_.isNaN( Number( rule.value.min ) ) && + !_.isNaN( Number( rule.value.max ) ) && + model.get(rule.field).toString().length >= rule.value.min && + model.get(rule.field).toString().length <= rule.value.max ) { + should_push = true; + } + + // The field's value is required to be equal to one of the values in rules.value + }else if(rule.type === "oneOf"){ + if( _.isArray( rule.value ) && + _.include( rule.value, model.get(rule.field) ) ) { + should_push = true; + } + + // The field's value is required to be equal to the value in rules.value + }else if(rule.type === "equalTo"){ + if( rule.value === model.get(rule.field) ) { + should_push = true; + } + + }else if(rule.type === "containsAllOf"){ + if( _.isArray( rule.value ) && + _.isArray(model.get(rule.field)) && + _.intersection( rule.value, model.get(rule.field)).length === rule.value.length) { + should_push = true; + } + + // The field's value is required to match the regular expression + }else if(rule.type === "pattern"){ + if( model.get(rule.field).toString().match(rule.value) ) { + should_push = true; + } + + //Unknown type + }else{ + should_push = false; + } + + }); + + if( should_push ){ + filteredModels.push(model); + } + + }); + + return filteredModels; + }, + + // The actual place where the collection is filtered. + // Check setFilter for arguments explicacion. + _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; + }, + + // You shouldn't need to call info() as this method is used to + // calculate internal data as first/prev/next/last page... + info: function () { + var self = this, + info = {}, + totalRecords = (self.sortedAndFilteredModels) ? self.sortedAndFilteredModels.length : self.length, + totalPages = Math.ceil(totalRecords / self.perPage); + + info = { + totalUnfilteredRecords: self.origModels.length, + totalRecords: totalRecords, + currentPage: self.currentPage, + perPage: this.perPage, + totalPages: totalPages, + lastPage: totalPages, + previous: false, + next: false, + startRecord: totalRecords === 0 ? 0 : (self.currentPage - 1) * this.perPage + 1, + endRecord: Math.min(totalRecords, self.currentPage * this.perPage) + }; + + if (self.currentPage > 1) { + info.previous = self.currentPage - 1; + } + + if (self.currentPage < info.totalPages) { + info.next = self.currentPage + 1; + } + + info.pageSet = self.setPagination(info); + + self.information = info; + return info; + }, + + + // setPagination also is an internal function that shouldn't be called directly. + // It will create an array containing the pages right before and right after the + // actual page. + setPagination: function ( info ) { + + var pages = [], i = 0, l = 0; + + // How many adjacent pages should be shown on each side? + var ADJACENTx2 = this.pagesInRange * 2, + LASTPAGE = Math.ceil(info.totalRecords / info.perPage); + + if (LASTPAGE > 1) { + + // not enough pages to bother breaking it up + if (LASTPAGE <= (1 + ADJACENTx2)) { + for (i = 1, l = LASTPAGE; i <= l; i++) { + pages.push(i); + } + } + + // enough pages to hide some + else { + + //close to beginning; only hide later pages + if (info.currentPage <= (this.pagesInRange + 1)) { + for (i = 1, l = 2 + ADJACENTx2; i < l; i++) { + pages.push(i); + } + } + + // in middle; hide some front and some back + else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) { + for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) { + pages.push(i); + } + } + + // close to end; only hide early pages + else { + for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) { + pages.push(i); + } + } + } + + } + + return pages; + + }, + + bootstrap: function(options) { + _.extend(this, options); + this.goTo(1); + this.info(); + return this; + } + + }); + + // function aliasing + Paginator.clientPager.prototype.prevPage = Paginator.clientPager.prototype.previousPage; + + // @name: requestPager + // + // Paginator for server-side data being requested from a backend/API + // + // @description: + // This paginator is responsible for providing pagination + // and sort capabilities for requests to a server-side + // data service (e.g an API) + // + Paginator.requestPager = Backbone.Collection.extend({ + + sync: function ( method, model, options ) { + + var self = this; + + self.setDefaults(); + + // Some values could be functions, let's make sure + // to change their scope too and run them + var queryAttributes = {}; + _.each(_.result(self, "server_api"), function(value, key){ + if( _.isFunction(value) ) { + value = _.bind(value, self); + value = value(); + } + queryAttributes[key] = value; + }); + + var queryOptions = _.clone(self.paginator_core); + _.each(queryOptions, function(value, key){ + if( _.isFunction(value) ) { + value = _.bind(value, self); + value = value(); + } + queryOptions[key] = value; + }); + + // Create default values if no others are specified + queryOptions = _.defaults(queryOptions, { + timeout: 25000, + cache: false, + type: 'GET', + dataType: 'jsonp' + }); + + // Allows the passing in of {data: {foo: 'bar'}} at request time to overwrite server_api defaults + if( options.data ){ + options.data = decodeURIComponent($.param(_.extend(queryAttributes,options.data))); + }else{ + options.data = decodeURIComponent($.param(queryAttributes)); + } + + queryOptions = _.extend(queryOptions, { + data: decodeURIComponent($.param(queryAttributes)), + processData: false, + url: _.result(queryOptions, 'url') + }, options); + + var bbVer = Backbone.VERSION.split('.'); + var promiseSuccessFormat = !(parseInt(bbVer[0], 10) === 0 && + parseInt(bbVer[1], 10) === 9 && + parseInt(bbVer[2], 10) === 10); + + var success = queryOptions.success; + queryOptions.success = function ( resp, status, xhr ) { + + if ( success ) { + // This is to keep compatibility with Backbone 0.9.10 + if (promiseSuccessFormat) { + success( resp, status, xhr ); + } else { + success( model, resp, queryOptions ); + } + } + if ( model && model.trigger ) { + model.trigger( 'sync', model, resp, queryOptions ); + } + }; + + var error = queryOptions.error; + queryOptions.error = function ( xhr ) { + if ( error ) { + error( model, xhr, queryOptions ); + } + if ( model && model.trigger ) { + model.trigger( 'error', model, xhr, queryOptions ); + } + }; + + var xhr = queryOptions.xhr = $.ajax( queryOptions ); + if ( model && model.trigger ) { + model.trigger('request', model, xhr, queryOptions); + } + return xhr; + }, + + setDefaults: function() { + var self = this; + + // Create default values if no others are specified + _.defaults(self.paginator_ui, { + firstPage: 0, + currentPage: 1, + perPage: 5, + totalPages: 10, + pagesInRange: 4 + }); + + // Change scope of 'paginator_ui' object values + _.each(self.paginator_ui, function(value, key) { + if (_.isUndefined(self[key])) { + self[key] = self.paginator_ui[key]; + } + }); + }, + + requestNextPage: function ( options ) { + if ( this.currentPage !== undefined ) { + this.currentPage += 1; + return this.pager( options ); + } else { + var response = new $.Deferred(); + response.reject(); + return response.promise(); + } + }, + + requestPreviousPage: function ( options ) { + if ( this.currentPage !== undefined ) { + this.currentPage -= 1; + return this.pager( options ); + } else { + var response = new $.Deferred(); + response.reject(); + return response.promise(); + } + }, + + updateOrder: function ( column ) { + if (column !== undefined) { + this.sortField = column; + this.pager(); + } + + }, + + goTo: function ( page, options ) { + if ( page !== undefined ) { + this.currentPage = parseInt(page, 10); + return this.pager( options ); + } else { + var response = new $.Deferred(); + response.reject(); + return response.promise(); + } + }, + + howManyPer: function ( count ) { + if( count !== undefined ){ + this.currentPage = this.firstPage; + this.perPage = count; + this.pager(); + } + }, + + info: function () { + + var info = { + // If parse() method is implemented and totalRecords is set to the length + // of the records returned, make it available. Else, default it to 0 + totalRecords: this.totalRecords || 0, + + currentPage: this.currentPage, + firstPage: this.firstPage, + totalPages: Math.ceil(this.totalRecords / this.perPage), + lastPage: this.totalPages, // should use totalPages in template + perPage: this.perPage, + previous:false, + next:false + }; + + if (this.currentPage > 1) { + info.previous = this.currentPage - 1; + } + + if (this.currentPage < info.totalPages) { + info.next = this.currentPage + 1; + } + + // left around for backwards compatibility + info.hasNext = info.next; + info.hasPrevious = info.next; + + info.pageSet = this.setPagination(info); + + this.information = info; + return info; + }, + + setPagination: function ( info ) { + + var pages = [], i = 0, l = 0; + + // How many adjacent pages should be shown on each side? + var ADJACENTx2 = this.pagesInRange * 2, + LASTPAGE = Math.ceil(info.totalRecords / info.perPage); + + if (LASTPAGE > 1) { + + // not enough pages to bother breaking it up + if (LASTPAGE <= (1 + ADJACENTx2)) { + for (i = 1, l = LASTPAGE; i <= l; i++) { + pages.push(i); + } + } + + // enough pages to hide some + else { + + //close to beginning; only hide later pages + if (info.currentPage <= (this.pagesInRange + 1)) { + for (i = 1, l = 2 + ADJACENTx2; i < l; i++) { + pages.push(i); + } + } + + // in middle; hide some front and some back + else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) { + for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) { + pages.push(i); + } + } + + // close to end; only hide early pages + else { + for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) { + pages.push(i); + } + } + } + + } + + return pages; + + }, + + // fetches the latest results from the server + pager: function ( options ) { + if ( !_.isObject(options) ) { + options = {}; + } + return this.fetch( options ); + }, + + url: function(){ + // Expose url parameter enclosed in this.paginator_core.url to properly + // extend Collection and allow Collection CRUD + if(this.paginator_core !== undefined && this.paginator_core.url !== undefined){ + return this.paginator_core.url; + } else { + return null; + } + }, + + bootstrap: function(options) { + _.extend(this, options); + this.setDefaults(); + this.info(); + return this; + } + }); + + // function aliasing + Paginator.requestPager.prototype.nextPage = Paginator.requestPager.prototype.requestNextPage; + Paginator.requestPager.prototype.prevPage = Paginator.requestPager.prototype.requestPreviousPage; + + return Paginator; + +}( Backbone, _, jQuery )); diff --git a/ajax/libs/backbone.paginator/0.7/backbone.paginator.min.js b/ajax/libs/backbone.paginator/0.7/backbone.paginator.min.js new file mode 100644 index 00000000000000..dd1a0f6d76866f --- /dev/null +++ b/ajax/libs/backbone.paginator/0.7/backbone.paginator.min.js @@ -0,0 +1,4 @@ +/*! backbone.paginator - v0.7.0 - 3/25/2013 +* http://github.com/addyosmani/backbone.paginator +* Copyright (c) 2013 Addy Osmani; Licensed MIT */ +Backbone.Paginator=function(e,t,i){"use strict";var r={};return r.version="0.7.0",r.clientPager=e.Collection.extend({useDiacriticsPlugin:!0,useLevenshteinPlugin:!0,sortColumn:"",sortDirection:"desc",lastSortColumn:"",fieldFilterRules:[],lastFieldFilterRules:[],filterFields:"",filterExpression:"",lastFilterExpression:"",defaults_ui:{firstPage:0,currentPage:1,perPage:5,totalPages:10,pagesInRange:4},initialize:function(){this.on("add",this.addModel,this),this.on("remove",this.removeModel,this),this.setDefaults()},setDefaults:function(){var e=t.defaults(this.paginator_ui,this.defaults_ui);t.defaults(this,e)},addModel:function(e){this.origModels.push(e)},removeModel:function(e){var i=t.indexOf(this.origModels,e);this.origModels.splice(i,1)},sync:function(r,s,n){var a=this;this.setDefaults();var o={};t.each(t.result(a,"server_api"),function(e,i){t.isFunction(e)&&(e=t.bind(e,a),e=e()),o[i]=e});var u=t.clone(a.paginator_core);t.each(u,function(e,i){t.isFunction(e)&&(e=t.bind(e,a),e=e()),u[i]=e}),u=t.defaults(u,{timeout:25e3,cache:!1,type:"GET",dataType:"jsonp"}),u=t.extend(u,{data:decodeURIComponent(i.param(o)),processData:!1,url:t.result(u,"url")},n);var l=e.VERSION.split("."),g=!(0===parseInt(l[0],10)&&9===parseInt(l[1],10)&&10===parseInt(l[2],10)),h=u.success;u.success=function(e,t,i){h&&(g?h(e,t,i):h(s,e,u)),s&&s.trigger&&s.trigger("sync",s,e,u)};var c=u.error;u.error=function(e){c&&c(s,e,u),s&&s.trigger&&s.trigger("error",s,e,u)};var f=u.xhr=i.ajax(u);return s&&s.trigger&&s.trigger("request",s,f,u),f},nextPage:function(e){this.currentPage1&&(this.currentPage=--this.currentPage,this.pager(e))},goTo:function(e,t){void 0!==e&&(this.currentPage=parseInt(e,10),this.pager(t))},howManyPer:function(e){if(void 0!==e){var t=this.perPage;this.perPage=parseInt(e,10),this.currentPage=Math.ceil((t*(this.currentPage-1)+1)/e),this.pager()}},setSort:function(e,t){void 0!==e&&void 0!==t&&(this.lastSortColumn=this.sortColumn,this.sortColumn=e,this.sortDirection=t,this.pager(),this.info())},setFieldFilter:function(e){t.isEmpty(e)?(this.lastFieldFilterRules=this.fieldFilterRules,this.fieldFilterRules="",this.pager(),this.info()):(this.lastFieldFilterRules=this.fieldFilterRules,this.fieldFilterRules=e,this.pager(),this.info())},doFakeFieldFilter:function(e){if(!t.isEmpty(e)){var i=this.origModels;return void 0===i&&(i=this.models),i=this._fieldFilter(i,e),""!==this.filterExpression&&(i=this._filter(i,this.filterFields,this.filterExpression)),i.length}},setFilter:function(e,t){void 0!==e&&void 0!==t&&(this.filterFields=e,this.lastFilterExpression=this.filterExpression,this.filterExpression=t,this.pager(),this.info())},doFakeFilter:function(e,i){if(void 0!==e&&void 0!==i){var r=this.origModels;return void 0===r&&(r=this.models),t.isEmpty(this.fieldFilterRules)||(r=this._fieldFilter(r,this.fieldFilterRules)),r=this._filter(r,e,i),r.length}},pager:function(e){var i=this,r=this.perPage,s=(i.currentPage-1)*r,n=s+r;void 0===i.origModels&&(i.origModels=i.models),i.models=i.origModels.slice(),""!==this.sortColumn&&(i.models=i._sort(i.models,this.sortColumn,this.sortDirection)),t.isEmpty(this.fieldFilterRules)||(i.models=i._fieldFilter(i.models,this.fieldFilterRules)),""!==this.filterExpression&&(i.models=i._filter(i.models,this.filterFields,this.filterExpression)),this.lastSortColumn===this.sortColumn&&this.lastFilterExpression===this.filterExpression&&t.isEqual(this.fieldFilterRules,this.lastFieldFilterRules)||(s=0,n=s+r,i.currentPage=1,this.lastSortColumn=this.sortColumn,this.lastFieldFilterRules=this.fieldFilterRules,this.lastFilterExpression=this.filterExpression),i.sortedAndFilteredModels=i.models.slice(),i.info(),i.reset(i.models.slice(s,n)),t.result(e,"success")},_sort:function(e,i,r){return e=e.sort(function(e,s){var n=e.get(i),a=s.get(i);if(t.isUndefined(n)||t.isUndefined(a)||null===n||null===a)return 0;if(n=(""+n).toLowerCase(),a=(""+a).toLowerCase(),"desc"===r)if(!n.match(/[^\-\d\.]/)&&n.match(/-?[\d\.]+/)&&!a.match(/[^\-\d\.]/)&&a.match(/-?[\d\.]+/)){if(a-0>n-0)return 1;if(n-0>a-0)return-1}else{if(a>n)return 1;if(n>a)return-1}else if(!n.match(/[^\-\d\.]/)&&n.match(/-?[\d\.]+/)&&!a.match(/[^\-\d\.]/)&&a.match(/-?[\d\.]+/)){if(a-0>n-0)return-1;if(n-0>a-0)return 1}else{if(a>n)return-1;if(n>a)return 1}if(e.cid&&s.cid){var o=e.cid,u=s.cid;if(u>o)return-1;if(o>u)return 1}return 0})},_fieldFilter:function(e,i){if(t.isEmpty(i))return e;var r=[];return t.each(e,function(e){var s=!0;t.each(i,function(i){if(!s)return!1;if(s=!1,"function"===i.type){var r=t.wrap(i.value,function(t){return t(e.get(i.field))});r()&&(s=!0)}else"required"===i.type?t.isEmpty(""+e.get(i.field))||(s=!0):"min"===i.type?!t.isNaN(Number(e.get(i.field)))&&!t.isNaN(Number(i.value))&&Number(e.get(i.field))>=Number(i.value)&&(s=!0):"max"===i.type?!t.isNaN(Number(e.get(i.field)))&&!t.isNaN(Number(i.value))&&Number(e.get(i.field))<=Number(i.value)&&(s=!0):"range"===i.type?!t.isNaN(Number(e.get(i.field)))&&t.isObject(i.value)&&!t.isNaN(Number(i.value.min))&&!t.isNaN(Number(i.value.max))&&Number(e.get(i.field))>=Number(i.value.min)&&Number(e.get(i.field))<=Number(i.value.max)&&(s=!0):"minLength"===i.type?(""+e.get(i.field)).length>=i.value&&(s=!0):"maxLength"===i.type?(""+e.get(i.field)).length<=i.value&&(s=!0):"rangeLength"===i.type?t.isObject(i.value)&&!t.isNaN(Number(i.value.min))&&!t.isNaN(Number(i.value.max))&&(""+e.get(i.field)).length>=i.value.min&&(""+e.get(i.field)).length<=i.value.max&&(s=!0):"oneOf"===i.type?t.isArray(i.value)&&t.include(i.value,e.get(i.field))&&(s=!0):"equalTo"===i.type?i.value===e.get(i.field)&&(s=!0):"containsAllOf"===i.type?t.isArray(i.value)&&t.isArray(e.get(i.field))&&t.intersection(i.value,e.get(i.field)).length===i.value.length&&(s=!0):"pattern"===i.type?(""+e.get(i.field)).match(i.value)&&(s=!0):s=!1}),s&&r.push(e)}),r},_filter:function(i,r,s){var n=this,a={};if(t.isString(r)?a[r]={cmp_method:"regexp"}:t.isArray(r)?t.each(r,function(e){a[e]={cmp_method:"regexp"}}):t.each(r,function(e,i){a[i]=t.defaults(e,{cmp_method:"regexp"})}),r=a,t.has(e.Paginator,"removeDiacritics")&&n.useDiacriticsPlugin&&(s=e.Paginator.removeDiacritics(s)),""===s||!t.isString(s))return i;var o=t.map(s.match(/\w+/gi),function(e){return e.toLowerCase()}),u="("+t.uniq(o).join("|")+")",l=RegExp(u,"igm"),g=[];return t.each(i,function(i){var a=[];t.each(r,function(r,u){var g=i.get(u);if(g){var h=[];if(g=t.has(e.Paginator,"removeDiacritics")&&n.useDiacriticsPlugin?e.Paginator.removeDiacritics(""+g):""+g,"levenshtein"===r.cmp_method&&t.has(e.Paginator,"levenshtein")&&n.useLevenshteinPlugin){var c=e.Paginator.levenshtein(g,s);t.defaults(r,{max_distance:0}),r.max_distance>=c&&(h=t.uniq(o))}else h=g.match(l);h=t.map(h,function(e){return(""+e).toLowerCase()}),t.each(h,function(e){a.push(e)})}}),a=t.uniq(t.without(a,"")),t.isEmpty(t.difference(o,a))&&g.push(i)}),g},info:function(){var e=this,t={},i=e.sortedAndFilteredModels?e.sortedAndFilteredModels.length:e.length,r=Math.ceil(i/e.perPage);return t={totalUnfilteredRecords:e.origModels.length,totalRecords:i,currentPage:e.currentPage,perPage:this.perPage,totalPages:r,lastPage:r,previous:!1,next:!1,startRecord:0===i?0:(e.currentPage-1)*this.perPage+1,endRecord:Math.min(i,e.currentPage*this.perPage)},e.currentPage>1&&(t.previous=e.currentPage-1),e.currentPage1)if(1+s>=n)for(i=1,r=n;r>=i;i++)t.push(i);else if(e.currentPage<=this.pagesInRange+1)for(i=1,r=2+s;r>i;i++)t.push(i);else if(n-this.pagesInRange>e.currentPage&&e.currentPage>this.pagesInRange)for(i=e.currentPage-this.pagesInRange;e.currentPage+this.pagesInRange>=i;i++)t.push(i);else for(i=n-s;n>=i;i++)t.push(i);return t},bootstrap:function(e){return t.extend(this,e),this.goTo(1),this.info(),this}}),r.clientPager.prototype.prevPage=r.clientPager.prototype.previousPage,r.requestPager=e.Collection.extend({sync:function(r,s,n){var a=this;a.setDefaults();var o={};t.each(t.result(a,"server_api"),function(e,i){t.isFunction(e)&&(e=t.bind(e,a),e=e()),o[i]=e});var u=t.clone(a.paginator_core);t.each(u,function(e,i){t.isFunction(e)&&(e=t.bind(e,a),e=e()),u[i]=e}),u=t.defaults(u,{timeout:25e3,cache:!1,type:"GET",dataType:"jsonp"}),n.data=n.data?decodeURIComponent(i.param(t.extend(o,n.data))):decodeURIComponent(i.param(o)),u=t.extend(u,{data:decodeURIComponent(i.param(o)),processData:!1,url:t.result(u,"url")},n);var l=e.VERSION.split("."),g=!(0===parseInt(l[0],10)&&9===parseInt(l[1],10)&&10===parseInt(l[2],10)),h=u.success;u.success=function(e,t,i){h&&(g?h(e,t,i):h(s,e,u)),s&&s.trigger&&s.trigger("sync",s,e,u)};var c=u.error;u.error=function(e){c&&c(s,e,u),s&&s.trigger&&s.trigger("error",s,e,u)};var f=u.xhr=i.ajax(u);return s&&s.trigger&&s.trigger("request",s,f,u),f},setDefaults:function(){var e=this;t.defaults(e.paginator_ui,{firstPage:0,currentPage:1,perPage:5,totalPages:10,pagesInRange:4}),t.each(e.paginator_ui,function(i,r){t.isUndefined(e[r])&&(e[r]=e.paginator_ui[r])})},requestNextPage:function(e){if(void 0!==this.currentPage)return this.currentPage+=1,this.pager(e);var t=new i.Deferred;return t.reject(),t.promise()},requestPreviousPage:function(e){if(void 0!==this.currentPage)return this.currentPage-=1,this.pager(e);var t=new i.Deferred;return t.reject(),t.promise()},updateOrder:function(e){void 0!==e&&(this.sortField=e,this.pager())},goTo:function(e,t){if(void 0!==e)return this.currentPage=parseInt(e,10),this.pager(t);var r=new i.Deferred;return r.reject(),r.promise()},howManyPer:function(e){void 0!==e&&(this.currentPage=this.firstPage,this.perPage=e,this.pager())},info:function(){var e={totalRecords:this.totalRecords||0,currentPage:this.currentPage,firstPage:this.firstPage,totalPages:Math.ceil(this.totalRecords/this.perPage),lastPage:this.totalPages,perPage:this.perPage,previous:!1,next:!1};return this.currentPage>1&&(e.previous=this.currentPage-1),this.currentPage1)if(1+s>=n)for(i=1,r=n;r>=i;i++)t.push(i);else if(e.currentPage<=this.pagesInRange+1)for(i=1,r=2+s;r>i;i++)t.push(i);else if(n-this.pagesInRange>e.currentPage&&e.currentPage>this.pagesInRange)for(i=e.currentPage-this.pagesInRange;e.currentPage+this.pagesInRange>=i;i++)t.push(i);else for(i=n-s;n>=i;i++)t.push(i);return t},pager:function(e){return t.isObject(e)||(e={}),this.fetch(e)},url:function(){return void 0!==this.paginator_core&&void 0!==this.paginator_core.url?this.paginator_core.url:null},bootstrap:function(e){return t.extend(this,e),this.setDefaults(),this.info(),this}}),r.requestPager.prototype.nextPage=r.requestPager.prototype.requestNextPage,r.requestPager.prototype.prevPage=r.requestPager.prototype.requestPreviousPage,r}(Backbone,_,jQuery); \ No newline at end of file diff --git a/ajax/libs/backbone.paginator/package.json b/ajax/libs/backbone.paginator/package.json index c85e17b4cad27b..d8c26d942d3431 100644 --- a/ajax/libs/backbone.paginator/package.json +++ b/ajax/libs/backbone.paginator/package.json @@ -1,7 +1,7 @@ { "name": "backbone.paginator", "filename": "backbone.paginator.min.js", - "version": "0.6", + "version": "0.7", "description": "Pagination component for backbone.js", "homepage": "https://github.com/addyosmani/backbone.paginator", "keywords": [