#2 DataTable

W poprzednim wpisie zebraliśmy wszystkie pliki potrzebne do uruchomienia skryptu DataTable wraz ze stylami bootstrap.

Obecnie tabelka jest obudowana mechanizmem DataTable. Dzięki temu możemy filtrować i sortować dane po tej tabelce.

Tabelka ma jednak wpisane dane na stałe. Powstaje więc pytanie, jak dynamicznie dane przekazać do tabelki? Można to zrobić na wiele sposobów.

Warto też wspomnieć, że DataTable ma wbudowaną funkcję pobierania danych z serwera. Na razie jednak zignorujemy tę opcję.

image_thumb2

Zanim zaczniemy, musimy stworzyć klasę, która będzie reprezentować jeden rekord w tabelce.

public class PersonContractModel
{
    public string Pesel { get; set; }
    public int Age { get; set; }
    public string NameAndSurName { get; set; }
    public string ProofOfPersonalNumbers { get; set; }
    public string NIP { get; set; }
    public string BirthDate { get; set; }
    public decimal Saldo { get; set; }
    public int NumberOfTransactions { get; set; }
    public decimal AmountOfTheLastTransaction { get; set; }
    public string DateOfLastTransaction { get; set; }
    public string TransferTitle { get; set; }
}

Będzie także potrzebna klasa, która zwróci kolekcję tych obiektów. Tę kolekcję przekażemy do tabelki.

public class PersonContractModelCollection
{
    public static IList<PersonContractModel> 
    GetContractModelCollections()
    {
        List<PersonContractModel> list = 
        new List<PersonContractModel>();

        list.Add(new PersonContractModel()
        {
            Age = 26,
            NameAndSurName = "Cezary Walenciuk",
            BirthDate = "12-11-1988",
            NIP = "9558451100",
            Pesel = "10062419671",
            ProofOfPersonalNumbers = "ADH665100",
            Saldo = 100,
            NumberOfTransactions = 2,
            AmountOfTheLastTransaction = 200,
            DateOfLastTransaction = "2014-11-12",
            TransferTitle = "Przeksięgowanie"
        });

        list.Add(new PersonContractModel()
        {
            Age = 30,
            NameAndSurName = "Jan Kowalski",
            BirthDate = "12-11-1988",
            NIP = "1100451100",
            Pesel = "62419671500",
            ProofOfPersonalNumbers = "AJK699900",
            Saldo = -60,
            NumberOfTransactions = 4,
            AmountOfTheLastTransaction = 400,
            DateOfLastTransaction = "2014-11-25",
            TransferTitle = "Przeksięgowanie"
        });


        list.Add(new PersonContractModel()
        {
            Age = 60,
            NameAndSurName = "Zygmunt Waza",
            BirthDate = "06-12-1954",
            NIP = "3337483333",
            Pesel = "72041908711",
            ProofOfPersonalNumbers = "AJA567322",
            Saldo = 500,
            NumberOfTransactions = 5,
            AmountOfTheLastTransaction = 800,
            DateOfLastTransaction = "2014-11-25",
            TransferTitle = "Zaplata za fakutrę 11/222"
        });

        return list;
    }
}

Przejdźmy do pierwszego najprostszego przykładu.

Razor : uzupełnienie tabelki w trakcie wywołania strony

Tabelka może zostać uzupełniona po stronie serwera przy użyciu odpowiednich wyrażeń Razor.

Jest to najprostsze rozwiązanie. Nie musimy niczego zmieniać w naszym obecnym kodzie po stronie JavaScript.

W trakcie wywołania strony wykona się metoda "Index()". W niej do widoku przekażemy kolekcję.

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var m = PersonContractModelCollection.
        GetContractModelCollections();

        return View(m);
    }
}

A jak ta kolekcja ma wyrenderować się w widoku? To już ustalamy w pliku .cshtml.

Na górze pliku widoku odpowiadającemu temu kontrolerowi musimy ustalić, z jakiego modelu będziemy korzystać. Obecnie jest to kolekcja elementów klasy "PersonalContractModel".

@{
    ViewBag.Title = "Index";
    @model IList<MVCDataTableJqueryBootStrap.Models.PersonContractModel>
}

