KeyEditableTable: Mixing DataTables, KeyTables and jEditable all together
Problem description
Now that Web 2.0 is here, everybody enjoys to metamorphose his web browser into a rich, very rich, client application, like a real desktop application.
Thats not always a good idea... Web has to be kept simple.
Nevertheless, Google and Yahoo have proved many times that is possible.
Since Google doc has been released, everybody wants to be able to manage HTML tables as OpenOffice or Excel spreadsheets do.
And fortunatly a lot of Javascript API provides this kind of functionalities:
- Google spreasheet api
- Yahoo UI datatable
- Dojo Enhanced grid
- jQuery jqGrid plugin
- jQueryDataTables plugin
- ...
As the CMF framework I work with is using jQuery I have choosed to work with DataTables:
- It is simpler than jqGrid to setup and configured.
- It can be mixed quite easily with others plugins like KeyTables and jEditable to enhanced its functionalities.
- It is well maintained
This howto explains how to manage all together DataTables, KeyTables and jEditable. It has been named KeyEditableTable for this howto.
KeyEditableTable plugin assertions
The goals of this plugin are the following:
- Have a real object approach to prevent using a global plugin objet that will manage all plugins instances in a table or whatever else property.
Each created object has to manage its life by himself. - Many key editable tables can co-exist on the same HTML page.
- Keep object creation and configuration simple.
Plugin source code
/*
* Object KeyEditableTable
*/
function KeyEditableTable(options) {
// Object properties
try {
this.keys = null;
this.datatable = null;
this.table_id = options['table_id'] || null;
this.update_url = options['update_url'] || null;
this.widget_type = options['widget_type'] || 'custom_grid_widget';
this.options = options || {};
} catch(err) {
alert('KeyEditableTable: ' + err);
}
// Object methods
/*
* Constructor: blur()
*
* Purpose:
* Blur the table and keys
*/
this.blur = function() {
this.keys.fnBlur();
}
/*
* Constructor: initialize()
*
* Purpose:
* Create the datatable, the keys table and associate them together
*/
this.initialize = function(){
try {
var table_id_selector = '#' + this.table_id;
var elem = document.getElementById(this.table_id);
if(elem) {
this.datatable = $(table_id_selector).dataTable(this.options.datatable);
this.setTableCellsType();
// initialize keys and select first row
this.initializeKeys(0,0);
// make table cells editable
this.makeEditable();
}
}
catch (err) {
alert('KeyEditableTable.initialize: ' + err);
}
}
/*
* Method: initializeKeys
*
* Purpose:
* Create the KeyTable object and associate event to each cell
*/
this.initializeKeys = function(col, row) {
try{
this.keys = new KeyTable({
"focus": [col, row],
"table": document.getElementById(this.table_id),
"datatable": this.datatable,
"focusClass": 'cellfocus'
});
// Add a click handler to the rows - this could be used as a callback
var nodes = this.datatable.fnGetNodes();
for ( var ind=0 ; ind < nodes.length ; ind++ ) {
var row = nodes[ind];
this.initializeTableRow(row);
}
} catch (err) {
alert(err);
}
}
/*
* Method: initializeTableRow
*
* Purpose:
* Assign event on row cell to keys
*/
this.initializeTableRow = function(row) {
try {
var table = this.datatable;
var keys = this.keys;
$('TD', row).each(function() {
// Add focus event on table cells
keys.event.focus(this, function(nCell){
$(table.fnSettings().aoData).each(function(){
$(this.nTr).removeClass('row_selected');
});
$(nCell.parentNode).addClass('row_selected');
});
// Add blur event on table celles
keys.event.blur(this, function(nCell){
if (oEditing) {
// prevent double submit if submit was clicked
t = setTimeout(function(){
try {
oEditing.submit();
//oEditing = null;
}
catch (err) {
alert(err);
};
}, 200);
}
});
// Add action(enter key) event on table cells
keys.event.action(this, function(nCell){
try {
// Dispatch click event to go into edit mode
//alert(1);
setTimeout( function () {$(nCell.getElementsByTagName('SPAN')[0]).dblclick();}, 500);
//alert(2);
}
catch (err) {
alert(err);
}
});
});
} catch(err) {
alert('initializeTableRow: ' + err);
}
}
/*
* Method: getSelectedRow
*
* Purpose:
* Return arraw of selected rows
*/
this.getSelectedRow = function() {
var aReturn = new Array();
var aTrs = this.datatable.fnGetNodes();
for ( var i=0 ; i< ; i++ )
{
if ( $(aTrs[i]).hasClass('row_selected') )
{
aReturn.push( aTrs[i] );
}
}
return aReturn;
}
/*
* Method: setTableCellsTypes
*
* Purpose:
* Define table cells types. Set HTML type for sorting columns
*/
this.setTableCellsType = function() {
try {
// Do what you want here to setup columns data types
var oSettings = this.datatable.fnSettings();
for (var j = 0; j < oSettings.aoColumns.length; j++) {
oSettings.aoColumns[j].sType = 'html';
}
this.datatable.aoColumns = oSettings.aoColumns;
} catch (err) {
alert ('setTableCellsType: ' + err);
}
}
/*
* Method: updateSubmitData
*
* Purpose:
* Append to form extra data from current line when needed
*/
this.updateSubmitData = function(field, data){
try {
// Do what you want here
return data;
} catch(err) {
alert('KeyEditableTable.updateSubmitData: ' + err);
}
}
/*
* Method: refreshDataAfterFieldUpdate
*
* Purpose:
* refresh table data after a given field has been updated
*/
this.refreshDataAfterFieldUpdate = function(field, result, settings){
try {
// Do what you want here
} catch (err) {
alert('KeyEditableTable.refreshDataAfterFieldUpdate: ' + err);
}
}
/*
* Method: makeEditableNodes
*
* Purpose:
* Associate selected DOM elements with JEditable widget and keys
*/
this.makeEditableNodes = function(nodes, submit_label) {
try {
var keys = this.keys;
var widget_type = this.widget_type;
var table = this.datatable;
var otable = this;
var params = {
placeholder: gPlaceHolder,
tooltip : 'Double click to edit...',
type : widget_type,
event : "dblclick",
width : 120,
onblur : 'submit',
onedit : function() {
// Do not allow edition, if already editing
//alert(gKeys.block +' - ' + oEditing);
if(keys.block || oEditing != null)
return false;
keys.block = true;
//alert('onedit');
return true;
},
onreset : function(setting, original) {
keys.block = false;
//setTimeout( function () {keys.block = false;}, 0);
oEditing = null;
//alert('reset');
return true;
},
submitdata : function(original, settings) {
try {
var data = $(this).formToArray(true);
// If this field depends on other parameters, get these
return otable.updateSubmitData(this, data);
} catch(err) {
alert('submitdata: ' + err);
}
},
callback : function( result, settings) {
//alert(result + ' - ' + aPos);
var cell = this.parentNode;
var row = cell.parentNode;
var aPos = table.fnGetPosition(cell);
table.fnUpdate(cell.innerHTML, aPos[0], aPos[1] );
otable.makeEditableRow(row);
otable.refreshDataAfterFieldUpdate(this, result, settings);
//table.fnDraw();
otable.keys.block = false;
oEditing = null;
//alert('callback');
// Set thickbox if any
tb_init($('.thickbox', cell));
return true;
},
indicator : '<' + 'img src="/spinner.gif" alt="loading..." title="loading...">'
};
if(submit_label) {
params['submit'] = submit_label;
params['cancel'] = 'Cancel';
}
if(nodes)
$(nodes).editable(this.update_url, params);
} catch(err) {
alert('makeEditableNodes: ' + err);
}
}
/*
* Method: makeEditable
*
* Purpose:
* Associate table cells with jeditable
*/
this.makeEditable = function() {
try {
var nodes = this.datatable.fnGetNodes();
for (var ind = 0; ind < nodes.length; ind++) {
var row = nodes[ind];
this.makeEditableRow(row);
}
}
catch (err) {
alert('makeEditable: ' + err);
}
}
/*
* Method: makeEditableRow
*
* Purpose:
* Associate table cells with jeditable
*
* Parameters:
* row: row to make editable
*/
this.makeEditableRow = function(row) {
try {
// If more than one value, it is not editable
// Suppose that value does not contains any comma
this.makeEditableNodes($('td span.editable', row), '');
}
catch (err) {
alert('makeEditableRow: ' + err);
}
}
this.initialize();
}
KeyEditableTable working example?
I am new to both DataTables and jQuery.
I cant figure out what difference makes your plugin
Do you have any working example?
Or could you explain how should this be used/called in a simple page with a couple of tables?
I look for is a grid web system that behaves more or less like a worksheet (excel) so cells are editable rightaway (no need to press return before and after edition) when you enter the cell using arrow keys ... and you can send the edited table to a database server.
I wonder also if your plugin lets use frozen rows/cols (like FixerHeader plugin) since I would create quite big tables
Thanks a lot for your answer
how to instantiate the class
There is an error in line 159 (missing upper limit for your "i" loop)!!
Apart from that, same problem for me: I couldn't figure out how to make this work
Could you please post a simple html code with small table(s) and all headers and scripts needed to invocate required libraries? (I suppose jQuery, DataTables, KeyTables and jEditable)
Specially, how should I create the options array and pass it to the class?
No idea :(
A little help please!
Andres
Hum...
I have never take the time to finish this tutorial.
Ok, I will work on it soon.