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.

This commit is contained in:
Ryan Millikin 2012-12-17 11:33:46 -06:00
parent 351e3ad699
commit 3d415333cc
3 changed files with 84 additions and 35 deletions

View File

@ -11,10 +11,12 @@
// target the pager markup // target the pager markup
container: null, container: null,
// use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}" // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}"
// where {page} is replaced by the page number and {size} is replaced by the number of records to show // 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. // {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 // 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, ajaxUrl: null,
// process ajax so that the following information is returned: // process ajax so that the following information is returned:
@ -69,7 +71,8 @@
totalRows: 0, totalRows: 0,
totalPages: 0, totalPages: 0,
filteredRows: 0, filteredRows: 0,
filteredPages: 0 filteredPages: 0,
cssErrorRow: 'tablesorter-errorRow'
}; };
@ -81,7 +84,6 @@
r = 'removeClass', r = 'removeClass',
d = c.cssDisabled, d = c.cssDisabled,
dis = !!disable, dis = !!disable,
// tr = Math.min( c.totalRows, c.filteredRows ),
tp = Math.min( c.totalPages, c.filteredPages ); tp = Math.min( c.totalPages, c.filteredPages );
if ( c.updateArrows ) { if ( c.updateArrows ) {
$(c.cssFirst + ',' + c.cssPrev, c.container)[ ( dis || c.page === 0 ) ? a : r ](d); $(c.cssFirst + ',' + c.cssPrev, c.container)[ ( dis || c.page === 0 ) ? a : r ](d);
@ -90,7 +92,7 @@
}, },
updatePageDisplay = function(table, c) { 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.filteredRows = (f) ? $(table).find('tbody tr:not(.filtered)').length : c.totalRows;
c.filteredPages = (f) ? Math.ceil( c.filteredRows / c.size ) : c.totalPages; c.filteredPages = (f) ? Math.ceil( c.filteredRows / c.size ) : c.totalPages;
if ( Math.min( c.totalPages, c.filteredPages ) > 0 ) { if ( Math.min( c.totalPages, c.filteredPages ) > 0 ) {
@ -187,7 +189,7 @@
tc = table.config, tc = table.config,
$b = $(table.tBodies).filter(':not(.' + tc.cssInfoBlock + ')'), $b = $(table.tBodies).filter(':not(.' + tc.cssInfoBlock + ')'),
hl = $t.find('thead th').length, tds = '', hl = $t.find('thead th').length, tds = '',
err = '<tr class="' + tc.selectorRemove + '"><td style="text-align: center;" colspan="' + hl + '">' + err = '<tr class="' + c.cssErrorRow + '"><td style="text-align: center;" colspan="' + hl + '">' +
(exception ? exception.message + ' (' + exception.name + ')' : 'No rows found') + '</td></tr>', (exception ? exception.message + ' (' + exception.name + ')' : 'No rows found') + '</td></tr>',
result = c.ajaxProcessing(data) || [ 0, [] ], result = c.ajaxProcessing(data) || [ 0, [] ],
d = result[1] || [], d = result[1] || [],
@ -225,13 +227,17 @@
$f.eq(j).html( th[j] ); $f.eq(j).html( th[j] );
}); });
} }
$t.find('thead tr.' + c.cssErrorRow).remove(); //Clean up any previous error.
if ( exception ) { if ( exception ) {
// add error row to thead instead of tbody, or clicking on the header will result in a parser error // add error row to thead instead of tbody, or clicking on the header will result in a parser error
$t.find('thead').append(err); $t.find('thead').append(err);
} else { } else {
$b.html( tds ); // add tbody $b.html( tds ); // add tbody
} }
if (tc.showProcessing) {
$.tablesorter.isProcessing(table); // remove loading icon $.tablesorter.isProcessing(table); // remove loading icon
}
$t.trigger('update'); $t.trigger('update');
c.totalRows = result[0] || 0; c.totalRows = result[0] || 0;
c.totalPages = Math.ceil( c.totalRows / c.size ); c.totalPages = Math.ceil( c.totalRows / c.size );
@ -246,30 +252,54 @@
}, },
getAjax = function(table, c){ getAjax = function(table, c){
var url = (c.ajaxUrl) ? c.ajaxUrl.replace(/\{page\}/g, c.page).replace(/\{size\}/g, c.size) : '', var url = getAjaxUrl(table, c),
arry = [], tc = table.config;
sl = table.config.sortList,
col = url.match(/\{sortList[\s+]?:[\s+]?(.*)\}/);
if (col) {
col = col[1];
$.each(sl, function(i,v){
arry.push(col + '[' + 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 );
}
if ( url !== '' ) { if ( url !== '' ) {
// loading icon if (tc.showProcessing) {
$.tablesorter.isProcessing(table, true); $.tablesorter.isProcessing(table, true); // show loading icon
$(document).ajaxError(function(e, xhr, settings, exception) { }
$(document).bind('ajaxError.pager', function(e, xhr, settings, exception) {
if (settings.url == url) {
renderAjax(null, table, c, exception); renderAjax(null, table, c, exception);
$(document).unbind('ajaxError.pager');
}
}); });
$.getJSON(url, function(data) { $.getJSON(url, function(data) {
renderAjax(data, table, c); 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) : '',
sl = table.config.sortList,
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(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('&') : sortCol );
}
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) { renderTable = function(table, rows, c) {
var i, j, o, var i, j, o,
f = document.createDocumentFragment(), f = document.createDocumentFragment(),
@ -329,13 +359,13 @@
if ( c.page < 0 || c.page > ( p - 1 ) ) { if ( c.page < 0 || c.page > ( p - 1 ) ) {
c.page = 0; c.page = 0;
} }
// change if page changed - fixes #182 if (c.ajax) {
if (c.ajax && $.data(table, 'pagerLastPage') !== c.page) {
getAjax(table, c); getAjax(table, c);
} else if (!c.ajax) { } else if (!c.ajax) {
renderTable(table, table.config.rowsCopy, c); renderTable(table, table.config.rowsCopy, c);
} }
$.data(table, 'pagerLastPage', c.page); $.data(table, 'pagerLastPage', c.page);
$.data(table, 'pagerUpdateTriggered', true);
if (c.initialized) { $(table).trigger('pageMoved', c); } if (c.initialized) { $(table).trigger('pageMoved', c); }
}, },
@ -414,6 +444,7 @@
var config = this.config, var config = this.config,
c = config.pager = $.extend( {}, $.tablesorterPager.defaults, settings ), c = config.pager = $.extend( {}, $.tablesorterPager.defaults, settings ),
table = this, table = this,
tc = table.config,
$t = $(table), $t = $(table),
pager = $(c.container).addClass('tablesorter-pager').show(); // added in case the pager is reinitialized after being destroyed. pager = $(c.container).addClass('tablesorter-pager').show(); // added in case the pager is reinitialized after being destroyed.
config.appender = $this.appender; config.appender = $this.appender;
@ -423,6 +454,9 @@
if ( typeof(c.ajaxUrl) === 'string' ) { if ( typeof(c.ajaxUrl) === 'string' ) {
// ajax pager; interact with database // ajax pager; interact with database
c.ajax = true; 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); getAjax(table, c);
} else { } else {
c.ajax = false; c.ajax = false;
@ -431,16 +465,26 @@
hideRowsSetup(table, c); hideRowsSetup(table, c);
} }
$(table)
.unbind('filterStart.pager')
.bind('filterStart.pager', function(e, filters) {
c.currentFilters = filters;
});
// update pager after filter widget completes // update pager after filter widget completes
$(table) $(table)
.unbind('filterEnd.pager updateComplete.pager ') .unbind('filterEnd.pager sortEnd.pager')
.bind('filterEnd.pager updateComplete.pager', function() { .bind('filterEnd.pager sortEnd.pager', function() {
if ($(this).hasClass('hasFilters')) { //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; c.page = 0;
updatePageDisplay(table, c); updatePageDisplay(table, c);
moveToPage(table, c); moveToPage(table, c);
changeHeight(table, c); changeHeight(table, c);
}
}); });
if ( $(c.cssGoto, pager).length ) { if ( $(c.cssGoto, pager).length ) {

View File

@ -40,6 +40,7 @@
sortMultiSortKey : 'shiftKey', // key used to select additional columns sortMultiSortKey : 'shiftKey', // key used to select additional columns
usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89" 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 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 // sort options
headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc. 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, var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config,
sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length, sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol; sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol;
if (tc.serverSideSorting) {
return;
}
if (tc.debug) { sortTime = new Date(); } if (tc.debug) { sortTime = new Date(); }
for (k = 0; k < bl; k++) { for (k = 0; k < bl; k++) {
colMax = tc.cache[k].colMax; colMax = tc.cache[k].colMax;

View File

@ -282,6 +282,7 @@ $.tablesorter.addWidget({
filter_searchDelay : 300 // typing delay in milliseconds before starting a search 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_startsWith : false // if true, filter start from the beginning of the cell contents
filter_useParsedData : false // filter all data using parsed content 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({ $.tablesorter.addWidget({
id: "filter", id: "filter",
@ -353,7 +354,7 @@ $.tablesorter.addWidget({
$tb = $.tablesorter.processTbody(table, $(b[k]), true); $tb = $.tablesorter.processTbody(table, $(b[k]), true);
$tr = $tb.children('tr'); $tr = $tb.children('tr');
l = $tr.length; l = $tr.length;
if (cv === ''){ if (cv === '' || wo.filter_serversideFiltering){
$tr.show().removeClass('filtered'); $tr.show().removeClass('filtered');
} else { } else {
// loop through the rows // loop through the rows