2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('datatable-paginator', function (Y, NAME) {
11 Adds support for paging through data in the DataTable.
14 @submodule datatable-paginator
20 PaginatorTemplates = Y.DataTable.Templates.Paginator,
22 getClassName = Y.ClassNameManager.getClassName,
23 CLASS_DISABLED = getClassName(NAME, 'control-disabled'),
24 EVENT_UI = 'paginator:ui';
28 @class DataTable.Paginator.Model
32 Model = Y.Base.create('dt-pg-model', Y.Model, [Y.Paginator.Core]),
35 @class DataTable.Paginator.View
39 View = Y.Base.create('dt-pg-view', Y.View, [], {
41 Array of event handles to keep track of what should be destroyed later
43 @property _eventHandles
50 Template for this view's container.
51 @property containerTemplate
53 @default '<div class="yui3-datatable-paginator"/>'
56 containerTemplate: '<div class="{paginator}"/>',
59 Template for content. Helps maintain order of controls.
60 @property contentTemplate
62 @default '{buttons}{goto}{perPage}'
65 contentTemplate: '{buttons}{goto}{perPage}',
68 Disables ad-hoc ATTRS for our view.
70 @property _allowAdHocAttrs
75 _allowAdHocAttrs: false,
78 Sets classnames on the templates and bind events
82 initializer: function () {
83 this.containerTemplate = sub(this.containerTemplate, {
84 paginator: getClassName(NAME)
88 this._initClassNames();
99 var model = this.get('model'),
100 content = sub(this.contentTemplate, {
101 'buttons': this._buildButtonsGroup(),
102 'goto': this._buildGotoGroup(),
103 'perPage': this._buildPerPageGroup()
106 this.get('container').append(content);
109 this._rendered = true;
111 this._updateControlsUI(model.get('page'));
112 this._updateItemsPerPageUI(model.get('itemsPerPage'));
121 attachEvents: function () {
122 View.superclass.attachEvents.apply(this, arguments);
124 var container = this.get('container');
126 if (!this.classNames) {
127 this._initClassNames();
130 this._attachedViewEvents.push(
131 container.delegate('click', this._controlClick, '.' + this.classNames.control, this),
132 this.get('model').after('change', this._modelChange, this)
135 container.all('form').each(Y.bind(function (frm) {
136 this._attachedViewEvents.push(
137 frm.after('submit', this._controlSubmit, this)
141 container.all('select').each(Y.bind(function (sel) {
142 this._attachedViewEvents.push(
143 sel.after('change', this._controlChange, this)
150 Returns a string built from the button and buttons templates.
152 @method _buildButtonsGroup
156 _buildButtonsGroup: function () {
157 var strings = this.get('strings'),
158 classNames = this.classNames,
161 buttons = PaginatorTemplates.button({
162 type: 'first', label: strings.first, classNames: classNames
164 PaginatorTemplates.button({
165 type: 'prev', label: strings.prev, classNames: classNames
167 PaginatorTemplates.button({
168 type: 'next', label: strings.next, classNames: classNames
170 PaginatorTemplates.button({
171 type: 'last', label: strings.last, classNames: classNames
174 return PaginatorTemplates.buttons({
175 classNames: classNames,
182 Returns a string built from the gotoPage template.
184 @method _buildGotoGroup
188 _buildGotoGroup: function () {
190 return PaginatorTemplates.gotoPage({
191 classNames: this.classNames,
192 strings: this.get('strings'),
193 page: this.get('model').get('page')
198 Returns a string built from the perPage template
200 @method _buildPerPageGroup
204 _buildPerPageGroup: function () {
205 var options = this.get('pageSizes'),
206 rowsPerPage = this.get('model').get('rowsPerPage'),
211 for (i = 0, len = options.length; i < len; i++ ) {
214 if (typeof option !== 'object') {
220 option.selected = (option.value === rowsPerPage) ? ' selected' : '';
223 return PaginatorTemplates.perPage({
224 classNames: this.classNames,
225 strings: this.get('strings'),
226 options: this.get('pageSizes')
232 Update the UI after the model has changed.
235 @param {EventFacade} e
238 _modelChange: function (e) {
239 var changed = e.changed,
240 page = (changed && changed.page),
241 itemsPerPage = (changed && changed.itemsPerPage);
244 this._updateControlsUI(page.newVal);
247 this._updateItemsPerPageUI(itemsPerPage.newVal);
249 this._updateControlsUI(e.target.get('page'));
256 Updates the button controls and the gotoPage form
258 @method _updateControlsUI
259 @param {Number} val Page number to set the UI input to
262 _updateControlsUI: function (val) {
263 if (!this._rendered) {
267 var model = this.get('model'),
268 controlClass = '.' + this.classNames.control,
269 container = this.get('container'),
270 hasPrev = model.hasPrevPage(),
271 hasNext = model.hasNextPage();
273 container.one(controlClass + '-first')
274 .toggleClass(CLASS_DISABLED, !hasPrev)
275 .set('disabled', !hasPrev);
277 container.one(controlClass + '-prev')
278 .toggleClass(CLASS_DISABLED, !hasPrev)
279 .set('disabled', !hasPrev);
281 container.one(controlClass + '-next')
282 .toggleClass(CLASS_DISABLED, !hasNext)
283 .set('disabled', !hasNext);
285 container.one(controlClass + '-last')
286 .toggleClass(CLASS_DISABLED, !hasNext)
287 .set('disabled', !hasNext);
289 container.one('form input').set('value', val);
293 Updates the drop down select for items per page
295 @method _updateItemsPerPageUI
296 @param {Number} val Number of items to display per page
299 _updateItemsPerPageUI: function (val) {
300 if (!this._rendered) {
304 this.get('container').one('select').set('value', val);
308 Fire EVENT_UI when an enabled control button is clicked
310 @method _controlClick
311 @param {EventFacade} e
314 _controlClick: function (e) { // buttons
316 var control = e.currentTarget;
317 // register click events from the four control buttons
318 if (control.hasClass(CLASS_DISABLED)) {
321 this.fire(EVENT_UI, {
322 type: control.getData('type'),
323 val: control.getData('page') || null
328 Fire EVENT_UI with `type:perPage` after the select drop down changes
330 @method _controlChange
331 @param {EventFacade} e
334 _controlChange: function (e) {
336 // register change events from the perPage select
337 if ( e.target.hasClass(CLASS_DISABLED) ) {
341 val = e.target.get('value');
342 this.fire(EVENT_UI, { type: 'perPage', val: parseInt(val, 10) });
346 Fire EVENT_UI with `type:page` after form is submitted
348 @method _controlSubmit
349 @param {EventFacade} e
352 _controlSubmit: function (e) {
353 if ( e.target.hasClass(CLASS_DISABLED) ) {
357 // the only form we have is the go to page form
360 input = e.target.one('input');
361 this.fire(EVENT_UI, { type: 'page', val: input.get('value') });
365 Initializes classnames to be used with the templates
367 @method _initClassNames
370 _initClassNames: function () {
372 control: getClassName(NAME, 'control'),
373 controls: getClassName(NAME, 'controls'),
374 group: getClassName(NAME, 'group'),
375 perPage: getClassName(NAME, 'per-page')
380 Initializes strings used for internationalization
385 _initStrings: function () {
386 // Not a valueFn because other class extensions may want to add to it
387 this.set('strings', Y.mix((this.get('strings') || {}),
388 Y.Intl.get('datatable-paginator')));
393 Returns an Array with default values for the Rows Per Page select option.
394 We had to use a valueFn to enable language string replacement.
397 @method _defPageSizeVal
400 _defPageSizeVal: function () {
403 var str = this.get('strings');
405 return [10, 50, 100, { label: str.showAll, value: -1 }]
411 Array of values used to populate the drop down for items per page
414 @default [ 10, 50, 100, { label: 'Show All', value: -1 } ]
418 valueFn: '_defPageSizeVal'
422 Model used for this view
433 @class DataTable.Paginator
436 function Controller () {}
440 A model instance or a configuration object for the Model.
441 @attribute paginatorModel
442 @type {Y.Model | Object}
447 setter: '_setPaginatorModel',
449 writeOnce: 'initOnly'
453 A pointer to a Model object to be instantiated, or a String off of the
456 This is only used if the `paginatorModel` is a configuration object or
458 @attribute paginatorModelType
459 @type {Y.Model | String}
460 @default 'DataTable.Paginator.Model'
463 paginatorModelType: {
464 getter: '_getConstructor',
465 value: 'DataTable.Paginator.Model',
466 writeOnce: 'initOnly'
470 A pointer to a `Y.View` object to be instantiated. A new view will be
471 created for each location provided. Each view created will be given the
473 @attribute paginatorView
474 @type {Y.View | String}
475 @default 'DataTable.Paginator.View'
479 getter: '_getConstructor',
480 value: 'DataTable.Paginator.View',
481 writeOnce: 'initOnly'
486 Array of values used to populate the values in the Paginator UI allowing
487 the end user to select the number of items to display per page.
490 @default [10, 50, 100, { label: 'Show All', value: -1 }]
494 setter: '_setPageSizesFn',
495 valueFn: '_defPageSizeVal'
498 paginatorStrings: {},
501 Number of rows to display per page. As the UI changes the number of pages
502 to display, this will update to reflect the value selected in the UI
503 @attribute rowsPerPage
504 @type {Number | null}
513 String of `footer` or `header`, a Y.Node, or an Array or any combination
515 @attribute paginatorLocation
516 @type {String | Array | Y.Node}
525 Y.mix(Controller.prototype, {
527 Sets the `paginatorModel` to the first page.
532 firstPage: function () {
533 this.get('paginatorModel').set('page', 1);
538 Sets the `paginatorModel` to the last page.
543 lastPage: function () {
544 var model = this.get('paginatorModel');
545 model.set('page', model.get('totalPages'));
550 Sets the `paginatorModel` to the previous page.
555 previousPage: function () {
556 this.get('paginatorModel').prevPage();
561 Sets the `paginatorModel` to the next page.
566 nextPage: function () {
567 this.get('paginatorModel').nextPage();
572 /// Init and protected
579 initializer: function () {
580 // allow DT to use paged data
581 this._initPaginatorStrings();
584 if (!this._eventHandles.paginatorRender) {
585 this._eventHandles.paginatorRender = Y.Do.after(this._paginatorRender, this, 'render');
590 Renders the paginator into locations and attaches events.
592 @method _paginatorRender
595 _paginatorRender: function () {
596 var model = this.get('paginatorModel');
598 this._paginatorRenderUI();
599 model.after('change', this._afterPaginatorModelChange, this);
600 this.after('dataChange', this._afterDataChangeWithPaginator, this);
601 this.after('rowsPerPageChange', this._afterRowsPerPageChange, this);
602 this.data.after(['add', 'remove', 'change'], this._afterDataUpdatesWithPaginator, this);
604 // ensure our model has the correct totalItems set
605 model.set('itemsPerPage', this.get('rowsPerPage'));
606 model.set('totalItems', this.get('data').size());
610 After the data changes, we ensure we are on the first page and the data
613 @method _afterDataChangeWithPaginator
616 _afterDataChangeWithPaginator: function () {
617 var data = this.get('data'),
618 model = this.get('paginatorModel');
620 model.set('totalItems', data.size());
622 if (model.get('page') !== 1) {
627 data.fire.call(data, 'reset', {
629 models: data._items.concat()
635 After data has changed due to a model being added, removed, or changed,
636 update paginator model totalItems to reflect the changes.
638 @method _afterDataUpdatesWithPaginator
639 @param {EventFacade} e
642 _afterDataUpdatesWithPaginator: function () {
643 var model = this.get('paginatorModel'),
644 data = this.get('data');
646 model.set('totalItems', data.size());
650 After the rowsPerPage changes, update the UI to reflect the new number of
651 rows to be displayed. If the new value is `null`, destroy all instances
654 @method _afterRowsPerPageChange
655 @param {EventFacade} e
658 _afterRowsPerPageChange: function (e) {
659 var data = this.get('data'),
660 model = this.get('paginatorModel'),
663 if (e.newVal !== null) {
665 this._paginatorRenderUI();
667 if (!(data._paged)) {
671 data._paged.index = (model.get('page') - 1) * model.get('itemsPerPage');
672 data._paged.length = model.get('itemsPerPage');
674 } else { // e.newVal === null
676 while(this._pgViews.length) {
677 view = this._pgViews.shift();
678 view.destroy({ remove: true });
679 view._rendered = null;
682 data._paged.index = 0;
683 data._paged.length = null;
686 this.get('paginatorModel').set('itemsPerPage', parseInt(e.newVal, 10));
690 Parse each location and render a new view into each area.
692 @method _paginatorRenderUI
695 _paginatorRenderUI: function () {
696 if (!this.get('rowsPerPage')) {
699 var views = this._pgViews,
700 ViewClass = this.get('paginatorView'),
702 pageSizes: this.get('pageSizes'),
703 model: this.get('paginatorModel')
705 locations = this.get('paginatorLocation');
707 if (!Y.Lang.isArray(locations)) {
708 locations = [locations];
711 if (!views) { // set up initial rendering of views
712 views = this._pgViews = [];
715 // for each placement area, push to views
716 Y.Array.each(locations, function (location) {
717 var view = new ViewClass(viewConfig),
718 container = view.render().get('container'),
721 view.after('*:ui', this._uiPgHandler, this);
724 if (location._node) { // assume Y.Node
725 location.append(container);
726 // remove this container row if the view is ever destroyed
727 this.after('destroy', function (/* e */) {
728 view.destroy({ remove: true });
730 } else if (location === 'footer') { // DT Footer
731 // Render a table footer if there isn't one
733 this.foot = new Y.DataTable.FooterView({ host: this });
735 this.fire('renderFooter', { view: this.foot });
738 // create a row for the paginator to sit in
739 row = Y.Node.create(PaginatorTemplates.rowWrapper({
740 wrapperClass: getClassName(NAME, 'wrapper'),
741 numOfCols: this.get('columns').length
744 row.one('td').append(container);
745 this.foot.tfootNode.append(row);
747 // remove this container row if the view is ever destroyed
748 view.after('destroy', function (/* e */) {
751 } else if (location === 'header') {
752 // 'header' means insert before the table
753 // placement with the caption may need to be addressed
754 if (this.view && this.view.tableNode) {
755 this.view.tableNode.insert(container, 'before');
757 this.get('contentBox').prepend(container);
765 Handles the paginator's UI event into a single location. Updates the
766 `paginatorModel` according to what type is provided.
769 @param {EventFacade} e
772 _uiPgHandler: function (e) {
773 // e.type = control type (first|prev|next|last|page|perPage)
774 // e.val = value based on the control type to pass to the model
775 var model = this.get('paginatorModel');
779 model.set('page', 1);
782 model.set('page', model.get('totalPages'));
785 case 'next': // overflow intentional
786 model[e.type + 'Page']();
789 model.set('page', e.val);
792 model.set('itemsPerPage', e.val);
793 model.set('page', 1);
799 Augments the model list with a paged structure, or updates the paged
800 data. Then fires reset on the model list.
802 @method _afterPaginatorModelChange
803 @param {EventFacade} [e]
806 _afterPaginatorModelChange: function () {
807 var model = this.get('paginatorModel'),
808 data = this.get('data');
813 data._paged.index = (model.get('page') - 1) * model.get('itemsPerPage');
814 data._paged.length = model.get('itemsPerPage');
817 data.fire.call(data, 'reset', {
819 models: data._items.concat()
824 Augments the model list data structure with paged implementations.
826 The model list will contain a method for `getPage` that will return the
827 given number of items listed within the range.
829 `each` will also loop over the items in the page
834 _augmentData: function () {
835 var model = this.get('paginatorModel');
837 if (this.get('rowsPerPage') === null) {
841 Y.mix(this.get('data'), {
844 index: (model.get('page') - 1) * model.get('itemsPerPage'),
845 length: model.get('itemsPerPage')
848 getPage: function () {
849 var _pg = this._paged,
852 // IE LTE 8 doesn't allow "undefined" as a second param - gh890
853 return (_pg.length >= 0) ?
854 this._items.slice(min, min + _pg.length) :
855 this._items.slice(min);
858 size: function (paged) {
859 return (paged && this._paged.length >=0 ) ?
865 var args = Array.prototype.slice.call(arguments);
866 args.unshift(this.getPage());
868 Y.Array.each.apply(null, args);
876 Ensures `pageSizes` value is an array of objects to be used in the
879 @method _setPageSizesFn
884 _setPageSizesFn: function (val) {
890 if (!Y.Lang.isArray(val)) {
895 for ( i = 0; i < len; i++ ) {
896 if (typeof val[i] !== 'object') {
900 // We want to check to see if we have a number or a string
901 // of a number. If we do not, we want the value to be -1 to
902 // indicate "all rows"
903 /*jshint eqeqeq:false */
904 if (parseInt(value, 10) != value) {
907 /*jshint eqeqeq:true */
908 val[i] = { label: label, value: value };
916 Ensures the object provided is an instance of a `Y.Model`. If it is not,
917 it assumes it is the configuration of a model, and gets the new model
918 type from `paginatorModelType`.
920 @method _setPaginatorModel
921 @param {Y.Model | Object} model
922 @return Y.Model instance
925 _setPaginatorModel: function (model) {
926 if (!(model && model._isYUIModel)) {
927 var ModelConstructor = this.get('paginatorModelType');
928 model = new ModelConstructor(model);
935 Returns a pointer to an object to be instantiated if the provided type is
938 @method _getConstructor
939 @param {Object | String} type Type of Object to contruct. If `type` is a
940 String, we assume it is a namespace off the Y object
944 _getConstructor: function (type) {
945 return typeof type === 'string' ?
946 Y.Object.getValue(Y, type.split('.')) :
951 Initializes paginatorStrings used for internationalization
953 @method _initPaginatorStrings
956 _initPaginatorStrings: function () {
957 // Not a valueFn because other class extensions may want to add to it
958 this.set('paginatorStrings', Y.mix((this.get('paginatorStrings') || {}),
959 Y.Intl.get('datatable-paginator')));
963 Returns an Array with default values for the Rows Per Page select option.
964 We had to use a valueFn to enable language string replacement.
967 @method _defPageSizeVal
970 _defPageSizeVal: function () {
971 this._initPaginatorStrings();
973 var str = this.get('paginatorStrings');
975 return [10, 50, 100, { label: str.showAll, value: -1 }]
980 Y.DataTable.Paginator = Controller;
981 Y.DataTable.Paginator.Model = Model;
982 Y.DataTable.Paginator.View = View;
984 Y.Base.mix(Y.DataTable, [Y.DataTable.Paginator]);
993 "datatable-paginator-templates"