123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- define(["../_StoreMixin", "dojo/_base/declare", "dojo/_base/array", "dojo/_base/lang", "dojo/_base/Deferred",
- "dojo/on", "dojo/query", "dojo/string", "dojo/has", "put-selector/put", "dojo/i18n!./nls/pagination",
- "dojo/_base/sniff", "xstyle/css!../css/extensions/Pagination.css"],
- function(_StoreMixin, declare, arrayUtil, lang, Deferred, on, query, string, has, put, i18n){
- function cleanupContent(grid){
- // Remove any currently-rendered rows, or noDataMessage
- if(grid.noDataNode){
- put(grid.noDataNode, "!");
- delete grid.noDataNode;
- }else{
- grid.cleanup();
- }
- grid.contentNode.innerHTML = "";
- }
- function cleanupLoading(grid){
- if(grid.loadingNode){
- put(grid.loadingNode, "!");
- delete grid.loadingNode;
- }else if(grid._oldPageNodes){
- // If cleaning up after a load w/ showLoadingMessage: false,
- // be careful to only clean up rows from the old page, not the new one
- for(var id in grid._oldPageNodes){
- grid.removeRow(grid._oldPageNodes[id]);
- }
- delete grid._oldPageNodes;
- // Also remove the observer from the previous page, if there is one
- if(grid._oldPageObserver){
- grid._oldPageObserver.cancel();
- grid._numObservers--;
- delete grid._oldPageObserver;
- }
- }
- delete grid._isLoading;
- }
-
- return declare(_StoreMixin, {
- // summary:
- // An extension for adding discrete pagination to a List or Grid.
-
- // rowsPerPage: Number
- // Number of rows (items) to show on a given page.
- rowsPerPage: 10,
-
- // pagingTextBox: Boolean
- // Indicates whether or not to show a textbox for paging.
- pagingTextBox: false,
- // previousNextArrows: Boolean
- // Indicates whether or not to show the previous and next arrow links.
- previousNextArrows: true,
- // firstLastArrows: Boolean
- // Indicates whether or not to show the first and last arrow links.
- firstLastArrows: false,
-
- // pagingLinks: Number
- // The number of page links to show on each side of the current page
- // Set to 0 (or false) to disable page links.
- pagingLinks: 2,
- // pageSizeOptions: Array[Number]
- // This provides options for different page sizes in a drop-down.
- // If it is empty (default), no page size drop-down will be displayed.
- pageSizeOptions: null,
-
- // showLoadingMessage: Boolean
- // If true, clears previous data and displays loading node when requesting
- // another page; if false, leaves previous data in place until new data
- // arrives, then replaces it immediately.
- showLoadingMessage: true,
-
- // i18nPagination: Object
- // This object contains all of the internationalized strings as
- // key/value pairs.
- i18nPagination: i18n,
-
- showFooter: true,
- _currentPage: 1,
- _total: 0,
-
- buildRendering: function(){
- this.inherited(arguments);
-
- // add pagination to footer
- var grid = this,
- paginationNode = this.paginationNode =
- put(this.footerNode, "div.dgrid-pagination"),
- statusNode = this.paginationStatusNode =
- put(paginationNode, "div.dgrid-status"),
- i18n = this.i18nPagination,
- navigationNode,
- node,
- i;
-
- statusNode.tabIndex = 0;
-
- // Initialize UI based on pageSizeOptions and rowsPerPage
- this._updatePaginationSizeSelect();
- this._updateRowsPerPageOption();
-
- // initialize some content into paginationStatusNode, to ensure
- // accurate results on initial resize call
- statusNode.innerHTML = string.substitute(i18n.status,
- { start: 1, end: 1, total: 0 });
-
- navigationNode = this.paginationNavigationNode =
- put(paginationNode, "div.dgrid-navigation");
-
- if(this.firstLastArrows){
- // create a first-page link
- node = this.paginationFirstNode =
- put(navigationNode, "span.dgrid-first.dgrid-page-link", "«");
- node.setAttribute("aria-label", i18n.gotoFirst);
- node.tabIndex = 0;
- }
- if(this.previousNextArrows){
- // create a previous link
- node = this.paginationPreviousNode =
- put(navigationNode, "span.dgrid-previous.dgrid-page-link", "‹");
- node.setAttribute("aria-label", i18n.gotoPrev);
- node.tabIndex = 0;
- }
-
- this.paginationLinksNode = put(navigationNode, "span.dgrid-pagination-links");
- if(this.previousNextArrows){
- // create a next link
- node = this.paginationNextNode =
- put(navigationNode, "span.dgrid-next.dgrid-page-link", "›");
- node.setAttribute("aria-label", i18n.gotoNext);
- node.tabIndex = 0;
- }
- if(this.firstLastArrows){
- // create a last-page link
- node = this.paginationLastNode =
- put(navigationNode, "span.dgrid-last.dgrid-page-link", "»");
- node.setAttribute("aria-label", i18n.gotoLast);
- node.tabIndex = 0;
- }
-
- this._listeners.push(on(navigationNode, ".dgrid-page-link:click,.dgrid-page-link:keydown", function(event){
- // For keyboard events, only respond to enter
- if(event.type === "keydown" && event.keyCode !== 13){
- return;
- }
-
- var cls = this.className,
- curr, max;
-
- if(grid._isLoading || cls.indexOf("dgrid-page-disabled") > -1){
- return;
- }
-
- curr = grid._currentPage;
- max = Math.ceil(grid._total / grid.rowsPerPage);
-
- // determine navigation target based on clicked link's class
- if(this === grid.paginationPreviousNode){
- grid.gotoPage(curr - 1);
- }else if(this === grid.paginationNextNode){
- grid.gotoPage(curr + 1);
- }else if(this === grid.paginationFirstNode){
- grid.gotoPage(1);
- }else if(this === grid.paginationLastNode){
- grid.gotoPage(max);
- }else if(cls === "dgrid-page-link"){
- grid.gotoPage(+this.innerHTML, true); // the innerHTML has the page number
- }
- }));
- },
-
- destroy: function(){
- this.inherited(arguments);
- if(this._pagingTextBoxHandle){
- this._pagingTextBoxHandle.remove();
- }
- },
- _updatePaginationSizeSelect: function(){
- // summary:
- // Creates or repopulates the pagination size selector based on
- // the values in pageSizeOptions. Called from buildRendering
- // and _setPageSizeOptions.
-
- var pageSizeOptions = this.pageSizeOptions,
- paginationSizeSelect = this.paginationSizeSelect,
- handle;
-
- if(pageSizeOptions && pageSizeOptions.length){
- if(!paginationSizeSelect){
- // First time setting page options; create the select
- paginationSizeSelect = this.paginationSizeSelect =
- put(this.paginationNode, "select.dgrid-page-size");
-
- handle = this._paginationSizeChangeHandle =
- on(paginationSizeSelect, "change", lang.hitch(this, function(){
- this.set("rowsPerPage", +this.paginationSizeSelect.value);
- }));
- this._listeners.push(handle);
- }
-
- // Repopulate options
- paginationSizeSelect.options.length = 0;
- for(i = 0; i < pageSizeOptions.length; i++){
- put(paginationSizeSelect, "option", pageSizeOptions[i], {
- value: pageSizeOptions[i],
- selected: this.rowsPerPage === pageSizeOptions[i]
- });
- }
- // Ensure current rowsPerPage value is in options
- this._updateRowsPerPageOption();
- }else if(!(pageSizeOptions && pageSizeOptions.length) && paginationSizeSelect){
- // pageSizeOptions was removed; remove/unhook the drop-down
- put(paginationSizeSelect, "!");
- this.paginationSizeSelect = null;
- this._paginationSizeChangeHandle.remove();
- }
- },
- _setPageSizeOptions: function(pageSizeOptions){
- this.pageSizeOptions = pageSizeOptions && pageSizeOptions.sort(function(a, b){
- return a - b;
- });
- this._updatePaginationSizeSelect();
- },
- _updateRowsPerPageOption: function(){
- // summary:
- // Ensures that an option for rowsPerPage's value exists in the
- // paginationSizeSelect drop-down (if one is rendered).
- // Called from buildRendering and _setRowsPerPage.
-
- var rowsPerPage = this.rowsPerPage,
- pageSizeOptions = this.pageSizeOptions,
- paginationSizeSelect = this.paginationSizeSelect;
-
- if(paginationSizeSelect){
- if(arrayUtil.indexOf(pageSizeOptions, rowsPerPage) < 0){
- this._setPageSizeOptions(pageSizeOptions.concat([rowsPerPage]));
- }else{
- paginationSizeSelect.value = "" + rowsPerPage;
- }
- }
- },
-
- _setRowsPerPage: function(rowsPerPage){
- this.rowsPerPage = rowsPerPage;
- this._updateRowsPerPageOption();
- this.gotoPage(1);
- },
- _updateNavigation: function(focusLink){
- // summary:
- // Update status and navigation controls based on total count from query
-
- var grid = this,
- i18n = this.i18nPagination,
- linksNode = this.paginationLinksNode,
- currentPage = this._currentPage,
- pagingLinks = this.pagingLinks,
- paginationNavigationNode = this.paginationNavigationNode,
- end = Math.ceil(this._total / this.rowsPerPage),
- pagingTextBoxHandle = this._pagingTextBoxHandle;
-
- function pageLink(page, addSpace){
- var link;
- if(grid.pagingTextBox && page == currentPage && end > 1){
- // use a paging text box if enabled instead of just a number
- link = put(linksNode, 'input.dgrid-page-input[type=text][value=$]', currentPage);
- link.setAttribute("aria-label", i18n.jumpPage);
- grid._pagingTextBoxHandle = on(link, "change", function(){
- var value = +this.value;
- if(!isNaN(value) && value > 0 && value <= end){
- grid.gotoPage(+this.value, true);
- }
- });
- }else{
- // normal link
- link = put(linksNode,
- 'span' + (page == currentPage ? '.dgrid-page-disabled' : '') + '.dgrid-page-link',
- page + (addSpace ? " " : ""));
- link.setAttribute("aria-label", i18n.gotoPage);
- link.tabIndex = 0;
- }
- if(page == currentPage && focusLink){
- // focus on it if we are supposed to retain the focus
- link.focus();
- }
- }
-
- if(pagingTextBoxHandle){ pagingTextBoxHandle.remove(); }
- linksNode.innerHTML = "";
- query(".dgrid-first, .dgrid-previous", paginationNavigationNode).forEach(function(link){
- put(link, (currentPage == 1 ? "." : "!") + "dgrid-page-disabled");
- });
- query(".dgrid-last, .dgrid-next", paginationNavigationNode).forEach(function(link){
- put(link, (currentPage >= end ? "." : "!") + "dgrid-page-disabled");
- });
-
- if(pagingLinks && end > 0){
- // always include the first page (back to the beginning)
- pageLink(1, true);
- var start = currentPage - pagingLinks;
- if(start > 2) {
- // visual indication of skipped page links
- put(linksNode, "span.dgrid-page-skip", "...");
- }else{
- start = 2;
- }
- // now iterate through all the page links we should show
- for(var i = start; i < Math.min(currentPage + pagingLinks + 1, end); i++){
- pageLink(i, true);
- }
- if(currentPage + pagingLinks + 1 < end){
- put(linksNode, "span.dgrid-page-skip", "...");
- }
- // last link
- if(end > 1){
- pageLink(end);
- }
- }else if(grid.pagingTextBox){
- // The pageLink function is also used to create the paging textbox.
- pageLink(currentPage);
- }
- },
-
- refresh: function(){
- var self = this;
-
- this.inherited(arguments);
-
- if(!this.store){
- console.warn("Pagination requires a store to operate.");
- return;
- }
-
- // Reset to first page and return promise from gotoPage
- return this.gotoPage(1).then(function(results){
- // Emit on a separate turn to enable event to be used consistently for
- // initial render, regardless of whether the backing store is async
- setTimeout(function() {
- on.emit(self.domNode, "dgrid-refresh-complete", {
- bubbles: true,
- cancelable: false,
- grid: self,
- results: results // QueryResults object (may be a wrapped promise)
- });
- }, 0);
-
- return results;
- });
- },
-
- _onNotification: function(rows){
- if(rows.length !== this._rowsOnPage){
- // Refresh the current page to maintain correct number of rows on page
- this.gotoPage(this._currentPage);
- }
- },
-
- renderArray: function(results, beforeNode){
- var grid = this,
- rows = this.inherited(arguments);
-
- // Make sure _lastCollection is cleared (due to logic in List)
- this._lastCollection = null;
-
- if(!beforeNode){
- if(this._topLevelRequest){
- // Cancel previous async request that didn't finish
- this._topLevelRequest.cancel();
- delete this._topLevelRequest;
- }
-
- if (typeof results.cancel === "function") {
- // Store reference to new async request in progress
- this._topLevelRequest = results;
- }
-
- Deferred.when(results, function(){
- if(grid._topLevelRequest){
- // Remove reference to request now that it's finished
- delete grid._topLevelRequest;
- }
- });
- }
-
- return rows;
- },
-
- insertRow: function(){
- var oldNodes = this._oldPageNodes,
- row = this.inherited(arguments);
-
- if(oldNodes && row === oldNodes[row.id]){
- // If the previous row was reused, avoid removing it in cleanup
- delete oldNodes[row.id];
- }
-
- return row;
- },
-
- gotoPage: function(page, focusLink){
- // summary:
- // Loads the given page. Note that page numbers start at 1.
- var grid = this,
- dfd = new Deferred();
-
- var result = this._trackError(function(){
- var count = grid.rowsPerPage,
- start = (page - 1) * count,
- options = lang.mixin(grid.get("queryOptions"), {
- start: start,
- count: count
- // current sort is also included by get("queryOptions")
- }),
- results,
- contentNode = grid.contentNode,
- loadingNode,
- oldNodes,
- children,
- i,
- len;
-
- if(grid.showLoadingMessage){
- cleanupContent(grid);
- loadingNode = grid.loadingNode = put(contentNode, "div.dgrid-loading");
- loadingNode.innerHTML = grid.loadingMessage;
- }else{
- // Reference nodes to be cleared later, rather than now;
- // iterate manually since IE < 9 doesn't like slicing HTMLCollections
- grid._oldPageNodes = oldNodes = {};
- children = contentNode.children;
- for(i = 0, len = children.length; i < len; i++){
- oldNodes[children[i].id] = children[i];
- }
- // Also reference the current page's observer (if any)
- grid._oldPageObserver = grid.observers.pop();
- }
-
- // set flag to deactivate pagination event handlers until loaded
- grid._isLoading = true;
-
- // Run new query and pass it into renderArray
- results = grid.store.query(grid.query, options);
-
- Deferred.when(grid.renderArray(results, null, options), function(rows){
- cleanupLoading(grid);
- // Reset scroll Y-position now that new page is loaded.
- grid.scrollTo({ y: 0 });
-
- Deferred.when(results.total, function(total){
- if(!total){
- if(grid.noDataNode){
- put(grid.noDataNode, "!");
- delete grid.noDataNode;
- }
- // If there are no results, display the no data message.
- grid.noDataNode = put(grid.contentNode, "div.dgrid-no-data");
- grid.noDataNode.innerHTML = grid.noDataMessage;
- }
-
- // Update status text based on now-current page and total.
- grid.paginationStatusNode.innerHTML = string.substitute(grid.i18nPagination.status, {
- start: Math.min(start + 1, total),
- end: Math.min(total, start + count),
- total: total
- });
- grid._total = total;
- grid._currentPage = page;
- grid._rowsOnPage = rows.length;
-
- // It's especially important that _updateNavigation is called only
- // after renderArray is resolved as well (to prevent jumping).
- grid._updateNavigation(focusLink);
- });
-
- if (has("ie") < 7 || (has("ie") && has("quirks"))) {
- // call resize in old IE in case grid is set to height: auto
- grid.resize();
- }
-
- dfd.resolve(results);
- }, function(error){
- cleanupLoading(grid);
- dfd.reject(error);
- });
-
- return dfd.promise;
- });
-
- if (!result) {
- // A synchronous error occurred; reject the promise.
- dfd.reject();
- }
- var self = this;
- dfd.promise.then(function (results) {
- // Emit on a separate turn to enable event to be used consistently for
- // initial render, regardless of whether the backing store is async
- setTimeout(function () {
- on.emit(self.domNode, "dgrid-page-complete", {
- bubbles: true,
- cancelable: false,
- grid: self,
- page: page,
- results: results // QueryResults object (may be a wrapped promise)
- });
- }, 0);
- return results;
- });
- return dfd.promise;
- }
- });
- });
|