W poprzedniej części kursu zapoznaliśmy się z dodawaniem i usuwaniem zdarzeń - pora na zdalne wywoływanie zdarzeń, ich klonowanie oraz omówienie kilku bardziej zaawansowanych zagadnień związanych ze zdarzeniami. Przy okazji poznamy pięć nowych metod klasy Element - clone, adopt, getNext, dispose oraz getProperty.

Zdalne wywoływanie zdarzeń

Dzięki metodzie fireEvent nie musimy polegać tylko na użytkowniku - możemy z poziomu skryptu wywołać dowolne zdarzenie danego elementu w dowolnym momencie. Powiedzmy sobie jednak, że w wypadku zdarzeń natywnych takich jak onClick czy onChange, metody fireEvent nie używa się zbyt często - w takiej sytuacji potrzeby muszą być dość wyszukane, ale czasem się zdarzają.

Składnia metody fireEvent wygląda następująco:

$(element).fireEvent("typ", argumenty, opoznienie);

Dwa ostatnie argumenty są opcjonalne - możemy przekazać argumenty do funkcji obsługi zdarzenia i zdecydować z jakim opóźnieniem zostanie ono wywołane. Oczywiście pamiętamy, że jeżeli argumentów jest więcej niż jeden to podajemy je jako tablicę. Zatem gdybyśmy chcieli wywołać zdarzenie onChange elementu "inputTestowy" wraz z przekazaniem dwóch liczb funkcji obsługi zdarzenia, a całe zdarzenie miałoby wystąpić po 2 sekundach to zapisalibyśmy:

$("inputTestowy").fireEvent("change", [5,25], 2000);

Klonowanie z charakterem

Ludzkość już dość dawno poznała tajniki klonowania żywych organizmów, ale takie klonowanie ma jedną wadę - możemy idealnie odtworzyć wygląd, ale nie ma możliwości skopiowania charakteru - czyli cech nabytych. W przypadku JavaScript mamy tu większe pole do popisu - owszem jeżeli po prostu skopiujemy element to będzie on identyczny jak pierwowzór, ale nie będzie miał swojego "charakteru" - zdarzeń. I do tego celu - klonowania "charakteru" elementu służy właśnie metoda cloneEvents. Ale zanim użyjemy tej metody w warunkach bojowych musimy poznać dwie metody klasy Element - clone i inject.

Mając uchwyt do danego elementu możemy stworzyć jego kopię dzięki metodzie clone:

var kopia = $(element).clone();

Przy czym metoda clone pobiera dwa opcjonalne argumenty - oba są wartościami logicznymi - pierwsza określa czy chcemy skopiować element wraz z potomkami (domyślnie wartość true), a druga opcja określa czy element ma zachować atrybut id swojego pierwowzoru - domyślnie opcja ta ma wartość false, ponieważ jak wiemy atrybut id powinien w obszarze dokumentu być unikalny.

Zatem chcąc skopiować element bez potomków i z atrybutem id pierwowzoru (na przekór regułom), musimy zapisać następujący kod:

var kopia = $(element).clone(false, true);

Bardzo ważną sprawą w wypadku kopiowania jest to, że element w zmiennej kopia nigdzie w naszym dokumencie jeszcze nie istnieje - musimy go w nim umieścić. Sposobów jest kilka, a my wykorzystamy metodę adopt - jest ona banalna w użyciu:

$(elementRodzica).adopt(element);

Powyższy kod sprawi, że element znajdzie się w elemencie elementRodzica. Dodatkowo można dodać tak kilka elementów naraz - rozdzielając je przecinkiem lub podając je jako tablicę:

$(elementRodzica).adopt(element1, element2, element3);
$(elementRodzica).adopt([element1, element2, element3]);

Pierwszy zapis polecam stosować gdy podajemy ręcznie wszystkie elementy, a drugi zapis owszem jest dozwolony, ale tak naprawdę oznacza on, że możemy do metody adopt przekazać tablicę elementów, która jest efektem na przykład selekcji elementów (o tym w późniejszych częściach kursu).

