Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataTables 1.10: Using saveState to remember filtering and order but need to update address URL to have shareable link for others

Tags:

I use saveState: true to keep the conditions for my DataTables when ordering columns or filtering results with a phrase. When I refresh the page, conditions remain thanks to the local storage.

enter image description here

This works great within the same browser, but imagine a quite common case when after you apply both ordering and filtering to your table, you would like to share this state with your friend by simply copying and pasting a long URL with query string so that when he / she opens it, the table looks exactly the same.

There was a plugin that used to do this but it's not maintained and after giving it a try, it does not work with 1.10 at all.

I wonder if it's possible to solve this by using just some native code and maybe replaceState to simply update URL when applicable?

This is my JS.

$(function () {
    let table = $('.table').DataTable({
        processing: true,
        serverSide: true,
        stateSave: true,
        stateSaveCallback: function (settings, data) {
            // save state
        },
        stateLoadCallback: function (settings) {
            // read state and change url?
        },

        ajax: '{{ route('admin.api.dt.customer.index') }}',
        columnDefs: [{
            orderable: false,
            targets: 2
        }],
        pageLength: {{ \App\Repositories\Sync\CustomerRepository::PER_PAGE_DATA_TABLES }},
    });
});

Based on this code the obvious issue is that the ajax URL is my hardcoded Laravel route. Simply passing query string does not work.

My Laravel route is simply: http://iosportal.local/admin/api/customer and it returns JSON shaped to suit DataTables.

enter image description here

Can somebody give my a hint how to approach this?

like image 392
Matt Komarnicki Avatar asked Apr 01 '19 01:04

Matt Komarnicki


2 Answers

I found storing the current state as base64 was easy and portable. You'll need to create some sort of UI for retrieving the state which could be anything, but at it's most primitive you can have a button:

<a id="get-state">Get State</a>

And do something like this:

$('#get-state').on('click', function() {
    var saved = btoa(JSON.stringify(table.state()));
    alert('state=' + saved);
})

Now append that as a query parameter to get your shareable url:

https://example.com?state=eyJzZWFyY2giOnsic2VhcmNoIjoibWVvdyIsInNtYXJ0Ijp0cnVlLCJyZWdleCI6ZmFsc2UsImNhc2VJbnNlbnNpdGl2ZSI6dHJ1ZX19

To restore the state have something like this in your datatable options:

// use stateLoadParams() instead of stateLoadCallback() since the former
// is designed for exactly what you want — manipulating the loaded state
"stateLoadParams": function (settings, data) {
    // check the current url to see if we've got a state to restore
    var url = new URL(window.location.href);
    var state = url.searchParams.get("state");
    if (state) {
        // if so, try to base64 decode it and parse into object from a json
        try {
            state = JSON.parse(atob(state));
            // now iterate over the object properties and assign any that
            // exist to the current loaded state (skipping "time")
            for (var k in state) {
                if (state.hasOwnProperty(k) && k != 'time') {
                    data[k] = state[k];
                }
            }
        } catch (e) {
            console.error(e);
        }
    }
}

Note I'm skipping the "time" attribute here since it may invalidate your state expiry.

like image 156
Eaten by a Grue Avatar answered Oct 05 '22 18:10

Eaten by a Grue


@billynoah's stateLoadParams utilization should work for most of the scenarios, however stateLoadParams is being fired only when there is already some saved state in local storage - so when you are accessing given URL for the first time it will not work.

Much cleaner way how to achieve sharing through URL is to edit stateSaveCallback and stateLoadCallback so they will not use local storage at all and will use url parameters instead.

This is all you need to add into respective datatable config. It also updates URL without need of reload on every state change (or state save to be more precise) and it works for multiple tables as well. The only requirement is that every table element has to have its own specific ID.

stateSaveCallback: function (settings, data) {
    //encode current state to base64
    const state = btoa(JSON.stringify(data));
    //get query part of the url
    let searchParams = new URLSearchParams(window.location.search);
    //add encoded state into query part
    searchParams.set($(this).attr('id') + '_state', state);
    //form url with new query parameter
    const newRelativePathQuery = window.location.pathname + '?' + searchParams.toString() + window.location.hash;
    //push new url into history object, this will change the current url without need of reload
    history.pushState(null, '', newRelativePathQuery);
},
stateLoadCallback: function (settings) {
    const url = new URL(window.location.href);
    let state = url.searchParams.get($(this).attr('id') + '_state');

    //check the current url to see if we've got a state to restore
    if (!state) {
        return null;
    }

    //if we got the state, decode it and add current timestamp
    state = JSON.parse(atob(state));
    state['time'] = Date.now();

    return state;
}
like image 25
kudlohlavec Avatar answered Oct 05 '22 17:10

kudlohlavec