From 3d415333cc21ae2ac34a30f5430c952801e26c5a Mon Sep 17 00:00:00 2001 From: Ryan Millikin Date: Mon, 17 Dec 2012 11:33:46 -0600 Subject: [PATCH] AJAX paging updates. This patch fixes issues with update events being re-triggered continuously and AJAX error handling being bound too many times. It also adds support for passing filtering parameters with the AJAX request. --- addons/pager/jquery.tablesorter.pager.js | 108 ++++++++++++++++------- js/jquery.tablesorter.js | 4 + js/jquery.tablesorter.widgets.js | 3 +- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/addons/pager/jquery.tablesorter.pager.js b/addons/pager/jquery.tablesorter.pager.js index 6b1e193b2..d2b5248c3 100644 --- a/addons/pager/jquery.tablesorter.pager.js +++ b/addons/pager/jquery.tablesorter.pager.js @@ -11,10 +11,12 @@ // target the pager markup container: null, - // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}" - // where {page} is replaced by the page number and {size} is replaced by the number of records to show - // {sortList:col} adds the sortList to the url into a "col" array. + // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}" + // where {page} is replaced by the page number, {size} is replaced by the number of records to show, + // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds + // the filterList to the url into an "fcol" array. // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url + // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url ajaxUrl: null, // process ajax so that the following information is returned: @@ -69,7 +71,8 @@ totalRows: 0, totalPages: 0, filteredRows: 0, - filteredPages: 0 + filteredPages: 0, + cssErrorRow: 'tablesorter-errorRow' }; @@ -81,7 +84,6 @@ r = 'removeClass', d = c.cssDisabled, dis = !!disable, - // tr = Math.min( c.totalRows, c.filteredRows ), tp = Math.min( c.totalPages, c.filteredPages ); if ( c.updateArrows ) { $(c.cssFirst + ',' + c.cssPrev, c.container)[ ( dis || c.page === 0 ) ? a : r ](d); @@ -90,7 +92,7 @@ }, updatePageDisplay = function(table, c) { - var i, p, s, t, out, f = $(table).hasClass('hasFilters'); + var i, p, s, t, out, f = $(table).hasClass('hasFilters') && !c.ajaxUrl; c.filteredRows = (f) ? $(table).find('tbody tr:not(.filtered)').length : c.totalRows; c.filteredPages = (f) ? Math.ceil( c.filteredRows / c.size ) : c.totalPages; if ( Math.min( c.totalPages, c.filteredPages ) > 0 ) { @@ -187,7 +189,7 @@ tc = table.config, $b = $(table.tBodies).filter(':not(.' + tc.cssInfoBlock + ')'), hl = $t.find('thead th').length, tds = '', - err = '' + + err = '' + (exception ? exception.message + ' (' + exception.name + ')' : 'No rows found') + '', result = c.ajaxProcessing(data) || [ 0, [] ], d = result[1] || [], @@ -225,13 +227,17 @@ $f.eq(j).html( th[j] ); }); } + + $t.find('thead tr.' + c.cssErrorRow).remove(); //Clean up any previous error. if ( exception ) { // add error row to thead instead of tbody, or clicking on the header will result in a parser error $t.find('thead').append(err); } else { $b.html( tds ); // add tbody } - $.tablesorter.isProcessing(table); // remove loading icon + if (tc.showProcessing) { + $.tablesorter.isProcessing(table); // remove loading icon + } $t.trigger('update'); c.totalRows = result[0] || 0; c.totalPages = Math.ceil( c.totalRows / c.size ); @@ -246,28 +252,52 @@ }, getAjax = function(table, c){ + var url = getAjaxUrl(table, c), + tc = table.config; + if ( url !== '' ) { + if (tc.showProcessing) { + $.tablesorter.isProcessing(table, true); // show loading icon + } + $(document).bind('ajaxError.pager', function(e, xhr, settings, exception) { + if (settings.url == url) { + renderAjax(null, table, c, exception); + $(document).unbind('ajaxError.pager'); + } + }); + $.getJSON(url, function(data) { + renderAjax(data, table, c); + $(document).unbind('ajaxError.pager'); + }); + } + }, + + getAjaxUrl = function(table, c) { var url = (c.ajaxUrl) ? c.ajaxUrl.replace(/\{page\}/g, c.page).replace(/\{size\}/g, c.size) : '', - arry = [], sl = table.config.sortList, - col = url.match(/\{sortList[\s+]?:[\s+]?(.*)\}/); - if (col) { - col = col[1]; + fl = c.currentFilters || [], + sortCol = url.match(/\{sortList[\s+]?:[\s+]?([^}]*)\}/), + filterCol = url.match(/\{filterList[\s+]?:[\s+]?([^}]*)\}/), + arry = []; + if (sortCol) { + sortCol = sortCol[1]; $.each(sl, function(i,v){ - arry.push(col + '[' + v[0] + ']=' + v[1]); + arry.push(sortCol + '[' + v[0] + ']=' + v[1]); }); // if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col" - url = url.replace(/\{sortList[\s+]?:[\s+]?(.*)\}/g, arry.length ? arry.join('&') : col ); + url = url.replace(/\{sortList[\s+]?:[\s+]?([^}]*)\}/g, arry.length ? arry.join('&') : sortCol ); } - if ( url !== '' ) { - // loading icon - $.tablesorter.isProcessing(table, true); - $(document).ajaxError(function(e, xhr, settings, exception) { - renderAjax(null, table, c, exception); - }); - $.getJSON(url, function(data) { - renderAjax(data, table, c); + if (filterCol) { + filterCol = filterCol[1]; + $.each(fl, function(i,v){ + if (v) { + arry.push(filterCol + '[' + i + ']=' + encodeURIComponent(v)); + } }); + // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol" + url = url.replace(/\{filterList[\s+]?:[\s+]?([^}]*)\}/g, arry.length ? arry.join('&') : filterCol ); } + + return url; }, renderTable = function(table, rows, c) { @@ -329,13 +359,13 @@ if ( c.page < 0 || c.page > ( p - 1 ) ) { c.page = 0; } - // change if page changed - fixes #182 - if (c.ajax && $.data(table, 'pagerLastPage') !== c.page) { + if (c.ajax) { getAjax(table, c); } else if (!c.ajax) { renderTable(table, table.config.rowsCopy, c); } $.data(table, 'pagerLastPage', c.page); + $.data(table, 'pagerUpdateTriggered', true); if (c.initialized) { $(table).trigger('pageMoved', c); } }, @@ -414,6 +444,7 @@ var config = this.config, c = config.pager = $.extend( {}, $.tablesorterPager.defaults, settings ), table = this, + tc = table.config, $t = $(table), pager = $(c.container).addClass('tablesorter-pager').show(); // added in case the pager is reinitialized after being destroyed. config.appender = $this.appender; @@ -423,6 +454,9 @@ if ( typeof(c.ajaxUrl) === 'string' ) { // ajax pager; interact with database c.ajax = true; + //When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side. + tc.widgetOptions.filter_serversideFiltering = true; + tc.serverSideSorting = true; getAjax(table, c); } else { c.ajax = false; @@ -430,17 +464,27 @@ $(this).trigger("appendCache", true); hideRowsSetup(table, c); } - + + $(table) + .unbind('filterStart.pager') + .bind('filterStart.pager', function(e, filters) { + c.currentFilters = filters; + }); + // update pager after filter widget completes $(table) - .unbind('filterEnd.pager updateComplete.pager ') - .bind('filterEnd.pager updateComplete.pager', function() { - if ($(this).hasClass('hasFilters')) { - c.page = 0; - updatePageDisplay(table, c); - moveToPage(table, c); - changeHeight(table, c); + .unbind('filterEnd.pager sortEnd.pager') + .bind('filterEnd.pager sortEnd.pager', function() { + //Prevent infinite event loops from occuring by setting this in all moveToPage calls and catching it here. + if ($.data(table, 'pagerUpdateTriggered')) { + $.data(table, 'pagerUpdateTriggered', false); + return; } + + c.page = 0; + updatePageDisplay(table, c); + moveToPage(table, c); + changeHeight(table, c); }); if ( $(c.cssGoto, pager).length ) { diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 3c6c290fc..188f01a90 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -40,6 +40,7 @@ sortMultiSortKey : 'shiftKey', // key used to select additional columns usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89" delayInit : false, // if false, the parsed table contents will not update until the first sort + serverSideSorting : false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used. // sort options headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc. @@ -459,6 +460,9 @@ var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config, sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length, sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol; + if (tc.serverSideSorting) { + return; + } if (tc.debug) { sortTime = new Date(); } for (k = 0; k < bl; k++) { colMax = tc.cache[k].colMax; diff --git a/js/jquery.tablesorter.widgets.js b/js/jquery.tablesorter.widgets.js index df1a4d072..99424581d 100644 --- a/js/jquery.tablesorter.widgets.js +++ b/js/jquery.tablesorter.widgets.js @@ -282,6 +282,7 @@ $.tablesorter.addWidget({ filter_searchDelay : 300 // typing delay in milliseconds before starting a search filter_startsWith : false // if true, filter start from the beginning of the cell contents filter_useParsedData : false // filter all data using parsed content + filter_serversideFiltering : false // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. **************************/ $.tablesorter.addWidget({ id: "filter", @@ -353,7 +354,7 @@ $.tablesorter.addWidget({ $tb = $.tablesorter.processTbody(table, $(b[k]), true); $tr = $tb.children('tr'); l = $tr.length; - if (cv === ''){ + if (cv === '' || wo.filter_serversideFiltering){ $tr.show().removeClass('filtered'); } else { // loop through the rows