Spis treści:

Kategoria:HTMLJavaScript


Sortowanie tabeli w HTML i JavaScript - cz.1

Naukowcy udowodnili,
  że ziemia nie jest okrągła.

Wedle ich badań jest brudna,
  czarna i chrzęści między zębami.

Po co nam sortowanie tabeli

Wiele jest gotowych rozwiązań pozwalających zamienić zwykłą tabelę HTML w tabelę niezwykłą. Mam na myśli przede wszystkim taką, która pozwala posortować swoje elementy według pewnego algorytmu. Problem polega na tym, że porównania najczęściej realizowane są w trybie tekstowym. Co w tym złego? W większości przypadków nic, jednak w pewnych przypadkach zauważamy drobne problemy. Pomyślmy o wartościach reprezentujących ilość towaru: postać tekstowa liczby 2, tj. "2", będzie alfabetycznie za liczbą 11, z postacią tekstową "11". Matematycznie jest to niedopuszczalne. Podobne nieporozumienia mogą wyniknąć w przypadku sortowania dat lub innych, własnych symboli.

Postąpię trochę jak naukowcy z przedstawionego cytatu: wyważę otwarte drzwi i napiszę swój własny mechanizm sortujący tabele HTML. Co więcej, umożliwię przekazywanie dowolnych funkcji sortujących, które zrealizują funkję porządkowania dowolnych elementów. Zaznaczę od razu, że będę korzystał z czystego kodu JavaScript. Tę samą operację da się szybciej i sprawniej wyrazić za pomocą jQuery, ale o tym innym razem. Przejdźmy zatem do dzieła.

Funkcje pomocnicze

Zanim przejdę do właściwego algorytmu napiszę kilka funkcji pomocniczych. Pierwsza z nich będzie służyła do sprawdzenia, czy wskazana tablica zawiera dany element. Jest to odpowiednik funkcji Contains znanej z wielu różnych języków programowania. Tak też nazwałem swoją funkcję. Kod funkcji pokazany jest poniżej:

function Contains(classArray,value){
  for (var i=0; i<classArray.length;i++)
    if (classArray[i]===value) return true;
  return false;
}

Do czego nam taka funkcja? Ja użyję jej do sprawdzenia, czy wskazany element nagłówka th tabeli jest oznaczony klasą o konkretnej nazwie. To właśnie poprzez nazwę klasy będę decydował, czy dana kolumna może być sortowana i jaki algorytm zostanie do tego sortowania użyty.

Pisząc poprzednie zdanie trochę zdradziłem, jakie będą pozostałe funkcje pomocnicze. Będą to funkcje określające sposób sortowania, czy jak kto woli, matematycznie, relacja przechodnia porządkująca elementy (funkcję porządkującą będę w dalszej części nazywał relacją). W przykładzie użyję dwóch metod - do porównywania liczb i do porównywania wartości tekstowych. Sposób dodawania kolejnych relacji będzie dość prosty (o tym za chwilę). Nie będzie zatem stanowiło problemu dodanie kolejnych. Przyjrzyjmy się ich implementacjom pokazanym na listingu poniżej:

function IntegerSort(a,b){return parseInt(a)>parseInt(b);}
function ValueSort(a,b){return a>b;}

Powyższy kod nie wymaga chyba szczegółowych wyjaśnień. Przejdźmy zatem do dalszej części.

Uruchamianie sortowania po kliknięciu nagłówka

Powszechnie przyjęło się, że sortowanie realizowane jest w momencie kliknięcia nagłówka tabeli. Nie będę tutaj odstawał od innych tego typu rozwiązań. Jak zatem podpiąć takie zdarzenia? Wspomniałem wcześniej, że kolumny, które mogą być sortowane będą zawierały atrybut class ustawiony na pewną charakterystyczną wartość. Ja uznałem, że będą to wartości ISort (od Integer Sort - sortowanie liczb całkowitych) oraz SSort (String Sort - sortowanie tekstowe). To na tym etapie decydujemy, jaki algorytm powinien być stosowany. Przypomnę, że atrybut class może przyjmować wiele wartości - nazwy poszczególnych klas będą wtedy oddzielone spacją. Taką wartość będę rozbijał na tablicę, a w niej będę wyszukiwał wartości ISort, SSort i w przyszłości być może innych. Tu znajdzie swoje zadanie funkcja Contains. Żeby nie omawiać zagadnienia na sucho, przyjrzyjmy się poniższemu fragmentowi kodu:

