Vlastní filtry s DataTables a AJAXem

PHP, JavaScript, Laravel, HTML

Již v základní instalaci DataTables podporují jednoduché vyhledávání. To lze ale dále rozšířit a přidat tak i vlastní filtry. A navíc to lze zkombinovat s AJAXem, kdy veškeré vyhledávaní i seřazování probíhá na serveru. Což se hodí systémy s velkým množstvím záznamů.

Vlastní filtry s DataTables a AJAXem

Tento článek patří do seriálu Jak na DataTables. Ostatní články seriálu:


DataTables je JavaSriptová knihovna, aktuálně stále vyžadující jQuery, pro tvorbu interaktivních tabulek s mnoha pluginy. Možnosti nastavení jsou obrovské a osobně jej využívám v systému pro SunOutdoor. V částech, kde se očekávají jednotky až nižší desítky záznamů používám client-side rendering. Tedy vše pošlu na klienta a DataTables se starají o seřazování i vyhledávaní. V tomto případě jsou vlastní filtry jednodušší a v dokumentaci existuje ukázka včetně kódů.

V administraci, kde jsou tisíce až desetitisíce záznamů, už není vhodné vypisovat vše. Lepší je provést vyhledání a seřazení na serveru a odeslat jen zlomek záznamů. Hodnota z integrovaného vyhledávaní je automaticky obsažena v XHR requestu, který se posílá na server. Následující návod popisuje, jak do XHR requestu i do tabulky přidat vlastní filtry.

DataTables s vlastním filtrem

Přidání vstupního pole

V client-side ukázce v odkazu výše jsou nová pole nad tabulkou. To se mi ale příliš nezamlouvá z UX pohledu. Proto je níže popsán způsob, jak pole vložit vedle vyhledávaní tak, jak je znázorněno na obrázku. V HTML šabloně je pole pro Archív nad tabulkou, aby se již ze serveru naplnilo daty.

<label class="js-archiveFilter">
    Archív<select class="form-control js-archiveFilterSelect">
        <option value="">Aktuální</option>
        <optgroup label="Ročník">
            <option value="2020">2020</option>
            <option value="2019">2019</option>
        </optgroup>
    </select>
</label>
<table class="table table-striped" data-table>
    <thead>
        <tr>
            <th>Jméno</th>
            <th>Datum konání</th>
            <th>Způsob platby</th>
            <th>Zbývá uhradit</th>
            <th style="width: 10%">Akce</th>
        </tr>
    </thead>
    <tbody><!-- Vyplní se automaticky pomocí odpovědi ze serveru --></tbody>
</table>

Následně se pomocí JavaScriptu pole s výběrem Archívu přesune přímo do tabulky a propojí se tak, aby při změně došlo k překreslení a tedy i stažení nového obsahu ze serveru.

const predefinedDom = {
    inCard:
        "<'px-3 pt-3 datatable_header_wrapper'<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>>" +
        "<'row'<'col-sm-12'tr>>" +
        "<'px-2 pb-3 small datatable_footer_wrapper'<'row'<'col-12'i><'col-12'p>>>",
    // Obsahuje navíc třídu withArchive a větší sloupec u wrapperu vyhledávání
    inCardWithArchive:
        "<'px-3 pt-3 datatable_header_wrapper'<'row'<'col-sm-12 col-md-4'l><'col-sm-12 col-md-8 withArchive'f>>>" +
        "<'row'<'col-sm-12'tr>>" +
        "<'px-2 pb-3 small datatable_footer_wrapper'<'row'<'col-12'i><'col-12'p>>>",
};
let usedDom = predefinedDom.inCard;
let instance = null;
let ajaxData = null;

if (showFilter === true) {
    $(document)
        .one('preInit.dt', function(){ // Před inicializací se přesune pole 
            let filter = $('.js-archiveFilter');
            filter.prependTo('.withArchive .dataTables_filter');
            // Při změně je nutné zavolat překreslení tabulky, což načte čerstvá data
            filter.find('select').addClass('form-control-sm').on('change',function () {
                instance.draw();
            });
        })
        .on('stateSaveParams.dt', function (e, settings, data) {
            // Při ukládání aktuálního stavu (stateSave) do local/session storage se uloží také stav archívu
            data.archive = { year: $('.js-archiveFilterSelect').val() };
        })
        .on('stateLoadParams.dt', function (e, settings, data) {
            // Při načítání aktuálního stavu (stateSave) z local/session storage se načte také stav archívu
            if (data.archive && data.archive.year) {
                $(".js-archiveFilterSelect option[value='" + data.archive.year + "']").prop('selected', true);
            }
        });
    usedDom = predefinedDom.inCardWithArchive; // Při použití archívu se přidá DOM s extra třídou
    
    ajaxData = function (data) {
        // Vždy před odesláním XHR requestu se přidá aktuální hodnota z filtru archív
        data.archive = $('.js-archiveFilterSelect').val();
    };
}

instance = $('table[data-table]').DataTable({
    stateSave: true, // Zapne ukládání aktuální strany, vyhledávání i archívu do local/session storage
    dom: usedDom,
    ajax: {
        url: "/path/to/backend/api",
        data: ajaxData,
    },
    serverSide: true, // Zapne načítání dat ze serveru definováno v ajax.url
});

XHR Request a Laravel

Při změně počtu zobrazovaných záznamů na stránku, změně vyhledávacího textu či archívu dojde k novému requestu na server, jehož obsah může vypadat podobně, jako je znázorněno níže. Ten je samozřejmě nutné zpracovat a vrátit požadovaná data zpět.

V systému pro SunOutdoor jsem si řešení vytvořil sám, což byla klasická začátečnická chyba. Protože již existují hotové knihovny přímo do Laravelu, jako třeba Laravel-Datatables. Ty stačí navázat na model nebo kolekci a o zpracování requestu a správný formát response se již postará.

[
    "draw" => "1",
    "columns" => [
        0 => [
            "data" => "name",
            "name" => null,
            "searchable" => "true",
            "orderable" => "true",
            "search" => [
                "value" => null,
                "regex" => "false",
            ]
        ],
        1 => [
            "data" => "term_range",
            "name" => null,
            "searchable" => "true",
            "orderable" => "false",
            "search" => [
                "value" => null,
                "regex" => "false",
            ]
        ],
        2 => [
            "data" => "payment",
            "name" => null,
            "searchable" => "true",
            "orderable" => "false",
            "search" => [
                "value" => null,
                "regex" => "false",
            ]
        ],
        3 => [
            "data" => "price_to_pay",
            "name" => null,
            "searchable" => "true",
            "orderable" => "false",
            "search" => [
                "value" => null,
                "regex" => "false",
            ]
        ]
    ],
    "order" => [
        0 => [
            "column" => "1",
            "dir" => "asc",
        ],
        1 => [
            "column" => "3",
            "dir" => "asc",
        ]
    ],
    "start" => "0",
    "length" => "30",
    "search" => [
        "value" => "Pavel",
        "regex" => "false",
    ],
    "archive" => "2020",
]

Osobní zkušenosti s DataTables či jinou alternativou můžete sdílet v komentářích

K tomuto článku již není možné přidávat další komentáře