]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/paginator/paginator.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / paginator / paginator.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
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      */
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]],
647             comp;
648
649         if (isFunction(UIComp)) {
650             comp = new UIComp(this);
651             if (isFunction(comp.render)) {
652                 par.replaceChild(comp.render(id_base),marker);
653             }
654         }
655     },
656
657     /**
658      * Removes controls from the page and unhooks events.
659      * @method destroy
660      */
661     destroy : function () {
662         this.fireEvent('beforeDestroy');
663         this.fireEvent('destroy');
664
665         this.setAttributeConfig('rendered',{value:false});
666         this.unsubscribeAll();
667     },
668
669     /**
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
674      */
675     updateVisibility : function (e) {
676         var alwaysVisible = this.get('alwaysVisible'),
677             totalRecords,visible,rpp,rppOptions,i,len;
678
679         if (!e || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
680             totalRecords = this.get('totalRecords');
681             visible      = true;
682             rpp          = this.get('rowsPerPage');
683             rppOptions   = this.get('rowsPerPageOptions');
684
685             if (isArray(rppOptions)) {
686                 for (i = 0, len = rppOptions.length; i < len; ++i) {
687                     rpp = Math.min(rpp,rppOptions[i]);
688                 }
689             }
690
691             if (totalRecords !== Paginator.VALUE_UNLIMITED &&
692                 totalRecords <= rpp) {
693                 visible = false;
694             }
695
696             visible = visible || alwaysVisible;
697
698             for (i = 0, len = this._containers.length; i < len; ++i) {
699                 Dom.setStyle(this._containers[i],'display',
700                     visible ? '' : 'none');
701             }
702         }
703     },
704
705
706
707
708     /**
709      * Get the configured container nodes
710      * @method getContainerNodes
711      * @return {Array} array of HTMLElement nodes
712      */
713     getContainerNodes : function () {
714         return this._containers;
715     },
716
717     /**
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
723      * @return {number}
724      */
725     getTotalPages : function () {
726         var records = this.get('totalRecords'),
727             perPage = this.get('rowsPerPage');
728
729         // rowsPerPage not set.  Can't calculate
730         if (!perPage) {
731             return null;
732         }
733
734         if (records === Paginator.VALUE_UNLIMITED) {
735             return Paginator.VALUE_UNLIMITED;
736         }
737
738         return Math.ceil(records/perPage);
739     },
740
741     /**
742      * Does the requested page have any records?
743      * @method hasPage
744      * @param page {number} the page in question
745      * @return {boolean}
746      */
747     hasPage : function (page) {
748         if (!lang.isNumber(page) || page < 1) {
749             return false;
750         }
751
752         var totalPages = this.getTotalPages();
753
754         return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
755     },
756
757     /**
758      * Get the page number corresponding to the current record offset.
759      * @method getCurrentPage
760      * @return {number}
761      */
762     getCurrentPage : function () {
763         var perPage = this.get('rowsPerPage');
764         if (!perPage || !this.get('totalRecords')) {
765             return 0;
766         }
767         return Math.floor(this.get('recordOffset') / perPage) + 1;
768     },
769
770     /**
771      * Are there records on the next page?
772      * @method hasNextPage
773      * @return {boolean}
774      */
775     hasNextPage : function () {
776         var currentPage = this.getCurrentPage(),
777             totalPages  = this.getTotalPages();
778
779         return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
780     },
781
782     /**
783      * Get the page number of the next page, or null if the current page is the
784      * last page.
785      * @method getNextPage
786      * @return {number}
787      */
788     getNextPage : function () {
789         return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
790     },
791
792     /**
793      * Is there a page before the current page?
794      * @method hasPreviousPage
795      * @return {boolean}
796      */
797     hasPreviousPage : function () {
798         return (this.getCurrentPage() > 1);
799     },
800
801     /**
802      * Get the page number of the previous page, or null if the current page
803      * is the first page.
804      * @method getPreviousPage
805      * @return {number}
806      */
807     getPreviousPage : function () {
808         return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
809     },
810
811     /**
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]
816      */
817     getPageRecords : function (page) {
818         if (!lang.isNumber(page)) {
819             page = this.getCurrentPage();
820         }
821
822         var perPage = this.get('rowsPerPage'),
823             records = this.get('totalRecords'),
824             start, end;
825
826         if (!page || !perPage) {
827             return null;
828         }
829
830         start = (page - 1) * perPage;
831         if (records !== Paginator.VALUE_UNLIMITED) {
832             if (start >= records) {
833                 return null;
834             }
835             end = Math.min(start + perPage, records) - 1;
836         } else {
837             end = start + perPage - 1;
838         }
839
840         return [start,end];
841     },
842
843     /**
844      * Set the current page to the provided page number if possible.
845      * @method setPage
846      * @param newPage {number} the new page number
847      * @param silent {boolean} whether to forcibly avoid firing the
848      * changeRequest event
849      */
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'));
854             } else {
855                 this.fireEvent('changeRequest',this.getState({'page':page}));
856             }
857         }
858     },
859
860     /**
861      * Get the number of rows per page.
862      * @method getRowsPerPage
863      * @return {number} the current setting of the rowsPerPage attribute
864      */
865     getRowsPerPage : function () {
866         return this.get('rowsPerPage');
867     },
868
869     /**
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
875      */
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);
881             } else {
882                 this.fireEvent('changeRequest',
883                     this.getState({'rowsPerPage':+rpp}));
884             }
885         }
886     },
887
888     /**
889      * Get the total number of records.
890      * @method getTotalRecords
891      * @return {number} the current setting of totalRecords attribute
892      */
893     getTotalRecords : function () {
894         return this.get('totalRecords');
895     },
896
897     /**
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
902      */
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);
908             } else {
909                 this.fireEvent('changeRequest',
910                     this.getState({'totalRecords':+total}));
911             }
912         }
913     },
914
915     /**
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
919      */
920     getStartIndex : function () {
921         return this.get('recordOffset');
922     },
923
924     /**
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
930      */
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);
936             } else {
937                 this.fireEvent('changeRequest',
938                     this.getState({'recordOffset':+offset}));
939             }
940         }
941     },
942
943     /**
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:
947      * <ul>
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>
955      * </ul>
956      * @method getState
957      * @return {object}
958      * @param changes {object} OPTIONAL object literal with proposed values
959      * Supported change keys include:
960      * <ul>
961      * <li>rowsPerPage</li>
962      * <li>totalRecords</li>
963      * <li>recordOffset OR</li>
964      * <li>page</li>
965      * </ul>
966      */
967     getState : function (changes) {
968         var UNLIMITED = Paginator.VALUE_UNLIMITED,
969             M = Math, max = M.max, ceil = M.ceil,
970             currentState, state, offset;
971
972         function normalizeOffset(offset,total,rpp) {
973             if (offset <= 0 || total === 0) {
974                 return 0;
975             }
976             if (total === UNLIMITED || total > offset) {
977                 return offset - (offset % rpp);
978             }
979             return total - (total % rpp || rpp);
980         }
981
982         currentState = {
983             paginator    : this,
984             totalRecords : this.get('totalRecords'),
985             rowsPerPage  : this.get('rowsPerPage'),
986             records      : this.getPageRecords()
987         };
988         currentState.recordOffset = normalizeOffset(
989                                         this.get('recordOffset'),
990                                         currentState.totalRecords,
991                                         currentState.rowsPerPage);
992         currentState.page = ceil(currentState.recordOffset /
993                                  currentState.rowsPerPage) + 1;
994
995         if (!changes) {
996             return currentState;
997         }
998
999         state = {
1000             paginator    : this,
1001             before       : currentState,
1002
1003             rowsPerPage  : changes.rowsPerPage || currentState.rowsPerPage,
1004             totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
1005                                 max(changes.totalRecords,UNLIMITED) :
1006                                 +currentState.totalRecords)
1007         };
1008
1009         if (state.totalRecords === 0) {
1010             state.recordOffset =
1011             state.page         = 0;
1012         } else {
1013             offset = Paginator.isNumeric(changes.page) ?
1014                         (changes.page - 1) * state.rowsPerPage :
1015                         Paginator.isNumeric(changes.recordOffset) ?
1016                             +changes.recordOffset :
1017                             currentState.recordOffset;
1018
1019             state.recordOffset = normalizeOffset(offset,
1020                                     state.totalRecords,
1021                                     state.rowsPerPage);
1022
1023             state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
1024         }
1025
1026         state.records = [ state.recordOffset,
1027                           state.recordOffset + state.rowsPerPage - 1 ];
1028
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;
1034         }
1035
1036         return state;
1037     },
1038
1039     /**
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.
1045      * @method setState
1046      * @param state {Object} Object literal of attribute:value pairs to set
1047      */
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({});
1052
1053             // use just the state props from the input obj
1054             state = {
1055                 page         : state.page,
1056                 rowsPerPage  : state.rowsPerPage,
1057                 totalRecords : state.totalRecords,
1058                 recordOffset : state.recordOffset
1059             };
1060
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'));
1066             }
1067
1068             this._batch = true;
1069             this._pageChanged = false;
1070
1071             for (var k in state) {
1072                 if (state.hasOwnProperty(k) && this._configs.hasOwnProperty(k)) {
1073                     this.set(k,state[k]);
1074                 }
1075             }
1076
1077             this._batch = false;
1078             
1079             if (this._pageChanged) {
1080                 this._pageChanged = false;
1081
1082                 this._firePageChange(this.getState(this._state));
1083             }
1084         }
1085     }
1086 };
1087
1088 lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
1089
1090 YAHOO.widget.Paginator = Paginator;
1091 })();
1092 (function () {
1093
1094 var Paginator = YAHOO.widget.Paginator,
1095     l         = YAHOO.lang;
1096
1097 /**
1098  * ui Component to generate the textual report of current pagination status.
1099  * E.g. "Now viewing page 1 of 13".
1100  *
1101  * @namespace YAHOO.widget.Paginator.ui
1102  * @class CurrentPageReport
1103  * @for YAHOO.widget.Paginator
1104  *
1105  * @constructor
1106  * @param p {Pagintor} Paginator instance to attach to
1107  */
1108 Paginator.ui.CurrentPageReport = function (p) {
1109     this.paginator = p;
1110
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);
1116
1117     //TODO: make this work
1118     p.subscribe('pageReportClassChange', this.update,this,true);
1119 };
1120
1121 /**
1122  * Decorates Paginator instances with new attributes. Called during
1123  * Paginator instantiation.
1124  * @method init
1125  * @param p {Paginator} Paginator instance to decorate
1126  * @static
1127  */
1128 Paginator.ui.CurrentPageReport.init = function (p) {
1129
1130     /**
1131      * CSS class assigned to the span containing the info.
1132      * @attribute pageReportClass
1133      * @default 'yui-pg-current'
1134      */
1135     p.setAttributeConfig('pageReportClass', {
1136         value : 'yui-pg-current',
1137         validator : l.isString
1138     });
1139
1140     /**
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
1147      */
1148     p.setAttributeConfig('pageReportTemplate', {
1149         value : '({currentPage} of {totalPages})',
1150         validator : l.isString
1151     });
1152
1153     /**
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:
1157      * <ul>
1158      * <li>currentPage</li>
1159      * <li>totalPages</li>
1160      * <li>startIndex</li>
1161      * <li>endIndex</li>
1162      * <li>startRecord</li>
1163      * <li>endRecord</li>
1164      * <li>totalRecords</li>
1165      * </ul>
1166      * @attribute pageReportValueGenarator
1167      */
1168     p.setAttributeConfig('pageReportValueGenerator', {
1169         value : function (paginator) {
1170             var curPage = paginator.getCurrentPage(),
1171                 records = paginator.getPageRecords();
1172
1173             return {
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')
1181             };
1182         },
1183         validator : l.isFunction
1184     });
1185 };
1186
1187 /**
1188  * Replace place holders in a string with the named values found in an
1189  * object literal.
1190  * @static
1191  * @method sprintf
1192  * @param template {string} The content string containing place holders
1193  * @param values {object} The key:value pairs used to replace the place holders
1194  * @return {string}
1195  */
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] : '';
1199         });
1200 };
1201
1202 Paginator.ui.CurrentPageReport.prototype = {
1203
1204     /**
1205      * Span node containing the formatted info
1206      * @property span
1207      * @type HTMLElement
1208      * @private
1209      */
1210     span : null,
1211
1212
1213     /**
1214      * Generate the span containing info formatted per the pageReportTemplate
1215      * attribute.
1216      * @method render
1217      * @param id_base {string} used to create unique ids for generated nodes
1218      * @return {HTMLElement}
1219      */
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');
1224         this.update();
1225         
1226         return this.span;
1227     },
1228     
1229     /**
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
1233      * function.
1234      * @method update
1235      * @param e {CustomEvent} The calling change event
1236      */
1237     update : function (e) {
1238         if (e && e.prevValue === e.newValue) {
1239             return;
1240         }
1241
1242         this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
1243             this.paginator.get('pageReportTemplate'),
1244             this.paginator.get('pageReportValueGenerator')(this.paginator));
1245     },
1246
1247     /**
1248      * Removes the link/span node and clears event listeners
1249      * removal.
1250      * @method destroy
1251      * @private
1252      */
1253     destroy : function () {
1254         this.span.parentNode.removeChild(this.span);
1255         this.span = null;
1256     }
1257
1258 };
1259
1260 })();
1261 (function () {
1262
1263 var Paginator = YAHOO.widget.Paginator,
1264     l         = YAHOO.lang;
1265
1266 /**
1267  * ui Component to generate the page links
1268  *
1269  * @namespace YAHOO.widget.Paginator.ui
1270  * @class PageLinks
1271  * @for YAHOO.widget.Paginator
1272  *
1273  * @constructor
1274  * @param p {Pagintor} Paginator instance to attach to
1275  */
1276 Paginator.ui.PageLinks = function (p) {
1277     this.paginator = p;
1278
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);
1286
1287     //TODO: Make this work
1288     p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
1289 };
1290
1291 /**
1292  * Decorates Paginator instances with new attributes. Called during
1293  * Paginator instantiation.
1294  * @method init
1295  * @param p {Paginator} Paginator instance to decorate
1296  * @static
1297  */
1298 Paginator.ui.PageLinks.init = function (p) {
1299
1300     /**
1301      * CSS class assigned to each page link/span.
1302      * @attribute pageLinkClass
1303      * @default 'yui-pg-page'
1304      */
1305     p.setAttributeConfig('pageLinkClass', {
1306         value : 'yui-pg-page',
1307         validator : l.isString
1308     });
1309
1310     /**
1311      * CSS class assigned to the current page span.
1312      * @attribute currentPageClass
1313      * @default 'yui-pg-current-page'
1314      */
1315     p.setAttributeConfig('currentPageClass', {
1316         value : 'yui-pg-current-page',
1317         validator : l.isString
1318     });
1319
1320     /**
1321      * CSS class assigned to the span containing the page links.
1322      * @attribute pageLinksContainerClass
1323      * @default 'yui-pg-pages'
1324      */
1325     p.setAttributeConfig('pageLinksContainerClass', {
1326         value : 'yui-pg-pages',
1327         validator : l.isString
1328     });
1329
1330     /**
1331      * Maximum number of page links to display at one time.
1332      * @attribute pageLinks
1333      * @default 10
1334      */
1335     p.setAttributeConfig('pageLinks', {
1336         value : 10,
1337         validator : Paginator.isNumeric
1338     });
1339
1340     /**
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
1343      * paginator object.
1344      * @attribute pageLabelBuilder
1345      * @default function (page, paginator) { return page; }
1346      */
1347     p.setAttributeConfig('pageLabelBuilder', {
1348         value : function (page, paginator) { return page; },
1349         validator : l.isFunction
1350     });
1351 };
1352
1353 /**
1354  * Calculates start and end page numbers given a current page, attempting
1355  * to keep the current page in the middle
1356  * @static
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]
1362  */
1363 Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
1364     var UNLIMITED = Paginator.VALUE_UNLIMITED,
1365         start, end, delta;
1366
1367     // Either has no pages, or unlimited pages.  Show none.
1368     if (!currentPage || numPages === 0 || totalPages === 0 ||
1369         (totalPages === UNLIMITED && numPages === UNLIMITED)) {
1370         return [0,-1];
1371     }
1372
1373     // Limit requested pageLinks if there are fewer totalPages
1374     if (totalPages !== UNLIMITED) {
1375         numPages = numPages === UNLIMITED ?
1376                     totalPages :
1377                     Math.min(numPages,totalPages);
1378     }
1379
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;
1384     } else {
1385         end = Math.min(totalPages, start + numPages - 1);
1386     }
1387
1388     // Adjust the start index when approaching the last page
1389     delta = numPages - (end - start + 1);
1390     start = Math.max(1, start - delta);
1391
1392     return [start,end];
1393 };
1394
1395
1396 Paginator.ui.PageLinks.prototype = {
1397
1398     /**
1399      * Current page
1400      * @property current
1401      * @type number
1402      * @private
1403      */
1404     current     : 0,
1405
1406     /**
1407      * Span node containing the page links
1408      * @property container
1409      * @type HTMLElement
1410      * @private
1411      */
1412     container   : null,
1413
1414
1415     /**
1416      * Generate the nodes and return the container node containing page links
1417      * appropriate to the current pagination state.
1418      * @method render
1419      * @param id_base {string} used to create unique ids for generated nodes
1420      * @return {HTMLElement}
1421      */
1422     render : function (id_base) {
1423         var p = this.paginator;
1424
1425         // Set up container
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);
1430
1431         // Call update, flagging a need to rebuild
1432         this.update({newValue : null, rebuild : true});
1433
1434         return this.container;
1435     },
1436
1437     /**
1438      * Update the links if appropriate
1439      * @method update
1440      * @param e {CustomEvent} The calling change event
1441      */
1442     update : function (e) {
1443         if (e && e.prevValue === e.newValue) {
1444             return;
1445         }
1446
1447         var p           = this.paginator,
1448             currentPage = p.getCurrentPage();
1449
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(
1454                                 currentPage,
1455                                 p.getTotalPages(),
1456                                 p.get('pageLinks')),
1457                 start        = range[0],
1458                 end          = range[1],
1459                 content      = '',
1460                 linkTemplate,i;
1461
1462             linkTemplate = '<a href="#" class="' + p.get('pageLinkClass') +
1463                            '" page="';
1464             for (i = start; i <= end; ++i) {
1465                 if (i === currentPage) {
1466                     content +=
1467                         '<span class="' + p.get('currentPageClass') + ' ' +
1468                                           p.get('pageLinkClass') + '">' +
1469                         labelBuilder(i,p) + '</span>';
1470                 } else {
1471                     content +=
1472                         linkTemplate + i + '">' + labelBuilder(i,p) + '</a>';
1473                 }
1474             }
1475
1476             this.container.innerHTML = content;
1477         }
1478     },
1479
1480     /**
1481      * Force a rebuild of the page links.
1482      * @method rebuild
1483      * @param e {CustomEvent} The calling change event
1484      */
1485     rebuild     : function (e) {
1486         e.rebuild = true;
1487         this.update(e);
1488     },
1489
1490     /**
1491      * Removes the page links container node and clears event listeners
1492      * @method destroy
1493      * @private
1494      */
1495     destroy : function () {
1496         YAHOO.util.Event.purgeElement(this.container,true);
1497         this.container.parentNode.removeChild(this.container);
1498         this.container = null;
1499     },
1500
1501     /**
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.
1505      * @method onClick
1506      * @param e {DOMEvent} The click event
1507      */
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'))) {
1512
1513             YAHOO.util.Event.stopEvent(e);
1514
1515             this.paginator.setPage(parseInt(t.getAttribute('page'),10));
1516         }
1517     }
1518
1519 };
1520
1521 })();
1522 (function () {
1523
1524 var Paginator = YAHOO.widget.Paginator,
1525     l         = YAHOO.lang;
1526
1527 /**
1528  * ui Component to generate the link to jump to the first page.
1529  *
1530  * @namespace YAHOO.widget.Paginator.ui
1531  * @class FirstPageLink
1532  * @for YAHOO.widget.Paginator
1533  *
1534  * @constructor
1535  * @param p {Pagintor} Paginator instance to attach to
1536  */
1537 Paginator.ui.FirstPageLink = function (p) {
1538     this.paginator = p;
1539
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);
1544
1545     // TODO: make this work
1546     p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1547     p.subscribe('firstPageLinkClassChange',this.update,this,true);
1548 };
1549
1550 /**
1551  * Decorates Paginator instances with new attributes. Called during
1552  * Paginator instantiation.
1553  * @method init
1554  * @param p {Paginator} Paginator instance to decorate
1555  * @static
1556  */
1557 Paginator.ui.FirstPageLink.init = function (p) {
1558
1559     /**
1560      * Used as innerHTML for the first page link/span.
1561      * @attribute firstPageLinkLabel
1562      * @default '&lt;&lt; first'
1563      */
1564     p.setAttributeConfig('firstPageLinkLabel', {
1565         value : '&lt;&lt; first',
1566         validator : l.isString
1567     });
1568
1569     /**
1570      * CSS class assigned to the link/span
1571      * @attribute firstPageLinkClass
1572      * @default 'yui-pg-first'
1573      */
1574     p.setAttributeConfig('firstPageLinkClass', {
1575         value : 'yui-pg-first',
1576         validator : l.isString
1577     });
1578 };
1579
1580 // Instance members and methods
1581 Paginator.ui.FirstPageLink.prototype = {
1582
1583     /**
1584      * The currently placed HTMLElement node
1585      * @property current
1586      * @type HTMLElement
1587      * @private
1588      */
1589     current   : null,
1590
1591     /**
1592      * Link node
1593      * @property link
1594      * @type HTMLElement
1595      * @private
1596      */
1597     link      : null,
1598
1599     /**
1600      * Span node (inactive link)
1601      * @property span
1602      * @type HTMLElement
1603      * @private
1604      */
1605     span      : null,
1606
1607     /**
1608      * Generate the nodes and return the appropriate node given the current
1609      * pagination state.
1610      * @method render
1611      * @param id_base {string} used to create unique ids for generated nodes
1612      * @return {HTMLElement}
1613      */
1614     render : function (id_base) {
1615         var p     = this.paginator,
1616             c     = p.get('firstPageLinkClass'),
1617             label = p.get('firstPageLinkLabel');
1618
1619         this.link     = document.createElement('a');
1620         this.span     = document.createElement('span');
1621
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);
1627
1628         this.span.id        = id_base + '-first-span';
1629         this.span.className = c;
1630         this.span.innerHTML = label;
1631
1632         this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1633         return this.current;
1634     },
1635
1636     /**
1637      * Swap the link and span nodes if appropriate.
1638      * @method update
1639      * @param e {CustomEvent} The calling change event
1640      */
1641     update : function (e) {
1642         if (e && e.prevValue === e.newValue) {
1643             return;
1644         }
1645
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;
1651             }
1652         } else {
1653             if (par && this.current === this.link) {
1654                 par.replaceChild(this.span,this.current);
1655                 this.current = this.span;
1656             }
1657         }
1658     },
1659
1660     /**
1661      * Removes the link/span node and clears event listeners
1662      * removal.
1663      * @method destroy
1664      * @private
1665      */
1666     destroy : function () {
1667         YAHOO.util.Event.purgeElement(this.link);
1668         this.current.parentNode.removeChild(this.current);
1669         this.link = this.span = null;
1670     },
1671
1672     /**
1673      * Listener for the link's onclick event.  Pass new value to setPage method.
1674      * @method onClick
1675      * @param e {DOMEvent} The click event
1676      */
1677     onClick : function (e) {
1678         YAHOO.util.Event.stopEvent(e);
1679         this.paginator.setPage(1);
1680     }
1681 };
1682
1683 })();
1684 (function () {
1685
1686 var Paginator = YAHOO.widget.Paginator,
1687     l         = YAHOO.lang;
1688
1689 /**
1690  * ui Component to generate the link to jump to the last page.
1691  *
1692  * @namespace YAHOO.widget.Paginator.ui
1693  * @class LastPageLink
1694  * @for YAHOO.widget.Paginator
1695  *
1696  * @constructor
1697  * @param p {Pagintor} Paginator instance to attach to
1698  */
1699 Paginator.ui.LastPageLink = function (p) {
1700     this.paginator = p;
1701
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);
1706
1707     // TODO: make this work
1708     p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1709     p.subscribe('lastPageLinkClassChange', this.update,this,true);
1710 };
1711
1712 /**
1713  * Decorates Paginator instances with new attributes. Called during
1714  * Paginator instantiation.
1715  * @method init
1716  * @param paginator {Paginator} Paginator instance to decorate
1717  * @static
1718  */
1719 Paginator.ui.LastPageLink.init = function (p) {
1720
1721     /**
1722      * Used as innerHTML for the last page link/span.
1723      * @attribute lastPageLinkLabel
1724      * @default 'last &gt;&gt;'
1725      */
1726     p.setAttributeConfig('lastPageLinkLabel', {
1727         value : 'last &gt;&gt;',
1728         validator : l.isString
1729     });
1730
1731     /**
1732      * CSS class assigned to the link/span
1733      * @attribute lastPageLinkClass
1734      * @default 'yui-pg-last'
1735      */
1736     p.setAttributeConfig('lastPageLinkClass', {
1737         value : 'yui-pg-last',
1738         validator : l.isString
1739     });
1740 };
1741
1742 Paginator.ui.LastPageLink.prototype = {
1743
1744     /**
1745      * Currently placed HTMLElement node
1746      * @property current
1747      * @type HTMLElement
1748      * @private
1749      */
1750     current   : null,
1751
1752     /**
1753      * Link HTMLElement node
1754      * @property link
1755      * @type HTMLElement
1756      * @private
1757      */
1758     link      : null,
1759
1760     /**
1761      * Span node (inactive link)
1762      * @property span
1763      * @type HTMLElement
1764      * @private
1765      */
1766     span      : null,
1767
1768     /**
1769      * Empty place holder node for when the last page link is inappropriate to
1770      * display in any form (unlimited paging).
1771      * @property na
1772      * @type HTMLElement
1773      * @private
1774      */
1775     na        : null,
1776
1777
1778     /**
1779      * Generate the nodes and return the appropriate node given the current
1780      * pagination state.
1781      * @method render
1782      * @param id_base {string} used to create unique ids for generated nodes
1783      * @return {HTMLElement}
1784      */
1785     render : function (id_base) {
1786         var p     = this.paginator,
1787             c     = p.get('lastPageLinkClass'),
1788             label = p.get('lastPageLinkLabel'),
1789             last  = p.getTotalPages();
1790
1791         this.link = document.createElement('a');
1792         this.span = document.createElement('span');
1793         this.na   = this.span.cloneNode(false);
1794
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);
1800
1801         this.span.id        = id_base + '-last-span';
1802         this.span.className = c;
1803         this.span.innerHTML = label;
1804
1805         this.na.id = id_base + '-last-na';
1806
1807         switch (last) {
1808             case Paginator.VALUE_UNLIMITED :
1809                     this.current = this.na; break;
1810             case p.getCurrentPage() :
1811                     this.current = this.span; break;
1812             default :
1813                     this.current = this.link;
1814         }
1815
1816         return this.current;
1817     },
1818
1819     /**
1820      * Swap the link, span, and na nodes if appropriate.
1821      * @method update
1822      * @param e {CustomEvent} The calling change event (ignored)
1823      */
1824     update : function (e) {
1825         if (e && e.prevValue === e.newValue) {
1826             return;
1827         }
1828
1829         var par   = this.current ? this.current.parentNode : null,
1830             after = this.link;
1831
1832         if (par) {
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;
1838             }
1839
1840             if (this.current !== after) {
1841                 par.replaceChild(after,this.current);
1842                 this.current = after;
1843             }
1844         }
1845     },
1846
1847     /**
1848      * Removes the link/span node and clears event listeners
1849      * @method destroy
1850      * @private
1851      */
1852     destroy : function () {
1853         YAHOO.util.Event.purgeElement(this.link);
1854         this.current.parentNode.removeChild(this.current);
1855         this.link = this.span = null;
1856     },
1857
1858     /**
1859      * Listener for the link's onclick event.  Passes to setPage method.
1860      * @method onClick
1861      * @param e {DOMEvent} The click event
1862      */
1863     onClick : function (e) {
1864         YAHOO.util.Event.stopEvent(e);
1865         this.paginator.setPage(this.paginator.getTotalPages());
1866     }
1867 };
1868
1869 })();
1870 (function () {
1871
1872 var Paginator = YAHOO.widget.Paginator,
1873     l         = YAHOO.lang;
1874
1875 /**
1876  * ui Component to generate the link to jump to the next page.
1877  *
1878  * @namespace YAHOO.widget.Paginator.ui
1879  * @class NextPageLink
1880  * @for YAHOO.widget.Paginator
1881  *
1882  * @constructor
1883  * @param p {Pagintor} Paginator instance to attach to
1884  */
1885 Paginator.ui.NextPageLink = function (p) {
1886     this.paginator = p;
1887
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);
1892
1893     // TODO: make this work
1894     p.subscribe('nextPageLinkLabelChange', this.update,this,true);
1895     p.subscribe('nextPageLinkClassChange', this.update,this,true);
1896 };
1897
1898 /**
1899  * Decorates Paginator instances with new attributes. Called during
1900  * Paginator instantiation.
1901  * @method init
1902  * @param p {Paginator} Paginator instance to decorate
1903  * @static
1904  */
1905 Paginator.ui.NextPageLink.init = function (p) {
1906
1907     /**
1908      * Used as innerHTML for the next page link/span.
1909      * @attribute nextPageLinkLabel
1910      * @default 'next &gt;'
1911      */
1912     p.setAttributeConfig('nextPageLinkLabel', {
1913         value : 'next &gt;',
1914         validator : l.isString
1915     });
1916
1917     /**
1918      * CSS class assigned to the link/span
1919      * @attribute nextPageLinkClass
1920      * @default 'yui-pg-next'
1921      */
1922     p.setAttributeConfig('nextPageLinkClass', {
1923         value : 'yui-pg-next',
1924         validator : l.isString
1925     });
1926 };
1927
1928 Paginator.ui.NextPageLink.prototype = {
1929
1930     /**
1931      * Currently placed HTMLElement node
1932      * @property current
1933      * @type HTMLElement
1934      * @private
1935      */
1936     current   : null,
1937
1938     /**
1939      * Link node
1940      * @property link
1941      * @type HTMLElement
1942      * @private
1943      */
1944     link      : null,
1945
1946     /**
1947      * Span node (inactive link)
1948      * @property span
1949      * @type HTMLElement
1950      * @private
1951      */
1952     span      : null,
1953
1954
1955     /**
1956      * Generate the nodes and return the appropriate node given the current
1957      * pagination state.
1958      * @method render
1959      * @param id_base {string} used to create unique ids for generated nodes
1960      * @return {HTMLElement}
1961      */
1962     render : function (id_base) {
1963         var p     = this.paginator,
1964             c     = p.get('nextPageLinkClass'),
1965             label = p.get('nextPageLinkLabel'),
1966             last  = p.getTotalPages();
1967
1968         this.link     = document.createElement('a');
1969         this.span     = document.createElement('span');
1970
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);
1976
1977         this.span.id        = id_base + '-next-span';
1978         this.span.className = c;
1979         this.span.innerHTML = label;
1980
1981         this.current = p.getCurrentPage() === last ? this.span : this.link;
1982
1983         return this.current;
1984     },
1985
1986     /**
1987      * Swap the link and span nodes if appropriate.
1988      * @method update
1989      * @param e {CustomEvent} The calling change event
1990      */
1991     update : function (e) {
1992         if (e && e.prevValue === e.newValue) {
1993             return;
1994         }
1995
1996         var last = this.paginator.getTotalPages(),
1997             par  = this.current ? this.current.parentNode : null;
1998
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;
2003             }
2004         } else if (this.current === this.link) {
2005             if (par) {
2006                 par.replaceChild(this.span,this.current);
2007                 this.current = this.span;
2008             }
2009         }
2010     },
2011
2012     /**
2013      * Removes the link/span node and clears event listeners
2014      * @method destroy
2015      * @private
2016      */
2017     destroy : function () {
2018         YAHOO.util.Event.purgeElement(this.link);
2019         this.current.parentNode.removeChild(this.current);
2020         this.link = this.span = null;
2021     },
2022
2023     /**
2024      * Listener for the link's onclick event.  Passes to setPage method.
2025      * @method onClick
2026      * @param e {DOMEvent} The click event
2027      */
2028     onClick : function (e) {
2029         YAHOO.util.Event.stopEvent(e);
2030         this.paginator.setPage(this.paginator.getNextPage());
2031     }
2032 };
2033
2034 })();
2035 (function () {
2036
2037 var Paginator = YAHOO.widget.Paginator,
2038     l         = YAHOO.lang;
2039
2040 /**
2041  * ui Component to generate the link to jump to the previous page.
2042  *
2043  * @namespace YAHOO.widget.Paginator.ui
2044  * @class PreviousPageLink
2045  * @for YAHOO.widget.Paginator
2046  *
2047  * @constructor
2048  * @param p {Pagintor} Paginator instance to attach to
2049  */
2050 Paginator.ui.PreviousPageLink = function (p) {
2051     this.paginator = p;
2052
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);
2057
2058     // TODO: make this work
2059     p.subscribe('previousPageLinkLabelChange',this.update,this,true);
2060     p.subscribe('previousPageLinkClassChange',this.update,this,true);
2061 };
2062
2063 /**
2064  * Decorates Paginator instances with new attributes. Called during
2065  * Paginator instantiation.
2066  * @method init
2067  * @param p {Paginator} Paginator instance to decorate
2068  * @static
2069  */
2070 Paginator.ui.PreviousPageLink.init = function (p) {
2071
2072     /**
2073      * Used as innerHTML for the previous page link/span.
2074      * @attribute previousPageLinkLabel
2075      * @default '&lt; prev'
2076      */
2077     p.setAttributeConfig('previousPageLinkLabel', {
2078         value : '&lt; prev',
2079         validator : l.isString
2080     });
2081
2082     /**
2083      * CSS class assigned to the link/span
2084      * @attribute previousPageLinkClass
2085      * @default 'yui-pg-previous'
2086      */
2087     p.setAttributeConfig('previousPageLinkClass', {
2088         value : 'yui-pg-previous',
2089         validator : l.isString
2090     });
2091 };
2092
2093 Paginator.ui.PreviousPageLink.prototype = {
2094
2095     /**
2096      * Currently placed HTMLElement node
2097      * @property current
2098      * @type HTMLElement
2099      * @private
2100      */
2101     current   : null,
2102
2103     /**
2104      * Link node
2105      * @property link
2106      * @type HTMLElement
2107      * @private
2108      */
2109     link      : null,
2110
2111     /**
2112      * Span node (inactive link)
2113      * @property span
2114      * @type HTMLElement
2115      * @private
2116      */
2117     span      : null,
2118
2119
2120     /**
2121      * Generate the nodes and return the appropriate node given the current
2122      * pagination state.
2123      * @method render
2124      * @param id_base {string} used to create unique ids for generated nodes
2125      * @return {HTMLElement}
2126      */
2127     render : function (id_base) {
2128         var p     = this.paginator,
2129             c     = p.get('previousPageLinkClass'),
2130             label = p.get('previousPageLinkLabel');
2131
2132         this.link     = document.createElement('a');
2133         this.span     = document.createElement('span');
2134
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);
2140
2141         this.span.id        = id_base + '-prev-span';
2142         this.span.className = c;
2143         this.span.innerHTML = label;
2144
2145         this.current = p.getCurrentPage() > 1 ? this.link : this.span;
2146         return this.current;
2147     },
2148
2149     /**
2150      * Swap the link and span nodes if appropriate.
2151      * @method update
2152      * @param e {CustomEvent} The calling change event
2153      */
2154     update : function (e) {
2155         if (e && e.prevValue === e.newValue) {
2156             return;
2157         }
2158
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;
2164             }
2165         } else {
2166             if (par && this.current === this.link) {
2167                 par.replaceChild(this.span,this.current);
2168                 this.current = this.span;
2169             }
2170         }
2171     },
2172
2173     /**
2174      * Removes the link/span node and clears event listeners
2175      * @method destroy
2176      * @private
2177      */
2178     destroy : function () {
2179         YAHOO.util.Event.purgeElement(this.link);
2180         this.current.parentNode.removeChild(this.current);
2181         this.link = this.span = null;
2182     },
2183
2184     /**
2185      * Listener for the link's onclick event.  Passes to setPage method.
2186      * @method onClick
2187      * @param e {DOMEvent} The click event
2188      */
2189     onClick : function (e) {
2190         YAHOO.util.Event.stopEvent(e);
2191         this.paginator.setPage(this.paginator.getPreviousPage());
2192     }
2193 };
2194
2195 })();
2196 (function () {
2197
2198 var Paginator = YAHOO.widget.Paginator,
2199     l         = YAHOO.lang;
2200
2201 /**
2202  * ui Component to generate the rows-per-page dropdown
2203  *
2204  * @namespace YAHOO.widget.Paginator.ui
2205  * @class RowsPerPageDropdown
2206  * @for YAHOO.widget.Paginator
2207  *
2208  * @constructor
2209  * @param p {Pagintor} Paginator instance to attach to
2210  */
2211 Paginator.ui.RowsPerPageDropdown = function (p) {
2212     this.paginator = p;
2213
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);
2218
2219     // TODO: make this work
2220     p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
2221 };
2222
2223 /**
2224  * Decorates Paginator instances with new attributes. Called during
2225  * Paginator instantiation.
2226  * @method init
2227  * @param p {Paginator} Paginator instance to decorate
2228  * @static
2229  */
2230 Paginator.ui.RowsPerPageDropdown.init = function (p) {
2231
2232     /**
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
2237      * @default []
2238      */
2239     p.setAttributeConfig('rowsPerPageOptions', {
2240         value : [],
2241         validator : l.isArray
2242     });
2243
2244     /**
2245      * CSS class assigned to the select node
2246      * @attribute rowsPerPageDropdownClass
2247      * @default 'yui-pg-rpp-options'
2248      */
2249     p.setAttributeConfig('rowsPerPageDropdownClass', {
2250         value : 'yui-pg-rpp-options',
2251         validator : l.isString
2252     });
2253 };
2254
2255 Paginator.ui.RowsPerPageDropdown.prototype = {
2256
2257     /**
2258      * select node
2259      * @property select
2260      * @type HTMLElement
2261      * @private
2262      */
2263     select  : null,
2264
2265
2266     /**
2267      * option node for the optional All value
2268      *
2269      * @property all
2270      * @type HTMLElement
2271      * @protected
2272      */
2273     all : null,
2274
2275     /**
2276      * Generate the select and option nodes and returns the select node.
2277      * @method render
2278      * @param id_base {string} used to create unique ids for generated nodes
2279      * @return {HTMLElement}
2280      */
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';
2286
2287         YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2288
2289         this.rebuild();
2290
2291         return this.select;
2292     },
2293
2294     /**
2295      * (Re)generate the select options.
2296      * @method rebuild
2297      */
2298     rebuild : function (e) {
2299         var p       = this.paginator,
2300             sel     = this.select,
2301             options = p.get('rowsPerPageOptions'),
2302             opt,cfg,val,i,len;
2303
2304         this.all = null;
2305
2306         for (i = 0, len = options.length; i < len; ++i) {
2307             cfg = options[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;
2312
2313             if (l.isString(val) && val.toLowerCase() === 'all') {
2314                 this.all  = opt;
2315                 opt.value = p.get('totalRecords');
2316             } else{
2317                 opt.value = val;
2318             }
2319
2320         }
2321
2322         while (sel.options.length > options.length) {
2323             sel.removeChild(sel.firstChild);
2324         }
2325
2326         this.update();
2327     },
2328
2329     /**
2330      * Select the appropriate option if changed.
2331      * @method update
2332      * @param e {CustomEvent} The calling change event
2333      */
2334     update : function (e) {
2335         if (e && e.prevValue === e.newValue) {
2336             return;
2337         }
2338
2339         var rpp     = this.paginator.get('rowsPerPage')+'',
2340             options = this.select.options,
2341             i,len;
2342
2343         for (i = 0, len = options.length; i < len; ++i) {
2344             if (options[i].value === rpp) {
2345                 options[i].selected = true;
2346                 break;
2347             }
2348         }
2349     },
2350
2351     /**
2352      * Listener for the select's onchange event.  Sent to setRowsPerPage method.
2353      * @method onChange
2354      * @param e {DOMEvent} The change event
2355      */
2356     onChange : function (e) {
2357         this.paginator.setRowsPerPage(
2358                 parseInt(this.select.options[this.select.selectedIndex].value,10));
2359     },
2360
2361     /**
2362      * Updates the all option value (and Paginator's rowsPerPage attribute if
2363      * necessary) in response to a change in the Paginator's totalRecords.
2364      *
2365      * @method _handleTotalRecordsChange
2366      * @param e {Event} attribute change event
2367      * @protected
2368      */
2369     _handleTotalRecordsChange : function (e) {
2370         if (!this.all || (e && e.prevValue === e.newValue)) {
2371             return;
2372         }
2373
2374         this.all.value = e.newValue;
2375         if (this.all.selected) {
2376             this.paginator.set('rowsPerPage',e.newValue);
2377         }
2378     },
2379
2380     /**
2381      * Removes the select node and clears event listeners
2382      * @method destroy
2383      * @private
2384      */
2385     destroy : function () {
2386         YAHOO.util.Event.purgeElement(this.select);
2387         this.select.parentNode.removeChild(this.select);
2388         this.select = null;
2389     }
2390 };
2391
2392 })();
2393 YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.8.0r4", build: "2449"});