2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
9 * The Paginator widget provides a set of controls to navigate through paged
13 * @uses YAHOO.util.EventProvider
14 * @uses YAHOO.util.AttributeProvider
17 var Dom = YAHOO.util.Dom,
19 isObject = lang.isObject,
20 isFunction = lang.isFunction,
21 isArray = lang.isArray,
22 isString = lang.isString;
25 * Instantiate a Paginator, passing a configuration object to the contructor.
26 * The configuration object should contain the following properties:
28 * <li>rowsPerPage : <em>n</em> (int)</li>
29 * <li>totalRecords : <em>n</em> (int or Paginator.VALUE_UNLIMITED)</li>
30 * <li>containers : <em>id | el | arr</em> (HTMLElement reference, its id, or an array of either)</li>
33 * @namespace YAHOO.widget
36 * @param config {Object} Object literal to set instance and ui component
39 function Paginator(config) {
40 var UNLIMITED = Paginator.VALUE_UNLIMITED,
41 attrib, initialPage, records, perPage, startIndex;
43 config = isObject(config) ? config : {};
49 // Set the basic config keys first
50 this.set('rowsPerPage',config.rowsPerPage,true);
51 if (Paginator.isNumeric(config.totalRecords)) {
52 this.set('totalRecords',config.totalRecords,true);
55 this.initUIComponents();
57 // Update the other config values
58 for (attrib in config) {
59 if (config.hasOwnProperty(attrib)) {
60 this.set(attrib,config[attrib],true);
64 // Calculate the initial record offset
65 initialPage = this.get('initialPage');
66 records = this.get('totalRecords');
67 perPage = this.get('rowsPerPage');
68 if (initialPage > 1 && perPage !== UNLIMITED) {
69 startIndex = (initialPage - 1) * perPage;
70 if (records === UNLIMITED || startIndex < records) {
71 this.set('recordOffset',startIndex,true);
78 lang.augmentObject(Paginator, {
80 * Incrementing index used to give instances unique ids.
82 * @property Paginator.id
89 * Base of id strings used for ui components.
91 * @property Paginator.ID_BASE
98 * Used to identify unset, optional configurations, or used explicitly in
99 * the case of totalRecords to indicate unlimited pagination.
101 * @property Paginator.VALUE_UNLIMITED
105 VALUE_UNLIMITED : -1,
108 * Default template used by Paginator instances. Update this if you want
109 * all new Paginators to use a different default template.
111 * @property Paginator.TEMPLATE_DEFAULT
114 TEMPLATE_DEFAULT : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",
117 * Common alternate pagination format, including page links, links for
118 * previous, next, first and last pages as well as a rows-per-page
119 * dropdown. Offered as a convenience.
121 * @property Paginator.TEMPLATE_ROWS_PER_PAGE
124 TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
127 * Storage object for UI Components
129 * @property Paginator.ui
134 * Similar to YAHOO.lang.isNumber, but allows numeric strings. This is
135 * is used for attribute validation in conjunction with getters that return
138 * @method Paginator.isNumeric
139 * @param v {Number|String} value to be checked for number or numeric string
140 * @returns {Boolean} true if the input is coercable into a finite number
143 isNumeric : function (v) {
148 * Return a number or null from input
150 * @method Paginator.toNumber
151 * @param n {Number|String} a number or numeric string
155 toNumber : function (n) {
156 return isFinite(+n) ? +n : null;
162 // Instance members and methods
163 Paginator.prototype = {
168 * Array of nodes in which to render pagination controls. This is set via
169 * the "containers" attribute.
170 * @property _containers
171 * @type Array(HTMLElement)
177 * Flag used to indicate multiple attributes are being updated via setState
185 * Used by setState to indicate when a page change has occurred
186 * @property _pageChanged
190 _pageChanged : false,
193 * Temporary state cache used by setState to keep track of the previous
194 * state for eventual pageChange event firing
205 * Initialize the Paginator's attributes (see YAHOO.util.Element class
206 * AttributeProvider).
210 initConfig : function () {
212 var UNLIMITED = Paginator.VALUE_UNLIMITED;
215 * REQUIRED. Number of records constituting a "page"
216 * @attribute rowsPerPage
219 this.setAttributeConfig('rowsPerPage', {
221 validator : Paginator.isNumeric,
222 setter : Paginator.toNumber
226 * REQUIRED. Node references or ids of nodes in which to render the
227 * pagination controls.
228 * @attribute containers
229 * @type {string|HTMLElement|Array(string|HTMLElement)}
231 this.setAttributeConfig('containers', {
233 validator : function (val) {
237 for (var i = 0, len = val.length; i < len; ++i) {
238 if (isString(val[i]) ||
239 (isObject(val[i]) && val[i].nodeType === 1)) {
246 method : function (val) {
251 this._containers = val;
256 * Total number of records to paginate through
257 * @attribute totalRecords
261 this.setAttributeConfig('totalRecords', {
263 validator : Paginator.isNumeric,
264 setter : Paginator.toNumber
268 * Zero based index of the record considered first on the current page.
269 * For page based interactions, don't modify this attribute directly;
271 * @attribute recordOffset
275 this.setAttributeConfig('recordOffset', {
277 validator : function (val) {
278 var total = this.get('totalRecords');
279 if (Paginator.isNumeric(val)) {
281 return total === UNLIMITED || total > val ||
282 (total === 0 && val === 0);
287 setter : Paginator.toNumber
291 * Page to display on initial paint
292 * @attribute initialPage
296 this.setAttributeConfig('initialPage', {
298 validator : Paginator.isNumeric,
299 setter : Paginator.toNumber
303 * Template used to render controls. The string will be used as
304 * innerHTML on all specified container nodes. Bracketed keys
305 * (e.g. {pageLinks}) in the string will be replaced with an instance
306 * of the so named ui component.
307 * @see Paginator.TEMPLATE_DEFAULT
308 * @see Paginator.TEMPLATE_ROWS_PER_PAGE
309 * @attribute template
312 this.setAttributeConfig('template', {
313 value : Paginator.TEMPLATE_DEFAULT,
318 * Class assigned to the element(s) containing pagination controls.
319 * @attribute containerClass
321 * @default 'yui-pg-container'
323 this.setAttributeConfig('containerClass', {
324 value : 'yui-pg-container',
329 * Display pagination controls even when there is only one page. Set
330 * to false to forgo rendering and/or hide the containers when there
331 * is only one page of data. Note if you are using the rowsPerPage
332 * dropdown ui component, visibility will be maintained as long as the
333 * number of records exceeds the smallest page size.
334 * @attribute alwaysVisible
338 this.setAttributeConfig('alwaysVisible', {
340 validator : lang.isBoolean
344 * Update the UI immediately upon interaction. If false, changeRequest
345 * subscribers or other external code will need to explicitly set the
346 * new values in the paginator to trigger repaint.
347 * @attribute updateOnChange
350 * @deprecated use changeRequest listener that calls setState
352 this.setAttributeConfig('updateOnChange', {
354 validator : lang.isBoolean
359 // Read only attributes
362 * Unique id assigned to this instance
367 this.setAttributeConfig('id', {
368 value : Paginator.id++,
373 * Indicator of whether the DOM nodes have been initially created
374 * @attribute rendered
378 this.setAttributeConfig('rendered', {
386 * Initialize registered ui components onto this instance.
387 * @method initUIComponents
390 initUIComponents : function () {
391 var ui = Paginator.ui,
394 if (ui.hasOwnProperty(name)) {
396 if (isObject(UIComp) && isFunction(UIComp.init)) {
404 * Initialize this instance's CustomEvents.
408 initEvents : function () {
410 * Event fired when the Paginator is initially rendered
413 this.createEvent('render');
416 * Event fired when the Paginator is initially rendered
418 * @deprecated use render event
420 this.createEvent('rendered'); // backward compatibility
423 * Event fired when a change in pagination values is requested,
424 * either by interacting with the various ui components or via the
425 * setStartIndex(n) etc APIs.
426 * Subscribers will receive the proposed state as the first parameter.
427 * The proposed state object will contain the following keys:
429 * <li>paginator - the Paginator instance</li>
431 * <li>totalRecords</li>
432 * <li>recordOffset - index of the first record on the new page</li>
433 * <li>rowsPerPage</li>
434 * <li>records - array containing [start index, end index] for the records on the new page</li>
435 * <li>before - object literal with all these keys for the current state</li>
437 * @event changeRequest
439 this.createEvent('changeRequest');
442 * Event fired when attribute changes have resulted in the calculated
443 * current page changing.
446 this.createEvent('pageChange');
449 * Event that fires before the destroy event.
450 * @event beforeDestroy
452 this.createEvent('beforeDestroy');
455 * Event used to trigger cleanup of ui components
458 this.createEvent('destroy');
460 this._selfSubscribe();
464 * Subscribes to instance attribute change events to automate certain
466 * @method _selfSubscribe
469 _selfSubscribe : function () {
470 // Listen for changes to totalRecords and alwaysVisible
471 this.subscribe('totalRecordsChange',this.updateVisibility,this,true);
472 this.subscribe('alwaysVisibleChange',this.updateVisibility,this,true);
474 // Fire the pageChange event when appropriate
475 this.subscribe('totalRecordsChange',this._handleStateChange,this,true);
476 this.subscribe('recordOffsetChange',this._handleStateChange,this,true);
477 this.subscribe('rowsPerPageChange',this._handleStateChange,this,true);
479 // Update recordOffset when totalRecords is reduced below
480 this.subscribe('totalRecordsChange',this._syncRecordOffset,this,true);
484 * Sets recordOffset to the starting index of the previous page when
485 * totalRecords is reduced below the current recordOffset.
486 * @method _syncRecordOffset
487 * @param e {Event} totalRecordsChange event
490 _syncRecordOffset : function (e) {
491 var v = e.newValue,rpp,state;
492 if (e.prevValue !== v) {
493 if (v !== Paginator.VALUE_UNLIMITED) {
494 rpp = this.get('rowsPerPage');
496 if (rpp && this.get('recordOffset') >= v) {
497 state = this.getState({
498 totalRecords : e.prevValue,
499 recordOffset : this.get('recordOffset')
502 this.set('recordOffset', state.before.recordOffset);
503 this._firePageChange(state);
510 * Fires the pageChange event when the state attributes have changed in
511 * such a way as to locate the current recordOffset on a new page.
512 * @method _handleStateChange
513 * @param e {Event} the attribute change event
516 _handleStateChange : function (e) {
517 if (e.prevValue !== e.newValue) {
518 var change = this._state || {},
521 change[e.type.replace(/Change$/,'')] = e.prevValue;
522 state = this.getState(change);
524 if (state.page !== state.before.page) {
526 this._pageChanged = true;
528 this._firePageChange(state);
535 * Fires a pageChange event in the form of a standard attribute change
536 * event with additional properties prevState and newState.
537 * @method _firePageChange
538 * @param state {Object} the result of getState(oldState)
541 _firePageChange : function (state) {
542 if (isObject(state)) {
543 var current = state.before;
545 this.fireEvent('pageChange',{
547 prevValue : state.page,
548 newValue : current.page,
556 * Render the pagination controls per the format attribute into the
557 * specified container nodes.
559 * @return the Paginator instance
562 render : function () {
563 if (this.get('rendered')) {
567 var template = this.get('template'),
568 state = this.getState(),
569 // ex. yui-pg0-1 (first paginator, second container)
570 id_base = Paginator.ID_BASE + this.get('id') + '-',
573 // Assemble the containers, keeping them hidden
574 for (i = 0, len = this._containers.length; i < len; ++i) {
575 this._renderTemplate(this._containers[i],template,id_base+i,true);
578 // Show the containers if appropriate
579 this.updateVisibility();
581 // Set render attribute manually to support its readOnly contract
582 if (this._containers.length) {
583 this.setAttributeConfig('rendered', { value: true });
585 this.fireEvent('render', state);
586 // For backward compatibility
587 this.fireEvent('rendered', state);
594 * Creates the individual ui components and renders them into a container.
596 * @method _renderTemplate
597 * @param container {HTMLElement} where to add the ui components
598 * @param template {String} the template to use as a guide for rendering
599 * @param id_base {String} id base for the container's ui components
600 * @param hide {Boolean} leave the container hidden after assembly
603 _renderTemplate : function (container, template, id_base, hide) {
604 var containerClass = this.get('containerClass'),
611 // Hide the container while its contents are rendered
612 Dom.setStyle(container,'display','none');
614 Dom.addClass(container, containerClass);
616 // Place the template innerHTML, adding marker spans to the template
617 // html to indicate drop zones for ui components
618 container.innerHTML = template.replace(/\{([a-z0-9_ \-]+)\}/gi,
619 '<span class="yui-pg-ui yui-pg-ui-$1"></span>');
621 // Replace each marker with the ui component's render() output
622 markers = Dom.getElementsByClassName('yui-pg-ui','span',container);
624 for (i = 0, len = markers.length; i < len; ++i) {
625 this.renderUIComponent(markers[i], id_base);
629 // Show the container allowing page reflow
630 Dom.setStyle(container,'display','');
635 * Replaces a marker node with a rendered UI component, determined by the
636 * yui-pg-ui-(UI component class name) in the marker's className. e.g.
637 * yui-pg-ui-PageLinks => new YAHOO.widget.Paginator.ui.PageLinks(this)
639 * @method renderUIComponent
640 * @param marker {HTMLElement} the marker node to replace
641 * @param id_base {String} string base the component's generated id
642 * @return the Paginator instance
645 renderUIComponent : function (marker, id_base) {
646 var par = marker.parentNode,
647 name = /yui-pg-ui-(\w+)/.exec(marker.className),
648 UIComp = name && Paginator.ui[name[1]],
651 if (isFunction(UIComp)) {
652 comp = new UIComp(this);
653 if (isFunction(comp.render)) {
654 par.replaceChild(comp.render(id_base),marker);
662 * Removes controls from the page and unhooks events.
665 destroy : function () {
666 this.fireEvent('beforeDestroy');
667 this.fireEvent('destroy');
669 this.setAttributeConfig('rendered',{value:false});
670 this.unsubscribeAll();
674 * Hides the containers if there is only one page of data and attribute
675 * alwaysVisible is false. Conversely, it displays the containers if either
676 * there is more than one page worth of data or alwaysVisible is turned on.
677 * @method updateVisibility
679 updateVisibility : function (e) {
680 var alwaysVisible = this.get('alwaysVisible'),
681 totalRecords, visible, rpp, rppOptions, i, len, opt;
683 if (!e || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
684 totalRecords = this.get('totalRecords');
686 rpp = this.get('rowsPerPage');
687 rppOptions = this.get('rowsPerPageOptions');
689 if (isArray(rppOptions)) {
690 for (i = 0, len = rppOptions.length; i < len; ++i) {
692 // account for value 'all'
693 if (lang.isNumber(opt || opt.value)) {
694 rpp = Math.min(rpp, (opt.value || opt));
699 if (totalRecords !== Paginator.VALUE_UNLIMITED &&
700 totalRecords <= rpp) {
704 visible = visible || alwaysVisible;
706 for (i = 0, len = this._containers.length; i < len; ++i) {
707 Dom.setStyle(this._containers[i],'display',
708 visible ? '' : 'none');
717 * Get the configured container nodes
718 * @method getContainerNodes
719 * @return {Array} array of HTMLElement nodes
721 getContainerNodes : function () {
722 return this._containers;
726 * Get the total number of pages in the data set according to the current
727 * rowsPerPage and totalRecords values. If totalRecords is not set, or
728 * set to YAHOO.widget.Paginator.VALUE_UNLIMITED, returns
729 * YAHOO.widget.Paginator.VALUE_UNLIMITED.
730 * @method getTotalPages
733 getTotalPages : function () {
734 var records = this.get('totalRecords'),
735 perPage = this.get('rowsPerPage');
737 // rowsPerPage not set. Can't calculate
742 if (records === Paginator.VALUE_UNLIMITED) {
743 return Paginator.VALUE_UNLIMITED;
746 return Math.ceil(records/perPage);
750 * Does the requested page have any records?
752 * @param page {number} the page in question
755 hasPage : function (page) {
756 if (!lang.isNumber(page) || page < 1) {
760 var totalPages = this.getTotalPages();
762 return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
766 * Get the page number corresponding to the current record offset.
767 * @method getCurrentPage
770 getCurrentPage : function () {
771 var perPage = this.get('rowsPerPage');
772 if (!perPage || !this.get('totalRecords')) {
775 return Math.floor(this.get('recordOffset') / perPage) + 1;
779 * Are there records on the next page?
780 * @method hasNextPage
783 hasNextPage : function () {
784 var currentPage = this.getCurrentPage(),
785 totalPages = this.getTotalPages();
787 return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
791 * Get the page number of the next page, or null if the current page is the
793 * @method getNextPage
796 getNextPage : function () {
797 return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
801 * Is there a page before the current page?
802 * @method hasPreviousPage
805 hasPreviousPage : function () {
806 return (this.getCurrentPage() > 1);
810 * Get the page number of the previous page, or null if the current page
812 * @method getPreviousPage
815 getPreviousPage : function () {
816 return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
820 * Get the start and end record indexes of the specified page.
821 * @method getPageRecords
822 * @param page {number} (optional) The page (current page if not specified)
823 * @return {Array} [start_index, end_index]
825 getPageRecords : function (page) {
826 if (!lang.isNumber(page)) {
827 page = this.getCurrentPage();
830 var perPage = this.get('rowsPerPage'),
831 records = this.get('totalRecords'),
834 if (!page || !perPage) {
838 start = (page - 1) * perPage;
839 if (records !== Paginator.VALUE_UNLIMITED) {
840 if (start >= records) {
843 end = Math.min(start + perPage, records) - 1;
845 end = start + perPage - 1;
852 * Set the current page to the provided page number if possible.
854 * @param newPage {number} the new page number
855 * @param silent {boolean} whether to forcibly avoid firing the
856 * changeRequest event
858 setPage : function (page,silent) {
859 if (this.hasPage(page) && page !== this.getCurrentPage()) {
860 if (this.get('updateOnChange') || silent) {
861 this.set('recordOffset', (page - 1) * this.get('rowsPerPage'));
863 this.fireEvent('changeRequest',this.getState({'page':page}));
869 * Get the number of rows per page.
870 * @method getRowsPerPage
871 * @return {number} the current setting of the rowsPerPage attribute
873 getRowsPerPage : function () {
874 return this.get('rowsPerPage');
878 * Set the number of rows per page.
879 * @method setRowsPerPage
880 * @param rpp {number} the new number of rows per page
881 * @param silent {boolean} whether to forcibly avoid firing the
882 * changeRequest event
884 setRowsPerPage : function (rpp,silent) {
885 if (Paginator.isNumeric(rpp) && +rpp > 0 &&
886 +rpp !== this.get('rowsPerPage')) {
887 if (this.get('updateOnChange') || silent) {
888 this.set('rowsPerPage',rpp);
890 this.fireEvent('changeRequest',
891 this.getState({'rowsPerPage':+rpp}));
897 * Get the total number of records.
898 * @method getTotalRecords
899 * @return {number} the current setting of totalRecords attribute
901 getTotalRecords : function () {
902 return this.get('totalRecords');
906 * Set the total number of records.
907 * @method setTotalRecords
908 * @param total {number} the new total number of records
909 * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
911 setTotalRecords : function (total,silent) {
912 if (Paginator.isNumeric(total) && +total >= 0 &&
913 +total !== this.get('totalRecords')) {
914 if (this.get('updateOnChange') || silent) {
915 this.set('totalRecords',total);
917 this.fireEvent('changeRequest',
918 this.getState({'totalRecords':+total}));
924 * Get the index of the first record on the current page
925 * @method getStartIndex
926 * @return {number} the index of the first record on the current page
928 getStartIndex : function () {
929 return this.get('recordOffset');
933 * Move the record offset to a new starting index. This will likely cause
934 * the calculated current page to change. You should probably use setPage.
935 * @method setStartIndex
936 * @param offset {number} the new record offset
937 * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
939 setStartIndex : function (offset,silent) {
940 if (Paginator.isNumeric(offset) && +offset >= 0 &&
941 +offset !== this.get('recordOffset')) {
942 if (this.get('updateOnChange') || silent) {
943 this.set('recordOffset',offset);
945 this.fireEvent('changeRequest',
946 this.getState({'recordOffset':+offset}));
952 * Get an object literal describing the current state of the paginator. If
953 * an object literal of proposed values is passed, the proposed state will
954 * be returned as an object literal with the following keys:
956 * <li>paginator - instance of the Paginator</li>
957 * <li>page - number</li>
958 * <li>totalRecords - number</li>
959 * <li>recordOffset - number</li>
960 * <li>rowsPerPage - number</li>
961 * <li>records - [ start_index, end_index ]</li>
962 * <li>before - (OPTIONAL) { state object literal for current state }</li>
966 * @param changes {object} OPTIONAL object literal with proposed values
967 * Supported change keys include:
969 * <li>rowsPerPage</li>
970 * <li>totalRecords</li>
971 * <li>recordOffset OR</li>
975 getState : function (changes) {
976 var UNLIMITED = Paginator.VALUE_UNLIMITED,
977 M = Math, max = M.max, ceil = M.ceil,
978 currentState, state, offset;
980 function normalizeOffset(offset,total,rpp) {
981 if (offset <= 0 || total === 0) {
984 if (total === UNLIMITED || total > offset) {
985 return offset - (offset % rpp);
987 return total - (total % rpp || rpp);
992 totalRecords : this.get('totalRecords'),
993 rowsPerPage : this.get('rowsPerPage'),
994 records : this.getPageRecords()
996 currentState.recordOffset = normalizeOffset(
997 this.get('recordOffset'),
998 currentState.totalRecords,
999 currentState.rowsPerPage);
1000 currentState.page = ceil(currentState.recordOffset /
1001 currentState.rowsPerPage) + 1;
1004 return currentState;
1009 before : currentState,
1011 rowsPerPage : changes.rowsPerPage || currentState.rowsPerPage,
1012 totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
1013 max(changes.totalRecords,UNLIMITED) :
1014 +currentState.totalRecords)
1017 if (state.totalRecords === 0) {
1018 state.recordOffset =
1021 offset = Paginator.isNumeric(changes.page) ?
1022 (changes.page - 1) * state.rowsPerPage :
1023 Paginator.isNumeric(changes.recordOffset) ?
1024 +changes.recordOffset :
1025 currentState.recordOffset;
1027 state.recordOffset = normalizeOffset(offset,
1031 state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
1034 state.records = [ state.recordOffset,
1035 state.recordOffset + state.rowsPerPage - 1 ];
1037 // limit upper index to totalRecords - 1
1038 if (state.totalRecords !== UNLIMITED &&
1039 state.recordOffset < state.totalRecords && state.records &&
1040 state.records[1] > state.totalRecords - 1) {
1041 state.records[1] = state.totalRecords - 1;
1048 * Convenience method to facilitate setting state attributes rowsPerPage,
1049 * totalRecords, recordOffset in batch. Also supports calculating
1050 * recordOffset from state.page if state.recordOffset is not provided.
1051 * Fires only a single pageChange event, if appropriate.
1052 * This will not fire a changeRequest event.
1054 * @param state {Object} Object literal of attribute:value pairs to set
1056 setState : function (state) {
1057 if (isObject(state)) {
1058 // get flux state based on current state with before state as well
1059 this._state = this.getState({});
1061 // use just the state props from the input obj
1064 rowsPerPage : state.rowsPerPage,
1065 totalRecords : state.totalRecords,
1066 recordOffset : state.recordOffset
1069 // calculate recordOffset from page if recordOffset not specified.
1070 // not using lang.isNumber for support of numeric strings
1071 if (state.page && state.recordOffset === undefined) {
1072 state.recordOffset = (state.page - 1) *
1073 (state.rowsPerPage || this.get('rowsPerPage'));
1077 this._pageChanged = false;
1079 for (var k in state) {
1080 if (state.hasOwnProperty(k) && this._configs.hasOwnProperty(k)) {
1081 this.set(k,state[k]);
1085 this._batch = false;
1087 if (this._pageChanged) {
1088 this._pageChanged = false;
1090 this._firePageChange(this.getState(this._state));
1096 lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
1098 YAHOO.widget.Paginator = Paginator;
1102 var Paginator = YAHOO.widget.Paginator,
1104 setId = YAHOO.util.Dom.generateId;
1107 * ui Component to generate the textual report of current pagination status.
1108 * E.g. "Now viewing page 1 of 13".
1110 * @namespace YAHOO.widget.Paginator.ui
1111 * @class CurrentPageReport
1112 * @for YAHOO.widget.Paginator
1115 * @param p {Pagintor} Paginator instance to attach to
1117 Paginator.ui.CurrentPageReport = function (p) {
1120 p.subscribe('recordOffsetChange', this.update,this,true);
1121 p.subscribe('rowsPerPageChange', this.update,this,true);
1122 p.subscribe('totalRecordsChange',this.update,this,true);
1123 p.subscribe('pageReportTemplateChange', this.update,this,true);
1124 p.subscribe('destroy',this.destroy,this,true);
1126 //TODO: make this work
1127 p.subscribe('pageReportClassChange', this.update,this,true);
1131 * Decorates Paginator instances with new attributes. Called during
1132 * Paginator instantiation.
1134 * @param p {Paginator} Paginator instance to decorate
1137 Paginator.ui.CurrentPageReport.init = function (p) {
1140 * CSS class assigned to the span containing the info.
1141 * @attribute pageReportClass
1142 * @default 'yui-pg-current'
1144 p.setAttributeConfig('pageReportClass', {
1145 value : 'yui-pg-current',
1146 validator : l.isString
1150 * Used as innerHTML for the span. Place holders in the form of {name}
1151 * will be replaced with the so named value from the key:value map
1152 * generated by the function held in the pageReportValueGenerator attribute.
1153 * @attribute pageReportTemplate
1154 * @default '({currentPage} of {totalPages})'
1155 * @see pageReportValueGenerator attribute
1157 p.setAttributeConfig('pageReportTemplate', {
1158 value : '({currentPage} of {totalPages})',
1159 validator : l.isString
1163 * Function to generate the value map used to populate the
1164 * pageReportTemplate. The function is passed the Paginator instance as a
1165 * parameter. The default function returns a map with the following keys:
1167 * <li>currentPage</li>
1168 * <li>totalPages</li>
1169 * <li>startIndex</li>
1171 * <li>startRecord</li>
1172 * <li>endRecord</li>
1173 * <li>totalRecords</li>
1175 * @attribute pageReportValueGenarator
1177 p.setAttributeConfig('pageReportValueGenerator', {
1178 value : function (paginator) {
1179 var curPage = paginator.getCurrentPage(),
1180 records = paginator.getPageRecords();
1183 'currentPage' : records ? curPage : 0,
1184 'totalPages' : paginator.getTotalPages(),
1185 'startIndex' : records ? records[0] : 0,
1186 'endIndex' : records ? records[1] : 0,
1187 'startRecord' : records ? records[0] + 1 : 0,
1188 'endRecord' : records ? records[1] + 1 : 0,
1189 'totalRecords': paginator.get('totalRecords')
1192 validator : l.isFunction
1197 * Replace place holders in a string with the named values found in an
1201 * @param template {string} The content string containing place holders
1202 * @param values {object} The key:value pairs used to replace the place holders
1205 Paginator.ui.CurrentPageReport.sprintf = function (template, values) {
1206 return template.replace(/\{([\w\s\-]+)\}/g, function (x,key) {
1207 return (key in values) ? values[key] : '';
1211 Paginator.ui.CurrentPageReport.prototype = {
1214 * Span node containing the formatted info
1223 * Generate the span containing info formatted per the pageReportTemplate
1226 * @param id_base {string} used to create unique ids for generated nodes
1227 * @return {HTMLElement}
1229 render : function (id_base) {
1230 this.span = document.createElement('span');
1231 this.span.className = this.paginator.get('pageReportClass');
1232 setId(this.span, id_base + '-page-report');
1239 * Regenerate the content of the span if appropriate. Calls
1240 * CurrentPageReport.sprintf with the value of the pageReportTemplate
1241 * attribute and the value map returned from pageReportValueGenerator
1244 * @param e {CustomEvent} The calling change event
1246 update : function (e) {
1247 if (e && e.prevValue === e.newValue) {
1251 this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
1252 this.paginator.get('pageReportTemplate'),
1253 this.paginator.get('pageReportValueGenerator')(this.paginator));
1257 * Removes the link/span node and clears event listeners
1262 destroy : function () {
1263 this.span.parentNode.removeChild(this.span);
1272 var Paginator = YAHOO.widget.Paginator,
1274 setId = YAHOO.util.Dom.generateId;
1277 * ui Component to generate the page links
1279 * @namespace YAHOO.widget.Paginator.ui
1281 * @for YAHOO.widget.Paginator
1284 * @param p {Pagintor} Paginator instance to attach to
1286 Paginator.ui.PageLinks = function (p) {
1289 p.subscribe('recordOffsetChange',this.update,this,true);
1290 p.subscribe('rowsPerPageChange',this.update,this,true);
1291 p.subscribe('totalRecordsChange',this.update,this,true);
1292 p.subscribe('pageLinksChange', this.rebuild,this,true);
1293 p.subscribe('pageLinkClassChange', this.rebuild,this,true);
1294 p.subscribe('currentPageClassChange', this.rebuild,this,true);
1295 p.subscribe('destroy',this.destroy,this,true);
1297 //TODO: Make this work
1298 p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
1302 * Decorates Paginator instances with new attributes. Called during
1303 * Paginator instantiation.
1305 * @param p {Paginator} Paginator instance to decorate
1308 Paginator.ui.PageLinks.init = function (p) {
1311 * CSS class assigned to each page link/span.
1312 * @attribute pageLinkClass
1313 * @default 'yui-pg-page'
1315 p.setAttributeConfig('pageLinkClass', {
1316 value : 'yui-pg-page',
1317 validator : l.isString
1321 * CSS class assigned to the current page span.
1322 * @attribute currentPageClass
1323 * @default 'yui-pg-current-page'
1325 p.setAttributeConfig('currentPageClass', {
1326 value : 'yui-pg-current-page',
1327 validator : l.isString
1331 * CSS class assigned to the span containing the page links.
1332 * @attribute pageLinksContainerClass
1333 * @default 'yui-pg-pages'
1335 p.setAttributeConfig('pageLinksContainerClass', {
1336 value : 'yui-pg-pages',
1337 validator : l.isString
1341 * Maximum number of page links to display at one time.
1342 * @attribute pageLinks
1345 p.setAttributeConfig('pageLinks', {
1347 validator : Paginator.isNumeric
1351 * Function used generate the innerHTML for each page link/span. The
1352 * function receives as parameters the page number and a reference to the
1354 * @attribute pageLabelBuilder
1355 * @default function (page, paginator) { return page; }
1357 p.setAttributeConfig('pageLabelBuilder', {
1358 value : function (page, paginator) { return page; },
1359 validator : l.isFunction
1363 * Function used generate the title for each page link. The
1364 * function receives as parameters the page number and a reference to the
1366 * @attribute pageTitleBuilder
1367 * @default function (page, paginator) { return page; }
1369 p.setAttributeConfig('pageTitleBuilder', {
1370 value : function (page, paginator) { return "Page " + page; },
1371 validator : l.isFunction
1376 * Calculates start and end page numbers given a current page, attempting
1377 * to keep the current page in the middle
1379 * @method calculateRange
1380 * @param {int} currentPage The current page
1381 * @param {int} totalPages (optional) Maximum number of pages
1382 * @param {int} numPages (optional) Preferred number of pages in range
1383 * @return {Array} [start_page_number, end_page_number]
1385 Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
1386 var UNLIMITED = Paginator.VALUE_UNLIMITED,
1389 // Either has no pages, or unlimited pages. Show none.
1390 if (!currentPage || numPages === 0 || totalPages === 0 ||
1391 (totalPages === UNLIMITED && numPages === UNLIMITED)) {
1395 // Limit requested pageLinks if there are fewer totalPages
1396 if (totalPages !== UNLIMITED) {
1397 numPages = numPages === UNLIMITED ?
1399 Math.min(numPages,totalPages);
1402 // Determine start and end, trying to keep current in the middle
1403 start = Math.max(1,Math.ceil(currentPage - (numPages/2)));
1404 if (totalPages === UNLIMITED) {
1405 end = start + numPages - 1;
1407 end = Math.min(totalPages, start + numPages - 1);
1410 // Adjust the start index when approaching the last page
1411 delta = numPages - (end - start + 1);
1412 start = Math.max(1, start - delta);
1418 Paginator.ui.PageLinks.prototype = {
1429 * Span node containing the page links
1430 * @property container
1438 * Generate the nodes and return the container node containing page links
1439 * appropriate to the current pagination state.
1441 * @param id_base {string} used to create unique ids for generated nodes
1442 * @return {HTMLElement}
1444 render : function (id_base) {
1445 var p = this.paginator;
1448 this.container = document.createElement('span');
1449 setId(this.container, id_base + '-pages');
1450 this.container.className = p.get('pageLinksContainerClass');
1451 YAHOO.util.Event.on(this.container,'click',this.onClick,this,true);
1453 // Call update, flagging a need to rebuild
1454 this.update({newValue : null, rebuild : true});
1456 return this.container;
1460 * Update the links if appropriate
1462 * @param e {CustomEvent} The calling change event
1464 update : function (e) {
1465 if (e && e.prevValue === e.newValue) {
1469 var p = this.paginator,
1470 currentPage = p.getCurrentPage();
1472 // Replace content if there's been a change
1473 if (this.current !== currentPage || !currentPage || e.rebuild) {
1474 var labelBuilder = p.get('pageLabelBuilder'),
1475 titleBuilder = p.get('pageTitleBuilder'),
1476 range = Paginator.ui.PageLinks.calculateRange(
1479 p.get('pageLinks')),
1483 linkTemplate,i,spanTemplate;
1485 linkTemplate = '<a href="#" class="{class}" page="{page}" title="{title}">{label}</a>';
1486 spanTemplate = '<span class="{class}">{label}</span>';
1487 for (i = start; i <= end; ++i) {
1489 if (i === currentPage) {
1490 content += l.substitute(spanTemplate, {
1491 'class' : p.get('currentPageClass') + ' ' + p.get('pageLinkClass'),
1492 'label' : labelBuilder(i,p)
1496 content += l.substitute(linkTemplate, {
1497 'class' : p.get('pageLinkClass'),
1499 'label' : labelBuilder(i,p),
1500 'title' : titleBuilder(i,p)
1505 this.container.innerHTML = content;
1510 * Force a rebuild of the page links.
1512 * @param e {CustomEvent} The calling change event
1514 rebuild : function (e) {
1520 * Removes the page links container node and clears event listeners
1524 destroy : function () {
1525 YAHOO.util.Event.purgeElement(this.container,true);
1526 this.container.parentNode.removeChild(this.container);
1527 this.container = null;
1531 * Listener for the container's onclick event. Looks for qualifying link
1532 * clicks, and pulls the page number from the link's page attribute.
1533 * Sends link's page attribute to the Paginator's setPage method.
1535 * @param e {DOMEvent} The click event
1537 onClick : function (e) {
1538 var t = YAHOO.util.Event.getTarget(e);
1539 if (t && YAHOO.util.Dom.hasClass(t,
1540 this.paginator.get('pageLinkClass'))) {
1542 YAHOO.util.Event.stopEvent(e);
1544 this.paginator.setPage(parseInt(t.getAttribute('page'),10));
1553 var Paginator = YAHOO.widget.Paginator,
1555 setId = YAHOO.util.Dom.generateId;
1558 * ui Component to generate the link to jump to the first page.
1560 * @namespace YAHOO.widget.Paginator.ui
1561 * @class FirstPageLink
1562 * @for YAHOO.widget.Paginator
1565 * @param p {Pagintor} Paginator instance to attach to
1567 Paginator.ui.FirstPageLink = function (p) {
1570 p.subscribe('recordOffsetChange',this.update,this,true);
1571 p.subscribe('rowsPerPageChange',this.update,this,true);
1572 p.subscribe('totalRecordsChange',this.update,this,true);
1573 p.subscribe('destroy',this.destroy,this,true);
1575 // TODO: make this work
1576 p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1577 p.subscribe('firstPageLinkClassChange',this.update,this,true);
1581 * Decorates Paginator instances with new attributes. Called during
1582 * Paginator instantiation.
1584 * @param p {Paginator} Paginator instance to decorate
1587 Paginator.ui.FirstPageLink.init = function (p) {
1590 * Used as innerHTML for the first page link/span.
1591 * @attribute firstPageLinkLabel
1592 * @default '<< first'
1594 p.setAttributeConfig('firstPageLinkLabel', {
1595 value : '<< first',
1596 validator : l.isString
1600 * CSS class assigned to the link/span
1601 * @attribute firstPageLinkClass
1602 * @default 'yui-pg-first'
1604 p.setAttributeConfig('firstPageLinkClass', {
1605 value : 'yui-pg-first',
1606 validator : l.isString
1610 * Used as title for the first page link.
1611 * @attribute firstPageLinkTitle
1612 * @default 'First Page'
1614 p.setAttributeConfig('firstPageLinkTitle', {
1615 value : 'First Page',
1616 validator : l.isString
1620 // Instance members and methods
1621 Paginator.ui.FirstPageLink.prototype = {
1624 * The currently placed HTMLElement node
1640 * Span node (inactive link)
1648 * Generate the nodes and return the appropriate node given the current
1651 * @param id_base {string} used to create unique ids for generated nodes
1652 * @return {HTMLElement}
1654 render : function (id_base) {
1655 var p = this.paginator,
1656 c = p.get('firstPageLinkClass'),
1657 label = p.get('firstPageLinkLabel'),
1658 title = p.get('firstPageLinkTitle');
1660 this.link = document.createElement('a');
1661 this.span = document.createElement('span');
1663 setId(this.link, id_base + '-first-link');
1664 this.link.href = '#';
1665 this.link.className = c;
1666 this.link.innerHTML = label;
1667 this.link.title = title;
1668 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1670 setId(this.span, id_base + '-first-span');
1671 this.span.className = c;
1672 this.span.innerHTML = label;
1674 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1675 return this.current;
1679 * Swap the link and span nodes if appropriate.
1681 * @param e {CustomEvent} The calling change event
1683 update : function (e) {
1684 if (e && e.prevValue === e.newValue) {
1688 var par = this.current ? this.current.parentNode : null;
1689 if (this.paginator.getCurrentPage() > 1) {
1690 if (par && this.current === this.span) {
1691 par.replaceChild(this.link,this.current);
1692 this.current = this.link;
1695 if (par && this.current === this.link) {
1696 par.replaceChild(this.span,this.current);
1697 this.current = this.span;
1703 * Removes the link/span node and clears event listeners
1708 destroy : function () {
1709 YAHOO.util.Event.purgeElement(this.link);
1710 this.current.parentNode.removeChild(this.current);
1711 this.link = this.span = null;
1715 * Listener for the link's onclick event. Pass new value to setPage method.
1717 * @param e {DOMEvent} The click event
1719 onClick : function (e) {
1720 YAHOO.util.Event.stopEvent(e);
1721 this.paginator.setPage(1);
1728 var Paginator = YAHOO.widget.Paginator,
1730 setId = YAHOO.util.Dom.generateId;
1733 * ui Component to generate the link to jump to the last page.
1735 * @namespace YAHOO.widget.Paginator.ui
1736 * @class LastPageLink
1737 * @for YAHOO.widget.Paginator
1740 * @param p {Pagintor} Paginator instance to attach to
1742 Paginator.ui.LastPageLink = function (p) {
1745 p.subscribe('recordOffsetChange',this.update,this,true);
1746 p.subscribe('rowsPerPageChange',this.update,this,true);
1747 p.subscribe('totalRecordsChange',this.update,this,true);
1748 p.subscribe('destroy',this.destroy,this,true);
1750 // TODO: make this work
1751 p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1752 p.subscribe('lastPageLinkClassChange', this.update,this,true);
1756 * Decorates Paginator instances with new attributes. Called during
1757 * Paginator instantiation.
1759 * @param paginator {Paginator} Paginator instance to decorate
1762 Paginator.ui.LastPageLink.init = function (p) {
1765 * Used as innerHTML for the last page link/span.
1766 * @attribute lastPageLinkLabel
1767 * @default 'last >>'
1769 p.setAttributeConfig('lastPageLinkLabel', {
1770 value : 'last >>',
1771 validator : l.isString
1775 * CSS class assigned to the link/span
1776 * @attribute lastPageLinkClass
1777 * @default 'yui-pg-last'
1779 p.setAttributeConfig('lastPageLinkClass', {
1780 value : 'yui-pg-last',
1781 validator : l.isString
1785 * Used as title for the last page link.
1786 * @attribute lastPageLinkTitle
1787 * @default 'Last Page'
1789 p.setAttributeConfig('lastPageLinkTitle', {
1790 value : 'Last Page',
1791 validator : l.isString
1796 Paginator.ui.LastPageLink.prototype = {
1799 * Currently placed HTMLElement node
1807 * Link HTMLElement node
1815 * Span node (inactive link)
1823 * Empty place holder node for when the last page link is inappropriate to
1824 * display in any form (unlimited paging).
1833 * Generate the nodes and return the appropriate node given the current
1836 * @param id_base {string} used to create unique ids for generated nodes
1837 * @return {HTMLElement}
1839 render : function (id_base) {
1840 var p = this.paginator,
1841 c = p.get('lastPageLinkClass'),
1842 label = p.get('lastPageLinkLabel'),
1843 last = p.getTotalPages(),
1844 title = p.get('lastPageLinkTitle');
1846 this.link = document.createElement('a');
1847 this.span = document.createElement('span');
1848 this.na = this.span.cloneNode(false);
1850 setId(this.link, id_base + '-last-link');
1851 this.link.href = '#';
1852 this.link.className = c;
1853 this.link.innerHTML = label;
1854 this.link.title = title;
1855 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
1857 setId(this.span, id_base + '-last-span');
1858 this.span.className = c;
1859 this.span.innerHTML = label;
1861 setId(this.na, id_base + '-last-na');
1864 case Paginator.VALUE_UNLIMITED :
1865 this.current = this.na; break;
1866 case p.getCurrentPage() :
1867 this.current = this.span; break;
1869 this.current = this.link;
1872 return this.current;
1876 * Swap the link, span, and na nodes if appropriate.
1878 * @param e {CustomEvent} The calling change event (ignored)
1880 update : function (e) {
1881 if (e && e.prevValue === e.newValue) {
1885 var par = this.current ? this.current.parentNode : null,
1889 switch (this.paginator.getTotalPages()) {
1890 case Paginator.VALUE_UNLIMITED :
1891 after = this.na; break;
1892 case this.paginator.getCurrentPage() :
1893 after = this.span; break;
1896 if (this.current !== after) {
1897 par.replaceChild(after,this.current);
1898 this.current = after;
1904 * Removes the link/span node and clears event listeners
1908 destroy : function () {
1909 YAHOO.util.Event.purgeElement(this.link);
1910 this.current.parentNode.removeChild(this.current);
1911 this.link = this.span = null;
1915 * Listener for the link's onclick event. Passes to setPage method.
1917 * @param e {DOMEvent} The click event
1919 onClick : function (e) {
1920 YAHOO.util.Event.stopEvent(e);
1921 this.paginator.setPage(this.paginator.getTotalPages());
1928 var Paginator = YAHOO.widget.Paginator,
1930 setId = YAHOO.util.Dom.generateId;
1933 * ui Component to generate the link to jump to the next page.
1935 * @namespace YAHOO.widget.Paginator.ui
1936 * @class NextPageLink
1937 * @for YAHOO.widget.Paginator
1940 * @param p {Pagintor} Paginator instance to attach to
1942 Paginator.ui.NextPageLink = function (p) {
1945 p.subscribe('recordOffsetChange', this.update,this,true);
1946 p.subscribe('rowsPerPageChange', this.update,this,true);
1947 p.subscribe('totalRecordsChange', this.update,this,true);
1948 p.subscribe('destroy',this.destroy,this,true);
1950 // TODO: make this work
1951 p.subscribe('nextPageLinkLabelChange', this.update,this,true);
1952 p.subscribe('nextPageLinkClassChange', this.update,this,true);
1956 * Decorates Paginator instances with new attributes. Called during
1957 * Paginator instantiation.
1959 * @param p {Paginator} Paginator instance to decorate
1962 Paginator.ui.NextPageLink.init = function (p) {
1965 * Used as innerHTML for the next page link/span.
1966 * @attribute nextPageLinkLabel
1967 * @default 'next >'
1969 p.setAttributeConfig('nextPageLinkLabel', {
1970 value : 'next >',
1971 validator : l.isString
1975 * CSS class assigned to the link/span
1976 * @attribute nextPageLinkClass
1977 * @default 'yui-pg-next'
1979 p.setAttributeConfig('nextPageLinkClass', {
1980 value : 'yui-pg-next',
1981 validator : l.isString
1985 * Used as title for the next page link.
1986 * @attribute nextPageLinkTitle
1987 * @default 'Next Page'
1989 p.setAttributeConfig('nextPageLinkTitle', {
1990 value : 'Next Page',
1991 validator : l.isString
1996 Paginator.ui.NextPageLink.prototype = {
1999 * Currently placed HTMLElement node
2015 * Span node (inactive link)
2024 * Generate the nodes and return the appropriate node given the current
2027 * @param id_base {string} used to create unique ids for generated nodes
2028 * @return {HTMLElement}
2030 render : function (id_base) {
2031 var p = this.paginator,
2032 c = p.get('nextPageLinkClass'),
2033 label = p.get('nextPageLinkLabel'),
2034 last = p.getTotalPages(),
2035 title = p.get('nextPageLinkTitle');
2037 this.link = document.createElement('a');
2038 this.span = document.createElement('span');
2040 setId(this.link, id_base + '-next-link');
2041 this.link.href = '#';
2042 this.link.className = c;
2043 this.link.innerHTML = label;
2044 this.link.title = title;
2045 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
2047 setId(this.span, id_base + '-next-span');
2048 this.span.className = c;
2049 this.span.innerHTML = label;
2051 this.current = p.getCurrentPage() === last ? this.span : this.link;
2053 return this.current;
2057 * Swap the link and span nodes if appropriate.
2059 * @param e {CustomEvent} The calling change event
2061 update : function (e) {
2062 if (e && e.prevValue === e.newValue) {
2066 var last = this.paginator.getTotalPages(),
2067 par = this.current ? this.current.parentNode : null;
2069 if (this.paginator.getCurrentPage() !== last) {
2070 if (par && this.current === this.span) {
2071 par.replaceChild(this.link,this.current);
2072 this.current = this.link;
2074 } else if (this.current === this.link) {
2076 par.replaceChild(this.span,this.current);
2077 this.current = this.span;
2083 * Removes the link/span node and clears event listeners
2087 destroy : function () {
2088 YAHOO.util.Event.purgeElement(this.link);
2089 this.current.parentNode.removeChild(this.current);
2090 this.link = this.span = null;
2094 * Listener for the link's onclick event. Passes to setPage method.
2096 * @param e {DOMEvent} The click event
2098 onClick : function (e) {
2099 YAHOO.util.Event.stopEvent(e);
2100 this.paginator.setPage(this.paginator.getNextPage());
2107 var Paginator = YAHOO.widget.Paginator,
2109 setId = YAHOO.util.Dom.generateId;
2112 * ui Component to generate the link to jump to the previous page.
2114 * @namespace YAHOO.widget.Paginator.ui
2115 * @class PreviousPageLink
2116 * @for YAHOO.widget.Paginator
2119 * @param p {Pagintor} Paginator instance to attach to
2121 Paginator.ui.PreviousPageLink = function (p) {
2124 p.subscribe('recordOffsetChange',this.update,this,true);
2125 p.subscribe('rowsPerPageChange',this.update,this,true);
2126 p.subscribe('totalRecordsChange',this.update,this,true);
2127 p.subscribe('destroy',this.destroy,this,true);
2129 // TODO: make this work
2130 p.subscribe('previousPageLinkLabelChange',this.update,this,true);
2131 p.subscribe('previousPageLinkClassChange',this.update,this,true);
2135 * Decorates Paginator instances with new attributes. Called during
2136 * Paginator instantiation.
2138 * @param p {Paginator} Paginator instance to decorate
2141 Paginator.ui.PreviousPageLink.init = function (p) {
2144 * Used as innerHTML for the previous page link/span.
2145 * @attribute previousPageLinkLabel
2146 * @default '< prev'
2148 p.setAttributeConfig('previousPageLinkLabel', {
2149 value : '< prev',
2150 validator : l.isString
2154 * CSS class assigned to the link/span
2155 * @attribute previousPageLinkClass
2156 * @default 'yui-pg-previous'
2158 p.setAttributeConfig('previousPageLinkClass', {
2159 value : 'yui-pg-previous',
2160 validator : l.isString
2164 * Used as title for the previous page link.
2165 * @attribute previousPageLinkTitle
2166 * @default 'Previous Page'
2168 p.setAttributeConfig('previousPageLinkTitle', {
2169 value : 'Previous Page',
2170 validator : l.isString
2175 Paginator.ui.PreviousPageLink.prototype = {
2178 * Currently placed HTMLElement node
2194 * Span node (inactive link)
2203 * Generate the nodes and return the appropriate node given the current
2206 * @param id_base {string} used to create unique ids for generated nodes
2207 * @return {HTMLElement}
2209 render : function (id_base) {
2210 var p = this.paginator,
2211 c = p.get('previousPageLinkClass'),
2212 label = p.get('previousPageLinkLabel'),
2213 title = p.get('previousPageLinkTitle');
2215 this.link = document.createElement('a');
2216 this.span = document.createElement('span');
2218 setId(this.link, id_base + '-prev-link');
2219 this.link.href = '#';
2220 this.link.className = c;
2221 this.link.innerHTML = label;
2222 this.link.title = title;
2223 YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
2225 setId(this.span, id_base + '-prev-span');
2226 this.span.className = c;
2227 this.span.innerHTML = label;
2229 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
2230 return this.current;
2234 * Swap the link and span nodes if appropriate.
2236 * @param e {CustomEvent} The calling change event
2238 update : function (e) {
2239 if (e && e.prevValue === e.newValue) {
2243 var par = this.current ? this.current.parentNode : null;
2244 if (this.paginator.getCurrentPage() > 1) {
2245 if (par && this.current === this.span) {
2246 par.replaceChild(this.link,this.current);
2247 this.current = this.link;
2250 if (par && this.current === this.link) {
2251 par.replaceChild(this.span,this.current);
2252 this.current = this.span;
2258 * Removes the link/span node and clears event listeners
2262 destroy : function () {
2263 YAHOO.util.Event.purgeElement(this.link);
2264 this.current.parentNode.removeChild(this.current);
2265 this.link = this.span = null;
2269 * Listener for the link's onclick event. Passes to setPage method.
2271 * @param e {DOMEvent} The click event
2273 onClick : function (e) {
2274 YAHOO.util.Event.stopEvent(e);
2275 this.paginator.setPage(this.paginator.getPreviousPage());
2282 var Paginator = YAHOO.widget.Paginator,
2284 setId = YAHOO.util.Dom.generateId;
2287 * ui Component to generate the rows-per-page dropdown
2289 * @namespace YAHOO.widget.Paginator.ui
2290 * @class RowsPerPageDropdown
2291 * @for YAHOO.widget.Paginator
2294 * @param p {Pagintor} Paginator instance to attach to
2296 Paginator.ui.RowsPerPageDropdown = function (p) {
2299 p.subscribe('rowsPerPageChange',this.update,this,true);
2300 p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
2301 p.subscribe('totalRecordsChange',this._handleTotalRecordsChange,this,true);
2302 p.subscribe('destroy',this.destroy,this,true);
2304 // TODO: make this work
2305 p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
2309 * Decorates Paginator instances with new attributes. Called during
2310 * Paginator instantiation.
2312 * @param p {Paginator} Paginator instance to decorate
2315 Paginator.ui.RowsPerPageDropdown.init = function (p) {
2318 * Array of available rows-per-page sizes. Converted into select options.
2319 * Array values may be positive integers or object literals in the form<br>
2320 * { value : NUMBER, text : STRING }
2321 * @attribute rowsPerPageOptions
2324 p.setAttributeConfig('rowsPerPageOptions', {
2326 validator : l.isArray
2330 * CSS class assigned to the select node
2331 * @attribute rowsPerPageDropdownClass
2332 * @default 'yui-pg-rpp-options'
2334 p.setAttributeConfig('rowsPerPageDropdownClass', {
2335 value : 'yui-pg-rpp-options',
2336 validator : l.isString
2340 Paginator.ui.RowsPerPageDropdown.prototype = {
2352 * option node for the optional All value
2361 * Generate the select and option nodes and returns the select node.
2363 * @param id_base {string} used to create unique ids for generated nodes
2364 * @return {HTMLElement}
2366 render : function (id_base) {
2367 this.select = document.createElement('select');
2368 setId(this.select, id_base + '-rpp');
2369 this.select.className = this.paginator.get('rowsPerPageDropdownClass');
2370 this.select.title = 'Rows per page';
2372 YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2380 * (Re)generate the select options.
2383 rebuild : function (e) {
2384 var p = this.paginator,
2386 options = p.get('rowsPerPageOptions'),
2391 for (i = 0, len = options.length; i < len; ++i) {
2393 opt = sel.options[i] ||
2394 sel.appendChild(document.createElement('option'));
2395 val = l.isValue(cfg.value) ? cfg.value : cfg;
2396 opt.text = l.isValue(cfg.text) ? cfg.text : cfg;
2398 if (l.isString(val) && val.toLowerCase() === 'all') {
2400 opt.value = p.get('totalRecords');
2407 while (sel.options.length > options.length) {
2408 sel.removeChild(sel.firstChild);
2415 * Select the appropriate option if changed.
2417 * @param e {CustomEvent} The calling change event
2419 update : function (e) {
2420 if (e && e.prevValue === e.newValue) {
2424 var rpp = this.paginator.get('rowsPerPage')+'',
2425 options = this.select.options,
2428 for (i = 0, len = options.length; i < len; ++i) {
2429 if (options[i].value === rpp) {
2430 options[i].selected = true;
2437 * Listener for the select's onchange event. Sent to setRowsPerPage method.
2439 * @param e {DOMEvent} The change event
2441 onChange : function (e) {
2442 this.paginator.setRowsPerPage(
2443 parseInt(this.select.options[this.select.selectedIndex].value,10));
2447 * Updates the all option value (and Paginator's rowsPerPage attribute if
2448 * necessary) in response to a change in the Paginator's totalRecords.
2450 * @method _handleTotalRecordsChange
2451 * @param e {Event} attribute change event
2454 _handleTotalRecordsChange : function (e) {
2455 if (!this.all || (e && e.prevValue === e.newValue)) {
2459 this.all.value = e.newValue;
2460 if (this.all.selected) {
2461 this.paginator.set('rowsPerPage',e.newValue);
2466 * Removes the select node and clears event listeners
2470 destroy : function () {
2471 YAHOO.util.Event.purgeElement(this.select);
2472 this.select.parentNode.removeChild(this.select);
2480 var Paginator = YAHOO.widget.Paginator,
2482 setId = YAHOO.util.Dom.generateId;
2485 * ui Component to generate the jump-to-page dropdown
2487 * @namespace YAHOO.widget.Paginator.ui
2488 * @class JumpToPageDropdown
2489 * @for YAHOO.widget.Paginator
2492 * @param p {Pagintor} Paginator instance to attach to
2494 Paginator.ui.JumpToPageDropdown = function (p) {
2497 p.subscribe('rowsPerPageChange',this.rebuild,this,true);
2498 p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
2499 p.subscribe('pageChange',this.update,this,true);
2500 p.subscribe('totalRecordsChange',this.rebuild,this,true);
2501 p.subscribe('destroy',this.destroy,this,true);
2506 * Decorates Paginator instances with new attributes. Called during
2507 * Paginator instantiation.
2509 * @param p {Paginator} Paginator instance to decorate
2512 Paginator.ui.JumpToPageDropdown.init = function (p) {
2517 * CSS class assigned to the select node
2518 * @attribute jumpToPageDropdownClass
2519 * @default 'yui-pg-jtp-options'
2521 p.setAttributeConfig('jumpToPageDropdownClass', {
2522 value : 'yui-pg-jtp-options',
2523 validator : l.isString
2527 Paginator.ui.JumpToPageDropdown.prototype = {
2540 * Generate the select and option nodes and returns the select node.
2542 * @param id_base {string} used to create unique ids for generated nodes
2543 * @return {HTMLElement}
2545 render : function (id_base) {
2546 this.select = document.createElement('select');
2547 setId(this.select, id_base + '-jtp');
2548 this.select.className = this.paginator.get('jumpToPageDropdownClass');
2549 this.select.title = 'Jump to page';
2551 YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
2559 * (Re)generate the select options.
2562 rebuild : function (e) {
2563 var p = this.paginator,
2565 numPages = p.getTotalPages(),
2570 for (i = 0, len = numPages; i < len; ++i ) {
2571 opt = sel.options[i] ||
2572 sel.appendChild(document.createElement('option'));
2574 opt.innerHTML = i + 1;
2581 for ( i = numPages, len = sel.options.length ; i < len ; i++ ) {
2582 sel.removeChild(sel.lastChild);
2589 * Select the appropriate option if changed.
2591 * @param e {CustomEvent} The calling change event
2593 update : function (e) {
2595 if (e && e.prevValue === e.newValue) {
2599 var cp = this.paginator.getCurrentPage()+'',
2600 options = this.select.options,
2603 for (i = 0, len = options.length; i < len; ++i) {
2604 if (options[i].value === cp) {
2605 options[i].selected = true;
2612 * Listener for the select's onchange event. Sent to setPage method.
2614 * @param e {DOMEvent} The change event
2616 onChange : function (e) {
2617 this.paginator.setPage(
2618 parseInt(this.select.options[this.select.selectedIndex].value,false));
2624 * Removes the select node and clears event listeners
2628 destroy : function () {
2629 YAHOO.util.Event.purgeElement(this.select);
2630 this.select.parentNode.removeChild(this.select);
2636 YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.9.0", build: "2800"});