fnFakeRowspan (server-side processing only)
fnFakeRowspan (server-side processing only)
I finally was assigned a case where I needed rowspan support, but since I didn't want to loose DataTables support there was just one option. Fake the rowspan support! Before you start cheering, this _only_ works with server-side processing of tables and is limited to just one column.
This plugin hooks in to the fnDrawCallback and remove cells and add rowspan to others, thus the data is impossible for DataTables to sort. When using server-side processing the data is always sorted by the server and then returns new data.
Sadly I don't have any time to handle support issues, this works for me and I'm releasing it as-is. Should I discover bugs I will fix them and then post updated versions here.
Support for several columns should be pretty easy to add for those who need it, I do not and haven't put any time to it.
[code]$.fn.dataTableExt.oApi.fnFakeRowspan = function ( oSettings, iColumn ) {
/*
* Type: Plugin for DataTables (http://datatables.net) JQuery plugin.
* Name: dataTableExt.oApi.fnFakeRowspan
* Requires: DataTables 1.6.0+
* Version: 1.0.0
* Description: Creates rowspan cells in a column when there are two or more
* cells in a row with the same content. It only works for
* server-side processing as cells are removed and can't be
* sorted by DataTables.
*
* Inputs: object:oSettings - dataTables settings object
* integer:iColumn - the column to fake rowspans in
* Returns: JQuery
* Usage: $('#example').dataTable().fnFakeRowspan(3);
*
* Author: Fredrik Wendel
* Created: 2010-02-10
* Language: Javascript
* License: GPL v2 or BSD 3 point style
*/
/* Fail silently on missing/errorenous parameter data. */
if (isNaN(iColumn)) {
return false;
}
if (iColumn < 0 || iColumn > oSettings.aoColumns.length-1) {
alert ('Invalid column number choosen, must be between 0 and ' + (oSettings.aoColumns.length-1));
return false;
}
var oSettings = oSettings,
iColumn = iColumn;
oSettings.aoDrawCallback.push({ "fn": fakeRowspan, "sName": "fnFakeRowspan" });
function fakeRowspan () {
var firstOccurance = null,
value = null,
rowspan = 0;
jQuery.each(oSettings.aoData, function (i, oData) {
var val = oData._aData[iColumn],
cell = oData.nTr.childNodes[iColumn];
/* Reset values on new cell data. */
if (val != value) {
value = val;
firstOccurance = cell;
rowspan = 0;
}
if (val == value) {
rowspan++;
}
if (firstOccurance !== null && val == value && rowspan > 1) {
oData.nTr.removeChild(cell);
firstOccurance.rowSpan = rowspan;
}
});
}
return this;
}[/code]
Code only tested with DataTables 1.6.1 (should work with 1.6.0 though) and Firefox 3.6, Opera 10.10, Chrome 4.0.249.78, Safari 4.0.4, Internet Explorer 8.0.
I hope that sharing this 2-3 hack helps someone, and if you appreciate it, please consider donating to Allan for future development of DataTables.
This plugin hooks in to the fnDrawCallback and remove cells and add rowspan to others, thus the data is impossible for DataTables to sort. When using server-side processing the data is always sorted by the server and then returns new data.
Sadly I don't have any time to handle support issues, this works for me and I'm releasing it as-is. Should I discover bugs I will fix them and then post updated versions here.
Support for several columns should be pretty easy to add for those who need it, I do not and haven't put any time to it.
[code]$.fn.dataTableExt.oApi.fnFakeRowspan = function ( oSettings, iColumn ) {
/*
* Type: Plugin for DataTables (http://datatables.net) JQuery plugin.
* Name: dataTableExt.oApi.fnFakeRowspan
* Requires: DataTables 1.6.0+
* Version: 1.0.0
* Description: Creates rowspan cells in a column when there are two or more
* cells in a row with the same content. It only works for
* server-side processing as cells are removed and can't be
* sorted by DataTables.
*
* Inputs: object:oSettings - dataTables settings object
* integer:iColumn - the column to fake rowspans in
* Returns: JQuery
* Usage: $('#example').dataTable().fnFakeRowspan(3);
*
* Author: Fredrik Wendel
* Created: 2010-02-10
* Language: Javascript
* License: GPL v2 or BSD 3 point style
*/
/* Fail silently on missing/errorenous parameter data. */
if (isNaN(iColumn)) {
return false;
}
if (iColumn < 0 || iColumn > oSettings.aoColumns.length-1) {
alert ('Invalid column number choosen, must be between 0 and ' + (oSettings.aoColumns.length-1));
return false;
}
var oSettings = oSettings,
iColumn = iColumn;
oSettings.aoDrawCallback.push({ "fn": fakeRowspan, "sName": "fnFakeRowspan" });
function fakeRowspan () {
var firstOccurance = null,
value = null,
rowspan = 0;
jQuery.each(oSettings.aoData, function (i, oData) {
var val = oData._aData[iColumn],
cell = oData.nTr.childNodes[iColumn];
/* Reset values on new cell data. */
if (val != value) {
value = val;
firstOccurance = cell;
rowspan = 0;
}
if (val == value) {
rowspan++;
}
if (firstOccurance !== null && val == value && rowspan > 1) {
oData.nTr.removeChild(cell);
firstOccurance.rowSpan = rowspan;
}
});
}
return this;
}[/code]
Code only tested with DataTables 1.6.1 (should work with 1.6.0 though) and Firefox 3.6, Opera 10.10, Chrome 4.0.249.78, Safari 4.0.4, Internet Explorer 8.0.
I hope that sharing this 2-3 hack helps someone, and if you appreciate it, please consider donating to Allan for future development of DataTables.
This discussion has been closed.
Replies
Very cool - in fact, how appropriate your slick plug-in function is thread number 1337 :-)
I'll have a play around with this in a bit, when I've got a bit of free time - but it looks great. Thanks very muhc indeed for sharing it with us!
Regards,
Allan
1.0.1 - add bCaseSensitive option for the ability do to case-insensitive comparisons.
[code]$.fn.dataTableExt.oApi.fnFakeRowspan = function ( oSettings, iColumn, bCaseSensitive ) {
/*
* Type: Plugin for DataTables (http://datatables.net) JQuery plugin.
* Name: dataTableExt.oApi.fnFakeRowspan
* Requires: DataTables 1.6.0+
* Version: 1.0.1
* Description: Creates rowspan cells in a column when there are two or more
* cells in a row with the same content. It only works for
* server-side processing as cells are removed and can't be
* sorted by DataTables.
*
* Inputs: object:oSettings - dataTables settings object
* integer:iColumn - the column to fake rowspans in
* boolean:bCaseSensitive - whether the comparison is case-sensitive or not (default: true)
* Returns: JQuery
* Usage: $('#example').dataTable().fnFakeRowspan(3);
* $('#example').dataTable().fnFakeRowspan(3, false);
*
* Author: Fredrik Wendel
* Created: 2010-02-10
* Language: Javascript
* License: GPL v2 or BSD 3 point style
*/
/* Fail silently on missing/errorenous parameter data. */
if (isNaN(iColumn)) {
return false;
}
if (iColumn < 0 || iColumn > oSettings.aoColumns.length-1) {
alert ('Invalid column number choosen, must be between 0 and ' + (oSettings.aoColumns.length-1));
return false;
}
var oSettings = oSettings,
iColumn = iColumn,
bCaseSensitive = (typeof(bCaseSensitive) != 'boolean' ? true : bCaseSensitive);
oSettings.aoDrawCallback.push({ "fn": fakeRowspan, "sName": "fnFakeRowspan" });
function fakeRowspan () {
var firstOccurance = null,
value = null,
rowspan = 0;
jQuery.each(oSettings.aoData, function (i, oData) {
var val = oData._aData[iColumn],
cell = oData.nTr.childNodes[iColumn];
/* Use lowercase comparison if not case-sensitive. */
if (!bCaseSensitive) {
val = val.toLowerCase();
}
/* Reset values on new cell data. */
if (val != value) {
value = val;
firstOccurance = cell;
rowspan = 0;
}
if (val == value) {
rowspan++;
}
if (firstOccurance !== null && val == value && rowspan > 1) {
oData.nTr.removeChild(cell);
firstOccurance.rowSpan = rowspan;
}
});
}
return this;
}[/code]
One little issue, which would be nice to resolve before I post it on the plug-ins page, if I put into this example page: http://datatables.net/examples/data_sources/server_side.html - then when I click 'next' it stalls. No JS error - it just stops. I can do a bit of sorting and stuff and that all works well - so I think one of the cells must have something in it which is causing a problem. Might you be able to have a little look at this?
Regards,
Allan
[code]<?php
$response = array(
'0' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Gecko","Firefox 1.0","Win 98+ / OSX.2+","1.7","A"],["Gecko","Firefox 1.5","Win 98+ / OSX.2+","1.8","A"],["Gecko","Firefox 2.0","Win 98+ / OSX.2+","1.8","A"],["Gecko","Firefox 3.0","Win 2k+ / OSX.3+","1.9","A"],["Gecko","Camino 1.0","OSX.2+","1.8","A"],["Gecko","Camino 1.5","OSX.3+","1.8","A"],["Gecko","Netscape 7.2","Win 95+ / Mac OS 8.6-9.2","1.7","A"],["Gecko","Netscape Browser 8","Win 98SE+","1.7","A"],["Gecko","Netscape Navigator 9","Win 98+ / OSX.2+","1.8","A"],["Gecko","Mozilla 1.0","Win 95+ / OSX.1+","1","A"]] }',
'10' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Gecko","Mozilla 1.1","Win 95+ / OSX.1+","1.1","A"],["Gecko","Mozilla 1.2","Win 95+ / OSX.1+","1.2","A"],["Gecko","Mozilla 1.3","Win 95+ / OSX.1+","1.3","A"],["Gecko","Mozilla 1.4","Win 95+ / OSX.1+","1.4","A"],["Gecko","Mozilla 1.5","Win 95+ / OSX.1+","1.5","A"],["Gecko","Mozilla 1.6","Win 95+ / OSX.1+","1.6","A"],["Gecko","Mozilla 1.7","Win 98+ / OSX.1+","1.7","A"],["Gecko","Mozilla 1.8","Win 98+ / OSX.1+","1.8","A"],["Gecko","Seamonkey 1.1","Win 98+ / OSX.2+","1.8","A"],["Gecko","Epiphany 2.20","Gnome","1.8","A"]] }',
'20' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["KHTML","Konqureror 3.1","KDE 3.1","3.1","C"],["KHTML","Konqureror 3.3","KDE 3.3","3.3","A"],["KHTML","Konqureror 3.5","KDE 3.5","3.5","A"],["Misc","NetFront 3.1","Embedded devices","-","C"],["Misc","NetFront 3.4","Embedded devices","-","A"],["Misc","Dillo 0.8","Embedded devices","-","X"],["Misc","Links","Text only","-","X"],["Misc","Lynx","Text only","-","X"],["Misc","IE Mobile","Windows Mobile 6","-","C"],["Misc","PSP browser","PSP","-","C"]] }',
'30' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Other browsers","All others","-","-","U"],["Presto","Opera 7.0","Win 95+ / OSX.1+","-","A"],["Presto","Opera 7.5","Win 95+ / OSX.2+","-","A"],["Presto","Opera 8.0","Win 95+ / OSX.2+","-","A"],["Presto","Opera 8.5","Win 95+ / OSX.2+","-","A"],["Presto","Opera 9.0","Win 95+ / OSX.3+","-","A"],["Presto","Opera 9.2","Win 88+ / OSX.3+","-","A"],["Presto","Opera 9.5","Win 88+ / OSX.3+","-","A"],["Presto","Opera for Wii","Wii","-","A"],["Presto","Nokia N800","N800","-","A"]] }',
'40' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Presto","Nintendo DS browser","Nintendo DS","8.5","C/A1"],["Tasman","Internet Explorer 4.5","Mac OS 8-9","-","X"],["Tasman","Internet Explorer 5.1","Mac OS 7.6-9","1","C"],["Tasman","Internet Explorer 5.2","Mac OS 8-X","1","C"],["Trident","Internet Explorer 4.0","Win 95+","4","X"],["Trident","Internet Explorer 5.0","Win 95+","5","C"],["Trident","Internet Explorer 5.5","Win 95+","5.5","A"],["Trident","Internet Explorer 6","Win 98+","6","A"],["Trident","Internet Explorer 7","Win XP SP2+","7","A"],["Trident","AOL browser (AOL desktop)","Win XP","6","A"]] }',
'50' => '{"sEcho": __ECHO__, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Webkit","Safari 1.2","OSX.3","125.5","A"],["Webkit","Safari 1.3","OSX.3","312.8","A"],["Webkit","Safari 2.0","OSX.4+","419.3","A"],["Webkit","Safari 3.0","OSX.4+","522.1","A"],["Webkit","OmniWeb 5.5","OSX.4+","420","A"],["Webkit","iPod Touch / iPhone","iPod","420.1","A"],["Webkit","S60","S60","413","A"]] }',
);
$ret = $response[$_GET['iDisplayStart']];
$ret = str_replace('__ECHO__', $_GET['sEcho'], $ret);
header('Content-Type: application/json');
header('Content-Length: ' . strlen($ret));
print $ret;
?>[/code]
How did you initialize it on the table? This works just fine for me:
[code] $(document).ready(function() {
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "http://localhost/datatables/datatables.php"
} ).fnFakeRowspan(0);
} );[/code]
Regards,
Allan
I've put the plug-in up on the plug-ins page: http://datatables.net/plug-ins/api#fnFakeRowspan . Thanks for taking the time to create and share the plug-in with us!
Regards,
Allan
i have an issue with simple quote and addslashes function.. like your saying previously, the problem is that a single escaped quote isn't valid in strict JSON format. In order to solve the problem i used the addcslashes which allows you to choose which character you wanna escape. With this solution i didn't encounter the problem again.
In all case, i would like to thank you for this wonderful plug-in but also for the other things you've done like all the web tools available on your website. I use them in my daily work and it's just awesome !
Best regards.
PS : Sorry for my english who has a little french accent.
[code]
$.fn.dataTableExt.oApi.fnMultiRowspan = function ( oSettings, oSpannedColumns, bCaseSensitive ) {
/*
* Type: Plugin for DataTables (http://datatables.net) JQuery plugin.
* Name: dataTableExt.oApi.fnMultiRowspan
* Requires: DataTables 1.6.0+
* Version: 0.7
* Description: Creates rowspan cells in one or more columns when there are two or more cells in a row with the same
* content.
*
* Inputs: object:oSettings - dataTables settings object
* object:oSpannedColumns - the columns to fake rowspans in
* boolean:bCaseSensitive - whether the comparison is case-sensitive or not (default: false)
* Returns: JQuery
* Usage: $('#example').dataTable().fnMultiRowspan([0]);
* $('#example').dataTable().fnMultiRowspan({0: 0, 1: 0, 2: 0}, true);
* $('#example').dataTable().fnMultiRowspan({"engine.name": "engine.name", "grade": "grade"});
*
* Author: Kaarel Nummert
* Comment: Based on the fnFakeRowspan (http://datatables.net/plug-ins/api#fnFakeRowspan) plug-in created by Fredrik Wendel.
* Created: 2011-09-02
* Language: Javascript
* License: GPL v2 or BSD 3 point style
*/
var oSettings = oSettings,
oSpannedColumns = oSpannedColumns,
bCaseSensitive = (typeof(bCaseSensitive) != 'boolean' ? false : bCaseSensitive);
oSettings.aoDrawCallback.push({ "fn": fnMultiRowspan, "sName": "fnMultiRowspan" });
function fnMultiRowspan () {
/* Reset rowspans. Should probably check if any of those columns are meant to be hidden. There is that option in DataTables, you know. */
oSettings.oInstance.children("tbody").find("td").removeAttr("rowspan").show();
/* Reset values on new cell data. */
var firstOccurance = {},
value = {},
rowspan = {};
for (i = 0; i < oSettings.aiDisplay.length; i++) {
oData = oSettings.aoData[oSettings.aiDisplay[i]];
for (key in oSpannedColumns) {
var index = fnCellIndexByKey(key);
if (oSpannedColumns[key] === null || index === null) {
continue;
}
var cell = $($(oData.nTr).children().get(index)),
comparisonKey = oSpannedColumns[key],
val = oSettings.oApi._fnGetObjectDataFn(comparisonKey).call(oData, oData._aData);
/* Use lowercase comparison if not case-sensitive. */
if (!bCaseSensitive) {
val = val.toLowerCase();
}
if (typeof value[key] == "undefined" || val != value[key]) {
value[key] = val;
firstOccurance[key] = cell;
rowspan[key] = 1;
} else {
rowspan[key]++;
}
if (typeof firstOccurance[key] != "undefined" && val == value[key] && rowspan[key] > 1) {
cell.hide();
firstOccurance[key].attr("rowspan", rowspan[key]);
}
}
}
}
function fnCellIndexByKey (key) {
for (index in oSettings.aoColumns) {
if (oSettings.aoColumns[index].mDataProp == key) {
return index;
}
}
return null;
}
/* Ensure rowspanning is done even if the table has already been drawn. */
fnMultiRowspan();
return this;
}
[/code]
Only tested it with DataTables 1.8.1 and all modern browsers at my disposal - Firefox 6.0.1, Opera 11.51, Chrome 13.0.782.218, Safari 5.1, Internet Explorer 9.0.
Can't see any reasons why it would have restrictions on data source, pagination, filtering or sorting. Although, when using it in conjunction with sorting, one has to keep in mind it does not do any data grouping of any sort. So if the same values appear in the comparison column in more than one block of rows, those blocks will be "rowspanned" separately. It probably will not work with column hiding, and I assume a native JS speaker can make it quite a bit more elegant.
EDITED: September 5th, 2011
I made some significant improvements to it:
1. Instead of specifying one particular comparison column, user can now specify a separate comparison column to every to-be-spanned column.
2. Instead of only supporting column indices, one can now use the (nested) keys of data input. Especially useful if data is given as object.
3. Removed option to hide comparison column (one may not consider this as an improvement, but the two improvements above made it obsolete).
Example to show off the improvement No 1 and No 2:
[code]
$(document).ready(function() {
var oTable = $('#example').dataTable({
aoColumns: [
{mDataProp: "engine.name"},
{mDataProp: "browser"},
{mDataProp: "platform"},
{mDataProp: "engine.version"},
{mDataProp: "grade"},
]
}).makeEditable().fnMultiRowspan({"engine.name": "engine.name", "engine.version": "engine.name", "grade": "grade"});
oTable.fnAddData([
{engine: {name: "Gecko", version: "1.7"}, browser: "Firefox 1.0", platform: "Win 98+ / OSX.2+", grade: "A"},
{engine: {name: "Gecko", version: "1.8"}, browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", grade: "A"},
{engine: {name: "Gecko", version: "1.8"}, browser: "Firefox 2.0", platform: "Win 98+ / OSX.2+", grade: "A"},
{engine: {name: "Gecko", version: "1.9 "}, browser: "Firefox 3.0", platform: "Win 98+ / OSX.2+", grade: "A"}
]);
} );
[/code]
This is my first time contributing to anything like open source code, so constructive criticism is welcome.
[code]
$.fn.dataTableExt.oApi.fnMultiRowspan = function ( oSettings, oSpannedColumns, bCaseSensitive ) {
/*
* Type: Plugin for DataTables (http://datatables.net) JQuery plugin.
* Name: dataTableExt.oApi.fnMultiRowspan
* Requires: DataTables 1.6.0+
* Version: 0.7
* Description: Creates rowspan cells in one or more columns when there are two or more cells in a row with the same
* content.
*
* Inputs: object:oSettings - dataTables settings object
* object:oSpannedColumns - the columns to fake rowspans in
* boolean:bCaseSensitive - whether the comparison is case-sensitive or not (default: false)
* Returns: JQuery
* Usage: $('#example').dataTable().fnMultiRowspan([0]);
* $('#example').dataTable().fnMultiRowspan({0: 0, 1: 0, 2: 0}, true);
* $('#example').dataTable().fnMultiRowspan({"engine.name": "engine.name", "grade": "grade"});
*
* Author: Kaarel Nummert
* Comment: Based on the fnFakeRowspan (http://datatables.net/plug-ins/api#fnFakeRowspan) plug-in created by Fredrik Wendel.
* Created: 2011-09-02
* Language: Javascript
* License: GPL v2 or BSD 3 point style
*/
var oSettings = oSettings,
oSpannedColumns = oSpannedColumns,
bCaseSensitive = (typeof(bCaseSensitive) != 'boolean' ? false : bCaseSensitive);
oSettings.aoDrawCallback.push({ "fn": fnMultiRowspan, "sName": "fnMultiRowspan" });
function fnMultiRowspan () {
/* Reset rowspans. Should probably check if any of those columns are meant to be hidden. There is that option in DataTables, you know. */
oSettings.oInstance.children("tbody").find("td").removeAttr("rowspan").show();
// Need to move all numeric keys and values one to the left where > hidden col index
var oSpannedColumnsTemp = new Object();
for (key in oSpannedColumns)
oSpannedColumnsTemp[key] = oSpannedColumns[key];
for (index in oSettings.aoColumns) {
var indexInt = parseInt(index);
if (typeof(indexInt) == 'number' && !oSettings.aoColumns[index].bVisible) {
var oSpannedColumnsKeyTransitions = new Object(); // Map of previous key to new key
for (key in oSpannedColumnsTemp) {
var newVal = oSpannedColumnsTemp[key];
var newValInt = parseInt(newVal);
if (typeof(newValInt) == 'number' && newValInt > indexInt)
oSpannedColumnsTemp[key] = '' + (newValInt-1);
var keyInt = parseInt(key);
if (typeof(keyInt) == 'number' && keyInt > indexInt)
oSpannedColumnsKeyTransitions[key] = '' + (keyInt-1);
}
for (key in oSpannedColumnsKeyTransitions) {
oSpannedColumnsTemp[oSpannedColumnsKeyTransitions[key]] = oSpannedColumnsTemp[key];
delete oSpannedColumnsTemp[key];
}
}
}
/* Reset values on new cell data. */
var firstOccurance = {},
value = {},
rowspan = {};
for (i = 0; i < oSettings.aiDisplay.length; i++) {
oData = oSettings.aoData[oSettings.aiDisplay[i]];
for (key in oSpannedColumnsTemp) {
var index = fnCellIndexByKey(key);
if (oSpannedColumnsTemp[key] === null || index === null)
continue;
var cell = $($(oData.nTr).children().get(index)),
comparisonKey = oSpannedColumnsTemp[key],
val = oSettings.oApi._fnGetObjectDataFn(comparisonKey).call(oData, oData._aData);
/* Use lowercase comparison if not case-sensitive. */
if (!bCaseSensitive)
val = val.toLowerCase();
if (typeof value[key] == "undefined" || val != value[key]) {
value[key] = val;
firstOccurance[key] = cell;
rowspan[key] = 1;
} else
rowspan[key]++;
if (typeof firstOccurance[key] != "undefined" && val == value[key] && rowspan[key] > 1) {
cell.hide();
firstOccurance[key].attr("rowspan", rowspan[key]);
}
}
}
}
function fnCellIndexByKey (key) {
for (index in oSettings.aoColumns) {
if (oSettings.aoColumns[index].mDataProp == key) {
return index;
}
}
return null;
}
/* Ensure rowspanning is done even if the table has already been drawn. */
fnMultiRowspan();
return this;
}
[/code]