Restore state of selected rows

Restore state of selected rows

gwhngwhn Posts: 4Questions: 0Answers: 0
edited June 2011 in TableTools
Hi. I need to restore the selected rows when a user goes back to the DataTable. I have used the bStateSave flag to restore the page length, search filter, current page etc which does not restore the row selection. I've had a look at the functions _fnSaveState and _fnLoadState in the DataTables code, but because the row selection functionality is a feature of TableTools, I was wondering what the best approach would be.

Thank you Allan for such a useable and feature rich component.

Replies

  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin
    There isn't any option to do row state saving in TableTools at the moment, although it's a nice idea for the future (having said that, I'd be a bit worried about hitting the 4KiB cookie limit quite quickly...).

    The method using the API to do this would be to use these methods:

    http://datatables.net/usage/callbacks#fnStateLoadCallback
    http://datatables.net/usage/callbacks#fnStateSaveCallback

    And call a state saving event each table the row selection changes.

    However, I wonder if it might be slightly easier to just create your own cookie for this exact case, rather than hooking into the DataTables one. You will still need to update your cookie when the row selection changes ( http://datatables.net/extras/tabletools/initialisation#fnRowSelected and fnRowDeselected ), and then reinstate it when the page is reloaded, but I suspect that it will be easier, and you will have more control, for if you do want to store that information on the server (via an Ajax request) to get around the 4KiB limit.

    Regards,
    Allan
  • gwhngwhn Posts: 4Questions: 0Answers: 0
    I followed your advice and avoided using the DataTables cookie and created one of my own. I created a handler for the oTableTools.fnRowSelected and oTableTools.fnRowDeselected callbacks that gets the selected rows and stores the row indexes as a comma-separated list:

    [code]
    function storeSelectedRows(node) {
    var selected = this.fnGetSelected();
    var rowIds = [];
    for (var i = 0, j = selected.length; i < j; i++) {
    rowIds.push(selected[i].rowIndex);
    }
    createCookie("DataTables_Row_Selection", rowIds.join(","), 1);
    }
    [/code]

    I found I had to use the fnInitComplete callback, rather than the fnStateLoadCallback, so that calling fnGetNodes worked because the data had already been retrieved from the server. I also discovered that the this reference didn't refer to the DataTable in the fnStateLoadCallback, and that attempting to call fnGetNodes via oSettings.oInstance returned null. Also note that rowIndex is 1 based and fnGetNodes expects a 0 based row index.

    [code]
    "fnInitComplete": function (oSettings) {
    var selection = readCookie("DataTables_Row_Selection").split(",");
    for (var i = 0, j = selection.length; i < j; i++) {
    var node = this.fnGetNodes(selection[i] - 1);
    var select = TableTools.fnGetInstance(oSettings.sTableId).s.select;
    $(node).addClass(select.selectedClass);
    select.selected.push(node);
    }
    }
    [/code]

    I'd be interested in any feedback you have on this approach. Thank you very much for you help. You definitely deserve a few beers on me.
  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin
    I'd say that fnInitComplete is exactly the right way to do this here since your code is effectively a layer above DataTables, and not integrated into the core. fnStateLoadCallback is call right at the start of the DataTables initialisation, so most of the table won't be configured by then - while fnInitComplete... well the name describes it well :-). So sounds good to me - nice one!

    Regards,
    Allan
  • gwhngwhn Posts: 4Questions: 0Answers: 0
    Thanks Allan. Good to know I'm on the right track. I'm starting to hit some of the edge cases now where the sort order/page length/page number is changed after the selection has been made (and the row indexes are stored in the cookie) so that the reinstated row selection does not tally. I've extended the select_all and select_none buttons to store the row selection, so I've tweaked the code in storeSelectedRows to expect the this reference as an argument.
    [code]
    function storeSelectedRows(oTT) {
    var selection = oTT.fnGetSelected();
    var rowIds = [];
    for (var i = 0, j = selection.length; i < j; i++) {
    if (selection[i] !== null) {
    rowIds.push(selection[i].rowIndex);
    }
    }
    createCookie("DataTables_" + oTT.s.dt.sTableId + "_Row_Selection", rowIds.join(","), 1);
    }
    [/code]
    And I've added a custom handler for fnClick that stores the row selection too.
    [code]
    "aButtons": [
    {
    "sExtends": "select_all",
    "fnClick": function (nButton, oConfig) {
    this.fnSelectAll();
    storeSelectedRows(this);
    }
    },
    {
    "sExtends": "select_none",
    "fnClick": function (nButton, oConfig) {
    this.fnSelectNone();
    storeSelectedRows(this);
    }
    }
    ],
    [/code]
    Now I'm going to have to bind to DataTables column sort, pagination and page length events as there aren't any callbacks for this purpose as far as I can tell.
  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin
    fnDrawCallback will cover all events such as those. If you have an id on each TR, it might be possible to take a slightly different approach than storing the index integer - store either the id or an integer postfix on the id ("row_{i}" for example) - that would allow it to work regardless of sorting or anything else - particularly since the index might be different due to sorting when saving, as well as loading (a 3D problem...).

    Allan
  • gwhngwhn Posts: 4Questions: 0Answers: 0
    Thanks for the tip on using row ids instead of the row's index. That's the route I went down using fnRowCallback adding the table id as a prefix to the data id.
    [code]
    "fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
    $(nRow).attr("id", this.fnSettings().sTableId + "_" + aData[1]);
    return nRow;
    },
    [/code]
    I call a function that stores the row selection from TableTools' fnRowSelected and fnRowDeselected. I also extended the select_all and select_none buttons, using fnSelect to store the row selection, and fnSelect and fnComplete to enable/disable the buttons.
    [code]
    "oTableTools": {
    "sRowSelect": "multi",
    "fnRowSelected": function (nNode) {
    storeSelectedRows(this);
    },
    "fnRowDeselected": function (nNode) {
    storeSelectedRows(this);
    },
    "aButtons": [
    {
    "sExtends": "select_all",
    "fnClick": function (nButton, oConfig) {
    this.fnSelectAll();
    storeSelectedRows(this);
    },
    "fnSelect": function (nButton, oConfig) {
    setSelectAllButtonState(this, nButton);
    },
    "fnComplete": function (nButton, oConfig) {
    setSelectAllButtonState(this, nButton);
    }
    },
    {
    "sExtends": "select_none",
    "fnClick": function (nButton, oConfig) {
    this.fnSelectNone();
    storeSelectedRows(this);
    },
    "fnSelect": function (nButton, oConfig) {
    setSelectNoneButtonState(this, nButton);
    },
    "fnComplete": function (nButton, oConfig) {
    setSelectNoneButtonState(this, nButton);
    }
    }
    ]
    }
    [/code]
    These are the function definitions used above:
    [code]
    function storeSelectedRows(oTT) {
    var selection = oTT.fnGetSelected();
    var rowIds = [];
    for (var i = 0, j = selection.length; i < j; i++) {
    if (selection[i] !== null) {
    rowIds.push(selection[i].id);
    }
    }
    createCookie("DataTables_" + oTT.s.dt.sTableId + "_Row_Selection", rowIds.join(","), 1);
    }

    function setSelectNoneButtonState(oTT, nButton) {
    if (oTT.fnGetSelected().length !== 0) {
    $(nButton).removeClass("DTTT_disabled");
    } else {
    $(nButton).addClass("DTTT_disabled");
    }
    }

    function setSelectAllButtonState(oTT, nButton) {
    if (oTT.fnGetSelected().length == oTT.s.dt.fnRecordsDisplay()) {
    $(nButton).addClass("DTTT_disabled");
    } else {
    $(nButton).removeClass("DTTT_disabled");
    }
    }
    [/code]
    Finally, to restore the row selection I used the fnDrawCallback to react to changes to page number, length, search filter, sort order etc.
    [code]
    "fnDrawCallback": function () {
    restoreSelectedRows(this);
    },
    "fnInitComplete": function (oSettings) {
    restoreSelectedRows(this);
    }
    [/code]
    These are the function definitions used above:
    [code]
    function restoreSelectedRows(oDT) {
    var settings = oDT.fnSettings();
    var cookie = readCookie("DataTables_" + settings.sTableId + "_Row_Selection");
    if (cookie === null) return;
    var selection = cookie.split(",");
    var select = TableTools.fnGetInstance(settings.sTableId).s.select;
    var nodes = oDT.fnGetNodes();
    for (var m = 0, n = nodes.length; m < n; m++) {
    for (var i = 0; i < selection.length; i++) {
    if (selection[i] === nodes[m].id) {
    $(nodes[m]).addClass(select.selectedClass);
    select.selected.push(nodes[m]);
    selection.splice(i, 1);
    }
    }
    }
    }
    [/code]
    Concatenating the table's id with the row's id means using more bytes and hitting the 4kb limit sooner, so I expect to have to swap using cookies for storing row selections server-side.
    Allan - Thanks for your help and I look forward to the next installment. I hope this proves useful to others while we wait for the feature to be incorporated in to a future version of DataTables.
This discussion has been closed.