Usuwamy zawartość tabelki, która jest określona tagu "tbody". Zamiast tego skorzystam z wyrażenia Razor, które w pętli utworzy każdy rekord pod postacią jednego tagu <tr>. Jeden element w kolekcji to jeden wiersz w tabeli.

<tbody>
    @foreach (var item in Model)
    {
        <tr>
            <td>@item.NameAndSurName</td>
            <td>@item.Age</td>
            <td>@item.Pesel</td>
            <td>@item.ProofOfPersonalNumbers</td>
            <td>@item.NIP</td>
            <td>@item.BirthDate</td>
            <td>@item.Saldo</td>
            <td>@item.NumberOfTransactions</td>
            <td>@item.AmountOfTheLastTransaction</td>
            <td>@item.DateOfLastTransaction</td>
            <td>@item.TransferTitle</td>
            <td><button type="button" 
            class="btn btn-xs btn-info">Szczegóły</button></td>
        </tr>
    }
</tbody>

Co się teraz stanie?

Przeglądarka wyśle zapytanie do strony. Serwer utworzy stronę i wypełni tabelkę.

image_thumb5

Gdy strona zostanie załadowana, uruchomią się skrypty JavaScript po stronie klienta. Te skrypty opakują tabelkę i uczynią ją tabelką DataTable.

Co, jeśli jednak chcemy uzupełnić tabelkę przy użyciu jQuery i wywołania AJAX, a potem wywołać skrypt opakowujący tabelkę w DataTable.

jQuery uzupełnienie tabelki

W tym przykładzie zrobimy dokładnie to samo, tym razem jednak uzupełnimy tabelkę skryptem jQuery.

W tym przykładzie będzie potrzebna metoda, która zwróci obiekt JSON. Później ten obiekt JSON zostanie przetłumaczony na rekordy w tabelce.

public class JqueryExampleController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult GetPersonData()
    {
        var collection = PersonContractModelCollection.
		GetContractModelCollections();

        return new JsonResult()
        {
            JsonRequestBehavior = JsonRequestBehavior.DenyGet,
            Data = collection
        };
    }
}

Metoda Index() tutaj zwróci tylko sam widok. Gdy strona zostanie utworzona, wtedy zapytamy serwer o rekordy przy użyciu protokołu HTTP i wywołania AJAX.

Serwer w kodzie C# , w metodzie "GetPersonData()" zwróci wtedy obiekt JSON.

W widoku nie mamy żadnych danych w tabelce.

<div class="row-fluid">
    <div class="col-sm-12">
        <table cellpadding="0" cellspacing="0" border="0"
               class="table table-striped table-bordered center-table"
               id="tableContract">
            <thead>
                <tr>
                    <th> Nazwisko i imię</th>
                    <th> Wiek </th>
                    <th> Pesel </th>
                    <th> Numerów Dowodu Osobistego</th>
                    <th> NIP </th>
                    <th> Data urodzenia</th>
                    <th> Saldo</th>
                    <th> Liczba transakcji</th>
                    <th> Kwota ostatniej transakcji</th>
                    <th> Data ostatniej transakcji</th>
                    <th> Tytuł przelewu</th>
                    <th> Akcje</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
</div>

Cała magia siedzi w skrypcie JavaScript. Na początku musimy pobrać dane. Oto jak wygląda wywołania AJAX w kodzie JavaScript.