function attachSorting(){
  var handlers=[["SSort", ValueSort],["ISort",IntegerSort]];
  for(var i=0, ths=document.getElementsByTagName('th'); th=ths[i]; i++){
    for (var h=0; h<handlers.length;h++) {
      if(Contains(th.className.split(" "), handlers[h][0])){
        th.columnIndex=i;
        th.order = -1;
        th.sortHandler = handlers[h][1];
        th.onclick=function(){sort(this);}
      }
    }
  }
}

Algorytm podpinania zdarzeń jest dość prosty. Kluczem do zrozumienia jest wstępnie zdefiniowana tablica handlers. Elementami tej tablicy są struktury opisujące połączenia NazwaKlasy-FunkcjaObsługi, zwane dalej definicjami. Dla każdego nagłówka th posiadającego klasę wymienioną pod indeksem 0 którejś z definicji przypisywana jest funkcja obsługi sortowania wymieniona pod indeksem 1. Oprócz zdarzenia zapamiętywane są również inne elementy potrzebne do prawidłowego sortowania: numer kolumny, w której są dane (odpowiada numerowi nagłówka), początkowy porządek sortowania oraz funkcję sortowania. Te wartości stają się integralną częścią elementu nagłówka. Aby te informacje były dostępne w zdarzeniu onclick, przekazujemy do metody sort referencję do przetworzonego nagłówka z danymi. Metoda sort będzie mogła odwoływać się do wszystkich ustawionych w pętli wartości.

Sortowanie wartości w kolumnach - implementacja

Przed nami najważniejsza część. Funkcja, która wykorzystująć przekazane wcześniej informacje potrafi posortować dane. Popatrzmy na przykładową implementację przedstawioną na poniższym listingu:

function sort(header){
    header.order *= -1;
    var table = header.parentNode.parentNode;
    for (var i=0, th, ths=table.getElementsByTagName('th'); th=ths[i]; i++)
      if (th!=header) th.order = -1;
    var rows=table.getElementsByTagName('tr');
    for(var i=1, tempRows=[], tr; tr=rows[i]; i++){tempRows[i-1]=tr}
    tempRows.sort(function(a,b){
      return header.order*
        (header.sortHandler(
          a.getElementsByTagName('td')[header.columnIndex].innerHTML,
          b.getElementsByTagName('td')[header.columnIndex].innerHTML)?1:-1)});
    for(var i=0; i<tempRows.length; i++){
        table.appendChild(tempRows[i]);
    }
}

Postaram się w miarę jasno opisać przeznaczenie poszczególnych linijek kodu. Pierwsza linia zmienia sposób sortowania: pierwsze kliknięcie zmieni wartość order z -1 na -1*-1, czyli 1. Oznacza to porządek rosnący. Drugie kliknięcie w ten sam nagłówek zmieni tę wartość na 1*-1, czyli 1. To oznacza porządek malejący. Linijka druga pobiera referencję do obiektu tabeli. Przypomnijmy, że rodzicem elementu th jest tr, a dopiero rodzicem tr jest table. To stąd podwójne odwołanie do wartości parentNode.

Linie 3 i 4 nie są konieczne, ale dzięki nim korzystanie z sortowania staje się bardziej intuicyjne: resetują one poprzednie ustawienia sortowania pozostałych kolumn tabeli. Dzięki temu mamy pewność, że pierwsze kliknięcie w nową kolumnę zawsze posortuje nam wartości rosnąco. Jeżeli nie zależy nam na tym, można te linijki wyrzucić.

Linijki 5 i 6 są bardzo ważne: najpierw pobieramy listę wszystkich elementów tr (linia 5), a następnie przerzucamy je do tablicy tymczasowej. To w niej odbędzie się sortowanie. Warto zwrócić uwagę na indeksowanie - nie jest od 0, lecz od 1. Pamiętajmy, że pierwszy z góry (indeks 0) jest rząd nagłówków th. Ich nie chcemy sortować razem z resztą.

