tablesorter -- pager_output variables shared across multiple tables - tablesorter

Using the latest jquery / tablesorter / widgets (as of Jan 18, 2016), using the pager widget, set pager_output to anything you want, so long as you include {totalRows}. Create two or more trivial tables with different id's and attach tablesorter to each id. Make sure the tables have a different number of rows. The pager will show the totalRows of the last-encountered table for every table rather than the appropriate number for each table.
The same is true for {filteredRows} when you including filtering.

It works fine for me... make sure that the code isn't pointing to the same pager container for both tables (demo)
$('table').each(function(){
$(this).tablesorter({
theme: "bootstrap",
widthFixed: true,
headerTemplate: '{content} {icon}',
widgets: ["uitheme", "filter", "zebra"],
})
.tablesorterPager({
container: '.' + this.id,
cssGoto: ".pagenum",
output: '{startRow} - {endRow} / {filteredRows} ({totalRows})'
});
});
Update: Oops, here is a demo using the pager widget
$('table').each(function(){
$(this).tablesorter({ debug: true,
theme: "bootstrap",
widthFixed: true,
headerTemplate: '{content} {icon}',
widgets: ["uitheme", "filter", "zebra", 'pager'],
widgetOptions: {
pager_selectors : {
container : '.' + this.id
},
pager_output: '{startRow} - {endRow} / {filteredRows} ({totalRows})'
}
});
});

Related

How to force a refresh of Ajax data

I'm using the Ajax pager and I have some code that adds a record to my database. What I want to do is force the records to be refreshed. I'm trying to use $("builders_table").trigger("update"), but that doesn't work. If I change pages or filter the records, then the updated records are returned, but I would like to force a refresh as soon as the database is changed.
Thanks
$('#builders_table')
.tablesorter({
theme: 'blue',
widthFixed: true,
cancelSelection: false,
sortLocaleCompare: true, // needed for accented characters in the data
sortList: [ [1,1] ],
widgets: ['zebra', 'filter']
})
.tablesorterPager({
container: $('.pager'),
ajaxUrl : '/builder_data.php?page={page}&size={size}&{filterList:filter}&{sortList:column}',
// use this option to manipulate and/or add additional parameters to the ajax url
customAjaxUrl: function(table, url) {
// manipulate the url string as you desire
//url += url_extras;
// trigger a custom event; if you want
$(table).trigger('changingUrl', url);
// send the server the current page
return url;
},
ajaxError: null,
ajaxObject: {
dataType: 'json'
},
ajaxProcessing: function(data){
if (data && data.hasOwnProperty('rows')) {
return [ data.total_rows, $(data.rows) ];
}
},
// Set this option to false if your table data is preloaded into the table, but you are still using ajax
processAjaxOnInit: true,
initialRows: {
// these are both set to 100 in the ajaxProcessing
// the these settings only show up initially
total: 50,
filtered: 50
},
output: '{startRow} to {endRow} ({totalRows})',
updateArrows: true,
page: 0,
size: 50,
savePages: false,
storageKey: 'tablesorter-pager',
pageReset: 0,
fixedHeight: false,
removeRows: false,
countChildRows: false,
// css class names of pager arrows
cssNext : '.next', // next page arrow
cssPrev : '.prev', // previous page arrow
cssFirst : '.first', // go to first page arrow
cssLast : '.last', // go to last page arrow
cssGoto : '.gotoPage', // page select dropdown - select dropdown that set the "page" option
cssPageDisplay : '.pagedisplay', // location of where the "output" is displayed
cssPageSize : '.pagesize', // page size selector - select dropdown that sets the "size" option
// class added to arrows when at the extremes; see the "updateArrows" option
// (i.e. prev/first arrows are "disabled" when on the first page)
cssDisabled : 'disabled', // Note there is no period "." in front of this class name
cssErrorRow : 'tablesorter-errorRow' // error information row
});
The pager has a built-in method to force an update named "pagerUpdate":
You can force an update as follows:
$('table').trigger('pagerUpdate');
or, if you want to force an update and change the page
$('table').trigger('pagerUpdate', 3); // update and set to page 3

jquery tablesorter: updating headers

