Sortowanie tabeli w HTML i JavaScript - cz.1
ż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:
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 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:
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:
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:
<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.:
Integer | String | Domyślne | Inne |
---|---|---|---|
2 | A | 2 | Dodatkowe dane 1 |
10 | B | 10 | Dodatkowe dane 2 |
3 | G | 3 | Dodatkowe dane 3 |
111 | D | 111 | Dodatkowe 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
Komentarze:
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
wielkie dzięki!!!!
pozdrawiam