70-480 – Wywoływanie i obsługa zdarzenia

70-480

Obsługa typowych zdarzeń udostępnianych przez model DOM (OnBlur, OnFocus, OnClick); deklarowanie i obsługa zdarzeń propagowanych; obsługa zdarzenia przy użyciu funkcji anonimowej.



Wracając już do tematu artykułu – zdarzenia w przeglądarce występują na porządku dziennym i to w ogromnych ilościach. Język JavaScript pozwala nam deklarować obsługę takich zdarzeń w postaci funkcji. Zdarzenia związane są także z terminem ich propagowania (bąbelkowaniem).

Zdarzenia DOM

JavaScript pozwala przypisywać zdarzenia do elementów HTML. Obsługa tych zdarzeń deklarowana jest w funkcjach przypisanych do konkretnego zdarzenia, a funkcje te są wywoływane dopiero w momencie wystąpienia zdarzenia. Ilość zdarzeń jest ogromna, a mamy również możliwość tworzenia własnych zdarzeń (o czym niżej).

Pewnie nie raz spotkaliście się z:

  • onclick (kliknięcie),
  • onblur (utrata aktywności – przeciwieństwo onfocus),
  • onfocus (aktywność elementu – przeciwieństwo onblur),
  • onscroll (zdarzenie wywoływane w momencie zmiany pozycji w pionie).

Pokaźna ich lista znajduje się na stronie w3schools.

Deklarowanie i obsługa zdarzeń

Mamy kilka możliwości na deklarowanie i obsługę zdarzeń DOM:

  • inline
  • przypisując funkcję do pola obiektu HTML
  • rejestrując obsługę konkretnego zdarzenia za pomocą funkcji addEventListener (attachEvent – starsze wersje IE)

Deklaracja inline

Najprostsza i najbardziej prymitywna opcja obsługi zdarzenia, to tak zwana deklaracja inline. Nie jest ona zbyt często spotykana, a powinna być wręcz niepożądana ze względu na mieszanie logiki JavaScript z widokiem HTML.

Spójrzmy na przykład deklaracji zdarzenia, które wywoła okienko typu alert w momencie kliknięcia w osadzony na stronie przycisk.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/1/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Widzimy tutaj deklaracje zdarzenia onclick za pomocą atrybutu i wywołania funkcji alert na elemencie button. Nic prostszego, ale warto wspomnieć, że w taki sposób nie mamy dostępu do słowa kluczowego this danego elementu.

Przypisanie funkcji do pola obiektu HTML

Spójrzmy od razu na przykład, który realizuje dokładnie taką samą funkcjonalność jak poprzednia wersja z deklaracją inline.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/2/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Tym razem brakuje atrybutu onlick dla elementu button, ale pojawił się kawałek kodu JavaScript. Pobieramy w nim referencje do przycisku, a następnie do pola onlick przypisujemy funkcję, która wykonuje dokładnie taki sam kod jak poprzedni, czyli funkcję alert.

Tak jak już wspomniałem wcześniej, deklaracje inline nie pozwalają używać słowa kluczowego this. Zobaczmy jeszcze raz na ten sam przykład, ale z wykorzystaniem słowa kluczowego this.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/3/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Tym razem funkcja alert przyjmuje jako argument wywołanie this.firstChild.nodeValue, a to nic innego jak wartość tekstowa przycisku, dla którego deklarujemy zdarzenie onlick. Poprzez użycie referencji do obiektu (this) mamy dostęp do elementu, dla którego zadeklarowane zostało zdarzenie.

To co można zauważyć, to fakt, że takie przypisanie funkcji do pola obiektu pozwala nam na obsługę danego zdarzenia tylko w jeden sposób. Jeżeli chcielibyśmy dodać kilka funkcji, mających się wywołać na kliknięcie myszką na przycisk, to nie jesteśmy w stanie zrobić tego w prosty sposób lub wręcz w ogóle nie jesteśmy w stanie tego wykonać.

By odrejestrować zdarzenie wystarczy przypisać do tego samego pola wartość null.

Funkcja addEventListener

Ostatnim i polecanym sposobem rejestracji zdarzenia jest funkcja addEventListener. Przyjmuje ona trzy argumenty:

  • nazwa zdarzenia
  • funkcja obsługująca zdarzenie
  • flaga włączająca lub wyłączająca bąbelkowanie zdarzenia (patrz kolejny podrozdział)

I od razu przykład, by nie było “na sucho”:

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/4/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Po raz kolejny jest to ta sama funkcjonalność, ale tym razem z użyciem funkcji addEventListener oraz słowa kluczowego this. Jak widać – w kodzie JavaScript zadeklarowana zostałą również funkcja invokeAlert, która przekazywana jest niżej jako drugi parametr funkcji. Sama nazwa zdarzenia nie zawiera także przedrostka on.

