[SOLVED] - Use server side data while retaining default Datatables features

[SOLVED] - Use server side data while retaining default Datatables features

Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
edited March 2011 in General
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.

Replies

  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    Prerequisites and steps:
    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]
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    3. Datatable initialisation - my table has an id [myTable].
    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]
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    6. Add the edit button functionality.
    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]
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    There. A basic outlay of what can be done. Play around and let me know if you can find better ways or add more functionality. For example, the editing form can be put on a separate page and then loaded into a JQuery UI dialog box with a simple ajax call (so that any database values that need to be fetched can be inserted).

    My testpage HTML as a whole is provided below for reference.
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    [code]
    <!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]
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    [code]





    ID
    Name
    Age
    Select
    Edit












    Female
    Male







    [/code]
  • Jibi AbrahamJibi Abraham Posts: 11Questions: 0Answers: 0
    Kudos to Allan for bringing out this great plugin. Thank you.
This discussion has been closed.