Zatem aby umieścić naszą kopię w jakimś elemencie (przykładowo posiadającym atrybut id "wrapper") zapiszemy:

var kopia = $(element).clone();
$("wrapper").adopt(kopia);

Jak ktoś chce bardzo skracać to można oczywiście zapisać:

$("wrapper").adopt($(element).clone());

Ale wtedy w razie potrzeby odwołania się do naszej kopii w późniejszym kodzie, będzie trzeba ją odnaleźć w strukturze dokumentu.

Skoro znamy już metody clone i adopt to możemy przejść do najbardziej interesującej nas metody czyli cloneEvents. Ma ona następującą składnię:

kopia.cloneEvents(pierwowzor);

Opcjonalnie jako drugi argument możemy określić jeden typ klonowanych zdarzeń. Zatem jeżeli interesują nas wyłącznie zdarzenia onClick, zapiszemy:

kopia.cloneEvents(pierwowzor, "click");

No dobrze - pora na przykład użycia klonowania elementów i ich zdarzeń. Stworzymy formularz, który będzie pozwalał utworzyć dowolną ilość pól input, które będą miały wspólną cechę - będą posiadały walidację wprowadzanego tekstu (w tym wypadku nakaz wpisywania wyłącznie małych liter alfabetu łacińskiego oraz zakaz wpisania tekstu krótszego niż 5 znaków) - dzięki temu użytkownik będzie wiedział, że wpisał złe dane. Całość będzie oczywiście bazowała na zdarzeniu onChange.

Najważniejsza częśc kodu strony prezentuje się następująco:

<p><button id="add">Dodaj nowe pole tekstowe</button></p>
 
<form action="mootools3.html" method="post" id="form">
      <div id="base">
            <input type="text" id="text" name="text" /> <strong id="valid"></strong>
       </div>
</form>

W divie "base" mamy pole input oraz pole komunikatu. Należy dodać, że ten div jest ukryty poprzez:

div#base{
    display:none;
}

Gdy klikniemy na przycisk "add" pojawią się jednak jego kopie, dzięki wykorzystaniu faktu, że kopia jest pozbawiona wszelkich atrybutów id (zatem powyższa reguła CSS nie dotyczy kopii diva bazowego).

Jeżeli chodzi o kod JavaScript, to jego podstawa wygląda następująco:

window.addEvent("load", function(){   
    $('add').addEvent("click", function(){
        var copy = $('base').clone();
        copy.cloneEvents($('text'));
        $('form').adopt(copy);
    });
 
    $('text').addEvent('change',function(e){
        var el = new Event(e).target;
        //...
    });
});

Po załadowaniu strony dodajemy zdarzenie onClick do przycisku - w momencie jego wystąpienia następuje sklonowanie diva bazowego, następuje też skopiowanie zdarzeń pola input, a następnie kopia już z przypisanymi zdarzeniami jest umieszczana w formularzu.

W zdarzeniu onChange pola input od razu tworzymy odwołanie do elementu, które jest powiązane ze zdarzeniem - nie możemy tutaj wykorzystać funkcji $, ponieważ wtedy odnosilibyśmy się stale do diva bazowego, a nie do nowo utworzonej kopii.

Poniżej prezentuję już kompletny kod:

window.addEvent("load", function(){   
    $('add').addEvent("click", function(){
        var copy = $('base').clone();
        copy.cloneEvents($('text'));
        $('form').adopt(copy);
    });
 
    $('text').addEvent('change',function(e){
        var el = new Event(e).target;
        if(el.value.test('^[a-z]{5,}$')){
            el.getNext().style.color = 'green';
            el.getNext().innerHTML = 'Pole wypełnione poprawnie';   
        }
        else{
            el.getNext().style.color = 'red';
            el.getNext().innerHTML = 'Pole wypełnione niepoprawnie';
        }
    });
});

