]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/paginator/paginator.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / yui / build / paginator / paginator.js
1 /*
2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 2.9.0
6 */
7 (function () {
8 /**
9  * The Paginator widget provides a set of controls to navigate through paged
10  * data.
11  *
12  * @module paginator
13  * @uses YAHOO.util.EventProvider
14  * @uses YAHOO.util.AttributeProvider
15  */
16
17 var Dom        = YAHOO.util.Dom,
18     lang       = YAHOO.lang,
19     isObject   = lang.isObject,
20     isFunction = lang.isFunction,
21     isArray    = lang.isArray,
22     isString   = lang.isString;
23
24 /**
25  * Instantiate a Paginator, passing a configuration object to the contructor.
26  * The configuration object should contain the following properties:
27  * <ul>
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>
31  * </ul>
32  *
33  * @namespace YAHOO.widget
34  * @class Paginator
35  * @constructor
36  * @param config {Object} Object literal to set instance and ui component
37  * configuration.
38  */
39 function Paginator(config) {
40     var UNLIMITED = Paginator.VALUE_UNLIMITED,
41         attrib, initialPage, records, perPage, startIndex;
42
43     config = isObject(config) ? config : {};
44
45     this.initConfig();
46
47     this.initEvents();
48
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);
53     }
54     
55     this.initUIComponents();
56
57     // Update the other config values
58     for (attrib in config) {
59         if (config.hasOwnProperty(attrib)) {
60             this.set(attrib,config[attrib],true);
61         }
62     }
63
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);
72         }
73     }
74 }
75
76
77 // Static members
78 lang.augmentObject(Paginator, {
79     /**
80      * Incrementing index used to give instances unique ids.
81      * @static
82      * @property Paginator.id
83      * @type number
84      * @private
85      */
86     id : 0,
87
88     /**
89      * Base of id strings used for ui components.
90      * @static
91      * @property Paginator.ID_BASE
92      * @type string
93      * @private
94      */
95     ID_BASE : 'yui-pg',
96
97     /**
98      * Used to identify unset, optional configurations, or used explicitly in
99      * the case of totalRecords to indicate unlimited pagination.
100      * @static
101      * @property Paginator.VALUE_UNLIMITED
102      * @type number
103      * @final
104      */
105     VALUE_UNLIMITED : -1,
106
107     /**
108      * Default template used by Paginator instances.  Update this if you want
109      * all new Paginators to use a different default template.
110      * @static
111      * @property Paginator.TEMPLATE_DEFAULT
112      * @type string
113      */
114     TEMPLATE_DEFAULT : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",
115
116     /**
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.
120      * @static
121      * @property Paginator.TEMPLATE_ROWS_PER_PAGE
122      * @type string
123      */
124     TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
125
126     /**
127      * Storage object for UI Components
128      * @static
129      * @property Paginator.ui
130      */
131     ui : {},
132
133     /**
134      * Similar to YAHOO.lang.isNumber, but allows numeric strings.  This is
135      * is used for attribute validation in conjunction with getters that return
136      * numbers.
137      *
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
141      * @static
142      */
143     isNumeric : function (v) {
144         return isFinite(+v);
145     },
146
147     /**
148      * Return a number or null from input
149      *
150      * @method Paginator.toNumber
151      * @param n {Number|String} a number or numeric string
152      * @return Number
153      * @static
154      */
155     toNumber : function (n) {
156         return isFinite(+n) ? +n : null;
157     }
158
159 },true);
160
161
162 // Instance members and methods
163 Paginator.prototype = {
164
165     // Instance members
166
167     /**
168      * Array of nodes in which to render pagination controls.  This is set via
169      * the &quot;containers&quot; attribute.
170      * @property _containers
171      * @type Array(HTMLElement)
172      * @private
173      */
174     _containers : [],
175
176     /**
177      * Flag used to indicate multiple attributes are being updated via setState
178      * @property _batch
179      * @type boolean
180      * @protected
181      */
182     _batch : false,
183
184     /**
185      * Used by setState to indicate when a page change has occurred
186      * @property _pageChanged
187      * @type boolean
188      * @protected
189      */
190     _pageChanged : false,
191
192     /**
193      * Temporary state cache used by setState to keep track of the previous
194      * state for eventual pageChange event firing
195      * @property _state
196      * @type Object
197      * @protected
198      */
199     _state : null,
200
201
202     // Instance methods
203
204     /**
205      * Initialize the Paginator's attributes (see YAHOO.util.Element class
206      * AttributeProvider).
207      * @method initConfig
208      * @private
209      */
210     initConfig : function () {
211
212         var UNLIMITED = Paginator.VALUE_UNLIMITED;
213
214         /**
215          * REQUIRED. Number of records constituting a &quot;page&quot;
216          * @attribute rowsPerPage
217          * @type integer
218          */
219         this.setAttributeConfig('rowsPerPage', {
220             value     : 0,
221             validator : Paginator.isNumeric,
222             setter    : Paginator.toNumber
223         });
224
225         /**
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)}
230          */
231         this.setAttributeConfig('containers', {
232             value     : null,
233             validator : function (val) {
234                 if (!isArray(val)) {
235                     val = [val];
236                 }
237                 for (var i = 0, len = val.length; i < len; ++i) {
238                     if (isString(val[i]) || 
239                         (isObject(val[i]) && val[i].nodeType === 1)) {
240                         continue;
241                     }
242                     return false;
243                 }
244                 return true;
245             },
246             method : function (val) {
247                 val = Dom.get(val);
248                 if (!isArray(val)) {
249                     val = [val];
250                 }
251                 this._containers = val;
252             }
253         });
254
255         /**
256          * Total number of records to paginate through
257          * @attribute totalRecords
258          * @type integer
259          * @default 0
260          */
261         this.setAttributeConfig('totalRecords', {
262             value     : 0,
263             validator : Paginator.isNumeric,
264             setter    : Paginator.toNumber
265         });
266
267         /**
268          * Zero based index of the record considered first on the current page.
269          * For page based interactions, don't modify this attribute directly;
270          * use setPage(n).
271          * @attribute recordOffset
272          * @type integer
273          * @default 0
274          */
275         this.setAttributeConfig('recordOffset', {
276             value     : 0,
277             validator : function (val) {
278                 var total = this.get('totalRecords');
279                 if (Paginator.isNumeric(val)) {
280                     val = +val;
281                     return total === UNLIMITED || total > val ||
282                            (total === 0 && val === 0);
283                 }
284
285                 return false;
286             },
287             setter    : Paginator.toNumber
288         });
289
290         /**
291          * Page to display on initial paint
292          * @attribute initialPage
293          * @type integer
294          * @default 1
295          */
296         this.setAttributeConfig('initialPage', {
297             value     : 1,
298             validator : Paginator.isNumeric,
299             setter    : Paginator.toNumber
300         });
301
302         /**
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
310          * @type string
311          */
312         this.setAttributeConfig('template', {
313             value : Paginator.TEMPLATE_DEFAULT,
314             validator : isString
315         });
316
317         /**
318          * Class assigned to the element(s) containing pagination controls.
319          * @attribute containerClass
320          * @type string
321          * @default 'yui-pg-container'
322          */
323         this.setAttributeConfig('containerClass', {
324             value : 'yui-pg-container',
325             validator : isString
326         });
327
328         /**
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
335          * @type boolean
336          * @default true
337          */
338         this.setAttributeConfig('alwaysVisible', {
339             value : true,
340             validator : lang.isBoolean
341         });
342
343         /**
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
348          * @type boolean
349          * @default false
350          * @deprecated use changeRequest listener that calls setState
351          */
352         this.setAttributeConfig('updateOnChange', {
353             value     : false,
354             validator : lang.isBoolean
355         });
356
357
358
359         // Read only attributes
360
361         /**
362          * Unique id assigned to this instance
363          * @attribute id
364          * @type integer
365          * @final
366          */
367         this.setAttributeConfig('id', {
368             value    : Paginator.id++,
369             readOnly : true
370         });
371
372         /**
373          * Indicator of whether the DOM nodes have been initially created
374          * @attribute rendered
375          * @type boolean
376          * @final
377          */
378         this.setAttributeConfig('rendered', {
379             value    : false,
380             readOnly : true
381         });
382
383     },
384
385     /**
386      * Initialize registered ui components onto this instance.
387      * @method initUIComponents
388      * @private
389      */
390     initUIComponents : function () {
391         var ui = Paginator.ui,
392             name,UIComp;
393         for (name in ui) {
394             if (ui.hasOwnProperty(name)) {
395                 UIComp = ui[name];
396                 if (isObject(UIComp) && isFunction(UIComp.init)) {
397                     UIComp.init(this);
398                 }
399             }
400         }
401     },
402
403     /**
404      * Initialize this instance's CustomEvents.
405      * @method initEvents
406      * @private
407      */
408     initEvents : function () {
409         /**
410          * Event fired when the Paginator is initially rendered
411          * @event render
412          */
413         this.createEvent('render');
414
415         /**
416          * Event fired when the Paginator is initially rendered
417          * @event rendered
418          * @deprecated use render event
419          */
420         this.createEvent('rendered'); // backward compatibility
421
422         /**
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:
428          * <ul>
429          *   <li>paginator - the Paginator instance</li>
430          *   <li>page</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>
436          * </ul>
437          * @event changeRequest
438          */
439         this.createEvent('changeRequest');
440
441         /**
442          * Event fired when attribute changes have resulted in the calculated
443          * current page changing.
444          * @event pageChange
445          */
446         this.createEvent('pageChange');
447
448         /**
449          * Event that fires before the destroy event.
450          * @event beforeDestroy
451          */
452         this.createEvent('beforeDestroy');
453
454         /**
455          * Event used to trigger cleanup of ui components
456          * @event destroy
457          */
458         this.createEvent('destroy');
459
460         this._selfSubscribe();
461     },
462
463     /**
464      * Subscribes to instance attribute change events to automate certain
465      * behaviors.
466      * @method _selfSubscribe
467      * @protected
468      */
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);
473
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);
478
479         // Update recordOffset when totalRecords is reduced below
480         this.subscribe('totalRecordsChange',this._syncRecordOffset,this,true);
481     },
482
483     /**
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
488      * @protected
489      */
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');
495
496                 if (rpp && this.get('recordOffset') >= v) {
497                     state = this.getState({
498                         totalRecords : e.prevValue,
499                         recordOffset : this.get('recordOffset')
500                     });
501
502                     this.set('recordOffset', state.before.recordOffset);
503                     this._firePageChange(state);
504                 }
505             }
506         }
507     },
508
509     /**
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
514      * @protected
515      */
516     _handleStateChange : function (e) {
517         if (e.prevValue !== e.newValue) {
518             var change = this._state || {},
519                 state;
520
521             change[e.type.replace(/Change$/,'')] = e.prevValue;
522             state = this.getState(change);
523
524             if (state.page !== state.before.page) {
525                 if (this._batch) {
526                     this._pageChanged = true;
527                 } else {
528                     this._firePageChange(state);
529                 }
530             }
531         }
532     },
533
534     /**
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)
539      * @protected
540      */
541     _firePageChange : function (state) {
542         if (isObject(state)) {
543             var current = state.before;
544             delete state.before;
545             this.fireEvent('pageChange',{
546                 type      : 'pageChange',
547                 prevValue : state.page,
548                 newValue  : current.page,
549                 prevState : state,
550                 newState  : current
551             });
552         }
553     },
554
555     /**
556      * Render the pagination controls per the format attribute into the
557      * specified container nodes.
558      * @method render
559      * @return the Paginator instance
560      * @chainable
561      */
562     render : function () {
563         if (this.get('rendered')) {
564             return this;
565         }
566
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') + '-',
571             i, len;
572
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);
576         }
577
578         // Show the containers if appropriate
579         this.updateVisibility();
580
581         // Set render attribute manually to support its readOnly contract
582         if (this._containers.length) {
583             this.setAttributeConfig('rendered', { value: true });
584
585             this.fireEvent('render', state);
586             // For backward compatibility
587             this.fireEvent('rendered', state);
588         }
589
590         return this;
591     },
592
593     /**
594      * Creates the individual ui components and renders them into a container.
595      *
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
601      * @protected
602      */
603     _renderTemplate : function (container, template, id_base, hide) {
604         var containerClass = this.get('containerClass'),
605             markers, i, len;
606
607         if (!container) {
608             return;
609         }
610
611         // Hide the container while its contents are rendered
612         Dom.setStyle(container,'display','none');
613
614         Dom.addClass(container, containerClass);
615
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>');
620
621         // Replace each marker with the ui component's render() output
622         markers = Dom.getElementsByClassName('yui-pg-ui','span',container);
623
624         for (i = 0, len = markers.length; i < len; ++i) {
625             this.renderUIComponent(markers[i], id_base);
626         }
627
628         if (!hide) {
629             // Show the container allowing page reflow
630             Dom.setStyle(container,'display','');
631         }
632     },
633
634     /**
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)
638      *
639      * @method renderUIComponent
640      * @param marker {HTMLElement} the marker node to replace
641      * @param id_base {String} string base the component's generated id
642      * @return the Paginator instance
643      * @chainable
644      */
645     renderUIComponent : function (marker, id_base) {
646         var par    = marker.parentNode,
647             name   = /yui-pg-ui-(\w+)/.exec(marker.className),
648             UIComp = name && Paginator.ui[name[1]],
649             comp;
650
651         if (isFunction(UIComp)) {
652             comp = new UIComp(this);
653             if (isFunction(comp.render)) {
654                 par.replaceChild(comp.render(id_base),marker);
655             }
656         }
657
658         return this;
659     },
660
661     /**
662      * Removes controls from the page and unhooks events.
663      * @method destroy
664      */
665     destroy : function () {
666         this.fireEvent('beforeDestroy');
667         this.fireEvent('destroy');
668
669         this.setAttributeConfig('rendered',{value:false});
670         this.unsubscribeAll();
671     },
672
673     /**
674      * Hides the containers if there is only one page of data and attribute
675      * alwaysVisible is false.  Conversely, it displays the containers if either
676      * there is more than one page worth of data or alwaysVisible is turned on.
677      * @method updateVisibility
678      */
679     updateVisibility : function (e) {
680         var alwaysVisible = this.get('alwaysVisible'),
681             totalRecords, visible, rpp, rppOptions, i, len, opt;
682
683         if (!e || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
684             totalRecords = this.get('totalRecords');
685             visible      = true;
686             rpp          = this.get('rowsPerPage');
687             rppOptions   = this.get('rowsPerPageOptions');
688
689             if (isArray(rppOptions)) {
690                 for (i = 0, len = rppOptions.length; i < len; ++i) {
691                     opt = rppOptions[i];
692                     // account for value 'all'
693                     if (lang.isNumber(opt || opt.value)) {
694                         rpp = Math.min(rpp, (opt.value || opt));
695                     }
696                 }
697             }
698
699             if (totalRecords !== Paginator.VALUE_UNLIMITED &&
700                 totalRecords <= rpp) {
701                 visible = false;
702             }
703
704             visible = visible || alwaysVisible;
705
706             for (i = 0, len = this._containers.length; i < len; ++i) {
707                 Dom.setStyle(this._containers[i],'display',
708                     visible ? '' : 'none');
709             }
710         }
711     },
712
713
714
715
716     /**
717      * Get the configured container nodes
718      * @method getContainerNodes
719      * @return {Array} array of HTMLElement nodes
720      */
721     getContainerNodes : function () {
722         return this._containers;
723     },
724
725     /**
726      * Get the total number of pages in the data set according to the current
727      * rowsPerPage and totalRecords values.  If totalRecords is not set, or
728      * set to YAHOO.widget.Paginator.VALUE_UNLIMITED, returns
729      * YAHOO.widget.Paginator.VALUE_UNLIMITED.
730      * @method getTotalPages
731      * @return {number}
732      */
733     getTotalPages : function () {
734         var records = this.get('totalRecords'),
735             perPage = this.get('rowsPerPage');
736
737         // rowsPerPage not set.  Can't calculate
738         if (!perPage) {
739             return null;
740         }
741
742         if (records === Paginator.VALUE_UNLIMITED) {
743             return Paginator.VALUE_UNLIMITED;
744         }
745
746         return Math.ceil(records/perPage);
747     },
748
749     /**
750      * Does the requested page have any records?
751      * @method hasPage
752      * @param page {number} the page in question
753      * @return {boolean}
754      */
755     hasPage : function (page) {
756         if (!lang.isNumber(page) || page < 1) {
757             return false;
758         }
759
760         var totalPages = this.getTotalPages();
761
762         return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
763     },
764
765     /**
766      * Get the page number corresponding to the current record offset.
767      * @method getCurrentPage
768      * @return {number}
769      */
770     getCurrentPage : function () {
771         var perPage = this.get('rowsPerPage');
772         if (!perPage || !this.get('totalRecords')) {
773             return 0;
774         }
775         return Math.floor(this.get('recordOffset') / perPage) + 1;
776     },
777
778     /**
779      * Are there records on the next page?
780      * @method hasNextPage
781      * @return {boolean}
782      */
783     hasNextPage : function () {
784         var currentPage = this.getCurrentPage(),
785             totalPages  = this.getTotalPages();
786
787         return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
788     },
789
790     /**
791      * Get the page number of the next page, or null if the current page is the
792      * last page.
793      * @method getNextPage
794      * @return {number}
795      */
796     getNextPage : function () {
797         return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
798     },
799
800     /**
801      * Is there a page before the current page?
802      * @method hasPreviousPage
803      * @return {boolean}
804      */
805     hasPreviousPage : function () {
806         return (this.getCurrentPage() > 1);
807     },
808
809     /**
810      * Get the page number of the previous page, or null if the current page
811      * is the first page.
812      * @method getPreviousPage
813      * @return {number}
814      */
815     getPreviousPage : function () {
816         return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
817     },
818
819     /**
820      * Get the start and end record indexes of the specified page.
821      * @method getPageRecords
822      * @param page {number} (optional) The page (current page if not specified)
823      * @return {Array} [start_index, end_index]
824      */
825     getPageRecords : function (page) {
826         if (!lang.isNumber(page)) {
827             page = this.getCurrentPage();
828         }
829
830         var perPage = this.get('rowsPerPage'),
831             records = this.get('totalRecords'),
832             start, end;
833
834         if (!page || !perPage) {
835             return null;
836         }
837
838         start = (page - 1) * perPage;
839         if (records !== Paginator.VALUE_UNLIMITED) {
840             if (start >= records) {
841                 return null;
842             }
843             end = Math.min(start + perPage, records) - 1;
844         } else {
845             end = start + perPage - 1;
846         }
847
848         return [start,end];
849     },
850
851     /**
852      * Set the current page to the provided page number if possible.
853      * @method setPage
854      * @param newPage {number} the new page number
855      * @param silent {boolean} whether to forcibly avoid firing the
856      * changeRequest event
857      */
858     setPage : function (page,silent) {
859         if (this.hasPage(page) && page !== this.getCurrentPage()) {
860             if (this.get('updateOnChange') || silent) {
861                 this.set('recordOffset', (page - 1) * this.get('rowsPerPage'));
862             } else {
863                 this.fireEvent('changeRequest',this.getState({'page':page}));
864             }
865         }
866     },
867
868     /**
869      * Get the number of rows per page.
870      * @method getRowsPerPage
871      * @return {number} the current setting of the rowsPerPage attribute
872      */
873     getRowsPerPage : function () {
874         return this.get('rowsPerPage');
875     },
876
877     /**
878      * Set the number of rows per page.
879      * @method setRowsPerPage
880      * @param rpp {number} the new number of rows per page
881      * @param silent {boolean} whether to forcibly avoid firing the
882      * changeRequest event
883      */
884     setRowsPerPage : function (rpp,silent) {
885         if (Paginator.isNumeric(rpp) && +rpp > 0 &&
886             +rpp !== this.get('rowsPerPage')) {
887             if (this.get('updateOnChange') || silent) {
888                 this.set('rowsPerPage',rpp);
889             } else {
890                 this.fireEvent('changeRequest',
891                     this.getState({'rowsPerPage':+rpp}));
892             }
893         }
894     },
895
896     /**
897      * Get the total number of records.
898      * @method getTotalRecords
899      * @return {number} the current setting of totalRecords attribute
900      */
901     getTotalRecords : function () {
902         return this.get('totalRecords');
903     },
904
905     /**
906      * Set the total number of records.
907      * @method setTotalRecords
908      * @param total {number} the new total number of records
909      * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
910      */
911     setTotalRecords : function (total,silent) {
912         if (Paginator.isNumeric(total) && +total >= 0 &&
913             +total !== this.get('totalRecords')) {
914             if (this.get('updateOnChange') || silent) {
915                 this.set('totalRecords',total);
916             } else {
917                 this.fireEvent('changeRequest',
918                     this.getState({'totalRecords':+total}));
919             }
920         }
921     },
922
923     /**
924      * Get the index of the first record on the current page
925      * @method getStartIndex
926      * @return {number} the index of the first record on the current page
927      */
928     getStartIndex : function () {
929         return this.get('recordOffset');
930     },
931
932     /**
933      * Move the record offset to a new starting index.  This will likely cause
934      * the calculated current page to change.  You should probably use setPage.
935      * @method setStartIndex
936      * @param offset {number} the new record offset
937      * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
938      */
939     setStartIndex : function (offset,silent) {
940         if (Paginator.isNumeric(offset) && +offset >= 0 &&
941             +offset !== this.get('recordOffset')) {
942             if (this.get('updateOnChange') || silent) {
943                 this.set('recordOffset',offset);
944             } else {
945                 this.fireEvent('changeRequest',
946                     this.getState({'recordOffset':+offset}));
947             }
948         }
949     },
950
951     /**
952      * Get an object literal describing the current state of the paginator.  If
953      * an object literal of proposed values is passed, the proposed state will
954      * be returned as an object literal with the following keys:
955      * <ul>
956      * <li>paginator - instance of the Paginator</li>
957      * <li>page - number</li>
958      * <li>totalRecords - number</li>
959      * <li>recordOffset - number</li>
960      * <li>rowsPerPage - number</li>
961      * <li>records - [ start_index, end_index ]</li>
962      * <li>before - (OPTIONAL) { state object literal for current state }</li>
963      * </ul>
964      * @method getState
965      * @return {object}
966      * @param changes {object} OPTIONAL object literal with proposed values
967      * Supported change keys include:
968      * <ul>
969      * <li>rowsPerPage</li>
970      * <li>totalRecords</li>
971      * <li>recordOffset OR</li>
972      * <li>page</li>
973      * </ul>
974      */
975     getState : function (changes) {
976         var UNLIMITED = Paginator.VALUE_UNLIMITED,
977             M = Math, max = M.max, ceil = M.ceil,
978             currentState, state, offset;
979
980         function normalizeOffset(offset,total,rpp) {
981             if (offset <= 0 || total === 0) {
982                 return 0;
983             }
984             if (total === UNLIMITED || total > offset) {
985                 return offset - (offset % rpp);
986             }
987             return total - (total % rpp || rpp);
988         }
989
990         currentState = {
991             paginator    : this,
992             totalRecords : this.get('totalRecords'),
993             rowsPerPage  : this.get('rowsPerPage'),
994             records      : this.getPageRecords()
995         };
996         currentState.recordOffset = normalizeOffset(
997                                         this.get('recordOffset'),
998                                         currentState.totalRecords,
999                                         currentState.rowsPerPage);
1000         currentState.page = ceil(currentState.recordOffset /
1001                                  currentState.rowsPerPage) + 1;
1002
1003         if (!changes) {
1004             return currentState;
1005         }
1006
1007         state = {
1008             paginator    : this,
1009             before       : currentState,
1010
1011             rowsPerPage  : changes.rowsPerPage || currentState.rowsPerPage,
1012             totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
1013                                 max(changes.totalRecords,UNLIMITED) :
1014                                 +currentState.totalRecords)
1015         };
1016
1017         if (state.totalRecords === 0) {
1018             state.recordOffset =
1019             state.page         = 0;
1020         } else {
1021             offset = Paginator.isNumeric(changes.page) ?
1022                         (changes.page - 1) * state.rowsPerPage :
1023                         Paginator.isNumeric(changes.recordOffset) ?
1024                             +changes.recordOffset :
1025                             currentState.recordOffset;
1026
1027             state.recordOffset = normalizeOffset(offset,
1028                                     state.totalRecords,
1029                                     state.rowsPerPage);
1030
1031             state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
1032         }
1033
1034         state.records = [ state.recordOffset,
1035                           state.recordOffset + state.rowsPerPage - 1 ];
1036
1037         // limit upper index to totalRecords - 1
1038         if (state.totalRecords !== UNLIMITED &&
1039             state.recordOffset < state.totalRecords && state.records &&
1040             state.records[1] > state.totalRecords - 1) {
1041             state.records[1] = state.totalRecords - 1;
1042         }
1043
1044         return state;
1045     },
1046
1047     /**
1048      * Convenience method to facilitate setting state attributes rowsPerPage,
1049      * totalRecords, recordOffset in batch.  Also supports calculating
1050      * recordOffset from state.page if state.recordOffset is not provided.
1051      * Fires only a single pageChange event, if appropriate.
1052      * This will not fire a changeRequest event.
1053      * @method setState
1054      * @param state {Object} Object literal of attribute:value pairs to set
1055      */
1056     setState : function (state) {
1057         if (isObject(state)) {
1058             // get flux state based on current state with before state as well
1059             this._state = this.getState({});
1060
1061             // use just the state props from the input obj
1062             state = {
1063                 page         : state.page,
1064                 rowsPerPage  : state.rowsPerPage,
1065                 totalRecords : state.totalRecords,
1066                 recordOffset : state.recordOffset
1067             };
1068
1069             // calculate recordOffset from page if recordOffset not specified.
1070             // not using lang.isNumber for support of numeric strings
1071             if (state.page && state.recordOffset === undefined) {
1072                 state.recordOffset = (state.page - 1) *
1073                     (state.rowsPerPage || this.get('rowsPerPage'));
1074             }
1075
1076             this._batch = true;
1077             this._pageChanged = false;
1078
1079             for (var k in state) {
1080                 if (state.hasOwnProperty(k) && this._configs.hasOwnProperty(k)) {
1081                     this.set(k,state[k]);
1082                 }
1083             }
1084
1085             this._batch = false;
1086             
1087             if (this._pageChanged) {
1088                 this._pageChanged = false;
1089
1090                 this._firePageChange(this.getState(this._state));
1091             }
1092         }
1093     }
1094 };
1095
1096 lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
1097
1098 YAHOO.widget.Paginator = Paginator;
1099 })();
1100 (function () {
1101
1102 var Paginator = YAHOO.widget.Paginator,
1103     l         = YAHOO.lang,
1104     setId     = YAHOO.util.Dom.generateId;
1105
1106 /**
1107  * ui Component to generate the textual report of current pagination status.
1108  * E.g. "Now viewing page 1 of 13".
1109  *
1110  * @namespace YAHOO.widget.Paginator.ui
1111  * @class CurrentPageReport
1112  * @for YAHOO.widget.Paginator
1113  *
1114  * @constructor
1115  * @param p {Pagintor} Paginator instance to attach to
1116  */
1117 Paginator.ui.CurrentPageReport = function (p) {
1118     this.paginator = p;
1119
1120     p.subscribe('recordOffsetChange', this.update,this,true);
1121     p.subscribe('rowsPerPageChange', this.update,this,true);
1122     p.subscribe('totalRecordsChange',this.update,this,true);
1123     p.subscribe('pageReportTemplateChange', this.update,this,true);
1124     p.subscribe('destroy',this.destroy,this,true);
1125
1126     //TODO: make this work
1127     p.subscribe('pageReportClassChange', this.update,this,true);
1128 };
1129
1130 /**
1131  * Decorates Paginator instances with new attributes. Called during
1132  * Paginator instantiation.
1133  * @method init
1134  * @param p {Paginator} Paginator instance to decorate
1135  * @static
1136  */
1137 Paginator.ui.CurrentPageReport.init = function (p) {
1138
1139     /**
1140      * CSS class assigned to the span containing the info.
1141      * @attribute pageReportClass
1142      * @default 'yui-pg-current'
1143      */
1144     p.setAttributeConfig('pageReportClass', {
1145         value : 'yui-pg-current',
1146         validator : l.isString
1147     });
1148
1149     /**
1150      * Used as innerHTML for the span.  Place holders in the form of {name}
1151      * will be replaced with the so named value from the key:value map
1152      * generated by the function held in the pageReportValueGenerator attribute.
1153      * @attribute pageReportTemplate
1154      * @default '({currentPage} of {totalPages})'
1155      * @see pageReportValueGenerator attribute
1156      */
1157     p.setAttributeConfig('pageReportTemplate', {
1158         value : '({currentPage} of {totalPages})',
1159         validator : l.isString
1160     });
1161
1162     /**
1163      * Function to generate the value map used to populate the
1164      * pageReportTemplate.  The function is passed the Paginator instance as a
1165      * parameter.  The default function returns a map with the following keys:
1166      * <ul>
1167      * <li>currentPage</li>
1168      * <li>totalPages</li>
1169      * <li>startIndex</li>
1170      * <li>endIndex</li>
1171      * <li>startRecord</li>
1172      * <li>endRecord</li>
1173      * <li>totalRecords</li>
1174      * </ul>
1175      * @attribute pageReportValueGenarator
1176      */
1177     p.setAttributeConfig('pageReportValueGenerator', {
1178         value : function (paginator) {
1179             var curPage = paginator.getCurrentPage(),
1180                 records = paginator.getPageRecords();
1181
1182             return {
1183                 'currentPage' : records ? curPage : 0,
1184                 'totalPages'  : paginator.getTotalPages(),
1185                 'startIndex'  : records ? records[0] : 0,
1186                 'endIndex'    : records ? records[1] : 0,
1187                 'startRecord' : records ? records[0] + 1 : 0,
1188                 'endRecord'   : records ? records[1] + 1 : 0,
1189                 'totalRecords': paginator.get('totalRecords')
1190             };
1191         },
1192         validator : l.isFunction
1193     });
1194 };
1195
1196 /**
1197  * Replace place holders in a string with the named values found in an
1198  * object literal.
1199  * @static
1200  * @method sprintf
1201  * @param template {string} The content string containing place holders
1202  * @param values {object} The key:value pairs used to replace the place holders
1203  * @return {string}
1204  */
1205 Paginator.ui.CurrentPageReport.sprintf = function (template, values) {
1206     return template.replace(/\{([\w\s\-]+)\}/g, function (x,key) {
1207             return (key in values) ? values[key] : '';
1208         });
1209 };
1210
1211 Paginator.ui.CurrentPageReport.prototype = {
1212
1213     /**
1214      * Span node containing the formatted info
1215      * @property span
1216      * @type HTMLElement
1217      * @private
1218      */
1219     span : null,
1220
1221
1222     /**
1223      * Generate the span containing info formatted per the pageReportTemplate
1224      * attribute.
1225      * @method render
1226      * @param id_base {string} used to create unique ids for generated nodes
1227      * @return {HTMLElement}
1228      */
1229     render : function (id_base) {
1230         this.span = document.createElement('span');
1231         this.span.className = this.paginator.get('pageReportClass');
1232         setId(this.span, id_base + '-page-report');
1233         this.update();
1234         
1235         return this.span;
1236     },
1237     
1238     /**
1239      * Regenerate the content of the span if appropriate. Calls
1240      * CurrentPageReport.sprintf with the value of the pageReportTemplate
1241      * attribute and the value map returned from pageReportValueGenerator
1242      * function.
1243      * @method update
1244      * @param e {CustomEvent} The calling change event
1245      */
1246     update : function (e) {
1247         if (e && e.prevValue === e.newValue) {
1248             return;
1249         }
1250
1251         this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
1252             this.paginator.get('pageReportTemplate'),
1253             this.paginator.get('pageReportValueGenerator')(this.paginator));
1254     },
1255
1256     /**
1257      * Removes the link/span node and clears event listeners
1258      * removal.
1259      * @method destroy
1260      * @private
1261      */
1262     destroy : function () {
1263         this.span.parentNode.removeChild(this.span);
1264         this.span = null;
1265     }
1266
1267 };
1268
1269 })();
1270 (function () {
1271
1272 var Paginator = YAHOO.widget.Paginator,
1273     l         = YAHOO.lang,
1274     setId     = YAHOO.util.Dom.generateId;
1275
1276 /**
1277  * ui Component to generate the page links
1278  *
1279  * @namespace YAHOO.widget.Paginator.ui
1280  * @class PageLinks
1281  * @for YAHOO.widget.Paginator
1282  *
1283  * @constructor
1284  * @param p {Pagintor} Paginator instance to attach to
1285  */
1286 Paginator.ui.PageLinks = function (p) {
1287     this.paginator = p;
1288
1289     p.subscribe('recordOffsetChange',this.update,this,true);
1290     p.subscribe('rowsPerPageChange',this.update,this,true);
1291     p.subscribe('totalRecordsChange',this.update,this,true);
1292     p.subscribe('pageLinksChange',   this.rebuild,this,true);
1293     p.subscribe('pageLinkClassChange', this.rebuild,this,true);
1294     p.subscribe('currentPageClassChange', this.rebuild,this,true);
1295     p.subscribe('destroy',this.destroy,this,true);
1296
1297     //TODO: Make this work
1298     p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
1299 };
1300
1301 /**
1302  * Decorates Paginator instances with new attributes. Called during
1303  * Paginator instantiation.
1304  * @method init
1305  * @param p {Paginator} Paginator instance to decorate
1306  * @static
1307  */
1308 Paginator.ui.PageLinks.init = function (p) {
1309
1310     /**
1311      * CSS class assigned to each page link/span.
1312      * @attribute pageLinkClass
1313      * @default 'yui-pg-page'
1314      */
1315     p.setAttributeConfig('pageLinkClass', {
1316         value : 'yui-pg-page',
1317         validator : l.isString
1318     });
1319
1320     /**
1321      * CSS class assigned to the current page span.
1322      * @attribute currentPageClass
1323      * @default 'yui-pg-current-page'
1324      */
1325     p.setAttributeConfig('currentPageClass', {
1326         value : 'yui-pg-current-page',
1327         validator : l.isString
1328     });
1329
1330     /**
1331      * CSS class assigned to the span containing the page links.
1332      * @attribute pageLinksContainerClass
1333      * @default 'yui-pg-pages'
1334      */
1335     p.setAttributeConfig('pageLinksContainerClass', {
1336         value : 'yui-pg-pages',
1337         validator : l.isString
1338     });
1339
1340     /**
1341      * Maximum number of page links to display at one time.
1342      * @attribute pageLinks
1343      * @default 10
1344      */
1345     p.setAttributeConfig('pageLinks', {
1346         value : 10,
1347         validator : Paginator.isNumeric
1348     });
1349
1350     /**
1351      * Function used generate the innerHTML for each page link/span.  The
1352      * function receives as parameters the page number and a reference to the
1353      * paginator object.
1354      * @attribute pageLabelBuilder
1355      * @default function (page, paginator) { return page; }
1356      */
1357     p.setAttributeConfig('pageLabelBuilder', {
1358         value : function (page, paginator) { return page; },
1359         validator : l.isFunction
1360     });
1361
1362     /**
1363      * Function used generate the title for each page link.  The
1364      * function receives as parameters the page number and a reference to the
1365      * paginator object.
1366      * @attribute pageTitleBuilder
1367      * @default function (page, paginator) { return page; }
1368      */
1369     p.setAttributeConfig('pageTitleBuilder', {
1370         value : function (page, paginator) { return "Page " + page; },
1371         validator : l.isFunction
1372     });
1373 };
1374
1375 /**
1376  * Calculates start and end page numbers given a current page, attempting
1377  * to keep the current page in the middle
1378  * @static
1379  * @method calculateRange
1380  * @param {int} currentPage  The current page
1381  * @param {int} totalPages   (optional) Maximum number of pages
1382  * @param {int} numPages     (optional) Preferred number of pages in range
1383  * @return {Array} [start_page_number, end_page_number]
1384  */
1385 Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
1386     var UNLIMITED = Paginator.VALUE_UNLIMITED,
1387         start, end, delta;
1388
1389     // Either has no pages, or unlimited pages.  Show none.
1390     if (!currentPage || numPages === 0 || totalPages === 0 ||
1391         (totalPages === UNLIMITED && numPages === UNLIMITED)) {
1392         return [0,-1];
1393     }
1394
1395     // Limit requested pageLinks if there are fewer totalPages
1396     if (totalPages !== UNLIMITED) {
1397         numPages = numPages === UNLIMITED ?
1398                     totalPages :
1399                     Math.min(numPages,totalPages);
1400     }
1401
1402     // Determine start and end, trying to keep current in the middle
1403     start = Math.max(1,Math.ceil(currentPage - (numPages/2)));
1404     if (totalPages === UNLIMITED) {
1405         end = start + numPages - 1;
1406     } else {
1407         end = Math.min(totalPages, start + numPages - 1);
1408     }
1409
1410     // Adjust the start index when approaching the last page
1411     delta = numPages - (end - start + 1);
1412     start = Math.max(1, start - delta);
1413
1414     return [start,end];
1415 };
1416
1417
1418 Paginator.ui.PageLinks.prototype = {
1419
1420     /**
1421      * Current page
1422      * @property current
1423      * @type number
1424      * @private
1425      */
1426     current     : 0,
1427
1428     /**
1429      * Span node containing the page links
1430      * @property container
1431      * @type HTMLElement
1432      * @private
1433      */
1434     container   : null,
1435
1436
1437     /**
1438      * Generate the nodes and return the container node containing page links
1439      * appropriate to the current pagination state.
1440      * @method render
1441      * @param id_base {string} used to create unique ids for generated nodes
1442      * @return {HTMLElement}
1443      */
1444     render : function (id_base) {
1445         var p = this.paginator;
1446
1447         // Set up container
1448         this.container = document.createElement('span');
1449         setId(this.container, id_base + '-pages');
1450         this.container.className = p.get('pageLinksContainerClass');
1451         YAHOO.util.Event.on(this.container,'click',this.onClick,this,true);
1452
1453         // Call update, flagging a need to rebuild
1454         this.update({newValue : null, rebuild : true});
1455
1456         return this.container;
1457     },
1458
1459     /**
1460      * Update the links if appropriate
1461      * @method update
1462      * @param e {CustomEvent} The calling change event
1463      */
1464     update : function (e) {
1465         if (e && e.prevValue === e.newValue) {
1466             return;
1467         }
1468
1469         var p           = this.paginator,
1470             currentPage = p.getCurrentPage();
1471
1472         // Replace content if there's been a change
1473         if (this.current !== currentPage || !currentPage || e.rebuild) {
1474             var labelBuilder = p.get('pageLabelBuilder'),
1475                 titleBuilder = p.get('pageTitleBuilder'),
1476                 range        = Paginator.ui.PageLinks.calculateRange(
1477                                 currentPage,
1478                                 p.getTotalPages(),
1479                                 p.get('pageLinks')),
1480                 start        = range[0],
1481                 end          = range[1],
1482                 content      = '',
1483                 linkTemplate,i,spanTemplate;
1484
1485             linkTemplate = '<a href="#" class="{class}" page="{page}" title="{title}">{label}</a>';
1486             spanTemplate = '<span class="{class}">{label}</span>';
1487             for (i = start; i <= end; ++i) {
1488
1489                 if (i === currentPage) {
1490                     content += l.substitute(spanTemplate, {
1491                         'class' : p.get('currentPageClass') + ' ' + p.get('pageLinkClass'),
1492                         'label' : labelBuilder(i,p)
1493                     });
1494
1495                 } else {
1496                     content += l.substitute(linkTemplate, {
1497                         'class' : p.get('pageLinkClass'),
1498                         'page'  : i,
1499                         'label' : labelBuilder(i,p),
1500                         'title' : titleBuilder(i,p)
1501                     });
1502                 }
1503             }
1504
1505             this.container.innerHTML = content;
1506         }
1507     },
1508
1509     /**
1510      * Force a rebuild of the page links.
1511      * @method rebuild
1512      * @param e {CustomEvent} The calling change event
1513      */
1514     rebuild     : function (e) {
1515         e.rebuild = true;
1516         this.update(e);
1517     },
1518
1519     /**
1520      * Removes the page links container node and clears event listeners
1521      * @method destroy
1522      * @private
1523      */
1524     destroy : function () {
1525         YAHOO.util.Event.purgeElement(this.container,true);
1526         this.container.parentNode.removeChild(this.container);
1527         this.container = null;
1528     },
1529
1530     /**
1531      * Listener for the container's onclick event.  Looks for qualifying link
1532      * clicks, and pulls the page number from the link's page attribute.
1533      * Sends link's page attribute to the Paginator's setPage method.
1534      * @method onClick
1535      * @param e {DOMEvent} The click event
1536      */
1537     onClick : function (e) {
1538         var t = YAHOO.util.Event.getTarget(e);
1539         if (t && YAHOO.util.Dom.hasClass(t,
1540                         this.paginator.get('pageLinkClass'))) {
1541
1542             YAHOO.util.Event.stopEvent(e);
1543
1544             this.paginator.setPage(parseInt(t.getAttribute('page'),10));
1545         }
1546     }
1547
1548 };
1549
1550 })();
1551 (function () {
1552
1553 var Paginator = YAHOO.widget.Paginator,
1554     l         = YAHOO.lang,
1555     setId     = YAHOO.util.Dom.generateId;
1556
1557 /**
1558  * ui Component to generate the link to jump to the first page.
1559  *
1560  * @namespace YAHOO.widget.Paginator.ui
1561  * @class FirstPageLink
1562  * @for YAHOO.widget.Paginator
1563  *
1564  * @constructor
1565  * @param p {Pagintor} Paginator instance to attach to
1566  */
1567 Paginator.ui.FirstPageLink = function (p) {
1568     this.paginator = p;
1569
1570     p.subscribe('recordOffsetChange',this.update,this,true);
1571     p.subscribe('rowsPerPageChange',this.update,this,true);
1572     p.subscribe('totalRecordsChange',this.update,this,true);
1573     p.subscribe('destroy',this.destroy,this,true);
1574
1575     // TODO: make this work
1576     p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1577     p.subscribe('firstPageLinkClassChange',this.update,this,true);
1578 };
1579
1580 /**
1581  * Decorates Paginator instances with new attributes. Called during
1582  * Paginator instantiation.
1583  * @method init
1584  * @param p {Paginator} Paginator instance to decorate
1585  * @static
1586  */
1587 Paginator.ui.FirstPageLink.init = function (p) {
1588
1589     /**
1590      * Used as innerHTML for the first page link/span.
1591      * @attribute firstPageLinkLabel
1592      * @default '&lt;&lt; first'
1593      */
1594     p.setAttributeConfig('firstPageLinkLabel', {
1595         value : '&lt;&lt; first',
1596         validator : l.isString
1597     });
1598
1599     /**
1600      * CSS class assigned to the link/span
1601      * @attribute firstPageLinkClass
1602      * @default 'yui-pg-first'
1603      */
1604     p.setAttributeConfig('firstPageLinkClass', {
1605         value : 'yui-pg-first',
1606         validator : l.isString
1607     });
1608
1609     /**
1610      * Used as title for the first page link.
1611      * @attribute firstPageLinkTitle
1612      * @default 'First Page'
1613      */
1614     p.setAttributeConfig('firstPageLinkTitle', {
1615         value : 'First Page',
1616         validator : l.isString
1617     });
1618 };
1619
1620 // Instance members and methods
1621 Paginator.ui.FirstPageLink.prototype = {
1622
1623     /**
1624      * The currently placed HTMLElement node
1625      * @property current
1626      * @type HTMLElement
1627      * @private
1628      */
1629     current   : null,
1630
1631     /**
1632      * Link node
1633      * @property link
1634      * @type HTMLElement
1635      * @private
1636      */
1637     link      : null,
1638
1639     /**
1640      * Span node (inactive link)
1641      * @property span
1642      * @type HTMLElement
1643      * @private
1644      */
1645     span      : null,
1646
1647     /**
1648      * Generate the nodes and return the appropriate node given the current
1649      * pagination state.
1650      * @method render
1651      * @param id_base {string} used to create unique ids for generated nodes
1652      * @return {HTMLElement}
1653      */
1654     render : function (id_base) {
1655         var p     = this.paginator,
1656             c     = p.get('firstPageLinkClass'),
1657             label = p.get('firstPageLinkLabel'),
1658             title = p.get('firstPageLinkTitle');
1659
1660         this.link     = document.createElement('a');
1661         this.span     = document.createElement('span');
1662
1663         setId(this.link, id_base + '-first-link');
1664         this.link.href      = '#';
1665         this.link.className = c;
1666         this.link.innerHTML = label;
1667         this.link.title     = title;
1668         YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1669
1670         setId(this.span, id_base + '-first-span');
1671         this.span.className = c;
1672         this.span.innerHTML = label;
1673
1674         this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1675         return this.current;
1676     },
1677
1678     /**
1679      * Swap the link and span nodes if appropriate.
1680      * @method update
1681      * @param e {CustomEvent} The calling change event
1682      */
1683     update : function (e) {
1684         if (e && e.prevValue === e.newValue) {
1685             return;
1686         }
1687
1688         var par = this.current ? this.current.parentNode : null;
1689         if (this.paginator.getCurrentPage() > 1) {
1690             if (par && this.current === this.span) {
1691                 par.replaceChild(this.link,this.current);
1692                 this.current = this.link;
1693             }
1694         } else {
1695             if (par && this.current === this.link) {
1696                 par.replaceChild(this.span,this.current);
1697                 this.current = this.span;
1698             }
1699         }
1700     },
1701
1702     /**
1703      * Removes the link/span node and clears event listeners
1704      * removal.
1705      * @method destroy
1706      * @private
1707      */
1708     destroy : function () {
1709         YAHOO.util.Event.purgeElement(this.link);
1710         this.current.parentNode.removeChild(this.current);
1711         this.link = this.span = null;
1712     },
1713
1714     /**
1715      * Listener for the link's onclick event.  Pass new value to setPage method.
1716      * @method onClick
1717      * @param e {DOMEvent} The click event
1718      */
1719     onClick : function (e) {
1720         YAHOO.util.Event.stopEvent(e);
1721         this.paginator.setPage(1);
1722     }
1723 };
1724
1725 })();
1726 (function () {
1727
1728 var Paginator = YAHOO.widget.Paginator,
1729     l         = YAHOO.lang,
1730     setId     = YAHOO.util.Dom.generateId;
1731
1732 /**
1733  * ui Component to generate the link to jump to the last page.
1734  *
1735  * @namespace YAHOO.widget.Paginator.ui
1736  * @class LastPageLink
1737  * @for YAHOO.widget.Paginator
1738  *
1739  * @constructor
1740  * @param p {Pagintor} Paginator instance to attach to
1741  */
1742 Paginator.ui.LastPageLink = function (p) {
1743     this.paginator = p;
1744
1745     p.subscribe('recordOffsetChange',this.update,this,true);
1746     p.subscribe('rowsPerPageChange',this.update,this,true);
1747     p.subscribe('totalRecordsChange',this.update,this,true);
1748     p.subscribe('destroy',this.destroy,this,true);
1749
1750     // TODO: make this work
1751     p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1752     p.subscribe('lastPageLinkClassChange', this.update,this,true);
1753 };
1754
1755 /**
1756  * Decorates Paginator instances with new attributes. Called during
1757  * Paginator instantiation.
1758  * @method init
1759  * @param paginator {Paginator} Paginator instance to decorate
1760  * @static
1761  */
1762 Paginator.ui.LastPageLink.init = function (p) {
1763
1764     /**
1765      * Used as innerHTML for the last page link/span.
1766      * @attribute lastPageLinkLabel
1767      * @default 'last &gt;&gt;'
1768      */
1769     p.setAttributeConfig('lastPageLinkLabel', {
1770         value : 'last &gt;&gt;',
1771         validator : l.isString
1772     });
1773
1774     /**
1775      * CSS class assigned to the link/span
1776      * @attribute lastPageLinkClass
1777      * @default 'yui-pg-last'
1778      */
1779     p.setAttributeConfig('lastPageLinkClass', {
1780         value : 'yui-pg-last',
1781         validator : l.isString
1782     });
1783
1784    /**
1785      * Used as title for the last page link.
1786      * @attribute lastPageLinkTitle
1787      * @default 'Last Page'
1788      */
1789     p.setAttributeConfig('lastPageLinkTitle', {
1790         value : 'Last Page',
1791         validator : l.isString
1792     });
1793
1794 };
1795
1796 Paginator.ui.LastPageLink.prototype = {
1797
1798     /**
1799      * Currently placed HTMLElement node
1800      * @property current
1801      * @type HTMLElement
1802      * @private
1803      */
1804     current   : null,
1805
1806     /**
1807      * Link HTMLElement node
1808      * @property link
1809      * @type HTMLElement
1810      * @private
1811      */
1812     link      : null,
1813
1814     /**
1815      * Span node (inactive link)
1816      * @property span
1817      * @type HTMLElement
1818      * @private
1819      */
1820     span      : null,
1821
1822     /**
1823      * Empty place holder node for when the last page link is inappropriate to
1824      * display in any form (unlimited paging).
1825      * @property na
1826      * @type HTMLElement
1827      * @private
1828      */
1829     na        : null,
1830
1831
1832     /**
1833      * Generate the nodes and return the appropriate node given the current
1834      * pagination state.
1835      * @method render
1836      * @param id_base {string} used to create unique ids for generated nodes
1837      * @return {HTMLElement}
1838      */
1839     render : function (id_base) {
1840         var p     = this.paginator,
1841             c     = p.get('lastPageLinkClass'),
1842             label = p.get('lastPageLinkLabel'),
1843             last  = p.getTotalPages(),
1844             title = p.get('lastPageLinkTitle');
1845
1846         this.link = document.createElement('a');
1847         this.span = document.createElement('span');
1848         this.na   = this.span.cloneNode(false);
1849
1850         setId(this.link, id_base + '-last-link');
1851         this.link.href      = '#';
1852         this.link.className = c;
1853         this.link.innerHTML = label;
1854         this.link.title     = title;
1855         YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1856
1857         setId(this.span, id_base + '-last-span');
1858         this.span.className = c;
1859         this.span.innerHTML = label;
1860
1861         setId(this.na, id_base + '-last-na');
1862
1863         switch (last) {
1864             case Paginator.VALUE_UNLIMITED :
1865                     this.current = this.na; break;
1866             case p.getCurrentPage() :
1867                     this.current = this.span; break;
1868             default :
1869                     this.current = this.link;
1870         }
1871
1872         return this.current;
1873     },
1874
1875     /**
1876      * Swap the link, span, and na nodes if appropriate.
1877      * @method update
1878      * @param e {CustomEvent} The calling change event (ignored)
1879      */
1880     update : function (e) {
1881         if (e && e.prevValue === e.newValue) {
1882             return;
1883         }
1884
1885         var par   = this.current ? this.current.parentNode : null,
1886             after = this.link;
1887
1888         if (par) {
1889             switch (this.paginator.getTotalPages()) {
1890                 case Paginator.VALUE_UNLIMITED :
1891                         after = this.na; break;
1892                 case this.paginator.getCurrentPage() :
1893                         after = this.span; break;
1894             }
1895
1896             if (this.current !== after) {
1897                 par.replaceChild(after,this.current);
1898                 this.current = after;
1899             }
1900         }
1901     },
1902
1903     /**
1904      * Removes the link/span node and clears event listeners
1905      * @method destroy
1906      * @private
1907      */
1908     destroy : function () {
1909         YAHOO.util.Event.purgeElement(this.link);
1910         this.current.parentNode.removeChild(this.current);
1911         this.link = this.span = null;
1912     },
1913
1914     /**
1915      * Listener for the link's onclick event.  Passes to setPage method.
1916      * @method onClick
1917      * @param e {DOMEvent} The click event
1918      */
1919     onClick : function (e) {
1920         YAHOO.util.Event.stopEvent(e);
1921         this.paginator.setPage(this.paginator.getTotalPages());
1922     }
1923 };
1924
1925 })();
1926 (function () {
1927
1928 var Paginator = YAHOO.widget.Paginator,
1929     l         = YAHOO.lang,
1930     setId     = YAHOO.util.Dom.generateId;
1931
1932 /**
1933  * ui Component to generate the link to jump to the next page.
1934  *
1935  * @namespace YAHOO.widget.Paginator.ui
1936  * @class NextPageLink
1937  * @for YAHOO.widget.Paginator
1938  *
1939  * @constructor
1940  * @param p {Pagintor} Paginator instance to attach to
1941  */
1942 Paginator.ui.NextPageLink = function (p) {
1943     this.paginator = p;
1944
1945     p.subscribe('recordOffsetChange', this.update,this,true);
1946     p.subscribe('rowsPerPageChange', this.update,this,true);
1947     p.subscribe('totalRecordsChange', this.update,this,true);
1948     p.subscribe('destroy',this.destroy,this,true);
1949
1950     // TODO: make this work
1951     p.subscribe('nextPageLinkLabelChange', this.update,this,true);
1952     p.subscribe('nextPageLinkClassChange', this.update,this,true);
1953 };
1954
1955 /**
1956  * Decorates Paginator instances with new attributes. Called during
1957  * Paginator instantiation.
1958  * @method init
1959  * @param p {Paginator} Paginator instance to decorate
1960  * @static
1961  */
1962 Paginator.ui.NextPageLink.init = function (p) {
1963
1964     /**
1965      * Used as innerHTML for the next page link/span.
1966      * @attribute nextPageLinkLabel
1967      * @default 'next &gt;'
1968      */
1969     p.setAttributeConfig('nextPageLinkLabel', {
1970         value : 'next &gt;',
1971         validator : l.isString
1972     });
1973
1974     /**
1975      * CSS class assigned to the link/span
1976      * @attribute nextPageLinkClass
1977      * @default 'yui-pg-next'
1978      */
1979     p.setAttributeConfig('nextPageLinkClass', {
1980         value : 'yui-pg-next',
1981         validator : l.isString
1982     });
1983
1984     /**
1985      * Used as title for the next page link.
1986      * @attribute nextPageLinkTitle
1987      * @default 'Next Page'
1988      */
1989     p.setAttributeConfig('nextPageLinkTitle', {
1990         value : 'Next Page',
1991         validator : l.isString
1992     });
1993
1994 };
1995
1996 Paginator.ui.NextPageLink.prototype = {
1997
1998     /**
1999      * Currently placed HTMLElement node
2000      * @property current
2001      * @type HTMLElement
2002      * @private
2003      */
2004     current   : null,
2005
2006     /**
2007      * Link node
2008      * @property link
2009      * @type HTMLElement
2010      * @private
2011      */
2012     link      : null,
2013
2014     /**
2015      * Span node (inactive link)
2016      * @property span
2017      * @type HTMLElement
2018      * @private
2019      */
2020     span      : null,
2021
2022
2023     /**
2024      * Generate the nodes and return the appropriate node given the current
2025      * pagination state.
2026      * @method render
2027      * @param id_base {string} used to create unique ids for generated nodes
2028      * @return {HTMLElement}
2029      */
2030     render : function (id_base) {
2031         var p     = this.paginator,
2032             c     = p.get('nextPageLinkClass'),
2033             label = p.get('nextPageLinkLabel'),
2034             last  = p.getTotalPages(),
2035             title = p.get('nextPageLinkTitle');
2036
2037         this.link     = document.createElement('a');
2038         this.span     = document.createElement('span');
2039
2040         setId(this.link, id_base + '-next-link');
2041         this.link.href      = '#';
2042         this.link.className = c;
2043         this.link.innerHTML = label;
2044         this.link.title     = title;
2045         YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
2046
2047         setId(this.span, id_base + '-next-span');
2048         this.span.className = c;
2049         this.span.innerHTML = label;
2050
2051         this.current = p.getCurrentPage() === last ? this.span : this.link;
2052
2053         return this.current;
2054     },
2055
2056     /**
2057      * Swap the link and span nodes if appropriate.
2058      * @method update
2059      * @param e {CustomEvent} The calling change event
2060      */
2061     update : function (e) {
2062         if (e && e.prevValue === e.newValue) {
2063             return;
2064         }
2065
2066         var last = this.paginator.getTotalPages(),
2067             par  = this.current ? this.current.parentNode : null;
2068
2069         if (this.paginator.getCurrentPage() !== last) {
2070             if (par && this.current === this.span) {
2071                 par.replaceChild(this.link,this.current);
2072                 this.current = this.link;
2073             }
2074         } else if (this.current === this.link) {
2075             if (par) {
2076                 par.replaceChild(this.span,this.current);
2077                 this.current = this.span;
2078             }
2079         }
2080     },
2081
2082     /**
2083      * Removes the link/span node and clears event listeners
2084      * @method destroy
2085      * @private
2086      */
2087     destroy : function () {
2088         YAHOO.util.Event.purgeElement(this.link);
2089         this.current.parentNode.removeChild(this.current);
2090         this.link = this.span = null;
2091     },
2092
2093     /**
2094      * Listener for the link's onclick event.  Passes to setPage method.
2095      * @method onClick
2096      * @param e {DOMEvent} The click event
2097      */
2098     onClick : function (e) {
2099         YAHOO.util.Event.stopEvent(e);
2100         this.paginator.setPage(this.paginator.getNextPage());
2101     }
2102 };
2103
2104 })();
2105 (function () {
2106
2107 var Paginator = YAHOO.widget.Paginator,
2108     l         = YAHOO.lang,
2109     setId     = YAHOO.util.Dom.generateId;
2110
2111 /**
2112  * ui Component to generate the link to jump to the previous page.
2113  *
2114  * @namespace YAHOO.widget.Paginator.ui
2115  * @class PreviousPageLink
2116  * @for YAHOO.widget.Paginator
2117  *
2118  * @constructor
2119  * @param p {Pagintor} Paginator instance to attach to
2120  */
2121 Paginator.ui.PreviousPageLink = function (p) {
2122     this.paginator = p;
2123
2124     p.subscribe('recordOffsetChange',this.update,this,true);
2125     p.subscribe('rowsPerPageChange',this.update,this,true);
2126     p.subscribe('totalRecordsChange',this.update,this,true);
2127     p.subscribe('destroy',this.destroy,this,true);
2128
2129     // TODO: make this work
2130     p.subscribe('previousPageLinkLabelChange',this.update,this,true);
2131     p.subscribe('previousPageLinkClassChange',this.update,this,true);
2132 };
2133
2134 /**
2135  * Decorates Paginator instances with new attributes. Called during
2136  * Paginator instantiation.
2137  * @method init
2138  * @param p {Paginator} Paginator instance to decorate
2139  * @static
2140  */
2141 Paginator.ui.PreviousPageLink.init = function (p) {
2142
2143     /**
2144      * Used as innerHTML for the previous page link/span.
2145      * @attribute previousPageLinkLabel
2146      * @default '&lt; prev'
2147      */
2148     p.setAttributeConfig('previousPageLinkLabel', {
2149         value : '&lt; prev',
2150         validator : l.isString
2151     });
2152
2153     /**
2154      * CSS class assigned to the link/span
2155      * @attribute previousPageLinkClass
2156      * @default 'yui-pg-previous'
2157      */
2158     p.setAttributeConfig('previousPageLinkClass', {
2159         value : 'yui-pg-previous',
2160         validator : l.isString
2161     });
2162
2163     /**
2164      * Used as title for the previous page link.
2165      * @attribute previousPageLinkTitle
2166      * @default 'Previous Page'
2167      */
2168     p.setAttributeConfig('previousPageLinkTitle', {
2169         value : 'Previous Page',
2170         validator : l.isString
2171     });
2172
2173 };
2174
2175 Paginator.ui.PreviousPageLink.prototype = {
2176
2177     /**
2178      * Currently placed HTMLElement node
2179      * @property current
2180      * @type HTMLElement
2181      * @private
2182      */
2183     current   : null,
2184
2185     /**
2186      * Link node
2187      * @property link
2188      * @type HTMLElement
2189      * @private
2190      */
2191     link      : null,
2192
2193     /**
2194      * Span node (inactive link)
2195      * @property span
2196      * @type HTMLElement
2197      * @private
2198      */
2199     span      : null,
2200
2201
2202     /**
2203      * Generate the nodes and return the appropriate node given the current
2204      * pagination state.
2205      * @method render
2206      * @param id_base {string} used to create unique ids for generated nodes
2207      * @return {HTMLElement}
2208      */
2209     render : function (id_base) {
2210         var p     = this.paginator,
2211             c     = p.get('previousPageLinkClass'),
2212             label = p.get('previousPageLinkLabel'),
2213             title = p.get('previousPageLinkTitle');
2214
2215         this.link     = document.createElement('a');
2216         this.span     = document.createElement('span');
2217
2218         setId(this.link, id_base + '-prev-link');
2219         this.link.href      = '#';
2220         this.link.className = c;
2221         this.link.innerHTML = label;
2222         this.link.title     = title;
2223         YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
2224
2225         setId(this.span, id_base + '-prev-span');
2226         this.span.className = c;
2227         this.span.innerHTML = label;
2228
2229         this.current = p.getCurrentPage() > 1 ? this.link : this.span;
2230         return this.current;
2231     },
2232
2233     /**
2234      * Swap the link and span nodes if appropriate.
2235      * @method update
2236      * @param e {CustomEvent} The calling change event
2237      */
2238     update : function (e) {
2239         if (e && e.prevValue === e.newValue) {
2240             return;
2241         }
2242
2243         var par = this.current ? this.current.parentNode : null;
2244         if (this.paginator.getCurrentPage() > 1) {
2245             if (par && this.current === this.span) {
2246                 par.replaceChild(this.link,this.current);
2247                 this.current = this.link;
2248             }
2249         } else {
2250             if (par && this.current === this.link) {
2251                 par.replaceChild(this.span,this.current);
2252                 this.current = this.span;
2253             }
2254         }
2255     },
2256
2257     /**
2258      * Removes the link/span node and clears event listeners
2259      * @method destroy
2260      * @private
2261      */
2262     destroy : function () {
2263         YAHOO.util.Event.purgeElement(this.link);
2264         this.current.parentNode.removeChild(this.current);
2265         this.link = this.span = null;
2266     },
2267
2268     /**
2269      * Listener for the link's onclick event.  Passes to setPage method.
2270      * @method onClick
2271      * @param e {DOMEvent} The click event
2272      */
2273     onClick : function (e) {
2274         YAHOO.util.Event.stopEvent(e);
2275         this.paginator.setPage(this.paginator.getPreviousPage());
2276     }
2277 };
2278
2279 })();
2280 (function () {
2281
2282 var Paginator = YAHOO.widget.Paginator,
2283     l         = YAHOO.lang,
2284     setId     = YAHOO.util.Dom.generateId;
2285
2286 /**
2287  * ui Component to generate the rows-per-page dropdown
2288  *
2289  * @namespace YAHOO.widget.Paginator.ui
2290  * @class RowsPerPageDropdown
2291  * @for YAHOO.widget.Paginator
2292  *
2293  * @constructor
2294  * @param p {Pagintor} Paginator instance to attach to
2295  */
2296 Paginator.ui.RowsPerPageDropdown = function (p) {
2297     this.paginator = p;
2298
2299     p.subscribe('rowsPerPageChange',this.update,this,true);
2300     p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
2301     p.subscribe('totalRecordsChange',this._handleTotalRecordsChange,this,true);
2302     p.subscribe('destroy',this.destroy,this,true);
2303
2304     // TODO: make this work
2305     p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
2306 };
2307
2308 /**
2309  * Decorates Paginator instances with new attributes. Called during
2310  * Paginator instantiation.
2311  * @method init
2312  * @param p {Paginator} Paginator instance to decorate
2313  * @static
2314  */
2315 Paginator.ui.RowsPerPageDropdown.init = function (p) {
2316
2317     /**
2318      * Array of available rows-per-page sizes.  Converted into select options.
2319      * Array values may be positive integers or object literals in the form<br>
2320      * { value : NUMBER, text : STRING }
2321      * @attribute rowsPerPageOptions
2322      * @default []
2323      */
2324     p.setAttributeConfig('rowsPerPageOptions', {
2325         value : [],
2326         validator : l.isArray
2327     });
2328
2329     /**
2330      * CSS class assigned to the select node
2331      * @attribute rowsPerPageDropdownClass
2332      * @default 'yui-pg-rpp-options'
2333      */
2334     p.setAttributeConfig('rowsPerPageDropdownClass', {
2335         value : 'yui-pg-rpp-options',
2336         validator : l.isString
2337     });
2338 };
2339
2340 Paginator.ui.RowsPerPageDropdown.prototype = {
2341
2342     /**
2343      * select node
2344      * @property select
2345      * @type HTMLElement
2346      * @private
2347      */
2348     select  : null,
2349
2350
2351     /**
2352      * option node for the optional All value
2353      *
2354      * @property all
2355      * @type HTMLElement
2356      * @protected
2357      */
2358     all : null,
2359
2360     /**
2361      * Generate the select and option nodes and returns the select node.
2362      * @method render
2363      * @param id_base {string} used to create unique ids for generated nodes
2364      * @return {HTMLElement}
2365      */
2366     render : function (id_base) {
2367         this.select = document.createElement('select');
2368         setId(this.select, id_base + '-rpp');
2369         this.select.className = this.paginator.get('rowsPerPageDropdownClass');
2370         this.select.title = 'Rows per page';
2371
2372         YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2373
2374         this.rebuild();
2375
2376         return this.select;
2377     },
2378
2379     /**
2380      * (Re)generate the select options.
2381      * @method rebuild
2382      */
2383     rebuild : function (e) {
2384         var p       = this.paginator,
2385             sel     = this.select,
2386             options = p.get('rowsPerPageOptions'),
2387             opt,cfg,val,i,len;
2388
2389         this.all = null;
2390
2391         for (i = 0, len = options.length; i < len; ++i) {
2392             cfg = options[i];
2393             opt = sel.options[i] ||
2394                   sel.appendChild(document.createElement('option'));
2395             val = l.isValue(cfg.value) ? cfg.value : cfg;
2396             opt.text = l.isValue(cfg.text) ? cfg.text : cfg;
2397
2398             if (l.isString(val) && val.toLowerCase() === 'all') {
2399                 this.all  = opt;
2400                 opt.value = p.get('totalRecords');
2401             } else{
2402                 opt.value = val;
2403             }
2404
2405         }
2406
2407         while (sel.options.length > options.length) {
2408             sel.removeChild(sel.firstChild);
2409         }
2410
2411         this.update();
2412     },
2413
2414     /**
2415      * Select the appropriate option if changed.
2416      * @method update
2417      * @param e {CustomEvent} The calling change event
2418      */
2419     update : function (e) {
2420         if (e && e.prevValue === e.newValue) {
2421             return;
2422         }
2423
2424         var rpp     = this.paginator.get('rowsPerPage')+'',
2425             options = this.select.options,
2426             i,len;
2427
2428         for (i = 0, len = options.length; i < len; ++i) {
2429             if (options[i].value === rpp) {
2430                 options[i].selected = true;
2431                 break;
2432             }
2433         }
2434     },
2435
2436     /**
2437      * Listener for the select's onchange event.  Sent to setRowsPerPage method.
2438      * @method onChange
2439      * @param e {DOMEvent} The change event
2440      */
2441     onChange : function (e) {
2442         this.paginator.setRowsPerPage(
2443                 parseInt(this.select.options[this.select.selectedIndex].value,10));
2444     },
2445
2446     /**
2447      * Updates the all option value (and Paginator's rowsPerPage attribute if
2448      * necessary) in response to a change in the Paginator's totalRecords.
2449      *
2450      * @method _handleTotalRecordsChange
2451      * @param e {Event} attribute change event
2452      * @protected
2453      */
2454     _handleTotalRecordsChange : function (e) {
2455         if (!this.all || (e && e.prevValue === e.newValue)) {
2456             return;
2457         }
2458
2459         this.all.value = e.newValue;
2460         if (this.all.selected) {
2461             this.paginator.set('rowsPerPage',e.newValue);
2462         }
2463     },
2464
2465     /**
2466      * Removes the select node and clears event listeners
2467      * @method destroy
2468      * @private
2469      */
2470     destroy : function () {
2471         YAHOO.util.Event.purgeElement(this.select);
2472         this.select.parentNode.removeChild(this.select);
2473         this.select = null;
2474     }
2475 };
2476
2477 })();
2478 (function () {
2479
2480 var Paginator = YAHOO.widget.Paginator,
2481     l         = YAHOO.lang,
2482     setId     = YAHOO.util.Dom.generateId;
2483
2484 /**
2485  * ui Component to generate the jump-to-page dropdown
2486  *
2487  * @namespace YAHOO.widget.Paginator.ui
2488  * @class JumpToPageDropdown
2489  * @for YAHOO.widget.Paginator
2490  *
2491  * @constructor
2492  * @param p {Pagintor} Paginator instance to attach to
2493  */
2494 Paginator.ui.JumpToPageDropdown = function (p) {
2495     this.paginator = p;
2496
2497     p.subscribe('rowsPerPageChange',this.rebuild,this,true);
2498     p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
2499     p.subscribe('pageChange',this.update,this,true);
2500     p.subscribe('totalRecordsChange',this.rebuild,this,true);
2501     p.subscribe('destroy',this.destroy,this,true);
2502
2503 };
2504
2505 /**
2506  * Decorates Paginator instances with new attributes. Called during
2507  * Paginator instantiation.
2508  * @method init
2509  * @param p {Paginator} Paginator instance to decorate
2510  * @static
2511  */
2512 Paginator.ui.JumpToPageDropdown.init = function (p) {
2513
2514
2515
2516     /**
2517      * CSS class assigned to the select node
2518      * @attribute jumpToPageDropdownClass
2519      * @default 'yui-pg-jtp-options'
2520      */
2521     p.setAttributeConfig('jumpToPageDropdownClass', {
2522         value : 'yui-pg-jtp-options',
2523         validator : l.isString
2524     });
2525 };
2526
2527 Paginator.ui.JumpToPageDropdown.prototype = {
2528
2529     /**
2530      * select node
2531      * @property select
2532      * @type HTMLElement
2533      * @private
2534      */
2535     select  : null,
2536
2537
2538
2539     /**
2540      * Generate the select and option nodes and returns the select node.
2541      * @method render
2542      * @param id_base {string} used to create unique ids for generated nodes
2543      * @return {HTMLElement}
2544      */
2545     render : function (id_base) {
2546         this.select = document.createElement('select');
2547         setId(this.select, id_base + '-jtp');
2548         this.select.className = this.paginator.get('jumpToPageDropdownClass');
2549         this.select.title = 'Jump to page';
2550
2551         YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2552
2553         this.rebuild();
2554
2555         return this.select;
2556     },
2557
2558     /**
2559      * (Re)generate the select options.
2560      * @method rebuild
2561      */
2562     rebuild : function (e) {
2563         var p       = this.paginator,
2564             sel     = this.select,
2565             numPages = p.getTotalPages(),
2566             opt,i,len;
2567
2568         this.all = null;
2569
2570         for (i = 0, len = numPages; i < len; ++i ) {
2571             opt = sel.options[i] ||
2572                   sel.appendChild(document.createElement('option'));
2573
2574             opt.innerHTML = i + 1;
2575
2576             opt.value = i + 1;
2577
2578
2579         }
2580
2581         for ( i = numPages, len = sel.options.length ; i < len ; i++ ) {
2582             sel.removeChild(sel.lastChild);
2583         }
2584
2585         this.update();
2586     },
2587
2588     /**
2589      * Select the appropriate option if changed.
2590      * @method update
2591      * @param e {CustomEvent} The calling change event
2592      */
2593     update : function (e) {
2594
2595         if (e && e.prevValue === e.newValue) {
2596             return;
2597         }
2598
2599         var cp      = this.paginator.getCurrentPage()+'',
2600             options = this.select.options,
2601             i,len;
2602
2603         for (i = 0, len = options.length; i < len; ++i) {
2604             if (options[i].value === cp) {
2605                 options[i].selected = true;
2606                 break;
2607             }
2608         }
2609     },
2610
2611     /**
2612      * Listener for the select's onchange event.  Sent to setPage method.
2613      * @method onChange
2614      * @param e {DOMEvent} The change event
2615      */
2616     onChange : function (e) {
2617         this.paginator.setPage(
2618                 parseInt(this.select.options[this.select.selectedIndex].value,false));
2619     },
2620
2621
2622
2623     /**
2624      * Removes the select node and clears event listeners
2625      * @method destroy
2626      * @private
2627      */
2628     destroy : function () {
2629         YAHOO.util.Event.purgeElement(this.select);
2630         this.select.parentNode.removeChild(this.select);
2631         this.select = null;
2632     }
2633 };
2634
2635 })();
2636 YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.9.0", build: "2800"});