Zaraz po utworzeniu tabeli tymczasowej znajduje się wywołanie instrukcji sort rozciągające się, dla przejrzystości, na pięć linijek (linijki 7-11). Sposób porównywania definiujemy własną funkcją. Dla każdej pary wartości porównywanych (dla nas są to pełne wiersze tr tabeli tymczasowej) określamy sposób określania wzajemnego położenia. To tutaj przyda się indeks nagłówka (do wydobycia odpowiedniej komórki), a także metoda sortująca, przekazana w polu o nazwie sortHandler. Po drodze należy jeszcze zamienić wartość logiczną true/false porównania na 1/-1 i zastosować mnożnik sortowania z pola order. Jeżeli opie wydaje się mało zrozumiały, wystarczy dobrze zapoznać się ze składnią metody sort będącej integralną częścią każdej tablicy w JavaScript (to nie jest wywołanie rekurencyjne!).

Ostatnie dwie linijki pozwalają wstawić posortowane elementy z powrotem na swoje miejsce - już we właściwej kolejności.

Należy oczywiście pamiętać, że po załadowaniu strony konieczne jest wywołanie funkcji attachSorting(). Bez niej zdarzenia nie zostaną podpięte.

Przykład działania

Kod JavaScript jest gotowy. Aby pokazać, jak to wszystko razem ze sobą współpracuje, potrzebna nam jeszcze odpowiednio oznaczona tabela. Przykład takiej tabeli pokazano poniżej:

<table>
<tr><th class="ISort">Integer</th><th class="SSort">String</th><th class="SSort">Domyślne</th><th>Inne</th></tr>
<tr><td>2</td><td>A</td><td>2</td><td>Dodatkowe dane 1</td></tr>
<tr><td>10</td><td>B</td><td>10</td><td>Dodatkowe dane 2</td></tr>
<tr><td>3</td><td>G</td><td>3</td><td>Dodatkowe dane 3</td></tr>
<tr><td>111</td><td>D</td><td>111</td><td>Dodatkowe dane 4</td></tr>
</table>

Warto zwrócić uwagę na sposób oznaczenia nagłówków th wspomnianymi wcześniej klasami. To tyle, skrypt zajmie się resztą. Wystarczy wszystko raz uruchomić wywołaniem funkcji attachSorting(). Wynik działania można obejrzeć na przykładzie poniższej tabeliŹródła tej strony mogą nieznacznie odbiegać od przedstawionych w opisie. Globalnie użyty mechanizm sortowania tabel stosowany dla całego portalu mógłby nakładać się z zaprezentowanym powyżej. Aby uniknąć dziwnego zachowania oraz wzajemnego nadpisywania zdarzeń i styli nazwy klas zostały zmienione.:

IntegerStringDomyślneInne
2A2Dodatkowe dane 1
10B10Dodatkowe dane 2
3G3Dodatkowe dane 3
111D111Dodatkowe dane 4

Podsumowanie

Styl tabeli nie jest integralną częścią całego rozwiązania - leży całkowicie w gestii autora strony. Tabela z sortowaniem wygląda prawie tak samo jak wszystkie inne tabele na innych podstronach. Ta drobna różnica to zmiana wskaźnika myszy po najechaniu na nagłówek. Może ona być równie dobrze ustawiona globalnie dla klas ISort oraz SSort. Taki sposób sortowania nie wyczerpuje tematu. Przydałby się jakis wskaźnik określający, która z kolumn jest obecnie posortowana. O tym jednak w kolejnym artykule: Sortowanie tabeli w HTML i JavaScript - cz.2.

Kategoria:HTMLJavaScript

, 2013-12-20

Komentarze:

Ian (2013-02-02 12:41:50)
Świetne i klarowne.
Jednak interesowałoby mnie uzupełnienie o możliwość sortowania tylko wybranego obszaru tabeli, a więc jedynie wskazanych wierszy.
Chciałbym to wykorzystać w arkuszu kalkulacyjnym, który stworzyłem na stronie http://www.csharp.strefa.pl/sheet01.htm .
Pozdrawiam
Vendrom (2017-06-19 09:28:22)
Nie znając javy z tym opisem można bardzo dobrze posortować tabelę!
wielkie dzięki!!!!
pozdrawiam
zainteresowany (2018-05-16 12:02:04)
A jak załatwić sprawę z polskimi znakami oraz przy okazji z niezmieniającą się wartością pierwszej kolumny będąca np. liczbą porządkową? ma ktoś jakiś pomysł?
Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?
Ciekawy artykuł.
Czy można za pomocą EF wysłać swoje zapytanie?
Czy lepiej do tego użyć ADO.net i DataTable?