This repository has been archived on 2018-10-12. You can view files and clone it, but cannot push or open issues or pull requests.
node-task/public/js/ink.pagination.js

528 lines
19 KiB
JavaScript

/**
* Pagination elements
* @module Ink.UI.Pagination_1
* @version 1
*/
Ink.createModule('Ink.UI.Pagination', '1',
['Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1'],
function(Common, Event, Css, Element, Selector ) {
'use strict';
/**
* Function to create the pagination anchors
*
* @method genAel
* @private
* @param {String} inner HTML to be placed inside the anchor.
* @return {DOMElement} Anchor created
*/
var genAEl = function(inner, index, options) {
var aEl = document.createElement('a');
aEl.setAttribute('href', '#');
if (typeof index === 'number') {
aEl.setAttribute('data-index', index);
}
if(options && options.wrapText) {
var spanEl = document.createElement('span');
aEl.appendChild(spanEl);
spanEl.innerHTML = inner;
} else {
aEl.innerHTML = inner;
}
return aEl;
};
/**
* @class Ink.UI.Pagination
* @constructor
* @version 1
* @param {String|DOMElement} selector Selector or element
* @param {Object} options Options
* @param {Number} [options.size] Number of pages.
* @param {Number} [options.totalItemCount] Total numeber of items to display
* @param {Number} [options.itemsPerPage] Number of items per page.
* @param {Number} [options.maxSize] If passed, only shows at most maxSize items. displays also first|prev page and next page|last buttons
* @param {Number} [options.start] Start page. defaults to 1
* @param {Boolean} [options.sideButtons=true] Whether to show the first, last, previous, next, previousPage and lastPage buttons. Do not use together with maxSize.
* @param {String} [options.firstLabel] Text for the first page button. Defaults to 'First'.
* @param {String} [options.lastLabel] Text for the last page button. Defaults to 'Last'.
* @param {String} [options.previousLabel] Text for the previous button. Defaults to 'Previous'-
* @param {String} [options.nextLabel] Text for the next button. Defaults to 'Next'
* @param {String} [options.previousPageLabel] Text for the previous page button. Defaults to 'Previous {Items per page}'.
* @param {String} [options.nextPageLabel] Text for the next page button. Defaults to 'Next {Items per page}'.
* @param {Function} [options.onChange] Callback to be called when a page changes. Called with `(thisPaginator, newPageNumber)`.
* @param {String} [options.hashParameter] Parameter to use on setHash. Defaults to 'page'.
* @param {String} [options.parentTag] HTML Tag used as the parent node.
* @param {String} [options.childTag] HTML Tag used as the child nodes.
* @param {String} [options.wrapperClass] CSS Class used in the wrapper element
* @param {String} [options.paginationClass] CSS Class used in the pagination element
* @param {String} [options.activeClass] CSS Class used to mark page as active
* @param {String} [options.disabledClass] CSS Class used to mark page as disabled
* @param {String} [options.hideClass] CSS Class used to hide elements
* @param {String} [options.previousClass] CSS Class used in the previous element
* @param {String} [options.previousPageClass] CSS Class used in the previous page element
* @param {String} [options.nextClass] CSS Class used in the next element
* @param {String} [options.nextPageClass] CSS Class used in the next page element
* @param {Function} [options.numberFormatter] Number formatter function. Receives a 0-indexed number and returns the text for the numbered page button.
*
* @sample Ink_UI_Pagination_1.html
*/
function Pagination() {
Common.BaseUIComponent.apply(this, arguments);
}
Pagination._name = 'Pagination_1';
Pagination._optionDefinition = {
size: ['Integer', null],
totalItemCount: ['Integer', null],
itemsPerPage: ['Integer', null],
maxSize: ['Integer', null],
start: ['Integer', 1],
sideButtons: ['Boolean', 1 /* actually `true` but we want to see if user is using the default or not. */],
// TODO add pagination-type which accepts color strings, "chevron" and "dotted". Basically classes to add to the UL.
firstLabel: ['String', 'First'],
lastLabel: ['String', 'Last'],
previousLabel: ['String', 'Previous'],
nextLabel: ['String', 'Next'],
previousPageLabel: ['String', null],
nextPageLabel: ['String', null],
onChange: ['Function', undefined],
hashParameter: ['String', 'page'],
parentTag: ['String', 'ul'],
childTag: ['String', 'li'],
wrapperClass: ['String', 'ink-navigation'],
paginationClass: ['String', 'pagination'],
activeClass: ['String', 'active'],
disabledClass: ['String', 'disabled'],
hideClass: ['String', 'hide-all'],
previousClass: ['String', 'previous'],
previousPageClass: ['String', 'previousPage'],
nextClass: ['String', 'next'],
nextPageClass: ['String', 'nextPage'],
numberFormatter: ['Function', function(i) { return i + 1; }]
};
Pagination.prototype = {
/**
* Init function called by the constructor
*
* @method _init
* @private
*/
_init: function() {
if (!this._options.previousPageLabel) {
this._options.previousPageLabel = this._options.previousLabel + ' ' + this._options.maxSize;
}
if (!this._options.nextPageLabel) {
this._options.nextPageLabel = this._options.nextLabel + ' ' + this._options.maxSize;
}
this._handlers = {
click: Ink.bindEvent(this._onClick,this)
};
if (Common.isInteger(this._options.totalItemCount) && Common.isInteger(this._options.itemsPerPage)) {
this._size = this._calculateSize(this._options.totalItemCount, this._options.itemsPerPage);
} else if (Common.isInteger(this._options.size)) {
this._size = this._options.size;
} else {
Ink.error('Ink.UI.Pagination: Please supply a size option or totalItemCount and itemsPerPage options.');
this._size = 0;
}
this._current = this._options.start - 1;
this._itemLiEls = [];
// generate and apply DOM
this._generateMarkup(this._element);
this._updateItems();
// subscribe events
this._observe();
},
/**
* Responsible for setting listener in the 'click' event of the Pagination element.
*
* @method _observe
* @private
*/
_observe: function() {
Event.observeDelegated(this._element, 'click', '.' + this._options.paginationClass + ' > ' + this._options.childTag, this._handlers.click);
},
/**
* Calculate how many pages are necessary for `count` items, and `itemsPerPage` items per page.
*
* @method _calculateSize
* @param count
* @param itemsPerPage
* @private
**/
_calculateSize: function (count, itemsPerPage) {
return Math.ceil(count / itemsPerPage);
},
/**
* Updates the markup everytime there's a change in the Pagination object.
*
* @method _updateItems
* @private
*/
_updateItems: function() {
var liEls = this._itemLiEls;
var isSimpleToggle = this._size === liEls.length;
var i, f, liEl;
if (isSimpleToggle) {
// just toggle active class
for (i = 0, f = this._size; i < f; ++i) {
Css.setClassName(liEls[i], this._options.activeClass, i === this._current);
}
}
else {
// remove old items
for (i = liEls.length - 1; i >= 0; --i) {
this._ulEl.removeChild(liEls[i]);
}
// add new items
liEls = [];
for (i = 0, f = this._size; i < f; ++i) {
liEl = document.createElement(this._options.childTag);
liEl.appendChild( genAEl( this._options.numberFormatter(i), i) );
// add "active" class if this is the active element.
Css.setClassName(liEl, this._options.activeClass, i === this._current);
if (this._nextEl) {
this._ulEl.insertBefore(liEl, this._nextEl);
} else {
this._ulEl.appendChild(liEl);
}
liEls.push(liEl);
}
this._itemLiEls = liEls;
}
if (this._options.maxSize) {
// toggle visible items
var page = Math.floor( this._current / this._options.maxSize );
var pi = this._options.maxSize * page;
var pf = pi + this._options.maxSize - 1;
for (i = 0, f = this._size; i < f; ++i) {
liEl = liEls[i];
Css.setClassName(liEl, this._options.hideClass, i < pi || i > pf);
}
this._pageStart = pi;
this._pageEnd = pf;
this._page = page;
Css.setClassName(this._prevPageEl, this._options.disabledClass, !this.hasPreviousPage());
Css.setClassName(this._nextPageEl, this._options.disabledClass, !this.hasNextPage());
Css.setClassName(this._firstEl, this._options.disabledClass, this.isFirst());
Css.setClassName(this._lastEl, this._options.disabledClass, this.isLast());
}
// update prev and next
if (this._prevEl) {
Css.setClassName(this._prevEl, this._options.disabledClass, !this.hasPrevious());
}
if (this._nextEl) {
Css.setClassName(this._nextEl, this._options.disabledClass, !this.hasNext());
}
},
/**
* Returns the top element for the gallery DOM representation
*
* @method _generateMarkup
* @param {DOMElement} el
* @private
*/
_generateMarkup: function(el) {
Css.addClassName(el, 'ink-navigation');
var ulEl = Ink.s('.' + this._options.paginationClass, el);
var hasUlAlready = false;
if( !ulEl ){
ulEl = document.createElement(this._options.parentTag);
Css.addClassName(ulEl, this._options.paginationClass);
} else {
hasUlAlready = true;
}
var isChevron = Css.hasClassName(ulEl, 'chevron');
var isDotted = Css.hasClassName(ulEl, 'dotted');
// Creates <li> elements for firstPage, nextPage, first, last, etc.
var createLiEl = Ink.bind(function (name, options) {
var liEl = document.createElement(this._options.childTag);
var aEl = genAEl(this._options[name + 'Label'], undefined, { wrapText: options && options.wrapText });
Css.addClassName(liEl, this._options[name + 'Class']);
liEl.appendChild(aEl);
ulEl.appendChild(liEl);
return liEl;
}, this);
if (!isDotted && this._options.maxSize) {
this._firstEl = createLiEl('first');
this._prevPageEl = createLiEl('previousPage');
}
// When we're dotted, the default for sideButtons is `false`. When we're note, it's `true`.
// Since the default is actually "1", we do a === true check when we're dotted, and a truthish check when we're not.
if ((isDotted && this._options.sideButtons === true) || (!isDotted && this._options.sideButtons)) {
this._prevEl = createLiEl('previous', { wrapText: isChevron });
this._nextEl = createLiEl('next', { wrapText: isChevron });
}
if (!isDotted && this._options.maxSize) {
this._nextPageEl = createLiEl('nextPage');
this._lastEl = createLiEl('last');
}
if( !hasUlAlready ){
el.appendChild(ulEl);
}
this._ulEl = ulEl;
},
/**
* Click handler
*
* @method _onClick
* @param {Event} ev
* @private
*/
_onClick: function(ev) {
Event.stop(ev);
var liEl = Event.element(ev);
if ( Css.hasClassName(liEl, this._options.activeClass) ||
Css.hasClassName(liEl, this._options.disabledClass) ) { return; }
var isPrev = Css.hasClassName(liEl, this._options.previousClass);
var isNext = Css.hasClassName(liEl, this._options.nextClass);
var isPrevPage = Css.hasClassName(liEl, this._options.previousPageClass);
var isNextPage = Css.hasClassName(liEl, this._options.nextPageClass);
var isFirst = Css.hasClassName(liEl, this._options.firstClass);
var isLast = Css.hasClassName(liEl, this._options.lastClass);
if (isFirst) {
this.setCurrent(0);
}
else if (isLast) {
this.setCurrent(this._size - 1);
}
else if (isPrevPage || isNextPage) {
this.setCurrent( (isPrevPage ? -1 : 1) * this._options.maxSize, true /* relative */);
}
else if (isPrev || isNext) {
this.setCurrent(isPrev ? -1 : 1, true /* relative */);
}
else {
var aElem = Selector.select('[data-index]', liEl)[0];
var nr = aElem && parseInt( aElem.getAttribute('data-index'), 10);
this.setCurrent(nr);
}
},
/**
* Allows you to subscribe to the onChange event
*
* @method setOnChange
* @param cb {Function} Callback called with `(thisPaginator, newPageNumber)`.
*/
setOnChange: function (onChange) {
if (onChange !== undefined && typeof onChange !== 'function') {
throw new TypeError('onChange option must be a function!');
}
this._options.onChange = onChange;
},
/**************
* PUBLIC API *
**************/
/**
* Sets the number of pages
*
* @method setSize
* @param {Number} sz number of pages
* @public
*/
setSize: function(sz) {
if (!Common.isInteger(sz)) {
throw new TypeError('1st argument must be an integer number!');
}
this._size = sz;
this._updateItems();
this._current = 0;
},
/**
* Sets the number of pages, then call setSize().
*
* @param setSizeInItems
* @param {Number} totalItems Total number of items
* @param {Number} itemsPerPage Items per page
*/
setSizeInItems: function (totalItems, itemsPerPage) {
var pageNumber = Math.ceil(totalItems / itemsPerPage);
this.setSize(pageNumber);
},
/**
* Sets the current page.
*
* @method setCurrent
* @param {Number} nr Sets the current page to given number.
* @param {Boolean} isRelative Flag to change the position from absolute to relative.
* @public
*/
setCurrent: function(nr, isRelative) {
if (!Common.isInteger(nr)) {
throw new TypeError('1st argument must be an integer number!');
}
if (isRelative) {
nr += this._current;
}
if (nr > this._size - 1) {
nr = this._size - 1;
}
if (nr < 0) {
nr = 0;
}
this._current = nr;
this._updateItems();
if (this._options.onChange) {
this._options.onChange(this, nr);
}
/*if (this._options.setHash) {
var o = {};
o[this._options.hashParameter] = nr;
Common.setHash(o);
}*/ // undocumented option, removing
},
/**
* Gets the number of pages
*
* @method getSize
* @return {Number} Number of pages
* @public
*/
getSize: function() {
return this._size;
},
/**
* Gets the current page index
*
* @method getCurrent
* @return {Number} Current page
* @public
*/
getCurrent: function() {
return this._current;
},
/**
* Checks if it's at the first page
*
* @method isFirst
* @return {Boolean} True if at first page
* @public
*/
isFirst: function() {
return this._current === 0;
},
/**
* Checks if it's on the last page
*
* @method isLast
* @return {Boolean} True if at last page
* @public
*/
isLast: function() {
return this._current === this._size - 1;
},
/**
* Checks if it has previous pages
*
* @method hasPrevious
* @return {Boolean} True if has prior pages
* @public
*/
hasPrevious: function() {
return this._current > 0;
},
/**
* Checks if it has next pages
*
* @method hasNext
* @return {Boolean} True if has pages ahead
* @public
*/
hasNext: function() {
return this._current < this._size - 1;
},
/**
* Checks if it has a previous set of pages
*
* @method hasPreviousPage
* @return {Boolean} Returns true iif has prior set of page(s)
* @public
*/
hasPreviousPage: function() {
return this._options.maxSize && this._current > this._options.maxSize - 1;
},
/**
* Checks if it has a next set of pages
*
* @method hasNextPage
* @return {Boolean} Returns true iif has set of page(s) ahead
* @public
*/
hasNextPage: function() {
return this._options.maxSize && this._size - this._current >= this._options.maxSize + 1;
},
/**
* Unregisters the component and removes its markup
*
* @method destroy
* @public
*/
destroy: Common.destroyComponent
};
Common.createUIComponent(Pagination);
return Pagination;
});