2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
9 * The Paginator widget provides a set of controls to navigate through paged
13 * @uses YAHOO.util.EventProvider
14 * @uses YAHOO.util.AttributeProvider
17 var Dom = YAHOO.util.Dom,
19 isObject = lang.isObject,
20 isFunction = lang.isFunction,
21 isArray = lang.isArray,
22 isString = lang.isString;
25 * Instantiate a Paginator, passing a configuration object to the contructor.
26 * The configuration object should contain the following properties:
28 * <li>rowsPerPage : <em>n</em> (int)</li>
29 * <li>totalRecords : <em>n</em> (int or Paginator.VALUE_UNLIMITED)</li>
30 * <li>containers : <em>id | el | arr</em> (HTMLElement reference, its id, or an array of either)</li>
33 * @namespace YAHOO.widget
36 * @param config {Object} Object literal to set instance and ui component
39 function Paginator(config) {
40 var UNLIMITED = Paginator.VALUE_UNLIMITED,
41 attrib, initialPage, records, perPage, startIndex;
43 config = isObject(config) ? config : {};
49 // Set the basic config keys first
50 this.set('rowsPerPage',config.rowsPerPage,true);
51 if (Paginator.isNumeric(config.totalRecords)) {
52 this.set('totalRecords',config.totalRecords,true);
55 this.initUIComponents();
57 // Update the other config values
58 for (attrib in config) {
59 if (config.hasOwnProperty(attrib)) {
60 this.set(attrib,config[attrib],true);
64 // Calculate the initial record offset
65 initialPage = this.get('initialPage');
66 records = this.get('totalRecords');
67 perPage = this.get('rowsPerPage');
68 if (initialPage > 1 && perPage !== UNLIMITED) {
69 startIndex = (initialPage - 1) * perPage;
70 if (records === UNLIMITED || startIndex < records) {
71 this.set('recordOffset',startIndex,true);
78 lang.augmentObject(Paginator, {
80 * Incrementing index used to give instances unique ids.
82 * @property Paginator.id
89 * Base of id strings used for ui components.
91 * @property Paginator.ID_BASE
98 * Used to identify unset, optional configurations, or used explicitly in
99 * the case of totalRecords to indicate unlimited pagination.
101 * @property Paginator.VALUE_UNLIMITED
105 VALUE_UNLIMITED : -1,
108 * Default template used by Paginator instances. Update this if you want
109 * all new Paginators to use a different default template.
111 * @property Paginator.TEMPLATE_DEFAULT
114 TEMPLATE_DEFAULT : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",
117 * Common alternate pagination format, including page links, links for
118 * previous, next, first and last pages as well as a rows-per-page
119 * dropdown. Offered as a convenience.
121 * @property Paginator.TEMPLATE_ROWS_PER_PAGE
124 TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
127 * Storage object for UI Components
129 * @property Paginator.ui
134 * Similar to YAHOO.lang.isNumber, but allows numeric strings. This is
135 * is used for attribute validation in conjunction with getters that return
138 * @method Paginator.isNumeric
139 * @param v {Number|String} value to be checked for number or numeric string
140 * @returns {Boolean} true if the input is coercable into a finite number
143 isNumeric : function (v) {
148 * Return a number or null from input
150 * @method Paginator.toNumber
151 * @param n {Number|String} a number or numeric string
155 toNumber : function (n) {
156 return isFinite(+n) ? +n : null;
162 // Instance members and methods
163 Paginator.prototype = {
168 * Array of nodes in which to render pagination controls. This is set via
169 * the "containers" attribute.
170 * @property _containers
171 * @type Array(HTMLElement)
177 * Flag used to indicate multiple attributes are being updated via setState
185 * Used by setState to indicate when a page change has occurred
186 * @property _pageChanged
190 _pageChanged : false,
193 * Temporary state cache used by setState to keep track of the previous
194 * state for eventual pageChange event firing
205 * Initialize the Paginator's attributes (see YAHOO.util.Element class
206 * AttributeProvider).
210 initConfig : function () {
212 var UNLIMITED = Paginator.VALUE_UNLIMITED;
215 * REQUIRED. Number of records constituting a "page"
216 * @attribute rowsPerPage
219 this.setAttributeConfig('rowsPerPage', {
221 validator : Paginator.isNumeric,
222 setter : Paginator.toNumber
226 * REQUIRED. Node references or ids of nodes in which to render the
227 * pagination controls.
228 * @attribute containers
229 * @type {string|HTMLElement|Array(string|HTMLElement)}
231 this.setAttributeConfig('containers', {
233 validator : function (val) {
237 for (var i = 0, len = val.length; i < len; ++i) {
238 if (isString(val[i]) ||
239 (isObject(val[i]) && val[i].nodeType === 1)) {
246 method : function (val) {
251 this._containers = val;
256 * Total number of records to paginate through
257 * @attribute totalRecords
261 this.setAttributeConfig('totalRecords', {
263 validator : Paginator.isNumeric,
264 setter : Paginator.toNumber
268 * Zero based index of the record considered first on the current page.
269 * For page based interactions, don't modify this attribute directly;
271 * @attribute recordOffset
275 this.setAttributeConfig('recordOffset', {
277 validator : function (val) {
278 var total = this.get('totalRecords');
279 if (Paginator.isNumeric(val)) {
281 return total === UNLIMITED || total > val ||
282 (total === 0 && val === 0);
287 setter : Paginator.toNumber
291 * Page to display on initial paint
292 * @attribute initialPage
296 this.setAttributeConfig('initialPage', {
298 validator : Paginator.isNumeric,
299 setter : Paginator.toNumber
303 * Template used to render controls. The string will be used as
304 * innerHTML on all specified container nodes. Bracketed keys
305 * (e.g. {pageLinks}) in the string will be replaced with an instance
306 * of the so named ui component.
307 * @see Paginator.TEMPLATE_DEFAULT
308 * @see Paginator.TEMPLATE_ROWS_PER_PAGE
309 * @attribute template
312 this.setAttributeConfig('template', {
313 value : Paginator.TEMPLATE_DEFAULT,
318 * Class assigned to the element(s) containing pagination controls.
319 * @attribute containerClass
321 * @default 'yui-pg-container'
323 this.setAttributeConfig('containerClass', {
324 value : 'yui-pg-container',
329 * Display pagination controls even when there is only one page. Set
330 * to false to forgo rendering and/or hide the containers when there
331 * is only one page of data. Note if you are using the rowsPerPage
332 * dropdown ui component, visibility will be maintained as long as the
333 * number of records exceeds the smallest page size.
334 * @attribute alwaysVisible
338 this.setAttributeConfig('alwaysVisible', {
340 validator : lang.isBoolean
344 * Update the UI immediately upon interaction. If false, changeRequest
345 * subscribers or other external code will need to explicitly set the
346 * new values in the paginator to trigger repaint.
347 * @attribute updateOnChange
350 * @deprecated use changeRequest listener that calls setState
352 this.setAttributeConfig('updateOnChange', {
354 validator : lang.isBoolean
359 // Read only attributes
362 * Unique id assigned to this instance
367 this.setAttributeConfig('id', {
368 value : Paginator.id++,
373 * Indicator of whether the DOM nodes have been initially created
374 * @attribute rendered
378 this.setAttributeConfig('rendered', {
386 * Initialize registered ui components onto this instance.
387 * @method initUIComponents
390 initUIComponents : function () {
391 var ui = Paginator.ui,
394 if (ui.hasOwnProperty(name)) {
396 if (isObject(UIComp) && isFunction(UIComp.init)) {
404 * Initialize this instance's CustomEvents.
408 initEvents : function () {
410 * Event fired when the Paginator is initially rendered
413 this.createEvent('render');
416 * Event fired when the Paginator is initially rendered
418 * @deprecated use render event
420 this.createEvent('rendered'); // backward compatibility
423 * Event fired when a change in pagination values is requested,
424 * either by interacting with the various ui components or via the
425 * setStartIndex(n) etc APIs.
426 * Subscribers will receive the proposed state as the first parameter.
427 * The proposed state object will contain the following keys:
429 * <li>paginator - the Paginator instance</li>
431 * <li>totalRecords</li>
432 * <li>recordOffset - index of the first record on the new page</li>
433 * <li>rowsPerPage</li>
434 * <li>records - array containing [start index, end index] for the records on the new page</li>
435 * <li>before - object literal with all these keys for the current state</li>
437 * @event changeRequest
439 this.createEvent('changeRequest');
442 * Event fired when attribute changes have resulted in the calculated
443 * current page changing.
446 this.createEvent('pageChange');
449 * Event that fires before the destroy event.
450 * @event beforeDestroy
452 this.createEvent('beforeDestroy');
455 * Event used to trigger cleanup of ui components
458 this.createEvent('destroy');
460 this._selfSubscribe();
464 * Subscribes to instance attribute change events to automate certain
466 * @method _selfSubscribe
469 _selfSubscribe : function () {
470 // Listen for changes to totalRecords and alwaysVisible
471 this.subscribe('totalRecordsChange',this.updateVisibility,this,true);
472 this.subscribe('alwaysVisibleChange',this.updateVisibility,this,true);
474 // Fire the pageChange event when appropriate
475 this.subscribe('totalRecordsChange',this._handleStateChange,this,true);
476 this.subscribe('recordOffsetChange',this._handleStateChange,this,true);
477 this.subscribe('rowsPerPageChange',this._handleStateChange,this,true);
479 // Update recordOffset when totalRecords is reduced below
480 this.subscribe('totalRecordsChange',this._syncRecordOffset,this,true);
484 * Sets recordOffset to the starting index of the previous page when
485 * totalRecords is reduced below the current recordOffset.
486 * @method _syncRecordOffset
487 * @param e {Event} totalRecordsChange event
490 _syncRecordOffset : function (e) {
491 var v = e.newValue,rpp,state;
492 if (e.prevValue !== v) {
493 if (v !== Paginator.VALUE_UNLIMITED) {
494 rpp = this.get('rowsPerPage');
496 if (rpp && this.get('recordOffset') >= v) {
497 state = this.getState({
498 totalRecords : e.prevValue,
499 recordOffset : this.get('recordOffset')
502 this.set('recordOffset', state.before.recordOffset);
503 this._firePageChange(state);
510 * Fires the pageChange event when the state attributes have changed in
511 * such a way as to locate the current recordOffset on a new page.
512 * @method _handleStateChange
513 * @param e {Event} the attribute change event
516 _handleStateChange : function (e) {
517 if (e.prevValue !== e.newValue) {
518 var change = this._state || {},
521 change[e.type.replace(/Change$/,'')] = e.prevValue;
522 state = this.getState(change);
524 if (state.page !== state.before.page) {
526 this._pageChanged = true;
528 this._firePageChange(state);
535 * Fires a pageChange event in the form of a standard attribute change
536 * event with additional properties prevState and newState.
537 * @method _firePageChange
538 * @param state {Object} the result of getState(oldState)
541 _firePageChange : function (state) {
542 if (isObject(state)) {
543 var current = state.before;
545 this.fireEvent('pageChange',{
547 prevValue : state.page,
548 newValue : current.page,
556 * Render the pagination controls per the format attribute into the
557 * specified container nodes.
559 * @return the Paginator instance
562 render : function () {
563 if (this.get('rendered')) {
567 var template = this.get('template'),
568 state = this.getState(),
569 // ex. yui-pg0-1 (first paginator, second container)
570 id_base = Paginator.ID_BASE + this.get('id') + '-',
573 // Assemble the containers, keeping them hidden
574 for (i = 0, len = this._containers.length; i < len; ++i) {
575 this._renderTemplate(this._containers[i],template,id_base+i,true);
578 // Show the containers if appropriate
579 this.updateVisibility();
581 // Set render attribute manually to support its readOnly contract
582 if (this._containers.length) {
583 this.setAttributeConfig('rendered', { value: true });
585 this.fireEvent('render', state);
586 // For backward compatibility
587 this.fireEvent('rendered', state);
594 * Creates the individual ui components and renders them into a container.
596 * @method _renderTemplate
597 * @param container {HTMLElement} where to add the ui components
598 * @param template {String} the template to use as a guide for rendering
599 * @param id_base {String} id base for the container's ui components
600 * @param hide {Boolean} leave the container hidden after assembly
603 _renderTemplate : function (container, template, id_base, hide) {
604 var containerClass = this.get('containerClass'),
611 // Hide the container while its contents are rendered
612 Dom.setStyle(container,'display','none');
614 Dom.addClass(container, containerClass);
616 // Place the template innerHTML, adding marker spans to the template
617 // html to indicate drop zones for ui components
618 container.innerHTML = template.replace(/\{([a-z0-9_ \-]+)\}/gi,
619 '<span class="yui-pg-ui yui-pg-ui-$1"></span>');
621 // Replace each marker with the ui component's render() output
622 markers = Dom.getElementsByClassName('yui-pg-ui','span',container);
624 for (i = 0, len = markers.length; i < len; ++i) {
625 this.renderUIComponent(markers[i], id_base);
629 // Show the container allowing page reflow
630 Dom.setStyle(container,'display','');
635 * Replaces a marker node with a rendered UI component, determined by the
636 * yui-pg-ui-(UI component class name) in the marker's className. e.g.
637 * yui-pg-ui-PageLinks => new YAHOO.widget.Paginator.ui.PageLinks(this)
639 * @method renderUIComponent
640 * @param marker {HTMLElement} the marker node to replace
641 * @param id_base {String} string base the component's generated id
643 renderUIComponent : function (marker, id_base) {
644 var par = marker.parentNode,
645 name = /yui-pg-ui-(\w+)/.exec(marker.className),
646 UIComp = name && Paginator.ui[name[1]],
649 if (isFunction(UIComp)) {
650 comp = new UIComp(this);
651 if (isFunction(comp.render)) {
652 par.replaceChild(comp.render(id_base),marker);
658 * Removes controls from the page and unhooks events.
661 destroy : function () {
662 this.fireEvent('beforeDestroy');
663 this.fireEvent('destroy');
665 this.setAttributeConfig('rendered',{value:false});
666 this.unsubscribeAll();
670 * Hides the containers if there is only one page of data and attribute
671 * alwaysVisible is false. Conversely, it displays the containers if either
672 * there is more than one page worth of data or alwaysVisible is turned on.
673 * @method updateVisibility
675 updateVisibility : function (e) {
676 var alwaysVisible = this.get('alwaysVisible'),
677 totalRecords,visible,rpp,rppOptions,i,len;
679 if (!e || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
680 totalRecords = this.get('totalRecords');
682 rpp = this.get('rowsPerPage');
683 rppOptions = this.get('rowsPerPageOptions');
685 if (isArray(rppOptions)) {
686 for (i = 0, len = rppOptions.length; i < len; ++i) {
687 rpp = Math.min(rpp,rppOptions[i]);
691 if (totalRecords !== Paginator.VALUE_UNLIMITED &&
692 totalRecords <= rpp) {
696 visible = visible || alwaysVisible;
698 for (i = 0, len = this._containers.length; i < len; ++i) {
699 Dom.setStyle(this._containers[i],'display',
700 visible ? '' : 'none');
709 * Get the configured container nodes
710 * @method getContainerNodes
711 * @return {Array} array of HTMLElement nodes
713 getContainerNodes : function () {
714 return this._containers;
718 * Get the total number of pages in the data set according to the current
719 * rowsPerPage and totalRecords values. If totalRecords is not set, or
720 * set to YAHOO.widget.Paginator.VALUE_UNLIMITED, returns
721 * YAHOO.widget.Paginator.VALUE_UNLIMITED.
722 * @method getTotalPages
725 getTotalPages : function () {
726 var records = this.get('totalRecords'),
727 perPage = this.get('rowsPerPage');
729 // rowsPerPage not set. Can't calculate
734 if (records === Paginator.VALUE_UNLIMITED) {
735 return Paginator.VALUE_UNLIMITED;
738 return Math.ceil(records/perPage);
742 * Does the requested page have any records?
744 * @param page {number} the page in question
747 hasPage : function (page) {
748 if (!lang.isNumber(page) || page < 1) {
752 var totalPages = this.getTotalPages();
754 return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
758 * Get the page number corresponding to the current record offset.
759 * @method getCurrentPage
762 getCurrentPage : function () {
763 var perPage = this.get('rowsPerPage');
764 if (!perPage || !this.get('totalRecords')) {
767 return Math.floor(this.get('recordOffset') / perPage) + 1;
771 * Are there records on the next page?
772 * @method hasNextPage
775 hasNextPage : function () {
776 var currentPage = this.getCurrentPage(),
777 totalPages = this.getTotalPages();
779 return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
783 * Get the page number of the next page, or null if the current page is the
785 * @method getNextPage
788 getNextPage : function () {
789 return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
793 * Is there a page before the current page?
794 * @method hasPreviousPage
797 hasPreviousPage : function () {
798 return (this.getCurrentPage() > 1);
802 * Get the page number of the previous page, or null if the current page
804 * @method getPreviousPage
807 getPreviousPage : function () {
808 return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
812 * Get the start and end record indexes of the specified page.
813 * @method getPageRecords
814 * @param page {number} (optional) The page (current page if not specified)
815 * @return {Array} [start_index, end_index]
817 getPageRecords : function (page) {
818 if (!lang.isNumber(page)) {
819 page = this.getCurrentPage();
822 var perPage = this.get('rowsPerPage'),
823 records = this.get('totalRecords'),
826 if (!page || !perPage) {
830 start = (page - 1) * perPage;
831 if (records !== Paginator.VALUE_UNLIMITED) {
832 if (start >= records) {
835 end = Math.min(start + perPage, records) - 1;
837 end = start + perPage - 1;
844 * Set the current page to the provided page number if possible.
846 * @param newPage {number} the new page number
847 * @param silent {boolean} whether to forcibly avoid firing the
848 * changeRequest event
850 setPage : function (page,silent) {
851 if (this.hasPage(page) && page !== this.getCurrentPage()) {
852 if (this.get('updateOnChange') || silent) {
853 this.set('recordOffset', (page - 1) * this.get('rowsPerPage'));
855 this.fireEvent('changeRequest',this.getState({'page':page}));
861 * Get the number of rows per page.
862 * @method getRowsPerPage
863 * @return {number} the current setting of the rowsPerPage attribute
865 getRowsPerPage : function () {
866 return this.get('rowsPerPage');
870 * Set the number of rows per page.
871 * @method setRowsPerPage
872 * @param rpp {number} the new number of rows per page
873 * @param silent {boolean} whether to forcibly avoid firing the
874 * changeRequest event
876 setRowsPerPage : function (rpp,silent) {
877 if (Paginator.isNumeric(rpp) && +rpp > 0 &&
878 +rpp !== this.get('rowsPerPage')) {
879 if (this.get('updateOnChange') || silent) {
880 this.set('rowsPerPage',rpp);
882 this.fireEvent('changeRequest',
883 this.getState({'rowsPerPage':+rpp}));
889 * Get the total number of records.
890 * @method getTotalRecords
891 * @return {number} the current setting of totalRecords attribute
893 getTotalRecords : function () {
894 return this.get('totalRecords');
898 * Set the total number of records.
899 * @method setTotalRecords
900 * @param total {number} the new total number of records
901 * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
903 setTotalRecords : function (total,silent) {
904 if (Paginator.isNumeric(total) && +total >= 0 &&
905 +total !== this.get('totalRecords')) {
906 if (this.get('updateOnChange') || silent) {
907 this.set('totalRecords',total);
909 this.fireEvent('changeRequest',
910 this.getState({'totalRecords':+total}));
916 * Get the index of the first record on the current page
917 * @method getStartIndex
918 * @return {number} the index of the first record on the current page
920 getStartIndex : function () {
921 return this.get('recordOffset');
925 * Move the record offset to a new starting index. This will likely cause
926 * the calculated current page to change. You should probably use setPage.
927 * @method setStartIndex
928 * @param offset {number} the new record offset
929 * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
931 setStartIndex : function (offset,silent) {
932 if (Paginator.isNumeric(offset) && +offset >= 0 &&
933 +offset !== this.get('recordOffset')) {
934 if (this.get('updateOnChange') || silent) {
935 this.set('recordOffset',offset);
937 this.fireEvent('changeRequest',
938 this.getState({'recordOffset':+offset}));
944 * Get an object literal describing the current state of the paginator. If
945 * an object literal of proposed values is passed, the proposed state will
946 * be returned as an object literal with the following keys:
948 * <li>paginator - instance of the Paginator</li>
949 * <li>page - number</li>
950 * <li>totalRecords - number</li>
951 * <li>recordOffset - number</li>
952 * <li>rowsPerPage - number</li>
953 * <li>records - [ start_index, end_index ]</li>
954 * <li>before - (OPTIONAL) { state object literal for current state }</li>
958 * @param changes {object} OPTIONAL object literal with proposed values
959 * Supported change keys include:
961 * <li>rowsPerPage</li>
962 * <li>totalRecords</li>
963 * <li>recordOffset OR</li>
967 getState : function (changes) {
968 var UNLIMITED = Paginator.VALUE_UNLIMITED,
969 M = Math, max = M.max, ceil = M.ceil,
970 currentState, state, offset;
972 function normalizeOffset(offset,total,rpp) {
973 if (offset <= 0 || total === 0) {
976 if (total === UNLIMITED || total > offset) {
977 return offset - (offset % rpp);
979 return total - (total % rpp || rpp);
984 totalRecords : this.get('totalRecords'),
985 rowsPerPage : this.get('rowsPerPage'),
986 records : this.getPageRecords()
988 currentState.recordOffset = normalizeOffset(
989 this.get('recordOffset'),
990 currentState.totalRecords,
991 currentState.rowsPerPage);
992 currentState.page = ceil(currentState.recordOffset /
993 currentState.rowsPerPage) + 1;
1001 before : currentState,
1003 rowsPerPage : changes.rowsPerPage || currentState.rowsPerPage,
1004 totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
1005 max(changes.totalRecords,UNLIMITED) :
1006 +currentState.totalRecords)
1009 if (state.totalRecords === 0) {
1010 state.recordOffset =
1013 offset = Paginator.isNumeric(changes.page) ?
1014 (changes.page - 1) * state.rowsPerPage :
1015 Paginator.isNumeric(changes.recordOffset) ?
1016 +changes.recordOffset :
1017 currentState.recordOffset;
1019 state.recordOffset = normalizeOffset(offset,
1023 state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
1026 state.records = [ state.recordOffset,
1027 state.recordOffset + state.rowsPerPage - 1 ];
1029 // limit upper index to totalRecords - 1
1030 if (state.totalRecords !== UNLIMITED &&
1031 state.recordOffset < state.totalRecords && state.records &&
1032 state.records[1] > state.totalRecords - 1) {
1033 state.records[1] = state.totalRecords - 1;
1040 * Convenience method to facilitate setting state attributes rowsPerPage,
1041 * totalRecords, recordOffset in batch. Also supports calculating
1042 * recordOffset from state.page if state.recordOffset is not provided.
1043 * Fires only a single pageChange event, if appropriate.
1044 * This will not fire a changeRequest event.
1046 * @param state {Object} Object literal of attribute:value pairs to set
1048 setState : function (state) {
1049 if (isObject(state)) {
1050 // get flux state based on current state with before state as well
1051 this._state = this.getState({});
1053 // use just the state props from the input obj
1056 rowsPerPage : state.rowsPerPage,
1057 totalRecords : state.totalRecords,
1058 recordOffset : state.recordOffset
1061 // calculate recordOffset from page if recordOffset not specified.
1062 // not using lang.isNumber for support of numeric strings
1063 if (state.page && state.recordOffset === undefined) {
1064 state.recordOffset = (state.page - 1) *
1065 (state.rowsPerPage || this.get('rowsPerPage'));
1069 this._pageChanged = false;
1071 for (var k in state) {
1072 if (state.hasOwnProperty(k) && this._configs.hasOwnProperty(k)) {
1073 this.set(k,state[k]);
1077 this._batch = false;
1079 if (this._pageChanged) {
1080 this._pageChanged = false;
1082 this._firePageChange(this.getState(this._state));
1088 lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
1090 YAHOO.widget.Paginator = Paginator;
1094 var Paginator = YAHOO.widget.Paginator,
1098 * ui Component to generate the textual report of current pagination status.
1099 * E.g. "Now viewing page 1 of 13".
1101 * @namespace YAHOO.widget.Paginator.ui
1102 * @class CurrentPageReport
1103 * @for YAHOO.widget.Paginator
1106 * @param p {Pagintor} Paginator instance to attach to
1108 Paginator.ui.CurrentPageReport = function (p) {
1111 p.subscribe('recordOffsetChange', this.update,this,true);
1112 p.subscribe('rowsPerPageChange', this.update,this,true);
1113 p.subscribe('totalRecordsChange',this.update,this,true);
1114 p.subscribe('pageReportTemplateChange', this.update,this,true);
1115 p.subscribe('destroy',this.destroy,this,true);
1117 //TODO: make this work
1118 p.subscribe('pageReportClassChange', this.update,this,true);
1122 * Decorates Paginator instances with new attributes. Called during
1123 * Paginator instantiation.
1125 * @param p {Paginator} Paginator instance to decorate
1128 Paginator.ui.CurrentPageReport.init = function (p) {
1131 * CSS class assigned to the span containing the info.
1132 * @attribute pageReportClass
1133 * @default 'yui-pg-current'
1135 p.setAttributeConfig('pageReportClass', {
1136 value : 'yui-pg-current',
1137 validator : l.isString
1141 * Used as innerHTML for the span. Place holders in the form of {name}
1142 * will be replaced with the so named value from the key:value map
1143 * generated by the function held in the pageReportValueGenerator attribute.
1144 * @attribute pageReportTemplate
1145 * @default '({currentPage} of {totalPages})'
1146 * @see pageReportValueGenerator attribute
1148 p.setAttributeConfig('pageReportTemplate', {
1149 value : '({currentPage} of {totalPages})',
1150 validator : l.isString
1154 * Function to generate the value map used to populate the
1155 * pageReportTemplate. The function is passed the Paginator instance as a
1156 * parameter. The default function returns a map with the following keys:
1158 * <li>currentPage</li>
1159 * <li>totalPages</li>
1160 * <li>startIndex</li>
1162 * <li>startRecord</li>
1163 * <li>endRecord</li>
1164 * <li>totalRecords</li>
1166 * @attribute pageReportValueGenarator
1168 p.setAttributeConfig('pageReportValueGenerator', {
1169 value : function (paginator) {
1170 var curPage = paginator.getCurrentPage(),
1171 records = paginator.getPageRecords();
1174 'currentPage' : records ? curPage : 0,
1175 'totalPages' : paginator.getTotalPages(),
1176 'startIndex' : records ? records[0] : 0,
1177 'endIndex' : records ? records[1] : 0,
1178 'startRecord' : records ? records[0] + 1 : 0,
1179 'endRecord' : records ? records[1] + 1 : 0,
1180 'totalRecords': paginator.get('totalRecords')
1183 validator : l.isFunction
1188 * Replace place holders in a string with the named values found in an
1192 * @param template {string} The content string containing place holders
1193 * @param values {object} The key:value pairs used to replace the place holders
1196 Paginator.ui.CurrentPageReport.sprintf = function (template, values) {
1197 return template.replace(/\{([\w\s\-]+)\}/g, function (x,key) {
1198 return (key in values) ? values[key] : '';
1202 Paginator.ui.CurrentPageReport.prototype = {
1205 * Span node containing the formatted info
1214 * Generate the span containing info formatted per the pageReportTemplate
1217 * @param id_base {string} used to create unique ids for generated nodes
1218 * @return {HTMLElement}
1220 render : function (id_base) {
1221 this.span = document.createElement('span');
1222 this.span.id = id_base + '-page-report';
1223 this.span.className = this.paginator.get('pageReportClass');
1230 * Regenerate the content of the span if appropriate. Calls
1231 * CurrentPageReport.sprintf with the value of the pageReportTemplate
1232 * attribute and the value map returned from pageReportValueGenerator
1235 * @param e {CustomEvent} The calling change event
1237 update : function (e) {
1238 if (e && e.prevValue === e.newValue) {
1242 this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
1243 this.paginator.get('pageReportTemplate'),
1244 this.paginator.get('pageReportValueGenerator')(this.paginator));
1248 * Removes the link/span node and clears event listeners
1253 destroy : function () {
1254 this.span.parentNode.removeChild(this.span);
1263 var Paginator = YAHOO.widget.Paginator,
1267 * ui Component to generate the page links
1269 * @namespace YAHOO.widget.Paginator.ui
1271 * @for YAHOO.widget.Paginator
1274 * @param p {Pagintor} Paginator instance to attach to
1276 Paginator.ui.PageLinks = function (p) {
1279 p.subscribe('recordOffsetChange',this.update,this,true);
1280 p.subscribe('rowsPerPageChange',this.update,this,true);
1281 p.subscribe('totalRecordsChange',this.update,this,true);
1282 p.subscribe('pageLinksChange', this.rebuild,this,true);
1283 p.subscribe('pageLinkClassChange', this.rebuild,this,true);
1284 p.subscribe('currentPageClassChange', this.rebuild,this,true);
1285 p.subscribe('destroy',this.destroy,this,true);
1287 //TODO: Make this work
1288 p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
1292 * Decorates Paginator instances with new attributes. Called during
1293 * Paginator instantiation.
1295 * @param p {Paginator} Paginator instance to decorate
1298 Paginator.ui.PageLinks.init = function (p) {
1301 * CSS class assigned to each page link/span.
1302 * @attribute pageLinkClass
1303 * @default 'yui-pg-page'
1305 p.setAttributeConfig('pageLinkClass', {
1306 value : 'yui-pg-page',
1307 validator : l.isString
1311 * CSS class assigned to the current page span.
1312 * @attribute currentPageClass
1313 * @default 'yui-pg-current-page'
1315 p.setAttributeConfig('currentPageClass', {
1316 value : 'yui-pg-current-page',
1317 validator : l.isString
1321 * CSS class assigned to the span containing the page links.
1322 * @attribute pageLinksContainerClass
1323 * @default 'yui-pg-pages'
1325 p.setAttributeConfig('pageLinksContainerClass', {
1326 value : 'yui-pg-pages',
1327 validator : l.isString
1331 * Maximum number of page links to display at one time.
1332 * @attribute pageLinks
1335 p.setAttributeConfig('pageLinks', {
1337 validator : Paginator.isNumeric
1341 * Function used generate the innerHTML for each page link/span. The
1342 * function receives as parameters the page number and a reference to the
1344 * @attribute pageLabelBuilder
1345 * @default function (page, paginator) { return page; }
1347 p.setAttributeConfig('pageLabelBuilder', {
1348 value : function (page, paginator) { return page; },
1349 validator : l.isFunction
1354 * Calculates start and end page numbers given a current page, attempting
1355 * to keep the current page in the middle
1357 * @method calculateRange
1358 * @param {int} currentPage The current page
1359 * @param {int} totalPages (optional) Maximum number of pages
1360 * @param {int} numPages (optional) Preferred number of pages in range
1361 * @return {Array} [start_page_number, end_page_number]
1363 Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
1364 var UNLIMITED = Paginator.VALUE_UNLIMITED,
1367 // Either has no pages, or unlimited pages. Show none.
1368 if (!currentPage || numPages === 0 || totalPages === 0 ||
1369 (totalPages === UNLIMITED && numPages === UNLIMITED)) {
1373 // Limit requested pageLinks if there are fewer totalPages
1374 if (totalPages !== UNLIMITED) {
1375 numPages = numPages === UNLIMITED ?
1377 Math.min(numPages,totalPages);
1380 // Determine start and end, trying to keep current in the middle
1381 start = Math.max(1,Math.ceil(currentPage - (numPages/2)));
1382 if (totalPages === UNLIMITED) {
1383 end = start + numPages - 1;
1385 end = Math.min(totalPages, start + numPages - 1);
1388 // Adjust the start index when approaching the last page
1389 delta = numPages - (end - start + 1);
1390 start = Math.max(1, start - delta);
1396 Paginator.ui.PageLinks.prototype = {
1407 * Span node containing the page links
1408 * @property container
1416 * Generate the nodes and return the container node containing page links
1417 * appropriate to the current pagination state.
1419 * @param id_base {string} used to create unique ids for generated nodes
1420 * @return {HTMLElement}
1422 render : function (id_base) {
1423 var p = this.paginator;
1426 this.container = document.createElement('span');
1427 this.container.id = id_base + '-pages';
1428 this.container.className = p.get('pageLinksContainerClass');
1429 YAHOO.util.Event.on(this.container,'click',this.onClick,this,true);
1431 // Call update, flagging a need to rebuild
1432 this.update({newValue : null, rebuild : true});
1434 return this.container;
1438 * Update the links if appropriate
1440 * @param e {CustomEvent} The calling change event
1442 update : function (e) {
1443 if (e && e.prevValue === e.newValue) {
1447 var p = this.paginator,
1448 currentPage = p.getCurrentPage();
1450 // Replace content if there's been a change
1451 if (this.current !== currentPage || !currentPage || e.rebuild) {
1452 var labelBuilder = p.get('pageLabelBuilder'),
1453 range = Paginator.ui.PageLinks.calculateRange(
1456 p.get('pageLinks')),
1462 linkTemplate = '<a href="#" class="' + p.get('pageLinkClass') +
1464 for (i = start; i <= end; ++i) {
1465 if (i === currentPage) {
1467 '<span class="' + p.get('currentPageClass') + ' ' +
1468 p.get('pageLinkClass') + '">' +
1469 labelBuilder(i,p) + '</span>';
1472 linkTemplate + i + '">' + labelBuilder(i,p) + '</a>';
1476 this.container.innerHTML = content;
1481 * Force a rebuild of the page links.
1483 * @param e {CustomEvent} The calling change event
1485 rebuild : function (e) {
1491 * Removes the page links container node and clears event listeners
1495 destroy : function () {
1496 YAHOO.util.Event.purgeElement(this.container,true);
1497 this.container.parentNode.removeChild(this.container);
1498 this.container = null;
1502 * Listener for the container's onclick event. Looks for qualifying link
1503 * clicks, and pulls the page number from the link's page attribute.
1504 * Sends link's page attribute to the Paginator's setPage method.
1506 * @param e {DOMEvent} The click event
1508 onClick : function (e) {
1509 var t = YAHOO.util.Event.getTarget(e);
1510 if (t && YAHOO.util.Dom.hasClass(t,
1511 this.paginator.get('pageLinkClass'))) {
1513 YAHOO.util.Event.stopEvent(e);
1515 this.paginator.setPage(parseInt(t.getAttribute('page'),10));
1524 var Paginator = YAHOO.widget.Paginator,
1528 * ui Component to generate the link to jump to the first page.
1530 * @namespace YAHOO.widget.Paginator.ui
1531 * @class FirstPageLink
1532 * @for YAHOO.widget.Paginator
1535 * @param p {Pagintor} Paginator instance to attach to
1537 Paginator.ui.FirstPageLink = function (p) {
1540 p.subscribe('recordOffsetChange',this.update,this,true);
1541 p.subscribe('rowsPerPageChange',this.update,this,true);
1542 p.subscribe('totalRecordsChange',this.update,this,true);
1543 p.subscribe('destroy',this.destroy,this,true);
1545 // TODO: make this work
1546 p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1547 p.subscribe('firstPageLinkClassChange',this.update,this,true);
1551 * Decorates Paginator instances with new attributes. Called during
1552 * Paginator instantiation.
1554 * @param p {Paginator} Paginator instance to decorate
1557 Paginator.ui.FirstPageLink.init = function (p) {
1560 * Used as innerHTML for the first page link/span.
1561 * @attribute firstPageLinkLabel
1562 * @default '<< first'
1564 p.setAttributeConfig('firstPageLinkLabel', {
1565 value : '<< first',
1566 validator : l.isString
1570 * CSS class assigned to the link/span
1571 * @attribute firstPageLinkClass
1572 * @default 'yui-pg-first'
1574 p.setAttributeConfig('firstPageLinkClass', {
1575 value : 'yui-pg-first',
1576 validator : l.isString
1580 // Instance members and methods
1581 Paginator.ui.FirstPageLink.prototype = {
1584 * The currently placed HTMLElement node
1600 * Span node (inactive link)
1608 * Generate the nodes and return the appropriate node given the current
1611 * @param id_base {string} used to create unique ids for generated nodes
1612 * @return {HTMLElement}
1614 render : function (id_base) {
1615 var p = this.paginator,
1616 c = p.get('firstPageLinkClass'),
1617 label = p.get('firstPageLinkLabel');
1619 this.link = document.createElement('a');
1620 this.span = document.createElement('span');
1622 this.link.id = id_base + '-first-link';
1623 this.link.href = '#';
1624 this.link.className = c;
1625 this.link.innerHTML = label;
1626 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1628 this.span.id = id_base + '-first-span';
1629 this.span.className = c;
1630 this.span.innerHTML = label;
1632 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1633 return this.current;
1637 * Swap the link and span nodes if appropriate.
1639 * @param e {CustomEvent} The calling change event
1641 update : function (e) {
1642 if (e && e.prevValue === e.newValue) {
1646 var par = this.current ? this.current.parentNode : null;
1647 if (this.paginator.getCurrentPage() > 1) {
1648 if (par && this.current === this.span) {
1649 par.replaceChild(this.link,this.current);
1650 this.current = this.link;
1653 if (par && this.current === this.link) {
1654 par.replaceChild(this.span,this.current);
1655 this.current = this.span;
1661 * Removes the link/span node and clears event listeners
1666 destroy : function () {
1667 YAHOO.util.Event.purgeElement(this.link);
1668 this.current.parentNode.removeChild(this.current);
1669 this.link = this.span = null;
1673 * Listener for the link's onclick event. Pass new value to setPage method.
1675 * @param e {DOMEvent} The click event
1677 onClick : function (e) {
1678 YAHOO.util.Event.stopEvent(e);
1679 this.paginator.setPage(1);
1686 var Paginator = YAHOO.widget.Paginator,
1690 * ui Component to generate the link to jump to the last page.
1692 * @namespace YAHOO.widget.Paginator.ui
1693 * @class LastPageLink
1694 * @for YAHOO.widget.Paginator
1697 * @param p {Pagintor} Paginator instance to attach to
1699 Paginator.ui.LastPageLink = function (p) {
1702 p.subscribe('recordOffsetChange',this.update,this,true);
1703 p.subscribe('rowsPerPageChange',this.update,this,true);
1704 p.subscribe('totalRecordsChange',this.update,this,true);
1705 p.subscribe('destroy',this.destroy,this,true);
1707 // TODO: make this work
1708 p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1709 p.subscribe('lastPageLinkClassChange', this.update,this,true);
1713 * Decorates Paginator instances with new attributes. Called during
1714 * Paginator instantiation.
1716 * @param paginator {Paginator} Paginator instance to decorate
1719 Paginator.ui.LastPageLink.init = function (p) {
1722 * Used as innerHTML for the last page link/span.
1723 * @attribute lastPageLinkLabel
1724 * @default 'last >>'
1726 p.setAttributeConfig('lastPageLinkLabel', {
1727 value : 'last >>',
1728 validator : l.isString
1732 * CSS class assigned to the link/span
1733 * @attribute lastPageLinkClass
1734 * @default 'yui-pg-last'
1736 p.setAttributeConfig('lastPageLinkClass', {
1737 value : 'yui-pg-last',
1738 validator : l.isString
1742 Paginator.ui.LastPageLink.prototype = {
1745 * Currently placed HTMLElement node
1753 * Link HTMLElement node
1761 * Span node (inactive link)
1769 * Empty place holder node for when the last page link is inappropriate to
1770 * display in any form (unlimited paging).
1779 * Generate the nodes and return the appropriate node given the current
1782 * @param id_base {string} used to create unique ids for generated nodes
1783 * @return {HTMLElement}
1785 render : function (id_base) {
1786 var p = this.paginator,
1787 c = p.get('lastPageLinkClass'),
1788 label = p.get('lastPageLinkLabel'),
1789 last = p.getTotalPages();
1791 this.link = document.createElement('a');
1792 this.span = document.createElement('span');
1793 this.na = this.span.cloneNode(false);
1795 this.link.id = id_base + '-last-link';
1796 this.link.href = '#';
1797 this.link.className = c;
1798 this.link.innerHTML = label;
1799 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1801 this.span.id = id_base + '-last-span';
1802 this.span.className = c;
1803 this.span.innerHTML = label;
1805 this.na.id = id_base + '-last-na';
1808 case Paginator.VALUE_UNLIMITED :
1809 this.current = this.na; break;
1810 case p.getCurrentPage() :
1811 this.current = this.span; break;
1813 this.current = this.link;
1816 return this.current;
1820 * Swap the link, span, and na nodes if appropriate.
1822 * @param e {CustomEvent} The calling change event (ignored)
1824 update : function (e) {
1825 if (e && e.prevValue === e.newValue) {
1829 var par = this.current ? this.current.parentNode : null,
1833 switch (this.paginator.getTotalPages()) {
1834 case Paginator.VALUE_UNLIMITED :
1835 after = this.na; break;
1836 case this.paginator.getCurrentPage() :
1837 after = this.span; break;
1840 if (this.current !== after) {
1841 par.replaceChild(after,this.current);
1842 this.current = after;
1848 * Removes the link/span node and clears event listeners
1852 destroy : function () {
1853 YAHOO.util.Event.purgeElement(this.link);
1854 this.current.parentNode.removeChild(this.current);
1855 this.link = this.span = null;
1859 * Listener for the link's onclick event. Passes to setPage method.
1861 * @param e {DOMEvent} The click event
1863 onClick : function (e) {
1864 YAHOO.util.Event.stopEvent(e);
1865 this.paginator.setPage(this.paginator.getTotalPages());
1872 var Paginator = YAHOO.widget.Paginator,
1876 * ui Component to generate the link to jump to the next page.
1878 * @namespace YAHOO.widget.Paginator.ui
1879 * @class NextPageLink
1880 * @for YAHOO.widget.Paginator
1883 * @param p {Pagintor} Paginator instance to attach to
1885 Paginator.ui.NextPageLink = function (p) {
1888 p.subscribe('recordOffsetChange', this.update,this,true);
1889 p.subscribe('rowsPerPageChange', this.update,this,true);
1890 p.subscribe('totalRecordsChange', this.update,this,true);
1891 p.subscribe('destroy',this.destroy,this,true);
1893 // TODO: make this work
1894 p.subscribe('nextPageLinkLabelChange', this.update,this,true);
1895 p.subscribe('nextPageLinkClassChange', this.update,this,true);
1899 * Decorates Paginator instances with new attributes. Called during
1900 * Paginator instantiation.
1902 * @param p {Paginator} Paginator instance to decorate
1905 Paginator.ui.NextPageLink.init = function (p) {
1908 * Used as innerHTML for the next page link/span.
1909 * @attribute nextPageLinkLabel
1910 * @default 'next >'
1912 p.setAttributeConfig('nextPageLinkLabel', {
1913 value : 'next >',
1914 validator : l.isString
1918 * CSS class assigned to the link/span
1919 * @attribute nextPageLinkClass
1920 * @default 'yui-pg-next'
1922 p.setAttributeConfig('nextPageLinkClass', {
1923 value : 'yui-pg-next',
1924 validator : l.isString
1928 Paginator.ui.NextPageLink.prototype = {
1931 * Currently placed HTMLElement node
1947 * Span node (inactive link)
1956 * Generate the nodes and return the appropriate node given the current
1959 * @param id_base {string} used to create unique ids for generated nodes
1960 * @return {HTMLElement}
1962 render : function (id_base) {
1963 var p = this.paginator,
1964 c = p.get('nextPageLinkClass'),
1965 label = p.get('nextPageLinkLabel'),
1966 last = p.getTotalPages();
1968 this.link = document.createElement('a');
1969 this.span = document.createElement('span');
1971 this.link.id = id_base + '-next-link';
1972 this.link.href = '#';
1973 this.link.className = c;
1974 this.link.innerHTML = label;
1975 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1977 this.span.id = id_base + '-next-span';
1978 this.span.className = c;
1979 this.span.innerHTML = label;
1981 this.current = p.getCurrentPage() === last ? this.span : this.link;
1983 return this.current;
1987 * Swap the link and span nodes if appropriate.
1989 * @param e {CustomEvent} The calling change event
1991 update : function (e) {
1992 if (e && e.prevValue === e.newValue) {
1996 var last = this.paginator.getTotalPages(),
1997 par = this.current ? this.current.parentNode : null;
1999 if (this.paginator.getCurrentPage() !== last) {
2000 if (par && this.current === this.span) {
2001 par.replaceChild(this.link,this.current);
2002 this.current = this.link;
2004 } else if (this.current === this.link) {
2006 par.replaceChild(this.span,this.current);
2007 this.current = this.span;
2013 * Removes the link/span node and clears event listeners
2017 destroy : function () {
2018 YAHOO.util.Event.purgeElement(this.link);
2019 this.current.parentNode.removeChild(this.current);
2020 this.link = this.span = null;
2024 * Listener for the link's onclick event. Passes to setPage method.
2026 * @param e {DOMEvent} The click event
2028 onClick : function (e) {
2029 YAHOO.util.Event.stopEvent(e);
2030 this.paginator.setPage(this.paginator.getNextPage());
2037 var Paginator = YAHOO.widget.Paginator,
2041 * ui Component to generate the link to jump to the previous page.
2043 * @namespace YAHOO.widget.Paginator.ui
2044 * @class PreviousPageLink
2045 * @for YAHOO.widget.Paginator
2048 * @param p {Pagintor} Paginator instance to attach to
2050 Paginator.ui.PreviousPageLink = function (p) {
2053 p.subscribe('recordOffsetChange',this.update,this,true);
2054 p.subscribe('rowsPerPageChange',this.update,this,true);
2055 p.subscribe('totalRecordsChange',this.update,this,true);
2056 p.subscribe('destroy',this.destroy,this,true);
2058 // TODO: make this work
2059 p.subscribe('previousPageLinkLabelChange',this.update,this,true);
2060 p.subscribe('previousPageLinkClassChange',this.update,this,true);
2064 * Decorates Paginator instances with new attributes. Called during
2065 * Paginator instantiation.
2067 * @param p {Paginator} Paginator instance to decorate
2070 Paginator.ui.PreviousPageLink.init = function (p) {
2073 * Used as innerHTML for the previous page link/span.
2074 * @attribute previousPageLinkLabel
2075 * @default '< prev'
2077 p.setAttributeConfig('previousPageLinkLabel', {
2078 value : '< prev',
2079 validator : l.isString
2083 * CSS class assigned to the link/span
2084 * @attribute previousPageLinkClass
2085 * @default 'yui-pg-previous'
2087 p.setAttributeConfig('previousPageLinkClass', {
2088 value : 'yui-pg-previous',
2089 validator : l.isString
2093 Paginator.ui.PreviousPageLink.prototype = {
2096 * Currently placed HTMLElement node
2112 * Span node (inactive link)
2121 * Generate the nodes and return the appropriate node given the current
2124 * @param id_base {string} used to create unique ids for generated nodes
2125 * @return {HTMLElement}
2127 render : function (id_base) {
2128 var p = this.paginator,
2129 c = p.get('previousPageLinkClass'),
2130 label = p.get('previousPageLinkLabel');
2132 this.link = document.createElement('a');
2133 this.span = document.createElement('span');
2135 this.link.id = id_base + '-prev-link';
2136 this.link.href = '#';
2137 this.link.className = c;
2138 this.link.innerHTML = label;
2139 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
2141 this.span.id = id_base + '-prev-span';
2142 this.span.className = c;
2143 this.span.innerHTML = label;
2145 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
2146 return this.current;
2150 * Swap the link and span nodes if appropriate.
2152 * @param e {CustomEvent} The calling change event
2154 update : function (e) {
2155 if (e && e.prevValue === e.newValue) {
2159 var par = this.current ? this.current.parentNode : null;
2160 if (this.paginator.getCurrentPage() > 1) {
2161 if (par && this.current === this.span) {
2162 par.replaceChild(this.link,this.current);
2163 this.current = this.link;
2166 if (par && this.current === this.link) {
2167 par.replaceChild(this.span,this.current);
2168 this.current = this.span;
2174 * Removes the link/span node and clears event listeners
2178 destroy : function () {
2179 YAHOO.util.Event.purgeElement(this.link);
2180 this.current.parentNode.removeChild(this.current);
2181 this.link = this.span = null;
2185 * Listener for the link's onclick event. Passes to setPage method.
2187 * @param e {DOMEvent} The click event
2189 onClick : function (e) {
2190 YAHOO.util.Event.stopEvent(e);
2191 this.paginator.setPage(this.paginator.getPreviousPage());
2198 var Paginator = YAHOO.widget.Paginator,
2202 * ui Component to generate the rows-per-page dropdown
2204 * @namespace YAHOO.widget.Paginator.ui
2205 * @class RowsPerPageDropdown
2206 * @for YAHOO.widget.Paginator
2209 * @param p {Pagintor} Paginator instance to attach to
2211 Paginator.ui.RowsPerPageDropdown = function (p) {
2214 p.subscribe('rowsPerPageChange',this.update,this,true);
2215 p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
2216 p.subscribe('totalRecordsChange',this._handleTotalRecordsChange,this,true);
2217 p.subscribe('destroy',this.destroy,this,true);
2219 // TODO: make this work
2220 p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
2224 * Decorates Paginator instances with new attributes. Called during
2225 * Paginator instantiation.
2227 * @param p {Paginator} Paginator instance to decorate
2230 Paginator.ui.RowsPerPageDropdown.init = function (p) {
2233 * Array of available rows-per-page sizes. Converted into select options.
2234 * Array values may be positive integers or object literals in the form<br>
2235 * { value : NUMBER, text : STRING }
2236 * @attribute rowsPerPageOptions
2239 p.setAttributeConfig('rowsPerPageOptions', {
2241 validator : l.isArray
2245 * CSS class assigned to the select node
2246 * @attribute rowsPerPageDropdownClass
2247 * @default 'yui-pg-rpp-options'
2249 p.setAttributeConfig('rowsPerPageDropdownClass', {
2250 value : 'yui-pg-rpp-options',
2251 validator : l.isString
2255 Paginator.ui.RowsPerPageDropdown.prototype = {
2267 * option node for the optional All value
2276 * Generate the select and option nodes and returns the select node.
2278 * @param id_base {string} used to create unique ids for generated nodes
2279 * @return {HTMLElement}
2281 render : function (id_base) {
2282 this.select = document.createElement('select');
2283 this.select.id = id_base + '-rpp';
2284 this.select.className = this.paginator.get('rowsPerPageDropdownClass');
2285 this.select.title = 'Rows per page';
2287 YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2295 * (Re)generate the select options.
2298 rebuild : function (e) {
2299 var p = this.paginator,
2301 options = p.get('rowsPerPageOptions'),
2306 for (i = 0, len = options.length; i < len; ++i) {
2308 opt = sel.options[i] ||
2309 sel.appendChild(document.createElement('option'));
2310 val = l.isValue(cfg.value) ? cfg.value : cfg;
2311 opt.innerHTML = l.isValue(cfg.text) ? cfg.text : cfg;
2313 if (l.isString(val) && val.toLowerCase() === 'all') {
2315 opt.value = p.get('totalRecords');
2322 while (sel.options.length > options.length) {
2323 sel.removeChild(sel.firstChild);
2330 * Select the appropriate option if changed.
2332 * @param e {CustomEvent} The calling change event
2334 update : function (e) {
2335 if (e && e.prevValue === e.newValue) {
2339 var rpp = this.paginator.get('rowsPerPage')+'',
2340 options = this.select.options,
2343 for (i = 0, len = options.length; i < len; ++i) {
2344 if (options[i].value === rpp) {
2345 options[i].selected = true;
2352 * Listener for the select's onchange event. Sent to setRowsPerPage method.
2354 * @param e {DOMEvent} The change event
2356 onChange : function (e) {
2357 this.paginator.setRowsPerPage(
2358 parseInt(this.select.options[this.select.selectedIndex].value,10));
2362 * Updates the all option value (and Paginator's rowsPerPage attribute if
2363 * necessary) in response to a change in the Paginator's totalRecords.
2365 * @method _handleTotalRecordsChange
2366 * @param e {Event} attribute change event
2369 _handleTotalRecordsChange : function (e) {
2370 if (!this.all || (e && e.prevValue === e.newValue)) {
2374 this.all.value = e.newValue;
2375 if (this.all.selected) {
2376 this.paginator.set('rowsPerPage',e.newValue);
2381 * Removes the select node and clears event listeners
2385 destroy : function () {
2386 YAHOO.util.Event.purgeElement(this.select);
2387 this.select.parentNode.removeChild(this.select);
2393 YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.8.0r4", build: "2449"});