MVCDataTableJqueryBootStrap = {

init: function () {
        this.intGetData();
    },

    initDataTable: function () {
	//tworzy DataTable
    },

intGetData: function () {

    $.ajax({
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        type: "POST",
        url: "/JqueryExample/GetPersonData/",
        cache: false
    }).done(function (data) {

        var contracts = data;
        $.each(contracts, function (index, value) {
            MVCDataTableJqueryBootStrap.buildContractRecord(value);
        });

        MVCDataTableJqueryBootStrap.initDataTable();
    }).fail(function (d) {

    });
},

Gdy już uzyskamy dane w pętli "$each" każdy element kolekcji wywoła funkcję "buildContractRecord". Po skończeniu pętli wykona się funkcja opakowującą tabelkę w DataTable.

Funkcja ta doda do elementu <tbody> elementy "tr", które reprezentują jeden rekord w tabelce. Można by powiedzieć, że robimy teraz dokładnie to samo, co wcześniej. Różnica polega na tym, że uzupełnienie tabelki dzieje się po stronie klienta już po załadowaniu strony.

buildContractRecord: function (contractModel) {
    $('#tableContract tbody ').append('<tr>' +
        '<td>' + contractModel.NameAndSurName +
        '</td>' +
        '<td>' + contractModel.Age +
        '</td>' +
        '<td>' + contractModel.Pesel +
        '</td>' +
        '<td>' + contractModel.ProofOfPersonalNumbers +
        '</td>' +
        '<td>' + contractModel.NIP  +
        '</td>' +
        '<td>' + contractModel.BirthDate  +
        '</td>' +
        '<td>' + contractModel.Saldo +
        '</td>' +
        '<td>' + contractModel.NumberOfTransactions +
        '</td>' +
        '<td>' + contractModel.AmountOfTheLastTransaction +
        '</td>' +
        '<td>' + contractModel.DateOfLastTransaction +
        '</td>' +
        '<td>' + contractModel.TransferTitle +
        '</td>' +
        '<td>' + '<button type="button" class=" btn btn-info btn-xs">Szczegóły</button>' +
        '</td>' +
        '</tr>'
    );
},
};

Na koniec skrypt zawiera frazę "document.ready", która upewnia nas, że to wszystko wykona się, gdy strona zostanie całkowicie załadowana.

$(function () {
    MVCDataTableJqueryBootStrap.init();
});

Co się teraz stanie. Strona zostanie załadowana. Później skrypt wyśle zapytanie AJAX i pobierze obiekt JSON z kolekcjami elementów. W pętli tabelka zostanie uzupełniona rekordami.

image_thumb8

Gdy tabelka skończy się uzupełniać, wtedy zostanie przetworzona na tabelkę DataTable.

Co, jeśli chcemy uzupełnić tabelkę, która została już opakowana skryptem DataTable.

mydataTable.addRow(rekord).draw()

Posiadanie już opakowanej tabelki DataTable trochę komplikuje sprawę.

Dodanie elementu dynamicznie do  tbody zostanie automatycznie zignorowane przez DataTable i wyczyszczone przy pierwszym sortowaniu.

Pamiętasz wcześniej tę ohydną funkcję, która dodała jeden rekord do tabelki. Można to zrobić trochę lepiej.

DataTable oferują funkcję "addRow". Wewnątrz tej metody możemy przekazać obiekt JSON albo tablicę elementów.

Spróbujmy sił z tablicą elementów.

public class JqueryAddRowTableVersionController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult GetPersonData()
    {
        var collection = PersonContractModelCollection
        .GetContractModelCollections();

        List<Object[]> ob = new List<object[]>();

        foreach (var v1 in collection)
        {
            ob.Add(new object[] 
            { 
                v1.NameAndSurName, 
                v1.Age, v1.Pesel,v1.ProofOfPersonalNumbers,
                v1.NIP, v1.BirthDate, 
                v1.Saldo, v1.AmountOfTheLastTransaction, 
                v1.DateOfLastTransaction, 
                v1.NumberOfTransactions, 
                v1.TransferTitle 
            });
        }

        return new JsonResult()
        {
            JsonRequestBehavior = JsonRequestBehavior.AllowGet,
            Data = ob
        };
    }
}

Przygotowanie metody po stronie serwera, która zwróci kolekcję tablic wartości JSON, zamiast kolekcję obiektu z właściwościami  może być czasochłonne.  Już na tym etapie widzimy, że nie jest to dobry pomysł.

Po stronie klienta każda przekazana tablica zostanie zamieniona na rekord przy użyciu funkcji DataTable "addRow". Po wywołaniu funkcji addRow() trzeba wywołać funkcję draw() tak, by nasze zmiany zostały zapamiętane przez DataTable.

intGetData: function () {
    $.ajax({
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        type: "POST",
        url: "/JqueryAddRowTableVersion/GetPersonData/",
        cache: false
    }).done(function (data) {

        var table = MVCDataTableJqueryBootStrap.returnDataTable();
        $.each(data, function (index, value) {

            table.row.add(value).draw();

        });
    }).fail(function (d) {

    });
},

Powstaje tutaj jednak pewien problem. W tabelce przekazujemy dane pod postacią tabelki. DataTable na podstawie kolejności umieści te dane w odpowiednich kolumnach.

image_thumb11