Deklaracja dodatkowej (nazwanej) funkcji pozwala ją również później odrejestrować za pomocą funkcji removeEventListener.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/5/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Funkcja removeEventListener zostaje wywołana dla przycisku w ciele funkcji invokeEvent. Skutkuje to tym, że zdarzenie zostaje zarejestrowane, co umożliwia wywołanie zadeklarowanej funkcji po kliknięciu na przycisk, a następnie funkcja removeEventListener usuwa to zdarzenie z przycisku. Tym samym przycisk nie pokazuje nam już okienka typu alert.

Funkcja podawana jako drugi argument obu funkcji nie musi być oczywiście nazwana, ale w takim momencie nie mamy możliwości jej odrejestrowania.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/6/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Ostatnia już przykład, który pokazuje nam możliwość definicji wielu funkcji, które reagować będą na to samo zdarzenie.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/7/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Dla przycisku zadeklarowane zostaje dwukrotnie zdarzenie click z obsługą w postaci dwóch funkcji invokeAlert oraz doSomething. Pierwsza funkcja jest nam dobrze znana. Druga natomiast wykonana zostaje zaraz po pierwszej i dodaje kolejny przycisk nas stronie, zaraz za przyciskiem, dla którego zadeklarowane zostało zdarzenie click.

Dla starszych przeglądarek Internet Explorer (<= 10) istnieje funkcja attachEvent.

Event bubbling – propagacja zdarzenia

Ostatnim elementem, o którym warto nadmienić w tym temacie, to bąbelkowe wywoływanie zdarzeń. Polega ono na przekazywaniu zdarzenia do kolejnych elementów w górę całej hierarchii, aż do elementu document. Trzeci argument funkcji addEventListener powoduje włączenie przekazywania zdarzeń (wartość true) lub ich wyłączenie (wartość false). W momencie włączenia przekazywania zdarzeń w górę, wszystkie elementy znajdujące się wyżej w hierarchii danego elementu HTML otrzymują powiadomienie o konieczności obsługi takiego zdarzenia. Oczywiście taka obsługa ma miejsce, gdy dla tych elementów, które są wyżej zadeklarowane zostało dane zdarzenie.

Spójrzmy na przykład, który powinien rozwiać wszystkie wątpliwości:

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/9/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Klikając w najbardziej wewnętrzny element (element3) wykonuje się propagacja zdarzenia click aż do elementu skrajnego (element1). Następnie wywołana zostaje obsługa zdarzenia, poczynając od najbardziej skrajnego elementu. Więc wyświetli nam się w tym wypadku trzy razy okienko typu alert z wartościami, odpowiednio: element1element2element3.

Obsługę zdarzenia możemy przerwać, wykonując funkcję stopPropagation() na obiekcie event przekazanym do funkcji obsługującej dane zdarzenie.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/qddothdt/11/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Widzimy tutaj deklarację kolejnej funkcji stop. Zostaje ona przypisana dla elementu element2. Po kliknięciu w najbardziej wewnętrzny element (element3) tym razem otrzymamy sekwencję alertów: element1element2. Nie wywoła się alert dla elementu element3 ze względu na to, że element wyżej w hierarchii zatrzymuje propagowanie zdarzenia click dalej.

Parsowanie XML z SimpleXML w PHP

XML SimpleXML PHP

Parsowanie składni XML oznacza zwykle nawigowanie po całej strukturze dokumentu XML. Duża liczba dzisiejszych serwisów zwraca dane w formacie JSON, ale jest jeszcze sporo takich, które używają do tego celu XML.

W dzisiejszym HowTo wykorzystam rozszerzenie SimpleXML, które wprowadzone zostało do PHP 5.0. Pozwala ono na łatwe nawigowanie po dokumencie XML.

Przejdźmy do pierwszego przykładu wykorzystania. Mamy do dyspozycji plik languages.xml:

<?xml version="1.0" encoding="utf-8"?>
<languages>
 <lang name="C">
  <appeared>1972</appeared>
  <creator>Dennis Ritchie</creator>
 </lang>
 <lang name="PHP">
  <appeared>1995</appeared>
  <creator>Rasmus Lerdorf</creator>
 </lang>
 <lang name="Java">
  <appeared>1995</appeared>
  <creator>James Gosling</creator>
 </lang>
</languages>

Powyższy dokument opisuje w pewien sposób trzy języki programowania i podaje ich kilka charakterystyk.

Do odczytania tego dokumentu wykorzystam funkcję simple_load_file().

<?php
$languages = simplexml_load_file("languages.xml");

Istnieje jeszcze funkcja simplexml_load_string(), która jak nie trudno się domyślić, ładuje do pamięci łańcuch znaków, który jest poprawną składnią dokumentu XML. Obie funkcje wczytują dokument DOM do pamięci tworząc tym samym obiekt SimpleXMLElement.

Wykonując var_dump($languages) lub print_r($languages) otrzymamy:

SimpleXMLElement Object
(
    [lang] => Array
        (
            [0] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => C
                        )
                    [appeared] => 1972
                    [creator] => Dennis Ritchie
                )
            [1] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => PHP
                        )
                    [appeared] => 1995
                    [creator] => Rasmus Lerdorf
                )
            [2] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => Java
                        )
                    [appeared] => 1995
                    [creator] => James Gosling
                )
        )
)

