/*
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.8.1
*/
(function () {
/**
* The Paginator widget provides a set of controls to navigate through paged
* data.
*
* @module paginator
* @uses YAHOO.util.EventProvider
* @uses YAHOO.util.AttributeProvider
*/
var Dom = YAHOO.util.Dom,
lang = YAHOO.lang,
isObject = lang.isObject,
isFunction = lang.isFunction,
isArray = lang.isArray,
isString = lang.isString;
/**
* 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,
attrib, initialPage, records, perPage, startIndex;
config = 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 (config.hasOwnProperty(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
lang.augmentObject(Paginator, {
/**
* Incrementing index used to give instances unique ids.
* @static
* @property Paginator.id
* @type number
* @private
*/
id : 0,
/**
* Base of id strings used for ui components.
* @static
* @property Paginator.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 Paginator.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 Paginator.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 Paginator.TEMPLATE_ROWS_PER_PAGE
* @type string
*/
TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
/**
* Storage object for UI Components
* @static
* @property Paginator.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 Paginator.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 Paginator.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;
/**
* 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 (!isArray(val)) {
val = [val];
}
for (var i = 0, len = val.length; i < len; ++i) {
if (isString(val[i]) ||
(isObject(val[i]) && val[i].nodeType === 1)) {
continue;
}
return false;
}
return true;
},
method : function (val) {
val = Dom.get(val);
if (!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 : 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 : 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 : lang.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
* @deprecated use changeRequest listener that calls setState
*/
this.setAttributeConfig('updateOnChange', {
value : false,
validator : lang.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 (ui.hasOwnProperty(name)) {
UIComp = ui[name];
if (isObject(UIComp) && 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 (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
* @return the Paginator instance
* @chainable
*/
render : function () {
if (this.get('rendered')) {
return this;
}
var template = this.get('template'),
state = this.getState(),
// ex. yui-pg0-1 (first paginator, second container)
id_base = Paginator.ID_BASE + this.get('id') + '-',
i, len;
// Assemble the containers, keeping them hidden
for (i = 0, len = this._containers.length; i < len; ++i) {
this._renderTemplate(this._containers[i],template,id_base+i,true);
}
// Show the containers if appropriate
this.updateVisibility();
// Set render attribute manually to support its readOnly contract
if (this._containers.length) {
this.setAttributeConfig('rendered', { value: true });
this.fireEvent('render', state);
// For backward compatibility
this.fireEvent('rendered', state);
}
return this;
},
/**
* Creates the individual ui components and renders them into a container.
*
* @method _renderTemplate
* @param container {HTMLElement} where to add the ui components
* @param template {String} the template to use as a guide for rendering
* @param id_base {String} id base for the container's ui components
* @param hide {Boolean} leave the container hidden after assembly
* @protected
*/
_renderTemplate : function (container, template, id_base, hide) {
var containerClass = this.get('containerClass'),
markers, i, len;
if (!container) {
return;
}
// Hide the container while its contents are rendered
Dom.setStyle(container,'display','none');
Dom.addClass(container, containerClass);
// Place the template innerHTML, adding marker spans to the template
// html to indicate drop zones for ui components
container.innerHTML = template.replace(/\{([a-z0-9_ \-]+)\}/gi,
'<span class="yui-pg-ui yui-pg-ui-$1"></span>');
// Replace each marker with the ui component's render() output
markers = Dom.getElementsByClassName('yui-pg-ui','span',container);
for (i = 0, len = markers.length; i < len; ++i) {
this.renderUIComponent(markers[i], id_base);
}
if (!hide) {
// Show the container allowing page reflow
Dom.setStyle(container,'display','');
}
},
/**
* Replaces a marker node with a rendered UI component, determined by the
* yui-pg-ui-(UI component class name) in the marker's className. e.g.
* yui-pg-ui-PageLinks => new YAHOO.widget.Paginator.ui.PageLinks(this)
*
* @method renderUIComponent
* @param marker {HTMLElement} the marker node to replace
* @param id_base {String} string base the component's generated id
*/
renderUIComponent : function (marker, id_base) {
var par = marker.parentNode,
name = /yui-pg-ui-(\w+)/.exec(marker.className),
UIComp = name && Paginator.ui[name[1]],
comp;
if (isFunction(UIComp)) {
comp = new UIComp(this);
if (isFunction(comp.render)) {
par.replaceChild(comp.render(id_base),marker);
}
}
},
/**
* Removes controls from the page and unhooks events.
* @method destroy
*/
destroy : function () {
this.fireEvent('beforeDestroy');
this.fireEvent('destroy');
this.setAttributeConfig('rendered',{value:false});
this.unsubscribeAll();
},
/**
* 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 || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
totalRecords = this.get('totalRecords');
visible = true;
rpp = this.get('rowsPerPage');
rppOptions = this.get('rowsPerPageOptions');
if (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) {
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 (!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 (!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 (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._configs.hasOwnProperty(k)) {
this.set(k,state[k]);
}
}
this._batch = false;
if (this._pageChanged) {
this._pageChanged = false;
this._firePageChange(this.getState(this._state));
}
}
}
};
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.8.1", build: "19"});