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