The table headers will change depending on dynamic ajax updates. When doing this using:
$('table thead').html(headers);
and then re-initialising the table using:
$("table").tablesorter({ theme : 'blue', sortList: [[2,1],[0,0]] });
The ability to sort is removed from the headers. How can I update the headers and re-initialise it so that those headers are treated correctly?
I think in your case you might need to destroy the instance of tablesorter before re-initializing it:
// initialisation
var initOptions = {
theme: 'blue',
sortList: [[2,1],[0,0]]
};
$("table").tablesorter(initOptions);
Then after the ajax update, use:
// Remove tablesorter and all classes
$("table").trigger("destroy", [false, function(){
// callback after the destroy method
$('table thead').html('<tr>' + headers + '</tr>');
$("table").tablesorter(initOptions);
}]);
In this example, make sure the initOptions variable is within the same closure as the initialisation code, or just duplicate it ;)

Persisting jqGrid column preferences

I've got a few jqGrids on my ASP.NET MVC 3 application that have a number of columns. I added the following to the column definitions to default some columns to be hidden:
colModel: [
{ name: 'IceCreamID', hidden: true},
{ name: 'RecipeID', hidden: true }
and this works nicely. Those columns aren't visible on my grid.
Then I added this to implement the column chooser:
var grid = $('#icecreamGrid');
grid.jqGrid('navButtonAdd', '#icecreamPager',
{ caption: "Columns", buttonicon: "ui-icon-calculator",
title: "Choose Columns",
onClickButton: function() {
grid.jqGrid('columnChooser');
}
});
Great, brings up the column chooser now. I then added the following to columns I never wanted to show up in the column chooser:
colModel: [
{ name: 'IceCreamID', hidden: true, hidedlg: true},
So I can now hide/show columns just fine. Now, how would you persist this information? DB? As a cookie? Other way? Is there a preferred way to store this sort of information that is really a user preference rather than something related to the data itself?
More Info
Based on Oleg's comment below, I want to provide a little more information.
The point here is that I've got grids with 10-15 columns which could be display based on the user's preference. For a simple example, one of my grid's has the following 9 columns:
IceCream|ShortName|HasNuts|SugarAdded|LimitedRun|PromoItem|Facility|FirstRun|LastRun
Users can hide/show any of these 9 columns based on their personal preferences.
What I want to do is provide a way to persist which columns a particular user wants to see so that s/he doesn't have to re-choose those columns to view each time the page with that grid is shown.
I found you question very interesting. The question about saving the user state of grid are interesting in many cases. There are some interesting answers on such problems which uses cookie (see here for example).
In my opinion saving of the grid state in database on the server or in the localStorage is better way as the usage of cookie. The best way depends on the project's requirements in which you use it. For example the usage of the database storage on the server allows you to implement roaming state of the grid. If you use the localStorage instead of cookies the user preferences will be lost if the user goes to another computer or just if the user will use another web browser on the same computer.
Another problem with the grid state is the maintenance. The information about the columns of the grid you hold typically in the JavaScript or HTML files and not in the database. In the case the both sources can be not synchronous on the changes in the grid. Different scenarios of the update problem could you easy imagine. Nevertheless the advantages of user's preferences so large in some scenarios that the problems with small disadvantages are not so important and can be solved relatively easy.
So I'll spend some time to implement two demos which shows how it can be implemented. I used localStorage in my demos because of many reasons. I mention only two from there:
Cookies is the way which send permanently different information to or from the server which is not really requited. It increases the size of HTTP header and decreases the performance of the web site (see here for example).
Cookies have very hard restrictions. Corresponds to the section 6.3 of rfc2109 or 6.1 of rfc6265: At least 4096 bytes per cookie, at least 50 cookies per domain (20 in rfc2109), at least 3000 cookies total (300 in rfc2109). So the cookies one can't use to save too many information. For example if you would save state of every grid of every your web page you can quickly achieve the limits.
On the other side localStorage are supported by all modern browsers and will be supported in Internet Explorer starting with IE8 (see here). The localStorage will be automatically saved per origins (like a1.example.com, a2.example.com, a3.example.com, etc) and has arbitrary limit of 5 MB per origin (see here). So if you use the space carefully you will far from the any limits.
So I used in my demos the localStorage. I should additionally mention that there are some plugins like jStorage which use localStorage if it's supported by the browser and use another storage, but the same interface for you in case of old browsers like IE6/IE7. In the case you has only less size of storage: 128 kB instead of 5 MB, but it's better as 4K which one has for cookies (see here).
Now about the implementation. I creates two demos: this and it's extended version: this.
In the first demo the following states of grid will be saved and automatically restored on the page reload (F5 in the most web browsers):
which column are hidden
the order of columns
the width of every column
the name of the column by which the grid will be sorted and the sort direction
the current page number
the current filter of the grid and the flag whether the filter are applied. I used multipleSearch: true setting in the grid.
In the same way one can extend (or reduce) the list of options which are the part of the saved grid state.
The most important parts of the code from the demo you will find below:
var $grid = $("#list"),
saveObjectInLocalStorage = function (storageItemName, object) {
if (typeof window.localStorage !== 'undefined') {
window.localStorage.setItem(storageItemName, JSON.stringify(object));
}
},
removeObjectFromLocalStorage = function (storageItemName) {
if (typeof window.localStorage !== 'undefined') {
window.localStorage.removeItem(storageItemName);
}
},
getObjectFromLocalStorage = function (storageItemName) {
if (typeof window.localStorage !== 'undefined') {
return $.parseJSON(window.localStorage.getItem(storageItemName));
}
},
myColumnStateName = 'ColumnChooserAndLocalStorage.colState',
saveColumnState = function (perm) {
var colModel = this.jqGrid('getGridParam', 'colModel'), i, l = colModel.length, colItem, cmName,
postData = this.jqGrid('getGridParam', 'postData'),
columnsState = {
search: this.jqGrid('getGridParam', 'search'),
page: this.jqGrid('getGridParam', 'page'),
sortname: this.jqGrid('getGridParam', 'sortname'),
sortorder: this.jqGrid('getGridParam', 'sortorder'),
permutation: perm,
colStates: {}
},
colStates = columnsState.colStates;
if (typeof (postData.filters) !== 'undefined') {
columnsState.filters = postData.filters;
}
for (i = 0; i < l; i++) {
colItem = colModel[i];
cmName = colItem.name;
if (cmName !== 'rn' && cmName !== 'cb' && cmName !== 'subgrid') {
colStates[cmName] = {
width: colItem.width,
hidden: colItem.hidden
};
}
}
saveObjectInLocalStorage(myColumnStateName, columnsState);
},
myColumnsState,
isColState,
restoreColumnState = function (colModel) {
var colItem, i, l = colModel.length, colStates, cmName,
columnsState = getObjectFromLocalStorage(myColumnStateName);
if (columnsState) {
colStates = columnsState.colStates;
for (i = 0; i < l; i++) {
colItem = colModel[i];
cmName = colItem.name;
if (cmName !== 'rn' && cmName !== 'cb' && cmName !== 'subgrid') {
colModel[i] = $.extend(true, {}, colModel[i], colStates[cmName]);
}
}
}
return columnsState;
},
firstLoad = true;
myColumnsState = restoreColumnState(cm);
isColState = typeof (myColumnsState) !== 'undefined' && myColumnsState !== null;
$grid.jqGrid({
// ... other options
page: isColState ? myColumnsState.page : 1,
search: isColState ? myColumnsState.search : false,
postData: isColState ? { filters: myColumnsState.filters } : {},
sortname: isColState ? myColumnsState.sortname : 'invdate',
sortorder: isColState ? myColumnsState.sortorder : 'desc',
loadComplete: function () {
if (firstLoad) {
firstLoad = false;
if (isColState) {
$(this).jqGrid("remapColumns", myColumnsState.permutation, true);
}
}
saveColumnState.call($(this), this.p.remapColumns);
}
});
$grid.jqGrid('navButtonAdd', '#pager', {
caption: "",
buttonicon: "ui-icon-calculator",
title: "choose columns",
onClickButton: function () {
$(this).jqGrid('columnChooser', {
done: function (perm) {
if (perm) {
this.jqGrid("remapColumns", perm, true);
saveColumnState.call(this, perm);
}
}
});
}
});
$grid.jqGrid('navButtonAdd', '#pager', {
caption: "",
buttonicon: "ui-icon-closethick",
title: "clear saved grid's settings",
onClickButton: function () {
removeObjectFromLocalStorage(myColumnStateName);
}
});
Be carefully to define myColumnStateName (the value `'ColumnChooserAndLocalStorage.colState'``) in the demo) to different values on the different pages.
The second demo is the extension of the first one using the technique from my old answer to your another question. The demo use the searching toolbar and synchronize additionally information between the advanced searching form and the searching toolbar.
UPDATED: The next answer contains extended version of the code included above. It shows how to persist the selected rows (or row) additionally. Another answer shows how to persist the list of expanded nodes of the tree grid and expand the nodes on the relaoding of the page.

Manage multiple highchart charts in a single webpage

I am having multiple highchart charts of various types(Bar,Pie, Scatter type) in a single web page. Currently I am creating config object for each graph like,
{
chart : {},
blah blah,
}
And feeding them to a custom function which will just call HighCharts.chart(). But this results in duplication of code. I want to manage all this chart rendering logic centrally.
Any Idea on how to do this?
You can use jQuery.extend() and Highcharts.setOptions.
So first you'll make the first object which will be extended by all your charts, this object will contain your Highchart default functions.
You can do it using namespacing.
The following way is good when you have very different charts.
Default graphic:
var defaultChart = {
chartContent: null,
highchart: null,
defaults: {
chart: {
alignTicks: false,
borderColor: '#656565',
borderWidth: 1,
zoomType: 'x',
height: 400,
width: 800
},
series: []
},
// here you'll merge the defauls with the object options
init: function(options) {
this.highchart= jQuery.extend({}, this.defaults, options);
this.highchart.chart.renderTo = this.chartContent;
},
create: function() {
new Highcharts.Chart(this.highchart);
}
};
Now, if you want to make a column chart, you'll extend defaultChart
var columnChart = {
chartContent: '#yourChartContent',
options: {
// your chart options
}
};
columnChart = jQuery.extend(true, {}, defaultChart, columnChart);
// now columnChart has all defaultChart functions
// now you'll init the object with your chart options
columnChart.init(columnChart.options);
// when you want to create the chart you just call
columnChart.create();
If you have similar charts use Highcharts.setOptions which will apply the options for all created charts after this.
// `options` will be used by all charts
Highcharts.setOptions(options);
// only data options
var chart1 = Highcharts.Chart({
chart: {
renderTo: 'container1'
},
series: []
});
var chart2 = Highcharts.Chart({
chart: {
renderTo: 'container2'
},
series: []
});
Reference
http://api.highcharts.com/highcharts#Highcharts.setOptions%28%29
COMPLETE DEMO
I know this has already been answered, but I feel that it can be taken yet further. I'm still newish to JavaScript and jQuery, so if anyone finds anything wrong, or thinks that this approach breaks guidelines or rules-of-thumb of some kind, I'd be grateful for feedback.
Building on the principles described by Ricardo Lohmann, I've created a jQuery plugin, which (in my opinion) allows Highcharts to work more seamlessly with jQuery (i.e. the way that jQuery works with other HTML objects).
I've never liked the fact that you have to supply an object ID to Highcharts before it draws the chart. So with the plug-in, I can assign the chart to the standard jQuery selector object, without having to give the containing <div> an id value.
(function($){
var chartType = {
myArea : {
chart: { type: 'area' },
title: { text: 'Example Line Chart' },
xAxis: { /* xAxis settings... */ },
yAxis: { /* yAxis settings... */ },
/* etc. */
series: []
},
myColumn : {
chart: { type: 'column' },
title: { text: 'Example Column Chart' },
xAxis: { /* xAxis settings... */ },
yAxis: { /* yAxis settings... */ },
/* etc. */
series: []
}
};
var methods = {
init:
function (chartName, options) {
return this.each(function(i) {
optsThis = options[i];
chartType[chartName].chart.renderTo = this;
optsHighchart = $.extend (true, {}, chartType[chartName], optsThis);
new Highcharts.Chart (optsHighchart);
});
}
};
$.fn.cbhChart = function (action,objSettings) {
if ( chartType[action] ) {
return methods.init.apply( this, arguments );
} else if ( methods[action] ) {
return methods[method].apply(this,Array.prototype.slice.call(arguments,1));
} else if ( typeof action === 'object' || !action ) {
$.error( 'Invalid arguments to plugin: jQuery.cbhChart' );
} else {
$.error( 'Action "' + action + '" does not exist on jQuery.cbhChart' );
}
};
})(jQuery);
With this plug-in, I can now assign a chart as follows:
$('.columnChart').cbhChart('myColumn', optionsArray);
This is a simplistic example of course; for a real example, you'd have to create more complex chart-properties. But it's the principles that concern us here, and I find that this approach addresses the original question. It re-uses code, while still allowing for individual chart alterations to be applied progressively on top of each other.
In principle, it also allows you to group together multiple Ajax calls into one, pushing each graph's options and data into a single JavaScript array.
The obligatory jFiddle example is here: http://jsfiddle.net/3GYHg/1/
Criticism welcome!!
To add to #Ricardo's great answer, I have also done something very similar. In fact, I won't be wrong if i said I went a step further than this. Hence would like to share the approach.
I have created a wrapper over the highchart library. This gives multiple benefits, following being the main advantages that encouraged going in this path
Decoupling: Decouples your code from highcharts
Easy Upgrades: This wrapper will be the only code that will require modification in case of any breaking changes in highchart api after upgrades, or even if one decides to move to a differnt charting library altogether (even from highchart to highstock can be exhaustive if your application uses charts extensively)
Easy of use: The wrapper api is kept very simple, only things that may vary are exposed as options (That too whose values won't be as a deep js object like HC already has, mostly 1 level deep), each having a default value. So most of the time our chart creation is very short, with the constructor taking 1 options object with merely 4-5 properties whose defaults don't suit the chart under creation
Consistent UX: Consistent look & feel across the application. eg: tool tip format & position, colors, font family, colors, toolbar (exporting) buttons, etc
Avoid duplication: Of course as a valid answer of the asked question it has to avoid duplication, and it does to a huge extent
Here is what the options look like with their default values
defaults : {
chartType : "line",
startTime : 0,
interval : 1000,
chartData : [],
title : "Product Name",
navigator : true,
legends : true,
presetTimeRanges : [],
primaryToolbarButtons : true,
secondaryToolbarButtons : true,
zoomX : true,
zoomY : false,
height : null,
width : null,
panning : false,
reflow : false,
yDecimals : 2,
container : "container",
allowFullScreen : true,
credits : false,
showAll : false,
fontSize : "normal", // other option available is "small"
showBtnsInNewTab : false,
xAxisTitle : null,
yAxisTitle : null,
onLoad : null,
pointMarkers : false,
categories : []
}
As you can see, most of the times, its just chartData that changes. Even if you need to set some property, its mainly just true/false types, nothing like the horror that highchart constructor expects (not critizing them, the amount of options they provide is just amazing from customization Point of View, but for every developer in the team to understand & master it can take some time)
So creation of chart is as simple as
var chart=new myLib.Chart({
chartData : [[1000000,1],[2000000,2],[3000000,1],[4000000,5]]
});

ASP.NET MVC + jqGrid without AJAX

I have an ASP.NET MVC application which is executing a search against a products database. I want to display the results in a jqGrid using the TreeGrid module. I don't really need the grid to be AJAX-y because the data is static and it is small enough that it can all be sent to the client at once.
First question: how do I set up jqGrid so that instead of pulling the JSON data from a URL it just looks in a JS variable or something?
Secondly, what is the most appropriate way to get ASP.NET MVC to put JSON data into a JavaScript variable? I already have the List in my controller and just want to somehow get it out into a JS variable after JSON-ing it.
Or am I fighting against the current too much and just accept the AJAX-y way that jqGrid seems to want to work?
Thanks,
~ Justin
Here is how to display a jqGrid tree using a JavaScript function.
$(document).ready(function() {
TreeDemo.setupGrid($("#tree"));
});
TreeDemo = {
data: { A: ["A1", "A2"], B: ["B1", "B2"] },
setupGrid: function(grid) {
grid.jqGrid({
colNames: ['Name'],
colModel: [
{ name: 'Name', index: 'Name', width: "250em" }
],
datatype: TreeDemo.treeData,
loadui: "none",
sortname: 'Number',
treeGrid: true,
treeGridModel: "adjacency",
sortorder: "asc"
})
},
treeData: function(postdata) {
var items = postdata.nodeid ? TreeDemo.data[postdata.nodeid] : TreeDemo.data;
var i = 0;
var rows = new Array();
for (val in items) {
var isLeaf = postdata.nodeid != undefined;
rows[i] = {
Name: val,
Id: val,
level: postdata.nodeid ? 1 : 0,
parent: postdata.nodeid || null,
isLeaf: isLeaf ? "true" : "false",
expanded: "false"
}
i++;
}
$("#tree")[0].addJSONData({
Total: 1,
Page: 1,
Records: 2,
Rows: rows
});
}
};
Note that there are lots of options for how you do this and my example is only one.
The way I would get the JSON into a JS var is to either:
Write a HTML Helper which emits a short script to the page.
Write an action which returns a JavaScriptResult to get the data in a file, if, for some reason, you can't have the data inline.
You create the JSON using the .NET JavaScript serializer. Look at the JsonResult.ExecuteResult in the MVC source code for an example.
See the Data Manipulation page in the jqGrid documentation wiki. There you'll find many ways to feed the data to the grid.
There is also a Table_to_jqGrid plugin that may be an useful option.

Resources