Jak widać w zdarzeniu onChange następuje porównanie wartości pola input ze wzorcem, a następnie poprzez metodę getNext odwołujemy się do elementu, który w divie bazowym ma id "valid". Metoda getNext powoduje po prostu wybranie następnego węzła na tym samym poziomie co węzeł dla którego wywołujemy tą metodę - zatem zapis:

el.getNext();

Zwróci nam uchwyt do elementu strong następującego bezpośrednio po polu input.

Całość w akcji można obejrzeć w poniższym przykładzie:

PRZYKŁAD 1

Dodatkowe zdarzenia w MooTools 1.2

Każdy kto narzekał na pewne niedogodności związane ze zdarzeniami onMouseOver i onMouseOut powinien się ucieszyć, bo w MooTools 1.2 mamy do dyspozycji zdarzenia mouseenter i mouseleave - są to ulepszone wersje mouseover i mouseout, które czasami występują w nieodpowiednim momencie. Krótko mówiąc - jeżeli standardowe zdarzenia powodują pewne nieoczekiwane zachowania, to prawdopodobnie najlepszym lekarstwem na te problemy jest użycie właśnie ich odpowiedników z MooTools 1.2. Zazwyczaj kod związany z tymi zdarzeniami będzie wyglądał następująco:

$(element).addEvents({
    "mouseenter" : function(){
        // gdy kursor jest nad elementem
    },
    "mouseleave" : function(){
        // gdy kursor opuści obszar elementu
    }
});

Innym dodatkowym zdarzeniem jest mousewheel - występuje ono w momencie poruszania rolką myszki. Standardowe kod używany przy tym zdarzeniu prezentuje się następująco:

$(element).addEvent("mousewheel", function(evnt){
    var scroll = new Event(evnt).wheel;
    // kod wykorzystujący wartość zmiennej scroll
});

Pora na własną twórczość

Dużą nowością w MooTools 1.2 jest obiekt Element.Events - pozwala on nam tworzyć własne zdarzenia. Tworzenie zdarzenia wygląda następująco:

Element.Events.nazwa = {
    base: 'bazoweZdarzenie',
    condition: function(event){
        // funkcja decydująca o zaistnieniu zdarzenia     
    },
    onAdd: function(){
        // funkcja wykonywana po dodaniu zdarzenia do elementu
    },
    onRemove: function(){
        // funkcja wykonywana po usunięciu zdarzenia z elementu
    }
};

Właściwość base określa zdarzenie bazowe - jest to jedno ze standardowo dostępnych zdarzeń, w momencie jego wystąpienia badany jest warunek w funkcji condition - jeżeli zwróci ona wartość true, oznacza to wystąpienie naszego zdarzenia. Właściwośći onAdd i onRemove to dodatkowe funkcje wywoływane w momencie dodania lub usunięcia naszego zdarzenia z elementu - są przydatne gdy musimy coś zmienić w elemencie przed dodaniem/usunięciem zdarzenia. My skupimy się na właściwościach base i condition, gdyż są one w wypadku tworzenia nowych zdarzeń najczęściej używane.

Stworzymy teraz zdarzenie ctrlclick, które będzie występowało tylko w momencie kliknięcia klawisza myszki i przytrzymania klawisza Ctrl.

Stworzenie naszego zdarzenia wymaga poniższego kodu:

Element.Events.ctrlclick = {
        base: 'click',
        condition: function(event){
            return (event.control);
        }
};

Powyższy zapis oznacza, że nasze zdarzenie bazuje na zdarzeniu onClick, ale wystąpi tylko wtedy gdy wraz z tym zdarzeniem wystąpi naciśnięcie klawisza Ctrl.

Stworzymy na bazie tego zdarzenia stronkę z której można usunąć wszystkie elementy, które mają przypisaną klasę removable. Oczywiście usunięcie będzie wymagało zastosowania naszego zdarzenia ctrlclick.

Nasze rozwiązanie będzie miało tą zaletę, że zastosujemy tylko jeden obserwator zdarzeń - dzięki temu nie musimy każdemu elementowi przypisywać zdarzenia ctrlclick, a dodatkowo możemy generować treść w locie i nie spowoduje to żadnych ubytków funkcjonalności strony, nawet w momencie dodania usuwalnego elementu:

$('wrapper').addEvent('ctrlclick', function(event){
        var el = new Event(event).target;
        if(el.getProperty('class') == 'removable'){
            el.dispose();
        }
});

W powyższym kodzie zastosowałem dwie metody klasy Element - getProperty, która zwraca wartość podanego jako argument atrybutu elementu - w naszym wypadku jest to nazwa klasy przypisanej do elementu. Natomiast metoda dispose usuwa element ze struktury strony.

Cały kod w akcji można obejrzeć na poniższej stronie:

PRZYKŁAD 2

Dla treningu

  • PRZYKŁAD 1 - do formularza dodać przycisk submit i w wypadku jego kliknięcia sprawdzić czy wszystkie pola są wypełnione poprawnie.
  • PRZYKŁAD 2 - metoda dispose ma pewną charakterystyczną cechę - zwraca uchwyt do usuwanego elementu, dzięki temu można dany element na nowo umieścić w dokumencie. Dlatego proponuję dodać możliwość przywracania ostatnio usuniętego elementu na bazie metod dispose i adopt.

Podsumowanie

Wiemy już sporo o zdarzeniach związanych z elementami strony - dzięki nim będziemy mogli stworzyć interaktywne przykłady przy okazji omawiania licznych metod klasy Element.

Komentarze do wpisu "MooTools 1.2 - elementy strony - zdarzenia cz. 2":

1. Bigismall napisał(a):
14 sierpnia 2008, 13:17:38

Fajne przykłady, a przy tym lekka treść. Nowa forma bardzo mi odpowiada. Było by miło gdybyś nie bieżąco publikował gdzieś całość (i w miarę tworzenia uzupełniał) tak jak tu
„http://dziudek.dreamhosters.com/mootools/printable/mootools111.html”

2. Dziudek napisał(a):
14 sierpnia 2008, 22:00:49

Publikacja całości dopiero na koniec – wtedy hurtem wyedytuję co trzeba ;)

3. sf napisał(a):
12 października 2008, 13:21:54

Czy istnieje jakiś krótszy sposób przekazania elementu (target), który kliknęliśmy niż ten co podam poniżej?

$$(’.clipboard’).addEvent(„click”, function(event) { clipboard(event, ‘clipboard’) });

i potem w funkcji :

var el = new Event(event).target;

Próbowałem od razu uruchamiać moją funkcję clipboard() z pierwszym argumentem event, ale wyskakiwał błąd, że nie jest on zdefiniowany.

4. Dziudek napisał(a):
12 października 2008, 13:27:46

@sf – można spróbować z metodą obiektu Function – bindWithEventlink – powinno być krócej bo nie trzeba wywoływać bezpośrednio:

var el = new Event(event).target;

5. sf napisał(a):
12 października 2008, 13:37:33

To może i by się sprawdziło gdybym miał konkretny element, a tutaj $$(’.clipboard’) wskazuje na kilkanaście checkbox i nie wiadomo, który jest kliknięty stąd ten target musi chyba jednak być.

6. Dziudek napisał(a):
12 października 2008, 13:40:19

@sf – zamiast dodawać kilkanaście zdarzeń addEvent (do każdego checkboksa) lepiej zrobić jedno zdarzenie addEvent dla kontenera je przechowującego i na tej samej zasadzie (z Event.target) sprawdzać, czy kliknięto checkbox, a jeśli tak to który :) O wiele bardziej optymalne, bo jest tylko jeden obserwator zdarzenia, a przy tym można dynamicznie dodawać nowe checkboksy i nie trzeba nowych obserwatorów zdarzeń dodawać;)

7. sf napisał(a):
12 października 2008, 13:43:54

Hm, rzeczywiście, ma to sens :) Dzięki za radę.

Dodaj komentarz:

Textile Lite włączony ( szczegółowy opis znaczników ):
*strong* | # lista numerowana | * lista wypunktowana | _em_ | __italic__ | "link":http:// | bq. cytat.