[SOLVED] - Use server side data while retaining default Datatables features
[SOLVED] - Use server side data while retaining default Datatables features
Jibi Abraham
Posts: 11Questions: 0Answers: 0
For the past month I have been going through a love-hate thing for the Datatables plugin. I love it because its bloody brilliant and I hate it because it drives me mad when I want to do simple things like update a row with formatting intact and so on and so forth. I'm pretty sure that of the 4000+ odd posts in this forum, everyone single one of them has at one point or the other banged their heads on the table and cussed at the universe. And since I'm one of them, and I expect a lot more people to join the club, I decided to post this solution a friend of mine devised for me. Please bear with me if this post is long, but if it helps anyone at all out there, I am satisfied. Don't be daunted by the post length, it's worth it. Trust me.
That being said, this tutorial involves the new JQuery DataLink plugin (official documentation can be found at http://api.jquery.com/category/plugins/data-link/ also read http://expansive-derivation.ossreleasefeed.com/2010/10/its-the-datalink-that-binds-us-jquery-datalink/) which is still a beta version (use at your own risk), extend.js (http://ejohn.org/blog/simple-javascript-inheritance/) with some custom code and a separation of your data and your presentation.
What my solution provides - Get the data for your datatables by any means that you desire (JSON, DOM, JS Array, you name it) but retain the default datatables features such as sorting, searching, pagination, blah blah without using server-side implementation or page refresh to display changed data. This will eventually be a client side CRUD with a back-end database. Sounds too good to be true? Well I leave that for you to decide. I will soon have a real-life implementation (http://locfind.com - currently in beta with a horrendous Datatables implementation) and will post the link and source codes here for reference.
Current Scenario : You want to have edit buttons inside each row of your datatable but don't want to provide inline editing as you may have checkboxes, select elements and a whole lot of stuff that need to be edited in each row. You want an external form for this, but can't figure out how to insert back the data into your table, without breaking it.
That being said, this tutorial involves the new JQuery DataLink plugin (official documentation can be found at http://api.jquery.com/category/plugins/data-link/ also read http://expansive-derivation.ossreleasefeed.com/2010/10/its-the-datalink-that-binds-us-jquery-datalink/) which is still a beta version (use at your own risk), extend.js (http://ejohn.org/blog/simple-javascript-inheritance/) with some custom code and a separation of your data and your presentation.
What my solution provides - Get the data for your datatables by any means that you desire (JSON, DOM, JS Array, you name it) but retain the default datatables features such as sorting, searching, pagination, blah blah without using server-side implementation or page refresh to display changed data. This will eventually be a client side CRUD with a back-end database. Sounds too good to be true? Well I leave that for you to decide. I will soon have a real-life implementation (http://locfind.com - currently in beta with a horrendous Datatables implementation) and will post the link and source codes here for reference.
Current Scenario : You want to have edit buttons inside each row of your datatable but don't want to provide inline editing as you may have checkboxes, select elements and a whole lot of stuff that need to be edited in each row. You want an external form for this, but can't figure out how to insert back the data into your table, without breaking it.
This discussion has been closed.
Replies
1. extend.js (Change to any name that you prefer and include in your sample page - testpage.html). More explanation on this will be provided only if requested.
[code]
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
/* Credited to Rahul Ramachandran (a.k.a ASTI) of Webyfy Infotech Pvt. Ltd., Trivandrum, Kerala, India (http://www.webyfy.com | http://pcmobileindia.com).
*/
var ArraySerializable = Class.extend(
{
toArray: function () {
var result = new Array();
for (key in this) {
var value = this[key];
if (typeof value != "function") {
if (value == null)
result.push(value);
else if(value.toArray != null)
result.push(value.toArray());
else
result.push(value);
}
}
return result;
},
toKeyArray: function () {
var result = new Array();
for (key in this) {
var value = this[key];
if (typeof value != "function")
result.push(key);
}
return result;
},
link: function (linkto) {
$(linkto).link(this);
t = $(this);
for (key in this) {
var value = this[key];
if (typeof value != "function")
t.setField(key, value);
}
},
fromArray: function (array) {
var keys = this.toKeyArray();
for (var i = 0; i < array.length; i++) {
if (array[i] != null)
this[keys[i]] = array[i];
}
return this;
}
});
[/code]
2. Model your table data as follows and insert in a in your testpage.html
I am going to create a datatable to display a person's first name, last name, gender and an edit button to edit the details. My data will be taken from a database (for sake of simplicity that part will not be explained right now)
The "extend" is going to be a life saver as you will see. Credit to John Resig - the man behind JQuery.
[code]
var Person = ArraySerializable.extend({
init: function (ID, firstName, lastName, gender) {
this.ID = ID; //The primary key of my data in the database; will not be displayed in the table
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender; //gender: a will be provided on editing
this.Edit = null;
}
});
[/code]
We will use "aoColumnDefs" and "fnRender" to define how our data is shown in the datatable. Please note, "oTable" is a global variable.
By using "fnrender" we ensure that the fifth column data is always displayed as a link with the attributes as specified.
[code]
oTable = $('#myTable').dataTable({ "aoColumnDefs":
[
{ "bVisible": false, "aTargets": [0] }, // Hide first column
{ "fnRender": function (oObj) {
return 'Edit';
},
"aTargets": [4]
}
]
});
[/code]
4. Add your data. For explanation purposes I will directly add data to the datatable. You will see how this can be changed to any data source, later on.
To explain the sample insertion code, you will need to keep in mind that the "fnAddData" expects an array input.
Create a new Person (be sure to include the "new" keyword !) and set his/her properties as shown.
From step 2 it is clear that we need to enter only the first four properties as the "Edit" property is set to null.
Once the properties are set, we will call the custom method "toArray()" to return these properties as an array to the Datatable's "fnAddData" method.
[code]
//Sample data
oTable.fnAddData((new Person(0, "Man", "Super","Male")).toArray());
oTable.fnAddData((new Person(100, "Guy", "Fly","Male")).toArray());
oTable.fnAddData((new Person(2, "Woman", "Wonder","Female")).toArray());
/* This is firebug console data to show you the format of the data provided to "fnAddData" using toArray()
[2, "Woman", "Wonder", "Female", undefined]
*/
[/code]
5. The required HTML. We will eventually bind the data of the row to be edited, to this form. For now, just let it be there in you HTML.
[code]
ID
Name
Age
Select
Edit
Female
Male
[/code]
We will use JQuery's "live" function so that if we add a new row, it too will have the edit functionality.
On a click event, we will use "fnGetPosition" and some JQuery to get the internal position of this row in the DataTable data array and then,get the data using "fnGetData".
Important note: "editing" is a global variable !.
Initialise "editing" as a new Person with random values and then modify its properties to match "data" using the fromArray() method.
Use the link() method to bind the object "editing" to our form. Note that the link() method shown here is a custom method (defined in extend.js) which uses the original JQuery link(). Do not confuse the two.
[code]
$("a.editButton").live("click", function () {
var pos = oTable.fnGetPosition($(this).parents("tr")[0]);
var data = oTable.fnGetData(pos);
editing = (new Person(0, "Enter First Name", "Enter Last Name","Gender")).fromArray(data);
editing.link('#editform');
});
[/code]
7. With this much done, everytime the "Edit" button is clicked, the form will be auto-populated with the row data. Now let's implement an update feature so that any change in the form data is reflected in the corresponding DataTable row that has been taken up for editing.
On button click, we will take in the DataTable data, along with the row id (technically the DB primary key id - already set as a property in the object "editing").
Now we will iterate through the data array until we find a row which matches our row id (the row id is defined as the first column (we have hidden that column from display) and hence the "[0]" in the findIndex function).
With the internal position of the row in hand, we now call the "fnUpdate" method to update our row, which is instantly reflected on the page.
[code]
function btn_onclick()
{
var position = findIndex(oTable.fnGetData(), editing.ID);
oTable.fnUpdate(editing.toArray(), position);
return false;
}
function findIndex(data, ID)
{
for (var i = 0; i < data.length; i++)
{
if (data[i][0] == ID)
return i;
}
}
[/code]
and include this in the HTML
[code]
[/code]
My testpage HTML as a whole is provided below for reference.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
Locfind.com
var Person = ArraySerializable.extend({
init: function (ID, firstName, lastName, gender) {
this.ID = ID;
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.Edit = null;
}
});
var oTable, editing;
$(function () {
oTable = $('#myTable').dataTable({ "aoColumnDefs":
[
{ "bVisible": false, "aTargets": [0] }, // Hide first column
{ "fnRender": function (oObj) {
return 'Edit';
},
"aTargets": [4]
}
]
});
$("a.editButton").live("click", function () {
var pos = oTable.fnGetPosition($(this).parents("tr")[0]);
var data = oTable.fnGetData(pos);
console.log(data);
editing = (new Person(0, "Enter First Name", "Enter Last Name","Gender")).fromArray(data);
editing.link('#editform');
});
//Sample data
oTable.fnAddData((new Person(0, "Man", "Super","Male")).toArray());
oTable.fnAddData((new Person(100, "Guy", "Fly","Male")).toArray());
oTable.fnAddData((new Person(2, "Woman", "Wonder","Female")).toArray());
/* Firebug console data to show you the format of the data provided to "fnAddData" using toArray()
[2, "Woman", "Wonder", "Female", undefined]
*/
});
function btn_onclick()
{
var position = findIndex(oTable.fnGetData(), editing.ID);
oTable.fnUpdate(editing.toArray(), position);
return false;
}
function findIndex(data, ID)
{
for (var i = 0; i < data.length; i++)
{
if (data[i][0] == ID)
return i;
}
}
[/code]
ID
Name
Age
Select
Edit
Female
Male
[/code]