W naszej tabelce jednak mamy kolumnę z przyciskiem.  Jak ona zostanie uzupełniona, jeśli w żaden sposób nie powiedzieliśmy, jak to ma się stać.

Otóż teraz z każdym wywołaniem metody addRow() DataTable zwróci komunikat o błędzie. Komunikat ten mówi, że mamy 11 kolumn więc DataTable spodziewa się 11 elementów, licząc od zera w tabeli, a jest ich dziesięć.

image_thumb14

Ten problem można naprawić, dodając definicję 11 kolumny i określić jej sposób renderowania. Oto jak to się robi.

Warto też dodać, że funkcja initDataTable będzie teraz deklarowała funkcję, w której będziemy zwracać utworzoną definicję dataTable.

initDataTable: function () {
    var table = $('#tableContract').DataTable({
        "columnDefs": [{
            "targets": 11,
            "render": function (data, type, full, meta) {
                return '<button type="button" ' +
                    'class=" btn btn-info btn-xs">Szczegóły</button>';
            }
        }]
    });

    MVCDataTableJqueryBootStrap.returnDataTable = function () {
        return table;
    }
},

Teraz dataTable będzie działać poprawnie, 11 kolumna zawiera przycisk. Co, jeśli jednak chciałbyś dodać dodatkową informację do tego przycisku.

Nie jest to obecnie możliwe, ponieważ w funkcji obiekt "data" nie zawiera w sobie niczego.

image_thumb17

initDataTable: function () {
    var table = $('#tableContract').DataTable({
        "columnDefs": [{
            "targets": 11,
            "data":0,
            "render": function (data, type, full, meta) {
                return '<button data-name="'+data+'" type="button" ' +
                    'class=" btn btn-info btn-xs">Szczegóły</button>';
            }
        }]
    });

Do "columnDefs" trzeba dodać parametr "data". W nim, ponieważ operujemy na tablicy, określamy indeks elementu, który nas interesuje.

image_thumb17[4]

Na podstawie obiektu “data” mogę dodać do mojego przycisku mój atrybut "data-name".

image_thumb24

W ten sposób uzupełniliśmy tablice, używając funkcji addRow().

Teraz powtórzę ten sam proces. Tym razem jednak prześlemy cały obiekt JSON.

Kod kontrolera MVC wygląda tak samo, jak wcześniej.

public class JqueryAddRowController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult GetPersonData()
    {
        var collection = PersonContractModelCollection.
        GetContractModelCollections();

        return new JsonResult()
        {
            JsonRequestBehavior = JsonRequestBehavior.DenyGet,
            Data = collection
        };
    }
}

Teraz gdy operujemy na obiektach, musimy powiedzieć DataTable, gdzie jaka właściwość obiektu ma trafić. Inaczej DataTable będzie wyrzucało alerty z informacjami o błędzie w każdym rekordzie.

var table = $('#tableContract').DataTable({
    "language": {},
    "columns": [
        { "data": "NameAndSurName" },
        { "data": "Age" },
        { "data": "Pesel" },
        { "data": "ProofOfPersonalNumbers" },
        { "data": "NIP" },
        { "data": "BirthDate" },
        { "data": "Saldo" },
        { "data": "NumberOfTransactions" },
        { "data": "AmountOfTheLastTransaction" },
        { "data": "DateOfLastTransaction" },
        { "data": "TransferTitle" },
        {
            "data": "Pesel",
            "render": function(data, type, full, meta) {
                return '<button id="btn_' + data + '" type="button" ' +
                    'class="btn btn-xs btn-info">Szczegóły</button>';
            }
        }
    ],
});

Jak widzisz, ustaliłem, też definicję utworzenia kolumny z przyciskiem. Przycisk będzie zawierał id ‘btn’ plus numer pesel.

image_thumb27

Oto cztery sposoby na uzupełnienie tabelki DataTable.

Nie mówiliśmy, jeszcze o możliwości uzupełnienia tabelki korzystając z budowanej funkcji Ajax, która istnieje w DataTable.

Istnieje też scenariusz, w którym tabelka może być utworzona w trakcie wywołania metody Razor po stronie C# "Ajax.BeginForm".

W następnym wpisie o mówię te scenariusze.

Edit z 2022 roku :
Kod można pobrać tutaj : PanNiebieski/MVCDataTableJqueryBootStrap (github.com)