+/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+(function () {
+/**
+ * The Paginator widget provides a set of controls to navigate through paged
+ * data.
+ *
+ * @module paginator
+ * @uses YAHOO.util.EventProvider
+ * @uses YAHOO.util.AttributeProvider
+ */
+
+/**
+ * Instantiate a Paginator, passing a configuration object to the contructor.
+ * The configuration object should contain the following properties:
+ * <ul>
+ * <li>rowsPerPage : <em>n</em> (int)</li>
+ * <li>totalRecords : <em>n</em> (int or Paginator.VALUE_UNLIMITED)</li>
+ * <li>containers : <em>id | el | arr</em> (HTMLElement reference, its id, or an array of either)</li>
+ * </ul>
+ *
+ * @namespace YAHOO.widget
+ * @class Paginator
+ * @constructor
+ * @param config {Object} Object literal to set instance and ui component
+ * configuration.
+ */
+function Paginator(config) {
+ var UNLIMITED = Paginator.VALUE_UNLIMITED,
+ lang = YAHOO.lang,
+ attrib, initialPage, records, perPage, startIndex;
+
+ config = lang.isObject(config) ? config : {};
+
+ this.initConfig();
+
+ this.initEvents();
+
+ // Set the basic config keys first
+ this.set('rowsPerPage',config.rowsPerPage,true);
+ if (Paginator.isNumeric(config.totalRecords)) {
+ this.set('totalRecords',config.totalRecords,true);
+ }
+
+ this.initUIComponents();
+
+ // Update the other config values
+ for (attrib in config) {
+ if (lang.hasOwnProperty(config,attrib)) {
+ this.set(attrib,config[attrib],true);
+ }
+ }
+
+ // Calculate the initial record offset
+ initialPage = this.get('initialPage');
+ records = this.get('totalRecords');
+ perPage = this.get('rowsPerPage');
+ if (initialPage > 1 && perPage !== UNLIMITED) {
+ startIndex = (initialPage - 1) * perPage;
+ if (records === UNLIMITED || startIndex < records) {
+ this.set('recordOffset',startIndex,true);
+ }
+ }
+}
+
+
+// Static members
+YAHOO.lang.augmentObject(Paginator, {
+ /**
+ * Incrementing index used to give instances unique ids.
+ * @static
+ * @property id
+ * @type number
+ * @private
+ */
+ id : 0,
+
+ /**
+ * Base of id strings used for ui components.
+ * @static
+ * @property ID_BASE
+ * @type string
+ * @private
+ */
+ ID_BASE : 'yui-pg',
+
+ /**
+ * Used to identify unset, optional configurations, or used explicitly in
+ * the case of totalRecords to indicate unlimited pagination.
+ * @static
+ * @property VALUE_UNLIMITED
+ * @type number
+ * @final
+ */
+ VALUE_UNLIMITED : -1,
+
+ /**
+ * Default template used by Paginator instances. Update this if you want
+ * all new Paginators to use a different default template.
+ * @static
+ * @property TEMPLATE_DEFAULT
+ * @type string
+ */
+ TEMPLATE_DEFAULT : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",
+
+ /**
+ * Common alternate pagination format, including page links, links for
+ * previous, next, first and last pages as well as a rows-per-page
+ * dropdown. Offered as a convenience.
+ * @static
+ * @property TEMPLATE_ROWS_PER_PAGE
+ * @type string
+ */
+ TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
+
+ /**
+ * Storage object for UI Components
+ * @static
+ * @property ui
+ */
+ ui : {},
+
+ /**
+ * Similar to YAHOO.lang.isNumber, but allows numeric strings. This is
+ * is used for attribute validation in conjunction with getters that return
+ * numbers.
+ *
+ * @method isNumeric
+ * @param v {Number|String} value to be checked for number or numeric string
+ * @returns {Boolean} true if the input is coercable into a finite number
+ * @static
+ */
+ isNumeric : function (v) {
+ return isFinite(+v);
+ },
+
+ /**
+ * Return a number or null from input
+ *
+ * @method toNumber
+ * @param n {Number|String} a number or numeric string
+ * @return Number
+ * @static
+ */
+ toNumber : function (n) {
+ return isFinite(+n) ? +n : null;
+ }
+
+},true);
+
+
+// Instance members and methods
+Paginator.prototype = {
+
+ // Instance members
+
+ /**
+ * Array of nodes in which to render pagination controls. This is set via
+ * the "containers" attribute.
+ * @property _containers
+ * @type Array(HTMLElement)
+ * @private
+ */
+ _containers : [],
+
+ /**
+ * Flag used to indicate multiple attributes are being updated via setState
+ * @property _batch
+ * @type boolean
+ * @protected
+ */
+ _batch : false,
+
+ /**
+ * Used by setState to indicate when a page change has occurred
+ * @property _pageChanged
+ * @type boolean
+ * @protected
+ */
+ _pageChanged : false,
+
+ /**
+ * Temporary state cache used by setState to keep track of the previous
+ * state for eventual pageChange event firing
+ * @property _state
+ * @type Object
+ * @protected
+ */
+ _state : null,
+
+
+ // Instance methods
+
+ /**
+ * Initialize the Paginator's attributes (see YAHOO.util.Element class
+ * AttributeProvider).
+ * @method initConfig
+ * @private
+ */
+ initConfig : function () {
+
+ var UNLIMITED = Paginator.VALUE_UNLIMITED,
+ l = YAHOO.lang;
+
+ /**
+ * REQUIRED. Number of records constituting a "page"
+ * @attribute rowsPerPage
+ * @type integer
+ */
+ this.setAttributeConfig('rowsPerPage', {
+ value : 0,
+ validator : Paginator.isNumeric,
+ setter : Paginator.toNumber
+ });
+
+ /**
+ * REQUIRED. Node references or ids of nodes in which to render the
+ * pagination controls.
+ * @attribute containers
+ * @type {string|HTMLElement|Array(string|HTMLElement)}
+ */
+ this.setAttributeConfig('containers', {
+ value : null,
+ validator : function (val) {
+ if (!l.isArray(val)) {
+ val = [val];
+ }
+ for (var i = 0, len = val.length; i < len; ++i) {
+ if (l.isString(val[i]) ||
+ (l.isObject(val[i]) && val[i].nodeType === 1)) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ },
+ method : function (val) {
+ val = YAHOO.util.Dom.get(val);
+ if (!l.isArray(val)) {
+ val = [val];
+ }
+ this._containers = val;
+ }
+ });
+
+ /**
+ * Total number of records to paginate through
+ * @attribute totalRecords
+ * @type integer
+ * @default 0
+ */
+ this.setAttributeConfig('totalRecords', {
+ value : 0,
+ validator : Paginator.isNumeric,
+ setter : Paginator.toNumber
+ });
+
+ /**
+ * Zero based index of the record considered first on the current page.
+ * For page based interactions, don't modify this attribute directly;
+ * use setPage(n).
+ * @attribute recordOffset
+ * @type integer
+ * @default 0
+ */
+ this.setAttributeConfig('recordOffset', {
+ value : 0,
+ validator : function (val) {
+ var total = this.get('totalRecords');
+ if (Paginator.isNumeric(val)) {
+ val = +val;
+ return total === UNLIMITED || total > val ||
+ (total === 0 && val === 0);
+ }
+
+ return false;
+ },
+ setter : Paginator.toNumber
+ });
+
+ /**
+ * Page to display on initial paint
+ * @attribute initialPage
+ * @type integer
+ * @default 1
+ */
+ this.setAttributeConfig('initialPage', {
+ value : 1,
+ validator : Paginator.isNumeric,
+ setter : Paginator.toNumber
+ });
+
+ /**
+ * Template used to render controls. The string will be used as
+ * innerHTML on all specified container nodes. Bracketed keys
+ * (e.g. {pageLinks}) in the string will be replaced with an instance
+ * of the so named ui component.
+ * @see Paginator.TEMPLATE_DEFAULT
+ * @see Paginator.TEMPLATE_ROWS_PER_PAGE
+ * @attribute template
+ * @type string
+ */
+ this.setAttributeConfig('template', {
+ value : Paginator.TEMPLATE_DEFAULT,
+ validator : l.isString
+ });
+
+ /**
+ * Class assigned to the element(s) containing pagination controls.
+ * @attribute containerClass
+ * @type string
+ * @default 'yui-pg-container'
+ */
+ this.setAttributeConfig('containerClass', {
+ value : 'yui-pg-container',
+ validator : l.isString
+ });
+
+ /**
+ * Display pagination controls even when there is only one page. Set
+ * to false to forgo rendering and/or hide the containers when there
+ * is only one page of data. Note if you are using the rowsPerPage
+ * dropdown ui component, visibility will be maintained as long as the
+ * number of records exceeds the smallest page size.
+ * @attribute alwaysVisible
+ * @type boolean
+ * @default true
+ */
+ this.setAttributeConfig('alwaysVisible', {
+ value : true,
+ validator : l.isBoolean
+ });
+
+ /**
+ * Update the UI immediately upon interaction. If false, changeRequest
+ * subscribers or other external code will need to explicitly set the
+ * new values in the paginator to trigger repaint.
+ * @attribute updateOnChange
+ * @type boolean
+ * @default false
+ */
+ this.setAttributeConfig('updateOnChange', {
+ value : false,
+ validator : l.isBoolean
+ });
+
+
+
+ // Read only attributes
+
+ /**
+ * Unique id assigned to this instance
+ * @attribute id
+ * @type integer
+ * @final
+ */
+ this.setAttributeConfig('id', {
+ value : Paginator.id++,
+ readOnly : true
+ });
+
+ /**
+ * Indicator of whether the DOM nodes have been initially created
+ * @attribute rendered
+ * @type boolean
+ * @final
+ */
+ this.setAttributeConfig('rendered', {
+ value : false,
+ readOnly : true
+ });
+
+ },
+
+ /**
+ * Initialize registered ui components onto this instance.
+ * @method initUIComponents
+ * @private
+ */
+ initUIComponents : function () {
+ var ui = Paginator.ui,
+ name,UIComp;
+ for (name in ui) {
+ if (YAHOO.lang.hasOwnProperty(ui,name)) {
+ UIComp = ui[name];
+ if (YAHOO.lang.isObject(UIComp) &&
+ YAHOO.lang.isFunction(UIComp.init)) {
+ UIComp.init(this);
+ }
+ }
+ }
+ },
+
+ /**
+ * Initialize this instance's CustomEvents.
+ * @method initEvents
+ * @private
+ */
+ initEvents : function () {
+ /**
+ * Event fired when the Paginator is initially rendered
+ * @event render
+ */
+ this.createEvent('render');
+
+ /**
+ * Event fired when the Paginator is initially rendered
+ * @event rendered
+ * @deprecated use render event
+ */
+ this.createEvent('rendered'); // backward compatibility
+
+ /**
+ * Event fired when a change in pagination values is requested,
+ * either by interacting with the various ui components or via the
+ * setStartIndex(n) etc APIs.
+ * Subscribers will receive the proposed state as the first parameter.
+ * The proposed state object will contain the following keys:
+ * <ul>
+ * <li>paginator - the Paginator instance</li>
+ * <li>page</li>
+ * <li>totalRecords</li>
+ * <li>recordOffset - index of the first record on the new page</li>
+ * <li>rowsPerPage</li>
+ * <li>records - array containing [start index, end index] for the records on the new page</li>
+ * <li>before - object literal with all these keys for the current state</li>
+ * </ul>
+ * @event changeRequest
+ */
+ this.createEvent('changeRequest');
+
+ /**
+ * Event fired when attribute changes have resulted in the calculated
+ * current page changing.
+ * @event pageChange
+ */
+ this.createEvent('pageChange');
+
+ /**
+ * Event that fires before the destroy event.
+ * @event beforeDestroy
+ */
+ this.createEvent('beforeDestroy');
+
+ /**
+ * Event used to trigger cleanup of ui components
+ * @event destroy
+ */
+ this.createEvent('destroy');
+
+ this._selfSubscribe();
+ },
+
+ /**
+ * Subscribes to instance attribute change events to automate certain
+ * behaviors.
+ * @method _selfSubscribe
+ * @protected
+ */
+ _selfSubscribe : function () {
+ // Listen for changes to totalRecords and alwaysVisible
+ this.subscribe('totalRecordsChange',this.updateVisibility,this,true);
+ this.subscribe('alwaysVisibleChange',this.updateVisibility,this,true);
+
+ // Fire the pageChange event when appropriate
+ this.subscribe('totalRecordsChange',this._handleStateChange,this,true);
+ this.subscribe('recordOffsetChange',this._handleStateChange,this,true);
+ this.subscribe('rowsPerPageChange',this._handleStateChange,this,true);
+
+ // Update recordOffset when totalRecords is reduced below
+ this.subscribe('totalRecordsChange',this._syncRecordOffset,this,true);
+ },
+
+ /**
+ * Sets recordOffset to the starting index of the previous page when
+ * totalRecords is reduced below the current recordOffset.
+ * @method _syncRecordOffset
+ * @param e {Event} totalRecordsChange event
+ * @protected
+ */
+ _syncRecordOffset : function (e) {
+ var v = e.newValue,rpp,state;
+ if (e.prevValue !== v) {
+ if (v !== Paginator.VALUE_UNLIMITED) {
+ rpp = this.get('rowsPerPage');
+
+ if (rpp && this.get('recordOffset') >= v) {
+ state = this.getState({
+ totalRecords : e.prevValue,
+ recordOffset : this.get('recordOffset')
+ });
+
+ this.set('recordOffset', state.before.recordOffset);
+ this._firePageChange(state);
+ }
+ }
+ }
+ },
+
+ /**
+ * Fires the pageChange event when the state attributes have changed in
+ * such a way as to locate the current recordOffset on a new page.
+ * @method _handleStateChange
+ * @param e {Event} the attribute change event
+ * @protected
+ */
+ _handleStateChange : function (e) {
+ if (e.prevValue !== e.newValue) {
+ var change = this._state || {},
+ state;
+
+ change[e.type.replace(/Change$/,'')] = e.prevValue;
+ state = this.getState(change);
+
+ if (state.page !== state.before.page) {
+ if (this._batch) {
+ this._pageChanged = true;
+ } else {
+ this._firePageChange(state);
+ }
+ }
+ }
+ },
+
+ /**
+ * Fires a pageChange event in the form of a standard attribute change
+ * event with additional properties prevState and newState.
+ * @method _firePageChange
+ * @param state {Object} the result of getState(oldState)
+ * @protected
+ */
+ _firePageChange : function (state) {
+ if (YAHOO.lang.isObject(state)) {
+ var current = state.before;
+ delete state.before;
+ this.fireEvent('pageChange',{
+ type : 'pageChange',
+ prevValue : state.page,
+ newValue : current.page,
+ prevState : state,
+ newState : current
+ });
+ }
+ },
+
+ /**
+ * Render the pagination controls per the format attribute into the
+ * specified container nodes.
+ * @method render
+ */
+ render : function () {
+ if (this.get('rendered')) {
+ return;
+ }
+
+ // Forgo rendering if only one page and alwaysVisible is off
+ var totalRecords = this.get('totalRecords'),
+ Dom = YAHOO.util.Dom,
+ template = this.get('template'),
+ containerClass = this.get('containerClass'),
+ i,len,c,id_base,markers,j,jlen,m,mp,name,UIComp,comp;
+
+ if (totalRecords !== Paginator.VALUE_UNLIMITED &&
+ totalRecords < this.get('rowsPerPage') &&
+ !this.get('alwaysVisible')) {
+ return;
+ }
+
+ // add marker spans to the template html to indicate drop zones
+ // for ui components
+ template = template.replace(/\{([a-z0-9_ \-]+)\}/gi,
+ '<span class="yui-pg-ui $1"></span>');
+ for (i = 0, len = this._containers.length; i < len; ++i) {
+ c = this._containers[i];
+ // ex. yui-pg0-1 (first paginator, second container)
+ id_base = Paginator.ID_BASE + this.get('id') + '-' + i;
+
+ if (!c) {
+ continue;
+ }
+ // Hide the container while its contents are rendered
+ c.style.display = 'none';
+
+ Dom.addClass(c,containerClass);
+
+ // Place the template innerHTML
+ c.innerHTML = template;
+
+ // Replace each marker with the ui component's render() output
+ markers = Dom.getElementsByClassName('yui-pg-ui','span',c);
+
+ for (j = 0, jlen = markers.length; j < jlen; ++j) {
+ m = markers[j];
+ mp = m.parentNode;
+ name = m.className.replace(/\s*yui-pg-ui\s+/g,'');
+ UIComp = Paginator.ui[name];
+
+ if (YAHOO.lang.isFunction(UIComp)) {
+ comp = new UIComp(this);
+ if (YAHOO.lang.isFunction(comp.render)) {
+ mp.replaceChild(comp.render(id_base),m);
+ }
+ }
+ }
+
+ // Show the container allowing page reflow
+ c.style.display = '';
+ }
+
+ // Set render attribute manually to support its readOnly contract
+ if (this._containers.length) {
+ this.setAttributeConfig('rendered',{value:true});
+
+ this.fireEvent('render',this.getState());
+ // For backward compatibility
+ this.fireEvent('rendered',this.getState());
+ }
+ },
+
+ /**
+ * Removes controls from the page and unhooks events.
+ * @method destroy
+ */
+ destroy : function () {
+ this.fireEvent('beforeDestroy');
+ this.fireEvent('destroy');
+
+ this.setAttributeConfig('rendered',{value:false});
+ },
+
+ /**
+ * Hides the containers if there is only one page of data and attribute
+ * alwaysVisible is false. Conversely, it displays the containers if either
+ * there is more than one page worth of data or alwaysVisible is turned on.
+ * @method updateVisibility
+ */
+ updateVisibility : function (e) {
+ var alwaysVisible = this.get('alwaysVisible'),
+ totalRecords,visible,rpp,rppOptions,i,len;
+
+ if (e.type === 'alwaysVisibleChange' || !alwaysVisible) {
+ totalRecords = this.get('totalRecords');
+ visible = true;
+ rpp = this.get('rowsPerPage');
+ rppOptions = this.get('rowsPerPageOptions');
+
+ if (YAHOO.lang.isArray(rppOptions)) {
+ for (i = 0, len = rppOptions.length; i < len; ++i) {
+ rpp = Math.min(rpp,rppOptions[i]);
+ }
+ }
+
+ if (totalRecords !== Paginator.VALUE_UNLIMITED &&
+ totalRecords <= rpp) {
+ visible = false;
+ }
+
+ visible = visible || alwaysVisible;
+
+ for (i = 0, len = this._containers.length; i < len; ++i) {
+ YAHOO.util.Dom.setStyle(this._containers[i],'display',
+ visible ? '' : 'none');
+ }
+ }
+ },
+
+
+
+
+ /**
+ * Get the configured container nodes
+ * @method getContainerNodes
+ * @return {Array} array of HTMLElement nodes
+ */
+ getContainerNodes : function () {
+ return this._containers;
+ },
+
+ /**
+ * Get the total number of pages in the data set according to the current
+ * rowsPerPage and totalRecords values. If totalRecords is not set, or
+ * set to YAHOO.widget.Paginator.VALUE_UNLIMITED, returns
+ * YAHOO.widget.Paginator.VALUE_UNLIMITED.
+ * @method getTotalPages
+ * @return {number}
+ */
+ getTotalPages : function () {
+ var records = this.get('totalRecords'),
+ perPage = this.get('rowsPerPage');
+
+ // rowsPerPage not set. Can't calculate
+ if (!perPage) {
+ return null;
+ }
+
+ if (records === Paginator.VALUE_UNLIMITED) {
+ return Paginator.VALUE_UNLIMITED;
+ }
+
+ return Math.ceil(records/perPage);
+ },
+
+ /**
+ * Does the requested page have any records?
+ * @method hasPage
+ * @param page {number} the page in question
+ * @return {boolean}
+ */
+ hasPage : function (page) {
+ if (!YAHOO.lang.isNumber(page) || page < 1) {
+ return false;
+ }
+
+ var totalPages = this.getTotalPages();
+
+ return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
+ },
+
+ /**
+ * Get the page number corresponding to the current record offset.
+ * @method getCurrentPage
+ * @return {number}
+ */
+ getCurrentPage : function () {
+ var perPage = this.get('rowsPerPage');
+ if (!perPage || !this.get('totalRecords')) {
+ return 0;
+ }
+ return Math.floor(this.get('recordOffset') / perPage) + 1;
+ },
+
+ /**
+ * Are there records on the next page?
+ * @method hasNextPage
+ * @return {boolean}
+ */
+ hasNextPage : function () {
+ var currentPage = this.getCurrentPage(),
+ totalPages = this.getTotalPages();
+
+ return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
+ },
+
+ /**
+ * Get the page number of the next page, or null if the current page is the
+ * last page.
+ * @method getNextPage
+ * @return {number}
+ */
+ getNextPage : function () {
+ return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
+ },
+
+ /**
+ * Is there a page before the current page?
+ * @method hasPreviousPage
+ * @return {boolean}
+ */
+ hasPreviousPage : function () {
+ return (this.getCurrentPage() > 1);
+ },
+
+ /**
+ * Get the page number of the previous page, or null if the current page
+ * is the first page.
+ * @method getPreviousPage
+ * @return {number}
+ */
+ getPreviousPage : function () {
+ return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
+ },
+
+ /**
+ * Get the start and end record indexes of the specified page.
+ * @method getPageRecords
+ * @param page {number} (optional) The page (current page if not specified)
+ * @return {Array} [start_index, end_index]
+ */
+ getPageRecords : function (page) {
+ if (!YAHOO.lang.isNumber(page)) {
+ page = this.getCurrentPage();
+ }
+
+ var perPage = this.get('rowsPerPage'),
+ records = this.get('totalRecords'),
+ start, end;
+
+ if (!page || !perPage) {
+ return null;
+ }
+
+ start = (page - 1) * perPage;
+ if (records !== Paginator.VALUE_UNLIMITED) {
+ if (start >= records) {
+ return null;
+ }
+ end = Math.min(start + perPage, records) - 1;
+ } else {
+ end = start + perPage - 1;
+ }
+
+ return [start,end];
+ },
+
+ /**
+ * Set the current page to the provided page number if possible.
+ * @method setPage
+ * @param newPage {number} the new page number
+ * @param silent {boolean} whether to forcibly avoid firing the
+ * changeRequest event
+ */
+ setPage : function (page,silent) {
+ if (this.hasPage(page) && page !== this.getCurrentPage()) {
+ if (this.get('updateOnChange') || silent) {
+ this.set('recordOffset', (page - 1) * this.get('rowsPerPage'));
+ } else {
+ this.fireEvent('changeRequest',this.getState({'page':page}));
+ }
+ }
+ },
+
+ /**
+ * Get the number of rows per page.
+ * @method getRowsPerPage
+ * @return {number} the current setting of the rowsPerPage attribute
+ */
+ getRowsPerPage : function () {
+ return this.get('rowsPerPage');
+ },
+
+ /**
+ * Set the number of rows per page.
+ * @method setRowsPerPage
+ * @param rpp {number} the new number of rows per page
+ * @param silent {boolean} whether to forcibly avoid firing the
+ * changeRequest event
+ */
+ setRowsPerPage : function (rpp,silent) {
+ if (Paginator.isNumeric(rpp) && +rpp > 0 &&
+ +rpp !== this.get('rowsPerPage')) {
+ if (this.get('updateOnChange') || silent) {
+ this.set('rowsPerPage',rpp);
+ } else {
+ this.fireEvent('changeRequest',
+ this.getState({'rowsPerPage':+rpp}));
+ }
+ }
+ },
+
+ /**
+ * Get the total number of records.
+ * @method getTotalRecords
+ * @return {number} the current setting of totalRecords attribute
+ */
+ getTotalRecords : function () {
+ return this.get('totalRecords');
+ },
+
+ /**
+ * Set the total number of records.
+ * @method setTotalRecords
+ * @param total {number} the new total number of records
+ * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
+ */
+ setTotalRecords : function (total,silent) {
+ if (Paginator.isNumeric(total) && +total >= 0 &&
+ +total !== this.get('totalRecords')) {
+ if (this.get('updateOnChange') || silent) {
+ this.set('totalRecords',total);
+ } else {
+ this.fireEvent('changeRequest',
+ this.getState({'totalRecords':+total}));
+ }
+ }
+ },
+
+ /**
+ * Get the index of the first record on the current page
+ * @method getStartIndex
+ * @return {number} the index of the first record on the current page
+ */
+ getStartIndex : function () {
+ return this.get('recordOffset');
+ },
+
+ /**
+ * Move the record offset to a new starting index. This will likely cause
+ * the calculated current page to change. You should probably use setPage.
+ * @method setStartIndex
+ * @param offset {number} the new record offset
+ * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
+ */
+ setStartIndex : function (offset,silent) {
+ if (Paginator.isNumeric(offset) && +offset >= 0 &&
+ +offset !== this.get('recordOffset')) {
+ if (this.get('updateOnChange') || silent) {
+ this.set('recordOffset',offset);
+ } else {
+ this.fireEvent('changeRequest',
+ this.getState({'recordOffset':+offset}));
+ }
+ }
+ },
+
+ /**
+ * Get an object literal describing the current state of the paginator. If
+ * an object literal of proposed values is passed, the proposed state will
+ * be returned as an object literal with the following keys:
+ * <ul>
+ * <li>paginator - instance of the Paginator</li>
+ * <li>page - number</li>
+ * <li>totalRecords - number</li>
+ * <li>recordOffset - number</li>
+ * <li>rowsPerPage - number</li>
+ * <li>records - [ start_index, end_index ]</li>
+ * <li>before - (OPTIONAL) { state object literal for current state }</li>
+ * </ul>
+ * @method getState
+ * @return {object}
+ * @param changes {object} OPTIONAL object literal with proposed values
+ * Supported change keys include:
+ * <ul>
+ * <li>rowsPerPage</li>
+ * <li>totalRecords</li>
+ * <li>recordOffset OR</li>
+ * <li>page</li>
+ * </ul>
+ */
+ getState : function (changes) {
+ var UNLIMITED = Paginator.VALUE_UNLIMITED,
+ M = Math, max = M.max, ceil = M.ceil,
+ currentState, state, offset;
+
+ function normalizeOffset(offset,total,rpp) {
+ if (offset <= 0 || total === 0) {
+ return 0;
+ }
+ if (total === UNLIMITED || total > offset) {
+ return offset - (offset % rpp);
+ }
+ return total - (total % rpp || rpp);
+ }
+
+ currentState = {
+ paginator : this,
+ totalRecords : this.get('totalRecords'),
+ rowsPerPage : this.get('rowsPerPage'),
+ records : this.getPageRecords()
+ };
+ currentState.recordOffset = normalizeOffset(
+ this.get('recordOffset'),
+ currentState.totalRecords,
+ currentState.rowsPerPage);
+ currentState.page = ceil(currentState.recordOffset /
+ currentState.rowsPerPage) + 1;
+
+ if (!changes) {
+ return currentState;
+ }
+
+ state = {
+ paginator : this,
+ before : currentState,
+
+ rowsPerPage : changes.rowsPerPage || currentState.rowsPerPage,
+ totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
+ max(changes.totalRecords,UNLIMITED) :
+ +currentState.totalRecords)
+ };
+
+ if (state.totalRecords === 0) {
+ state.recordOffset =
+ state.page = 0;
+ } else {
+ offset = Paginator.isNumeric(changes.page) ?
+ (changes.page - 1) * state.rowsPerPage :
+ Paginator.isNumeric(changes.recordOffset) ?
+ +changes.recordOffset :
+ currentState.recordOffset;
+
+ state.recordOffset = normalizeOffset(offset,
+ state.totalRecords,
+ state.rowsPerPage);
+
+ state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
+ }
+
+ state.records = [ state.recordOffset,
+ state.recordOffset + state.rowsPerPage - 1 ];
+
+ // limit upper index to totalRecords - 1
+ if (state.totalRecords !== UNLIMITED &&
+ state.recordOffset < state.totalRecords && state.records &&
+ state.records[1] > state.totalRecords - 1) {
+ state.records[1] = state.totalRecords - 1;
+ }
+
+ return state;
+ },
+
+ /**
+ * Convenience method to facilitate setting state attributes rowsPerPage,
+ * totalRecords, recordOffset in batch. Also supports calculating
+ * recordOffset from state.page if state.recordOffset is not provided.
+ * Fires only a single pageChange event, if appropriate.
+ * This will not fire a changeRequest event.
+ * @method setState
+ * @param state {Object} Object literal of attribute:value pairs to set
+ */
+ setState : function (state) {
+ if (YAHOO.lang.isObject(state)) {
+ // get flux state based on current state with before state as well
+ this._state = this.getState({});
+
+ // use just the state props from the input obj
+ state = {
+ page : state.page,
+ rowsPerPage : state.rowsPerPage,
+ totalRecords : state.totalRecords,
+ recordOffset : state.recordOffset
+ };
+
+ // calculate recordOffset from page if recordOffset not specified.
+ // not using lang.isNumber for support of numeric strings
+ if (state.page && state.recordOffset === undefined) {
+ state.recordOffset = (state.page - 1) *
+ (state.rowsPerPage || this.get('rowsPerPage'));
+ }
+
+ this._batch = true;
+ this._pageChanged = false;
+
+ for (var k in state) {
+ if (state.hasOwnProperty(k)) {
+ this.set(k,state[k]);
+ }
+ }
+
+ this._batch = false;
+
+ if (this._pageChanged) {
+ this._pageChanged = false;
+
+ this._firePageChange(this.getState(this._state));
+ }
+ }
+ }
+};
+
+YAHOO.lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
+
+YAHOO.widget.Paginator = Paginator;
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the textual report of current pagination status.
+ * E.g. "Now viewing page 1 of 13".
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class CurrentPageReport
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.CurrentPageReport = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange', this.update,this,true);
+ p.subscribe('rowsPerPageChange', this.update,this,true);
+ p.subscribe('totalRecordsChange',this.update,this,true);
+ p.subscribe('pageReportTemplateChange', this.update,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ //TODO: make this work
+ p.subscribe('pageReportClassChange', this.update,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.CurrentPageReport.init = function (p) {
+
+ /**
+ * CSS class assigned to the span containing the info.
+ * @attribute pageReportClass
+ * @default 'yui-pg-current'
+ */
+ p.setAttributeConfig('pageReportClass', {
+ value : 'yui-pg-current',
+ validator : l.isString
+ });
+
+ /**
+ * Used as innerHTML for the span. Place holders in the form of {name}
+ * will be replaced with the so named value from the key:value map
+ * generated by the function held in the pageReportValueGenerator attribute.
+ * @attribute pageReportTemplate
+ * @default '({currentPage} of {totalPages})'
+ * @see pageReportValueGenerator attribute
+ */
+ p.setAttributeConfig('pageReportTemplate', {
+ value : '({currentPage} of {totalPages})',
+ validator : l.isString
+ });
+
+ /**
+ * Function to generate the value map used to populate the
+ * pageReportTemplate. The function is passed the Paginator instance as a
+ * parameter. The default function returns a map with the following keys:
+ * <ul>
+ * <li>currentPage</li>
+ * <li>totalPages</li>
+ * <li>startIndex</li>
+ * <li>endIndex</li>
+ * <li>startRecord</li>
+ * <li>endRecord</li>
+ * <li>totalRecords</li>
+ * </ul>
+ * @attribute pageReportValueGenarator
+ */
+ p.setAttributeConfig('pageReportValueGenerator', {
+ value : function (paginator) {
+ var curPage = paginator.getCurrentPage(),
+ records = paginator.getPageRecords();
+
+ return {
+ 'currentPage' : records ? curPage : 0,
+ 'totalPages' : paginator.getTotalPages(),
+ 'startIndex' : records ? records[0] : 0,
+ 'endIndex' : records ? records[1] : 0,
+ 'startRecord' : records ? records[0] + 1 : 0,
+ 'endRecord' : records ? records[1] + 1 : 0,
+ 'totalRecords': paginator.get('totalRecords')
+ };
+ },
+ validator : l.isFunction
+ });
+};
+
+/**
+ * Replace place holders in a string with the named values found in an
+ * object literal.
+ * @static
+ * @method sprintf
+ * @param template {string} The content string containing place holders
+ * @param values {object} The key:value pairs used to replace the place holders
+ * @return {string}
+ */
+Paginator.ui.CurrentPageReport.sprintf = function (template, values) {
+ return template.replace(/\{([\w\s\-]+)\}/g, function (x,key) {
+ return (key in values) ? values[key] : '';
+ });
+};
+
+Paginator.ui.CurrentPageReport.prototype = {
+
+ /**
+ * Span node containing the formatted info
+ * @property span
+ * @type HTMLElement
+ * @private
+ */
+ span : null,
+
+
+ /**
+ * Generate the span containing info formatted per the pageReportTemplate
+ * attribute.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ this.span = document.createElement('span');
+ this.span.id = id_base + '-page-report';
+ this.span.className = this.paginator.get('pageReportClass');
+ this.update();
+
+ return this.span;
+ },
+
+ /**
+ * Regenerate the content of the span if appropriate. Calls
+ * CurrentPageReport.sprintf with the value of the pageReportTemplate
+ * attribute and the value map returned from pageReportValueGenerator
+ * function.
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
+ this.paginator.get('pageReportTemplate'),
+ this.paginator.get('pageReportValueGenerator')(this.paginator));
+ },
+
+ /**
+ * Removes the link/span node and clears event listeners
+ * removal.
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ this.span.parentNode.removeChild(this.span);
+ this.span = null;
+ }
+
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the page links
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class PageLinks
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.PageLinks = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange',this.update,this,true);
+ p.subscribe('rowsPerPageChange',this.update,this,true);
+ p.subscribe('totalRecordsChange',this.update,this,true);
+ p.subscribe('pageLinksChange', this.rebuild,this,true);
+ p.subscribe('pageLinkClassChange', this.rebuild,this,true);
+ p.subscribe('currentPageClassChange', this.rebuild,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ //TODO: Make this work
+ p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.PageLinks.init = function (p) {
+
+ /**
+ * CSS class assigned to each page link/span.
+ * @attribute pageLinkClass
+ * @default 'yui-pg-page'
+ */
+ p.setAttributeConfig('pageLinkClass', {
+ value : 'yui-pg-page',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the current page span.
+ * @attribute currentPageClass
+ * @default 'yui-pg-current-page'
+ */
+ p.setAttributeConfig('currentPageClass', {
+ value : 'yui-pg-current-page',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the span containing the page links.
+ * @attribute pageLinksContainerClass
+ * @default 'yui-pg-pages'
+ */
+ p.setAttributeConfig('pageLinksContainerClass', {
+ value : 'yui-pg-pages',
+ validator : l.isString
+ });
+
+ /**
+ * Maximum number of page links to display at one time.
+ * @attribute pageLinks
+ * @default 10
+ */
+ p.setAttributeConfig('pageLinks', {
+ value : 10,
+ validator : Paginator.isNumeric
+ });
+
+ /**
+ * Function used generate the innerHTML for each page link/span. The
+ * function receives as parameters the page number and a reference to the
+ * paginator object.
+ * @attribute pageLabelBuilder
+ * @default function (page, paginator) { return page; }
+ */
+ p.setAttributeConfig('pageLabelBuilder', {
+ value : function (page, paginator) { return page; },
+ validator : l.isFunction
+ });
+};
+
+/**
+ * Calculates start and end page numbers given a current page, attempting
+ * to keep the current page in the middle
+ * @static
+ * @method calculateRange
+ * @param {int} currentPage The current page
+ * @param {int} totalPages (optional) Maximum number of pages
+ * @param {int} numPages (optional) Preferred number of pages in range
+ * @return {Array} [start_page_number, end_page_number]
+ */
+Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
+ var UNLIMITED = Paginator.VALUE_UNLIMITED,
+ start, end, delta;
+
+ // Either has no pages, or unlimited pages. Show none.
+ if (!currentPage || numPages === 0 || totalPages === 0 ||
+ (totalPages === UNLIMITED && numPages === UNLIMITED)) {
+ return [0,-1];
+ }
+
+ // Limit requested pageLinks if there are fewer totalPages
+ if (totalPages !== UNLIMITED) {
+ numPages = numPages === UNLIMITED ?
+ totalPages :
+ Math.min(numPages,totalPages);
+ }
+
+ // Determine start and end, trying to keep current in the middle
+ start = Math.max(1,Math.ceil(currentPage - (numPages/2)));
+ if (totalPages === UNLIMITED) {
+ end = start + numPages - 1;
+ } else {
+ end = Math.min(totalPages, start + numPages - 1);
+ }
+
+ // Adjust the start index when approaching the last page
+ delta = numPages - (end - start + 1);
+ start = Math.max(1, start - delta);
+
+ return [start,end];
+};
+
+
+Paginator.ui.PageLinks.prototype = {
+
+ /**
+ * Current page
+ * @property current
+ * @type number
+ * @private
+ */
+ current : 0,
+
+ /**
+ * Span node containing the page links
+ * @property container
+ * @type HTMLElement
+ * @private
+ */
+ container : null,
+
+
+ /**
+ * Generate the nodes and return the container node containing page links
+ * appropriate to the current pagination state.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ var p = this.paginator;
+
+ // Set up container
+ this.container = document.createElement('span');
+ this.container.id = id_base + '-pages';
+ this.container.className = p.get('pageLinksContainerClass');
+ YAHOO.util.Event.on(this.container,'click',this.onClick,this,true);
+
+ // Call update, flagging a need to rebuild
+ this.update({newValue : null, rebuild : true});
+
+ return this.container;
+ },
+
+ /**
+ * Update the links if appropriate
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var p = this.paginator,
+ currentPage = p.getCurrentPage();
+
+ // Replace content if there's been a change
+ if (this.current !== currentPage || !currentPage || e.rebuild) {
+ var labelBuilder = p.get('pageLabelBuilder'),
+ range = Paginator.ui.PageLinks.calculateRange(
+ currentPage,
+ p.getTotalPages(),
+ p.get('pageLinks')),
+ start = range[0],
+ end = range[1],
+ content = '',
+ linkTemplate,i;
+
+ linkTemplate = '<a href="#" class="' + p.get('pageLinkClass') +
+ '" page="';
+ for (i = start; i <= end; ++i) {
+ if (i === currentPage) {
+ content +=
+ '<span class="' + p.get('currentPageClass') + ' ' +
+ p.get('pageLinkClass') + '">' +
+ labelBuilder(i,p) + '</span>';
+ } else {
+ content +=
+ linkTemplate + i + '">' + labelBuilder(i,p) + '</a>';
+ }
+ }
+
+ this.container.innerHTML = content;
+ }
+ },
+
+ /**
+ * Force a rebuild of the page links.
+ * @method rebuild
+ * @param e {CustomEvent} The calling change event
+ */
+ rebuild : function (e) {
+ e.rebuild = true;
+ this.update(e);
+ },
+
+ /**
+ * Removes the page links container node and clears event listeners
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.container,true);
+ this.container.parentNode.removeChild(this.container);
+ this.container = null;
+ },
+
+ /**
+ * Listener for the container's onclick event. Looks for qualifying link
+ * clicks, and pulls the page number from the link's page attribute.
+ * Sends link's page attribute to the Paginator's setPage method.
+ * @method onClick
+ * @param e {DOMEvent} The click event
+ */
+ onClick : function (e) {
+ var t = YAHOO.util.Event.getTarget(e);
+ if (t && YAHOO.util.Dom.hasClass(t,
+ this.paginator.get('pageLinkClass'))) {
+
+ YAHOO.util.Event.stopEvent(e);
+
+ this.paginator.setPage(parseInt(t.getAttribute('page'),10));
+ }
+ }
+
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the link to jump to the first page.
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class FirstPageLink
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.FirstPageLink = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange',this.update,this,true);
+ p.subscribe('rowsPerPageChange',this.update,this,true);
+ p.subscribe('totalRecordsChange',this.update,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ // TODO: make this work
+ p.subscribe('firstPageLinkLabelChange',this.update,this,true);
+ p.subscribe('firstPageLinkClassChange',this.update,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.FirstPageLink.init = function (p) {
+
+ /**
+ * Used as innerHTML for the first page link/span.
+ * @attribute firstPageLinkLabel
+ * @default '<< first'
+ */
+ p.setAttributeConfig('firstPageLinkLabel', {
+ value : '<< first',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the link/span
+ * @attribute firstPageLinkClass
+ * @default 'yui-pg-first'
+ */
+ p.setAttributeConfig('firstPageLinkClass', {
+ value : 'yui-pg-first',
+ validator : l.isString
+ });
+};
+
+// Instance members and methods
+Paginator.ui.FirstPageLink.prototype = {
+
+ /**
+ * The currently placed HTMLElement node
+ * @property current
+ * @type HTMLElement
+ * @private
+ */
+ current : null,
+
+ /**
+ * Link node
+ * @property link
+ * @type HTMLElement
+ * @private
+ */
+ link : null,
+
+ /**
+ * Span node (inactive link)
+ * @property span
+ * @type HTMLElement
+ * @private
+ */
+ span : null,
+
+ /**
+ * Generate the nodes and return the appropriate node given the current
+ * pagination state.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ var p = this.paginator,
+ c = p.get('firstPageLinkClass'),
+ label = p.get('firstPageLinkLabel');
+
+ this.link = document.createElement('a');
+ this.span = document.createElement('span');
+
+ this.link.id = id_base + '-first-link';
+ this.link.href = '#';
+ this.link.className = c;
+ this.link.innerHTML = label;
+ YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
+
+ this.span.id = id_base + '-first-span';
+ this.span.className = c;
+ this.span.innerHTML = label;
+
+ this.current = p.getCurrentPage() > 1 ? this.link : this.span;
+ return this.current;
+ },
+
+ /**
+ * Swap the link and span nodes if appropriate.
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var par = this.current ? this.current.parentNode : null;
+ if (this.paginator.getCurrentPage() > 1) {
+ if (par && this.current === this.span) {
+ par.replaceChild(this.link,this.current);
+ this.current = this.link;
+ }
+ } else {
+ if (par && this.current === this.link) {
+ par.replaceChild(this.span,this.current);
+ this.current = this.span;
+ }
+ }
+ },
+
+ /**
+ * Removes the link/span node and clears event listeners
+ * removal.
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.link);
+ this.current.parentNode.removeChild(this.current);
+ this.link = this.span = null;
+ },
+
+ /**
+ * Listener for the link's onclick event. Pass new value to setPage method.
+ * @method onClick
+ * @param e {DOMEvent} The click event
+ */
+ onClick : function (e) {
+ YAHOO.util.Event.stopEvent(e);
+ this.paginator.setPage(1);
+ }
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the link to jump to the last page.
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class LastPageLink
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.LastPageLink = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange',this.update,this,true);
+ p.subscribe('rowsPerPageChange',this.update,this,true);
+ p.subscribe('totalRecordsChange',this.update,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ // TODO: make this work
+ p.subscribe('lastPageLinkLabelChange',this.update,this,true);
+ p.subscribe('lastPageLinkClassChange', this.update,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param paginator {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.LastPageLink.init = function (p) {
+
+ /**
+ * Used as innerHTML for the last page link/span.
+ * @attribute lastPageLinkLabel
+ * @default 'last >>'
+ */
+ p.setAttributeConfig('lastPageLinkLabel', {
+ value : 'last >>',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the link/span
+ * @attribute lastPageLinkClass
+ * @default 'yui-pg-last'
+ */
+ p.setAttributeConfig('lastPageLinkClass', {
+ value : 'yui-pg-last',
+ validator : l.isString
+ });
+};
+
+Paginator.ui.LastPageLink.prototype = {
+
+ /**
+ * Currently placed HTMLElement node
+ * @property current
+ * @type HTMLElement
+ * @private
+ */
+ current : null,
+
+ /**
+ * Link HTMLElement node
+ * @property link
+ * @type HTMLElement
+ * @private
+ */
+ link : null,
+
+ /**
+ * Span node (inactive link)
+ * @property span
+ * @type HTMLElement
+ * @private
+ */
+ span : null,
+
+ /**
+ * Empty place holder node for when the last page link is inappropriate to
+ * display in any form (unlimited paging).
+ * @property na
+ * @type HTMLElement
+ * @private
+ */
+ na : null,
+
+
+ /**
+ * Generate the nodes and return the appropriate node given the current
+ * pagination state.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ var p = this.paginator,
+ c = p.get('lastPageLinkClass'),
+ label = p.get('lastPageLinkLabel'),
+ last = p.getTotalPages();
+
+ this.link = document.createElement('a');
+ this.span = document.createElement('span');
+ this.na = this.span.cloneNode(false);
+
+ this.link.id = id_base + '-last-link';
+ this.link.href = '#';
+ this.link.className = c;
+ this.link.innerHTML = label;
+ YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
+
+ this.span.id = id_base + '-last-span';
+ this.span.className = c;
+ this.span.innerHTML = label;
+
+ this.na.id = id_base + '-last-na';
+
+ switch (last) {
+ case Paginator.VALUE_UNLIMITED :
+ this.current = this.na; break;
+ case p.getCurrentPage() :
+ this.current = this.span; break;
+ default :
+ this.current = this.link;
+ }
+
+ return this.current;
+ },
+
+ /**
+ * Swap the link, span, and na nodes if appropriate.
+ * @method update
+ * @param e {CustomEvent} The calling change event (ignored)
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var par = this.current ? this.current.parentNode : null,
+ after = this.link;
+
+ if (par) {
+ switch (this.paginator.getTotalPages()) {
+ case Paginator.VALUE_UNLIMITED :
+ after = this.na; break;
+ case this.paginator.getCurrentPage() :
+ after = this.span; break;
+ }
+
+ if (this.current !== after) {
+ par.replaceChild(after,this.current);
+ this.current = after;
+ }
+ }
+ },
+
+ /**
+ * Removes the link/span node and clears event listeners
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.link);
+ this.current.parentNode.removeChild(this.current);
+ this.link = this.span = null;
+ },
+
+ /**
+ * Listener for the link's onclick event. Passes to setPage method.
+ * @method onClick
+ * @param e {DOMEvent} The click event
+ */
+ onClick : function (e) {
+ YAHOO.util.Event.stopEvent(e);
+ this.paginator.setPage(this.paginator.getTotalPages());
+ }
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the link to jump to the next page.
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class NextPageLink
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.NextPageLink = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange', this.update,this,true);
+ p.subscribe('rowsPerPageChange', this.update,this,true);
+ p.subscribe('totalRecordsChange', this.update,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ // TODO: make this work
+ p.subscribe('nextPageLinkLabelChange', this.update,this,true);
+ p.subscribe('nextPageLinkClassChange', this.update,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.NextPageLink.init = function (p) {
+
+ /**
+ * Used as innerHTML for the next page link/span.
+ * @attribute nextPageLinkLabel
+ * @default 'next >'
+ */
+ p.setAttributeConfig('nextPageLinkLabel', {
+ value : 'next >',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the link/span
+ * @attribute nextPageLinkClass
+ * @default 'yui-pg-next'
+ */
+ p.setAttributeConfig('nextPageLinkClass', {
+ value : 'yui-pg-next',
+ validator : l.isString
+ });
+};
+
+Paginator.ui.NextPageLink.prototype = {
+
+ /**
+ * Currently placed HTMLElement node
+ * @property current
+ * @type HTMLElement
+ * @private
+ */
+ current : null,
+
+ /**
+ * Link node
+ * @property link
+ * @type HTMLElement
+ * @private
+ */
+ link : null,
+
+ /**
+ * Span node (inactive link)
+ * @property span
+ * @type HTMLElement
+ * @private
+ */
+ span : null,
+
+
+ /**
+ * Generate the nodes and return the appropriate node given the current
+ * pagination state.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ var p = this.paginator,
+ c = p.get('nextPageLinkClass'),
+ label = p.get('nextPageLinkLabel'),
+ last = p.getTotalPages();
+
+ this.link = document.createElement('a');
+ this.span = document.createElement('span');
+
+ this.link.id = id_base + '-next-link';
+ this.link.href = '#';
+ this.link.className = c;
+ this.link.innerHTML = label;
+ YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
+
+ this.span.id = id_base + '-next-span';
+ this.span.className = c;
+ this.span.innerHTML = label;
+
+ this.current = p.getCurrentPage() === last ? this.span : this.link;
+
+ return this.current;
+ },
+
+ /**
+ * Swap the link and span nodes if appropriate.
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var last = this.paginator.getTotalPages(),
+ par = this.current ? this.current.parentNode : null;
+
+ if (this.paginator.getCurrentPage() !== last) {
+ if (par && this.current === this.span) {
+ par.replaceChild(this.link,this.current);
+ this.current = this.link;
+ }
+ } else if (this.current === this.link) {
+ if (par) {
+ par.replaceChild(this.span,this.current);
+ this.current = this.span;
+ }
+ }
+ },
+
+ /**
+ * Removes the link/span node and clears event listeners
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.link);
+ this.current.parentNode.removeChild(this.current);
+ this.link = this.span = null;
+ },
+
+ /**
+ * Listener for the link's onclick event. Passes to setPage method.
+ * @method onClick
+ * @param e {DOMEvent} The click event
+ */
+ onClick : function (e) {
+ YAHOO.util.Event.stopEvent(e);
+ this.paginator.setPage(this.paginator.getNextPage());
+ }
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the link to jump to the previous page.
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class PreviousPageLink
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.PreviousPageLink = function (p) {
+ this.paginator = p;
+
+ p.subscribe('recordOffsetChange',this.update,this,true);
+ p.subscribe('rowsPerPageChange',this.update,this,true);
+ p.subscribe('totalRecordsChange',this.update,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ // TODO: make this work
+ p.subscribe('previousPageLinkLabelChange',this.update,this,true);
+ p.subscribe('previousPageLinkClassChange',this.update,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.PreviousPageLink.init = function (p) {
+
+ /**
+ * Used as innerHTML for the previous page link/span.
+ * @attribute previousPageLinkLabel
+ * @default '< prev'
+ */
+ p.setAttributeConfig('previousPageLinkLabel', {
+ value : '< prev',
+ validator : l.isString
+ });
+
+ /**
+ * CSS class assigned to the link/span
+ * @attribute previousPageLinkClass
+ * @default 'yui-pg-previous'
+ */
+ p.setAttributeConfig('previousPageLinkClass', {
+ value : 'yui-pg-previous',
+ validator : l.isString
+ });
+};
+
+Paginator.ui.PreviousPageLink.prototype = {
+
+ /**
+ * Currently placed HTMLElement node
+ * @property current
+ * @type HTMLElement
+ * @private
+ */
+ current : null,
+
+ /**
+ * Link node
+ * @property link
+ * @type HTMLElement
+ * @private
+ */
+ link : null,
+
+ /**
+ * Span node (inactive link)
+ * @property span
+ * @type HTMLElement
+ * @private
+ */
+ span : null,
+
+
+ /**
+ * Generate the nodes and return the appropriate node given the current
+ * pagination state.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ var p = this.paginator,
+ c = p.get('previousPageLinkClass'),
+ label = p.get('previousPageLinkLabel');
+
+ this.link = document.createElement('a');
+ this.span = document.createElement('span');
+
+ this.link.id = id_base + '-prev-link';
+ this.link.href = '#';
+ this.link.className = c;
+ this.link.innerHTML = label;
+ YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
+
+ this.span.id = id_base + '-prev-span';
+ this.span.className = c;
+ this.span.innerHTML = label;
+
+ this.current = p.getCurrentPage() > 1 ? this.link : this.span;
+ return this.current;
+ },
+
+ /**
+ * Swap the link and span nodes if appropriate.
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var par = this.current ? this.current.parentNode : null;
+ if (this.paginator.getCurrentPage() > 1) {
+ if (par && this.current === this.span) {
+ par.replaceChild(this.link,this.current);
+ this.current = this.link;
+ }
+ } else {
+ if (par && this.current === this.link) {
+ par.replaceChild(this.span,this.current);
+ this.current = this.span;
+ }
+ }
+ },
+
+ /**
+ * Removes the link/span node and clears event listeners
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.link);
+ this.current.parentNode.removeChild(this.current);
+ this.link = this.span = null;
+ },
+
+ /**
+ * Listener for the link's onclick event. Passes to setPage method.
+ * @method onClick
+ * @param e {DOMEvent} The click event
+ */
+ onClick : function (e) {
+ YAHOO.util.Event.stopEvent(e);
+ this.paginator.setPage(this.paginator.getPreviousPage());
+ }
+};
+
+})();
+(function () {
+
+var Paginator = YAHOO.widget.Paginator,
+ l = YAHOO.lang;
+
+/**
+ * ui Component to generate the rows-per-page dropdown
+ *
+ * @namespace YAHOO.widget.Paginator.ui
+ * @class RowsPerPageDropdown
+ * @for YAHOO.widget.Paginator
+ *
+ * @constructor
+ * @param p {Pagintor} Paginator instance to attach to
+ */
+Paginator.ui.RowsPerPageDropdown = function (p) {
+ this.paginator = p;
+
+ p.subscribe('rowsPerPageChange',this.update,this,true);
+ p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
+ p.subscribe('totalRecordsChange',this._handleTotalRecordsChange,this,true);
+ p.subscribe('destroy',this.destroy,this,true);
+
+ // TODO: make this work
+ p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
+};
+
+/**
+ * Decorates Paginator instances with new attributes. Called during
+ * Paginator instantiation.
+ * @method init
+ * @param p {Paginator} Paginator instance to decorate
+ * @static
+ */
+Paginator.ui.RowsPerPageDropdown.init = function (p) {
+
+ /**
+ * Array of available rows-per-page sizes. Converted into select options.
+ * Array values may be positive integers or object literals in the form<br>
+ * { value : NUMBER, text : STRING }
+ * @attribute rowsPerPageOptions
+ * @default []
+ */
+ p.setAttributeConfig('rowsPerPageOptions', {
+ value : [],
+ validator : l.isArray
+ });
+
+ /**
+ * CSS class assigned to the select node
+ * @attribute rowsPerPageDropdownClass
+ * @default 'yui-pg-rpp-options'
+ */
+ p.setAttributeConfig('rowsPerPageDropdownClass', {
+ value : 'yui-pg-rpp-options',
+ validator : l.isString
+ });
+};
+
+Paginator.ui.RowsPerPageDropdown.prototype = {
+
+ /**
+ * select node
+ * @property select
+ * @type HTMLElement
+ * @private
+ */
+ select : null,
+
+
+ /**
+ * option node for the optional All value
+ *
+ * @property all
+ * @type HTMLElement
+ * @protected
+ */
+ all : null,
+
+ /**
+ * Generate the select and option nodes and returns the select node.
+ * @method render
+ * @param id_base {string} used to create unique ids for generated nodes
+ * @return {HTMLElement}
+ */
+ render : function (id_base) {
+ this.select = document.createElement('select');
+ this.select.id = id_base + '-rpp';
+ this.select.className = this.paginator.get('rowsPerPageDropdownClass');
+ this.select.title = 'Rows per page';
+
+ YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
+
+ this.rebuild();
+
+ return this.select;
+ },
+
+ /**
+ * (Re)generate the select options.
+ * @method rebuild
+ */
+ rebuild : function (e) {
+ var p = this.paginator,
+ sel = this.select,
+ options = p.get('rowsPerPageOptions'),
+ opt,cfg,val,i,len;
+
+ this.all = null;
+
+ for (i = 0, len = options.length; i < len; ++i) {
+ cfg = options[i];
+ opt = sel.options[i] ||
+ sel.appendChild(document.createElement('option'));
+ val = l.isValue(cfg.value) ? cfg.value : cfg;
+ opt.innerHTML = l.isValue(cfg.text) ? cfg.text : cfg;
+
+ if (l.isString(val) && val.toLowerCase() === 'all') {
+ this.all = opt;
+ opt.value = p.get('totalRecords');
+ } else{
+ opt.value = val;
+ }
+
+ }
+
+ while (sel.options.length > options.length) {
+ sel.removeChild(sel.firstChild);
+ }
+
+ this.update();
+ },
+
+ /**
+ * Select the appropriate option if changed.
+ * @method update
+ * @param e {CustomEvent} The calling change event
+ */
+ update : function (e) {
+ if (e && e.prevValue === e.newValue) {
+ return;
+ }
+
+ var rpp = this.paginator.get('rowsPerPage')+'',
+ options = this.select.options,
+ i,len;
+
+ for (i = 0, len = options.length; i < len; ++i) {
+ if (options[i].value === rpp) {
+ options[i].selected = true;
+ break;
+ }
+ }
+ },
+
+ /**
+ * Listener for the select's onchange event. Sent to setRowsPerPage method.
+ * @method onChange
+ * @param e {DOMEvent} The change event
+ */
+ onChange : function (e) {
+ this.paginator.setRowsPerPage(
+ parseInt(this.select.options[this.select.selectedIndex].value,10));
+ },
+
+ /**
+ * Updates the all option value (and Paginator's rowsPerPage attribute if
+ * necessary) in response to a change in the Paginator's totalRecords.
+ *
+ * @method _handleTotalRecordsChange
+ * @param e {Event} attribute change event
+ * @protected
+ */
+ _handleTotalRecordsChange : function (e) {
+ if (!this.all || (e && e.prevValue === e.newValue)) {
+ return;
+ }
+
+ this.all.value = e.newValue;
+ if (this.all.selected) {
+ this.paginator.set('rowsPerPage',e.newValue);
+ }
+ },
+
+ /**
+ * Removes the select node and clears event listeners
+ * @method destroy
+ * @private
+ */
+ destroy : function () {
+ YAHOO.util.Event.purgeElement(this.select);
+ this.select.parentNode.removeChild(this.select);
+ this.select = null;
+ }
+};
+
+})();
+YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.7.0", build: "1799"});