localStorage for state saving and updates in 1.9

localStorage for state saving and updates in 1.9

allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
edited January 2012 in DataTables 1.9
Hello all,

I've just posted a new blog entry describing the state changing API changes that are in DataTables 1.9 and showing how the new state saving API can be used very easily for saving your table's state with localStorage: http://datatables.net/blog/localStorage_for_state_saving .

Enjoy,
Allan
«1

Replies

  • John ArcherJohn Archer Posts: 56Questions: 1Answers: 0
    Yeah, thanks a lot! :-)
  • tapsboytapsboy Posts: 11Questions: 0Answers: 0
    This is fantastic... ofcourse the curse of IE7 continues,

    http://datatables.net/beta/1.9/examples/advanced_init/localstorage.html throws an error localstorage is undefined... looks gr8 on FF and chrome... waiting to get my hands on the new state saving functions
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    It would do yes, since localStorage isn't available in IE7-. Probably the easiest thing to do would be to check for the localStorage object in the state saving code and just discard the state in IE7-. If the user has a rubbish old browser, then they aren't going to get the cool new features :-). The alternative is to use the built in cookie saving as a callback by calling those functions.

    > waiting to get my hands on the new state saving functions

    Already available in 1.9.0 :-)

    Allan
  • sebastienbarresebastienbarre Posts: 24Questions: 1Answers: 0
    Great new changes. In the post you link to you said, "note that as such this will not work in browser which do not support localStorage". Searching for a cross-platform solution for persistent storage, I noticed a lot of people on StackOverflow recommending PersistJS: http://pablotron.org/?cid=1557
    This small plugin uses flash, gears, localstorage, whatwg_db, globalstorage, cookie, etc. transparently.

    Example:
    [code]
    var store = new Persist.Store('My Data Store');
    var data = "pretend this is really long data that won't fit in a cookie";
    store.set('saved_data', data);
    [/code]

    Unfortunately, the get() method is... peculiar in that it is asynchronous (the reason being, I quote, "It turns out that some backends -- specifically the WHATWG Database (used for Safari 3.1) -- only function asynchronously, so using a callback function is the only reliable means of ensuring the correct order of operations").

    Example:
    [code]
    store.get('saved_data', function(ok, val) {
    if (ok)
    alert('saved data = ' + val);
    });
    [/code]

    I was wondering if you could advise me how I could make that work with fnStateLoad()? fnStateLoad is a callback, from which you return a value. The issue is that from that callback I will need to call get() itself with an anonymous/async function, from which I can't quite return anything in the same scope.

    Recommendations?
    Thanks!
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    > Unfortunately, the get() method is... peculiar in that it is asynchronous

    That's very unfortunate because at the moment DataTables requires that the state loading function give be synchronous - there is currently no callback method available for this function at the moment. Changing it could of course be done, but its not trivial in the DataTables core since the constructor is expecting the function to be synchronous.

    The only option I've really got to suggest, without changing the DataTables core (which of course I am willing to do, but it will take a bit of time), is to load your settings object first, then initialise your DataTable on the callback. Then you can use fnStateLoad to return the object that was loaded. The only downside is that the table will be uninitialised for that extra period of time while the state is being loaded.

    Allan
  • sebastienbarresebastienbarre Posts: 24Questions: 1Answers: 0
    Thanks Alan. I switched from PersistJS to jStorage. That covered enough bases.
    http://www.jstorage.info/
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    Hey Allan I'm trying these new save/load callbacks from the examples (fnStateLoad seems to have the wrong signature in the docs) but I'm implementing something pretty similar to this. I have checked my object o that is returned at the end and it looks ok, but the table seems to ignore the visibility flags that are in the data. Is there something I need to do to get around this? Also I'm wondering if I need to do anything extra to have the table remember column order etc with the plugins on the table or if I can just return the serialized oData object and the plugins will know what to do with it.

    "fnStateLoad": function (oSettings) {
    var o;

    // Send an Ajax request to the server to get the data. Note that
    // this is a synchronous request.
    $.ajax( {
    "url": "/state_load",
    "async": false,
    "dataType": "json",
    "success": function (json) {
    o = json;
    }
    } );

    return o;
    }
    } );
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    > fnStateLoad seems to have the wrong signature in the docs

    This is a bug in the docs which is fixed in the git repo, but because the docs are tired to each release, it will be the 1.9.1 before the docs on the site are updated.

    > I have checked my object o that is returned at the end and it looks ok

    That looks okay to me as well - can you link me to your page please, so I can see what might be happening.

    > Also I'm wondering if I need to do anything extra to have the table remember column order etc with the plugins on the table or if I can just return the serialized oData object and the plugins will know what to do with it.

    Assuming the state loading works okay, then the ColReorder plug-in should deal with restoring state, since it hooks into the DataTables state saving API.

    Allan
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    I'm just working locally right now unfortunately, our software is for download (and is web based) so I don't really have this current version hosted anywhere. I'll see if we have some kind of QA server I can upload this to in the meantime. Neither the ColVis nor the ColReorder plugins seem to be able to get their state back though, it just defaults to all columns being visible even if I can see false returned in the config object for bVisible. Things like the # of displayed rows (10, 25, 50) are able to restore though.
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    hey until I can hopefully host this somewhere can you tell me if iCreate is important in the saving/loading process? fnStateSave seems to be fired twice most times when I'm selecting or deselecting a column using the ColVis plugin.
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    hey Allan

    http://airtime-dev.sourcefabric.org/ here is a public dev version of the site. The table I'm trying to save/load using a db storage is on the page "playlist builder" The fnSaveState and fnLoadState are written in library.js starting around line 226.
    Thanks again if you could take a look at it!
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    hey I figured it out

    oData.abVisCols it gives you an array like ["true", "false"], but to actually load properly the values need to be actual booleans [true, false] etc or of course it was just thinking "false" was actually true so I always got all my columns loading.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Hi - thanks for putting up the example! Great to hear you've got a solution - I'm slightly confused though, DataTables is storing boolean values in its state saving from what I've just tested, or is there a conversion to a string somewhere in in your code, or am I missing something completely!?

    Allan
  • naomiaronaomiaro Posts: 8Questions: 0Answers: 0
    oh sorry about that just checked again, yes it wasn't any fault of datatables, just when the data was sent to the server to save the settings all the booleans were turned into strings (used php's serialize after I got the request data with ZF). So annoying! Anyways it's working great now :D
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    That's most frustrating - and I suspect not a valid thing to do for a "strict" JSON parser.... true !== "true" :-).

    Anyway - great to hear you've got it sorted out!

    Regards,
    Allan
  • andrew2311andrew2311 Posts: 11Questions: 0Answers: 0
    Is there a simple way to 'go back' to the old way of storing? I know cookies aren't nice but unfortunately I need to support IE7 so local storage isn't a possibility.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Its built in by default - https://github.com/DataTables/DataTables/blob/master/media/src/model/model.defaults.js#L1002

    Allan
  • burtondavburtondav Posts: 10Questions: 0Answers: 0
    Would it be possible to save the state in the app database? Then you could have a list of views to select.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Yes absolutely - no reason why that wouldn't be possible :-)

    Allan
  • thiagocolebruscothiagocolebrusco Posts: 3Questions: 0Answers: 0
    Since I set the option to save the state of the DataTables (in the old way), I'm having trouble. Sometimes, my application crashes (getting the error 502 - Bad Request) and I found out that is some kind of cookie problem.

    So now I'm trying to change this to localStorage, but my DataTable isn't storing the external filters (which I have created manually and I'm using them with aoData.push method). How can I make the DataTable to store these filters too?

    Thanks
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    > error 502 - Bad Request

    Sounds like the cookie is too large - there should be protection in DataTables to stop that, but 4KiB isn't much - if you have a lot of columns then it can overflow.

    > my DataTable isn't storing the external filters

    DataTables will store column filters applied by fnFilter (is that what you are using?), but it won't restore them, since it knows nothing about your input controls. You would need to use fnStateLoadParams to get the saved parameters and display them in the document.

    Allan
  • thiagocolebruscothiagocolebrusco Posts: 3Questions: 0Answers: 0
    Yes, I'm using fnFilter to filter my table.

    Is there a way that I can solve the problem of the error 502 without change the entire system to localStorage? Or change is the best workaround?

    Thanks
    Thiago
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    You could use fnStateSaveParams to remove the parameters that you don't want to be saved from the data object that will be stored in the cookie - that would certainly help. But ultimately yes localStorage is a much better option.

    How many columns do you have in your table? As I say, DataTables should have protection code that stops it breaking, but it sounds like there might be a bug in there.

    Allan
  • thiagocolebruscothiagocolebrusco Posts: 3Questions: 0Answers: 0
    My datatable has only 6 columns, but yes, I can remove 3 of them if I need.

    Otherwise, I'll change my application to use this localStorage functionality.

    Thanks for the help.
  • blankenhornblankenhorn Posts: 6Questions: 0Answers: 0
    +1 great help
  • pajafumopajafumo Posts: 5Questions: 0Answers: 0
    edited November 2012
    I just have a question: how to set localStorage time?? is still usefull the iCookieDuration": 600??

    Thanks

    Forget it, I found it is persitent until I delete or the user clean it.

    Thanks
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    DataTables 1.10 will correctly implement the duration option for localStorage (indeed, that change has already been committed :-) ).

    Allan
  • tomas_eklundtomas_eklund Posts: 14Questions: 1Answers: 0
    edited January 2013
    I read your blog post about using localStorage for state saving and wanted to do something like your last example, except I would like to use the table's ID instead of window.location.pathname (which is useless as it is the same on all pages). Maybe this is really obvious but I have trouble figuring out how to get the table's ID from the dataTable object.

    Given the HTML
    [code]...
    ...[/code]
    And the JS
    [code]$('.datatable').dataTable({ /* initialization */ });[/code]

    How do I access "some_id" and "some_other_id" from within the initialization code?
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    [code]
    var id = table.fnSettings().sTableId;
    [/code]

    It is worth noting that the parameters in fnSettings are considered private, and generally shouldn't be used as they might change in future... v1.10 is going to introduce a new public API to make this kind of thing easier. But until then, this is how to do it.

    Allan
  • tomas_eklundtomas_eklund Posts: 14Questions: 1Answers: 0
    edited January 2013
    Great! For some reason cookies didn't quite cut it. They remembered the state for two tables at the most. As soon as you visited the third page, the state of the table on the first visited page was lost - probably due to the cookie size limitation. It seems the code below actually works. I can now jump back and forth between several pages with different DataTables and state is remembered. Wonderful!
    [code]var oTable = $('table.datatable').dataTable({
    bStateSave: true,
    fnStateSave: function(oSettings, oData) {
    localStorage.setItem('DataTables_' + this.fnSettings().sTableId, JSON.stringify(oData));
    },
    fnStateLoad: function (oSettings) {
    return JSON.parse(localStorage.getItem('DataTables_' + this.fnSettings().sTableId));
    }
    });[/code]

    However, I ran into a little problem with the new plugin LengthLinks. Whenever one chose to display "All" rows, left the page and came back, the length control was not rendered at all. This only happened with state saving enabled, and only with LengthLinks, not if one used the regular length control. I commented out the following lines in the plugin and that fixed the problem:
    [code]$.fn.dataTable.LengthLinks = function ( oSettings ) {
    var container = $('').addClass( oSettings.oClasses.sLength );
    var lastLength = -1;
    var draw = function () {
    // No point in updating - nothing has changed
    // if ( oSettings._iDisplayLength === lastLength ) {
    // return;
    // }
    /* ... rest of plugin ...*/
    }[/code]

    If you made the plugin public, you might want to take a quick look at the logic there. It seems my quick fix could come with a slight performance penalty.
This discussion has been closed.