Accueil / Blog / Métier / Archives / KeyEditableTable: Mixing DataTables, KeyTables and jEditable all together

KeyEditableTable: Mixing DataTables, KeyTables and jEditable all together

Par Gaël Pegliasco publié 11/03/2010

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:

 

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();

}
ABONNEZ-VOUS À LA NEWSLETTER !