text: /*globals Backbone:true, _:true, jQuery:true*/
  Backbone.Paginator = (function ( Backbone, _, $ ) {
    "use strict";
  
  
    var bbVer = _.map(Backbone.VERSION.split('.'), function(digit) {
      return parseInt(digit, 10);
    });
  
    var Paginator = {};
    Paginator.version = "<%= pkg.version %>";
  
    // @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) {
          //TODO: why
        this.origModels = this.origModels || [];
        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 promiseSuccessFormat = !(bbVer[0] === 0 &&
                                     bbVer[1] === 9 &&
                                     bbVer[2] === 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 = Backbone.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) {
            
            function getDotProperty(obj, dotNotation) {
                var arr = dotNotation.split('.');
                while (arr.length && (obj = obj[arr.shift()]))
                    ;
                return obj;
            }
            
            //getDotProperty(resource.attributes, sortField);          
  //        var ac = a.get(sort),
  //        bc = b.get(sort);
  
          var ac = getDotProperty(a, sort);
          var bc = getDotProperty(b, 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;
  
    // Helper function to generate rejected Deferred
    var reject = function () {
      var response = new $.Deferred();
      response.reject();
      return response.promise();
    };
  
    // @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 promiseSuccessFormat = !(bbVer[0] === 0 &&
                                     bbVer[1] === 9 &&
                                     bbVer[2] === 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 (bbVer[0] < 1 && model && model.trigger ) {
            model.trigger( 'sync', model, resp, queryOptions );
          }
        };
  
        var error = queryOptions.error;
        queryOptions.error = function ( xhr ) {
          if ( error ) {
            error( xhr );
          }
          if ( model && model.trigger ) {
            model.trigger( 'error', model, xhr, queryOptions );
          }
        };
  
        var xhr = queryOptions.xhr = Backbone.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 {
          return reject();
        }
      },
  
      requestPreviousPage: function ( options ) {
        if ( this.currentPage !== undefined ) {
          this.currentPage -= 1;
          return this.pager( options );
        } else {
          return reject();
        }
      },
  
      updateOrder: function ( column, options ) {
        if (column !== undefined) {
          this.sortField = column;
          return this.pager( options );
        } else {
          return reject();
        }
      },
  
      goTo: function ( page, options ) {
        if ( page !== undefined ) {
          this.currentPage = parseInt(page, 10);
          return this.pager( options );
        } else {
          return reject();
        }
      },
  
      howManyPer: function ( count, options ) {
        if ( count !== undefined ) {
          this.currentPage = this.firstPage;
          this.perPage = count;
          return this.pager( options );
        } else {
          return reject();
        }
      },
  
      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 ));
  