Tuesday 24th October, 2017

Queuing changes in Editor

When presenting editable data to users, there will be occasions where you (or the end user!) wish to queue up multiple changes to be submitted all at the same time. Editor's multi-row editing ability addresses this for fields which are to be edited so they all have a single value, but you might wish to edit rows individually and then batch submit them. This also can be done with Editor and in this post I'll explore how exactly to achieve this.

As usual, let's just straight into an example - you will see here that as you edit the data in the table, the Save changes and Discard changes buttons are enabled. Click the Save changes button and the changes that have been made will be saved to the server. Click the Discard changes button and yes, you guessed it, the changes made will be discarded and the original data restored.

Name Salary
Name Salary

Architecture

Before we discuss the code used to build the example, I want to highlight three specific features of the libraries that will be used to make this possible:

  1. Editor's local table editing feature - Editor 1.6 made it possible to have Editor simply update the data in the table without submitting to the server.
  2. Editor's multi-row editing API - Submit the data for multiple rows in a single request, programmatically.
  3. Custom buttons - Lets us define buttons to do anything we need - in this case submit data to the server.

Putting these three together with a simple array that contains the row identifiers for the rows that were edited provides everything we need to build a queued editing interface.

The final piece of the puzzle is that we need two Editor instances for this - the first will be used for local table editing (configured without a ajax option) and the second will be used to submit the Ajax requests for the batch editing (configured with a ajax option).

Local table editing

Local table editing in Editor is simply a case of defining an Editor with the fields required (fields) and a target table to be edited (table). Combine that with a standard DataTable configuration and we can have something like:

var editorOpts = {
    table: '#myTable',
    fields: [
        {
            label: 'First name:',
            name: 'first_name'
        },
        {
            label: 'Last name:',
            name: 'last_name'
        },
        // ...
    ]
};

// Local table editing instance
var localEditor = new $.fn.dataTable.Editor(editorOpts);

// Ajax editor instance
var ajaxEditor = new $.fn.dataTable.Editor(
    $.extend(true, {
        ajax: '../php/staff'
    }, editorOpts)
);

var table = $('#myTable').DataTable({
    ajax: '../php/staff',
    columns: [
        {
            data: null,
            render: function(data, type, row) {
                // Combine the first and last names into a single table field
                return data.first_name + ' ' + data.last_name;
            }
        },
        { data: 'position' },
        // ...
    ],
    select: true,
    layout: {
        topStart: {
            buttons: [
                { extend: 'create', editor: ajaxEditor },
                { extend: 'edit',   editor: localEditor },
                { extend: 'remove', editor: ajaxEditor }
            ]
        }
    }
});

Note that the Editor options have be placed into an object called editorOpts - this allows the same object to be reused for the localEditor and ajaxEditor instances since the only difference between them is the ajax option (which is added to the object using jQuery's $.extend() method).

For the editing buttons that are shown above the table the localEditor is used for editing, while ajaxEditor is used for creating and delete rows (they are submitted immediately - you could queue them separately if you needed to).

Buttons

Next, consider the Save changes and Discard changes buttons. Here we simply need two custom buttons, which are added to the buttons array as follows:

{
    text: 'Save changes',
    init: function () {
        this.disable();
    },
    action: function () {
        // ...
    }
},
{
    text: 'Discard changes',
    init: function () {
        this.disable();
    },
    action: function () {
        // ...
    }
}

Note that both define an init function which is used to disable their actions by default. This is used because we only want them to be enabled when a user actually has edited one or more rows.

As such, we not only need to enable the buttons when a row has been edited, but we must also keep track of which rows have been edited, so when we need to submit them to the server, we can without the overhead of sending every single row in the table. This can be done with a simple array, buttons().enable() to enable the buttons and the postEdit event to know when they should be updated:

// Array which will store the id's of the rows which have been locally edited (for later submission)
var changedRows = [];

editor.on('postEdit', function (e, json, data) {
    // Store the row id so it can be submitted with the Ajax editor in future
    changedRows.push( '#'+data.DT_RowId );

    // Enable the save / discard buttons
    table.buttons([3,4]).enable();
});

The buttons selector (button-selector) of [3,4] indicates that button's in indexes 3 and 4 (0 based) should be selected, which is their position in the buttons array when added there.

Multi-row editing

With everything else in place we can now either submit the data, or discard it. This is done in the action functions for the respective buttons (where ellipsis are shown above). In the case of the submit action we can use edit() with our array of the edited rows and then submit(). Due to Editor's multi-row editing ability, it can submit the multiple rows from the array as required:

action: function () {
    // Submit the Ajax Editor without asking for confirmation
    ajaxEditor
        .edit( changedRows, false )
        .submit();
    
    // Clear out the saved rows and disable the save / discard buttons
    changedRows.length = 0;
    table.buttons([3,4]).disable();
}

The final piece is the discard button, which is simply a case of emptying the changedRows array and disabling the buttons again. An ajax.reload() call is also made to reload the currently saved data from the server. If you prefer not to have this server interaction you could optionally store the original data that was edited in initEdit, but this is the most efficient method to keep the data up-to-date.

action: function () {
    this.ajax.reload();

    changedRows.length = 0;
    table.buttons([3,4]).disable();
}

Conclusion

We've seen here how, with the newer features of Editor, and a couple of custom buttons, a queued editing interface can easily be created. If you'd like to see the complete Javascript that is used for the running demo above, you can do so here.

I've focused on Editor for the last few blog posts - I'll return to DataTables for the next one! As always, if you have any questions or suggestions about this or any other aspect of DataTables, please feel free to post in the forum. Feedback is always welcome.

Enjoy!