Widać tutaj główny, publiczny element lang (SimpleXMLElement), który jest tablicą i zawiera trzy elementy.

By dostać się do konkretnych elementów trzeba wykorzystać operator ->

<?php
$languages->lang[0]->appeared;
$languages->lang[0]->creator;

Można także iterować po liście za pomocą metody foreach

<?php
foreach ($languages->ang as $lang) {
    echo "Language: ".$lang["name"];
    echo "Appeared: ".$lang->appeared;
    echo "Creator: ".$lang->creator;
}

Należy zwrócić uwagę, że do wartości atrybutu lang docieramy za pomocą parametru w tablicy, a do publicznych elementów appeared i creator za pomocą operatora ->.

Wszystko fajnie, pięknie, ale co jeżeli trzeba sobie poradzić z przestrzenią nazw, która zdefiniowana została dla poszczególnych elementów? Jest i na to sposób!

Zmodyfikuję teraz plik languages.xml do postaci:

<?xml version="1.0" encoding="utf-8"?>
<languages
 xmlns:dc="http://purl.org/dc/elements/1.1/">
 <lang name="C">
  <appeared>1972</appeared>
  <dc:creator>Dennis Ritchie</dc:creator>
 </lang>
 <lang name="PHP">
  <appeared>1995</appeared>
  <dc:creator>Rasmus Lerdorf</dc:creator>
 </lang>
 <lang name="Java">
  <appeared>1995</appeared>
  <dc:creator>James Gosling</dc:creator>
 </lang>
</languages>

Element creator posiada teraz namespace, który kieruje na http://purl.org/dc/elements/1.1/. Dostęp do tego elementu poprzez wykorzystanie operatora -> nie jest już możliwy.

By poradzić sobie z tym problemem trzeba zrobić coś takiego:

<?php
$dc = $languages->lang[1]- >children("http://purl.org/dc/elements/1.1/");
echo $dc->creator;

Metoda children() pobiera namespace i zwraca wartość elementu dla którego dc jest prefixem.

Istnieje inna możliwość odczytania wartości elementu creator:

<?php
$namespaces = $languages->getNamespaces(true);
$dc = $languages->lang[1]->children($namespaces["dc"]);

echo $dc->creator;

Pobieramy w tym celu wszystkie namespace użyte w dokumencie XML i za pomocą elementu w tablicy odwołujemy się do konkretnego namespace i pobieramy wartość dla którego jest on prefixem.

I podobnie jak wcześniej – przykład iterowania po wszystkich elementach:

<?php
$languages = simplexml_load_file("languages.xml");
$ns = $languages->getNamespaces(true);

foreach ($languages->lang as $lang) {
    $dc = $lang->children($ns["dc"]);
    echo "Language: ".$lang["name"];
    echo "Appeared: ".$lang->appeared;
    echo "Creator: ".$dc->creator;
}

Czas na praktyczny przykład. Parsowanie kanału YouTube.

W tym celu należy wykorzystać API dostępne pod adresem: http://gdata.youtube.com/feeds/api/users/{channel}/uploads, gdzie w miejscu {channel} podana zostanie nazwa konkretnego użytkownika.

W danych, które zostaną zwrócone w przypadku podania poprawnej nazwy użytkownika, dla każdego filmu zdefiniowane będą:

  • jego URL
  • miniatura
  • tytuł
<?php
$channel = "channelName";
$url = "http://gdata.youtube.com/feeds/api/users/".$channel."/uploads";
$xml = file_get_contents($url);

$feed = simplexml_load_string($xml);
$ns = $feed->getNameSpaces(true);

Spoglądając w strukturę otrzymanego XML możemy zauważyć, że ważne dla nas dane znajdują się w elemencie group, który poprzedzony jest namespace media.

<entry>
   …
   <media:group>
      …
      <media:player url="video url"/>
      <media:thumbnail url="image url" height="height" width="width"/>
      <media:title type="plain">Title…</media:title>
      …
   </media:group>
   …
</entry>

Wystarczy, że ponownie wykorzystana zostanie metoda foreach, dzięki której będziemy w stanie wyświetlić odpowiednie dla nas dane.

<?php
foreach ($feed->entry as $entry) {
	$group = $entry->children($ns["media"]);
	$group = $group->group;
	$thumbnailAttrs = $group->thumbnail[1]->attributes();
	$image = $thumbnailAttrs["url"];
	$player = $group->player->attributes();
	$link = $player["url"];
	$title = $group->title;
	
	echo "<p><a href=".$link."><img src=".$image." alt=".$title."></a></p>";
}

Podsumowując. Operowanie na dokumentach XML jest bardzo proste. Dzięki wykorzystaniu rozszerzenia SimpleXML, drzewo DOM nie jest już dla nas problemem.

Pełną dokumentację SimpleXML znajdziecie pod adresem: http://php.net/manual/en/book.simplexml.php