Zabawa z tabelkami
Wednesday, 23. July 2008, 11:52:40

Layout jest dynamiczny i konfigurowalny, toteż ktoś, kto, tak jak ja, ma pulpit szeroki na 1900 pikseli mógłby poczuć się niekomfortowo otrzymując tabelkę z kolumną zawierającą tytuły podstron rozjechaną na 1600 pikseli a resztę ściśniętą, gdzieś pod koniec, jak labrador w mikrofalówce. Oczywiście przewidziałem to i tak naprawdę, taka sytuacja nie ma prawa zajść, jednak mimo to chciałem dać użytkownikom wybór, która kolumna jest dla nich aktualnie najważniejsza i do której chcieliby mieć w danej chwili jak najwygodnieszy dostęp. Wybór był jeden - dynamiczna zmiana szerokości kolumn.
Jako, że robiłem to pierwszy raz, ucząc się przy okazji, chciałbym teraz trochę pouczyć was. Tak więc dzisiejszy temat lekcji, to wspomiana już:
"Dynamiczna zmiana szerokości kolumn - czyli, dlaczego lubimy Javascript."
Jedziemy
Potrzebna nam będzie tabelka. Ja zrobiłem sobie taką:

Wy możecie zrobić zwykłą, prostą tabelkę zawierająca nagłówki i kilka rzędów komórek, dla zachowania przejrzystości:
<table border="0" cellpadding="2" cellspacing="0">
<thead>
<tr>
<th>Lp</th>
<th>Lorem</th>
<th>Ipsum</th>
<th>Dolor</th>
</tr>
<thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td
</tr>
<tr>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
</tbody>
</table>
Teraz musimy przygotować sobie jakiś uchwyt, za który będzie można złapać i zacząć woogie boogie. Wystarczy jakiś DIV. Problem w tym, że elementy TH/TD ignorują position: absolute;... musimy więc użyć dwóch DIVów. Jeden będzie robił za containing block dla drugiego, pozycjonowanego absolutnie handlera (uchwytu):
<th>
<div>
Lorem <div class="handler"></div>
</div>
</th>
Takie same DIVy dodajemy do pozostałych nagłówków tabelki. Następnie ostylujemy je trochę abyśmy wiedzieli, co się dzieje:
th > div {
position: relative; /* tworzymy containing block */
}
.handler {
position: absolute;
top: 0;
right: 99.5; /* uchwyt będzie na końcu każdego nagłówka a właściwie pomiędzy końcem a początkiem następnego */
height: 100%;
width: 9px; /* optymalna szerokośc dla uchwytu nie powodująca efektu "Kur... gdzie ten cycek?!" */
cursor: ew-resize; /* ew działa tylko w Safari, możecie użyć innego */
border: 1px #900 solid; /* tymczasowo, żebyśmy wiedzieli, gdzie jesteśmy */
}
W rezultacie otrzymamy coś takiego:

Javascript
Front już mamy, teraz zaplecze, które wykona całą robote. Potrzebujemy dwie funkcje - jedną, która zajmie się zmianą szerokości i drugą, która wywoła cały mechanizm. Nie będziemy bawić się w żadne onclicki w TH a użyjemy do tego DOM poziomu drugiego i ładnie odseparujemy bebechy od warstwy prezentacji. Zaczniemy od resizowania. Funkcja, która napiszemy, będzie potrzebowała dwa parametry - referencje do obiektu, który będzie uchwytem (nasz DIV.handler) oraz drugi, obiekt Event zdarzenia mousedown.
function resizeColumn(handler, event) { }
Teraz, musimy sprawdzić, jaką szerokość ma dany nagłówek i wyliczyć różnicę między nim a punktem kliknięcia myszką. Użyłem tu własnej funkcji do wyciągania reguł CSS - getStyleOf(), wy możecie użyć swojej.
function resizeColumn(handler, event) {
var headerSize = parseInt(getStyleOf(handler.parentNode.parentNode, 0).getPropertyValue('width'));
var deltaX = event.clientX - headerSize;
}
Następnie musimy zarejestrować procedury obsługi zdarzenia, które będą odpowiadać na zdarzenia mousedown i mouseup, występującą bezpośednio po niej. Te procedury będą aktywne dopóty, dopóki będzie naciśniety lewy przycisk myszy i zostaną zwolnione gdy go puścimy. Dopisujemy dalej w ciele funkcji:
function resizeColumn(handler, event) {
... /* tu poprzednie wyliczenia deltyX i headerSize */
document.addEventListener('mousemove', resizeDo, true);
document.addEventListener('mouseup', resizeStop, true);
event.stopPropagation();
event.preventDefault();
}
O stopPropagation() i preventDefault() oraz propagacji zdarzeń innym razem. Teraz po prostu przyjmij, że musimy tak zrobić. Ostatnim krokiem jest przygotowanie procedur resizeDo i resizeStop, które wykonają całą robotę:
function resizeColumn(handler, event) {
...
function resizeDo(event) {
handler.parentNode.parentNode.style.width = (event.clientX - deltaX) + "px";
event.stopPropagation();
}
function resizeStop(event) {
document.removeEventListener('mouseup', resizeStop, true);
document.removeEventListener('mousemove', resizeDo, true);
event.stopPropagation();
}
Okey, funkcja resizeColumn() gotowa. Teraz musimy ją odpowiednio wywołać. Napiszemy drugą funkcje, która się tym zajmie i wywołamy ją z <BODY onload="">. Nię będe się tu rozpisywał, bo działanie jest chyba jasne - bierzemy wszystkie DIVy, szukamy tych, których nazwa klasy równa jest "handler" i aplikujemy im odpowiedni mechanizm. Oczywiście można to zrobić bardziej optymalnie (zadanie domowe
function doResize() {
var handlers = document.getElementsByTagName('div');
for(var i=0; i<handlers.length; i++) {
if(handlers[i].className == 'handler') {
handlers[i].onmousedown = function (event) {resizeColumn(this, event)};
}
}
}
Tak, jak wspomniałem - aplikujemy tę funkcję w BODY:
<body onload="doResize();">
...i usuwamy czerwoną ramkę:
.handler {
border: 1px #900 solid; /* ciach! */
}

...i jeśli niczego po drodze nie popsuliśmy, to wszystko powinno fajnie hulać
PS: uprzedzając komentarze - w IE nie działa i nie będzie. Mamy tę skromną i satysfakcjonującą możliwość wyboru środowiska pracy

