70-480 – Sprawdzanie poprawności danych wejściowych użytkownika przy użyciu języka JavaScript

70-480

Ocena wyrażenia regularnego w celu sprawdzenia poprawności formatu wejściowego; weryfikowanie poprawności typu uzyskiwanych danych poprzez stosowanie funkcji wbudowanych, zapobieganie iniekcji kodu.



W poprzednim wpisie pisałem o walidacji wprowadzanych danych za pomocą kontrolek HTML5. Dzisiaj pokażę w jaki sposób pomóc nam w tym może język JavaScript. Do dzieła więc!

Kontrolki formularzy HTML5 pozwalają w zasadzie na podstawową walidację wprowadzanych danych. Bardziej złożone przypadki powinny być rozpatrywane bezpośrednio w kodzie JavaScript. Pozwala nam to zablokować wysłanie danych formularza do serwera oraz wyświetlić stosowne komunikaty użytkownikowi.

Wyrażenia regularne

Do sprawdzenia poprawności wprowadzonych wartości w pola formularza posłużą nam wyrażenia regularne. Wyobraźmy sobie, że kontrolka o typie email nie istnieje i walidację poprawności wprowadzonego adresu email musimy przeprowadzić bezpośrednio w JavaScript:

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

W powyższym kodzie rejestrujemy obsługę kliknięcia w przycisk Wyślij i wykonujemy walidację wprowadzonej wartości. W przypadku poprawności adresu email zwracamy wartość true i formularz zostaje wysłany na serwer. W przeciwnym wypadku wykonujemy funkcję e.preventDefault(), która zatrzymuje wykonanie akcji wysłania formularza, dodajemy komunikat błędu do zdefiniowanego wcześniej kontenera, zaznaczamy pole, w którym nastąpił błąd walidacji oraz wracamy wartość false w funkcji obsługującej kliknięcie w przycisk typu submit.

Skomplikowanie wyrażenia regularnego jest w tym przypadku spore, a w zasadzie zależy to od konkretnej implementacji. W sieci można znaleźć ich wiele, ale co najważniejsze – w ten sposób formularz zostanie poprawnie zwalidowany i nie będzie wysłany w razie błędnych danych. Użytkownik otrzyma również stosowny komunikat odnośnie popełnionych błędów.

Świetny artykuł na temat wyrażeń regularnych przygotował zespół Mozilla na swojej stronie. Nie ma sensu bym tutaj powtarzał zawarte tam mądrości, bo na pewno nie zrobię tego lepiej. Zachęcam jednak do zapoznania się z tym materiałem. Jest on niewątpliwie obowiązkowy dla każdego programisty.

Zapobieganie wstrzykiwaniu kodu

Tutaj również posłużę się źródłem zewnętrznym, które świetnie opisuje tematykę oraz sposoby zapobiegania wstrzykiwaniu kodu oraz atakom ze strony użytkowników.

Chodzi tutaj przede wszystkim o to by starać się zapobiegać zabiegom pozwalającym uruchamiać kod na naszej stronie, a tym bardziej przesyłaniu takiego kodu do części serwerowej. Nie muszę chyba dodawać, że taki niechciany kod może wyrządzić sporo złego.

70-480 – Sprawdzanie poprawności danych wejściowych użytkownika przy użyciu elementów HTML5

70-480

Wybór odpowiednich środków kontroli na podstawie wymagań; wdrażanie elementów wejściowych typu HTML i atrybutów zawartości (np. wymaganych) do zbierania danych wejściowych użytkownika.



Dzisiejszym wpisem rozpoczynam kolejny rozdział materiału, który pozwala przygotować się do egzaminu 70-480. Będą to cztery artykuły na temat walidacji danych za pomocą kontrolek HTML5 i kodu JavaScript oraz przesyłaniu danych w różnych formatach do części backendowej, a także pobieranie danych z serwera, ich serializacja, deserializacja oraz enkodowanie.

Zaczniemy od sprawdzania poprawności danych wejściowych użytkownika przy użyciu elementów HTML5. Specyfikacja kolejnej wersji HTML przewidziała sporo komponentów, które pozwalają na walidację danych w prosty i przyjemny sposób, przy użyciu kontrolek formularzy z odpowiednimi atrybutami i parametrami.

Posłużę się tutaj rozbudowanym przykładem, który pozwoli mi od razu zaprezentować możliwości tych kontrolek. Każdy z typów pola input pozwala na zdefiniowanie formatu danych, który wybrać / podać może użytkownik.

UWAGA: Nie wszystkie przeglądarki poprawnie obsługują nowe kontrolki. W przypadku braku obsługi, kontrolka będzie wyglądać jak zwykłe pole typu text. Obsługę danego typu w przeglądarce możemy sprawdzić na stronie: caniuse.com

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

Poza samymi formatami pola input możemy korzystać także z dodatkowych atrybutów, które zapewnią nam walidację wprowadzanych danych bez dodatkowego kodu JavaScript:

  • autofocus [boolean] – automatyczny focus na danej kontrolce – tylko jedna powinna mieć taką wartość
  • required [boolean] – atrybut określający, że dane pole jest wymagane i nie może być puste
  • pattern [string] – atrybut, który pozwala podać jako swoją wartość wyrażenie regularne, które będzie musiała spełniać wartość wprowadzona w daną kontrolkę
  • placeholder [string] – wartość podana w tym atrybucie będzie się wyświetlać w danej kontrolce, gdy będzie ona pusta – znika po wpisaniu wartości
  • autocomplete [string|”on”\”off”] – przyjmuje wartość “on” | “off” i określa, czy przeglądarka ma pamiętać wartość wprowadzoną dla danej kontrolki
  • min [integer] – minimalna wartość dla pól typu number oraz range
  • max [integer] – maksymalna wartość dla pól typu number oraz range
  • step [integer] – wielkość skoku dla pola typu range

Spójrzmy na przykład pokazujący użycie owych atrybutów:

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

Te oraz wiele innych atrybutów opisanych w oficjalnej specyfikacji pozwalają na łatwą, ale co ważne podstawową walidację wprowadzanych danych przez użytkowników.

70-480 – Tworzenie procesu roboczego sieci Web

70-480

Uruchamianie i zatrzymywanie procesu roboczego sieci Web; przekazywanie danych do procesu roboczego sieci Web; konfiguracja limitów czasu i interwałów dla procesu roboczego sieci Web; rejestrowanie odbiornika zdarzeń dla procesu roboczego sieci Web; ograniczenia procesu roboczego sieci Web.



To już ostatni wpis z rozdziału Wdrażanie przepływu programu, a tym samym kolejne 25% materiału za nami. Można stwierdzić tym samym, że dotarliśmy do półmetka. Kolejne wpisy nie powstają tak szybko jakbym sam tego oczekiwał, ale staram się, uwierzcie.

Dzisiejszy temat będzie raczej przyjemny. Zajmiemy się kolejnym API, które pojawiło się wraz ze specyfikacją HTML5, a mianowicie Web Workers. Wykorzystanie Workerów polega na tym, że skrypt JavaScript może być uruchamiany w osobnym procesie (wątku), a tym samym nie blokuje nam on interfejsu użytkownika i zwraca odpowiedź, gdy ta jest gotowa. Jest to idealne rozwiązanie do uruchamiania zasobożernych części kodu JavaScript. Dzięki API Web Workerów możemy utworzyć taki proces, komunikować się z nim, otrzymywać informacje oraz zdalnie go zamknąć. Oczywiście nic nie jest kolorowe i Web Workery posiadają również swoje ograniczenia. Poznamy je na sam koniec dzisiejszego wpisu.

Tworzenie Workera

Standardowo kod JavaScript wykonuje się w jednym wątku i jest interpretowany przez silnik przeglądarki krok po kroku. Wszystkie procesy i zadania wykonywane są w ten sposób synchronicznie. By zadziałać wielowątkowo i uruchomić skrypt w osobnym wątku musimy zdefiniować obiekt workera:

var worker = new Worker('worker.js');

Widzimy tutaj, że parametrem konstruktora jest String, który jest ścieżką do zewnętrznego pliku z kodem do uruchomienia wewnątrz nowo tworzonego wątku.

Konstruktor workera znajduje się w obiekcie window. Dostępność API sprawdzić możemy prostym warunkiem:

if (!!window.Worker) {
  ...
}

Wsparcie przeglądarek dla Web Workers.

Komunikacja z workerem i obsługa błędów

Komunikacja z wątkiem pobocznym następuje na zasadzie przesyłanych zdarzeń. By rozpocząć proces workera należy wywołać funkcję postMessage():

worker.postMessage();

Po stronie kodu worker.js musi istnieć obsługa eventu onmessage. W ten sposób worker rozpocznie swoje działanie i otrzyma parametr wysłany przez funkcję postMessage z wątku głównego:

// app.js
if (!!window.Worker) {
  var worker = new Worker('worker.js');
	
  worker.onmessage = function () {
    console.log(e.data);
    worker.postMessage('Ping!');
    worker.terminate();
  };
	
  worker.postMessage('Ping!');
}

// worker.js
this.addEventListener('message', function (e) {
  console.log(e.data);
  this.postMessage('Pong!');
}, false);

W kodzie worker.js może dojść do powstania błędu i  / lub rzucenia wyjątku. Błąd obsłużyć możemy poprzez rejestrację przechwytywania zdarzenia onerror:

worker.onerror = function () {
  console.log(e.message);
};

Spójrzmy na pełny przykład:

// app.js
if (!!window.Worker) {
  var worker = new Worker('worker.js');
	
  worker.onmessage = function () {
    console.log(e.data);
    worker.postMessage('Ping!');
  };
	
  worker.onerror = function () {
    console.log(e.message);
    worker.terminate();
};
	
  worker.postMessage('Ping!');
}

// worker.js
this.addEventListener('message', function (e) {
  console.log(e.data);
  this.postMessage('Pong!');
	
  throw new Error('Error occurred!');
}, false);

Zamykanie Workera

Działanie workera może zostać zatrzymane zarówno z wątku głównego poprzez wykonanie funkcji terminate() na workerze lub bezpośrednio w kodzie workera, który skończył już swoje zadanie lub odebrał odpowiednią wiadomość z procesu głównego.

Zakończenie wątku workera poprzez wątek główny:

worker.terminate();

Zakończenie wątku workera wewnątrz jego kodu, gdy wysłano parametr stop:

this.addEventListener('message', function (e) {
    if ('stop' === e.data) {
        this.close();
    }
}, false);

Służy do tego funkcja close().

Ograniczenia

Jako, że poboczny wątek musi być całkowicie wyizolowany i spełniać zasadę tzw. thread safe, to wewnątrz workera nie mamy dostępu do:

  • obiektów DOM – nie są thread safe
  • obiektu window
  • obiektu document
  • obiektu parent

Poza tym – funkcja postMessage ogranicza się do przyjmowania wartości String | JSON. Przekazanie obiektu do tej funkcji skutkuje konwersją do formatu JSON, co uniemożliwia nam przesyłanie funkcji.

70-480 – Wdrażanie wywołania zwrotnego

70-480

Odbieranie wiadomości z HTML5 WebSocket API; stosowanie kwerendy jQuery w celu realizacji wywołania AJAX; podłączanie zdarzenia; wdrażanie wywołania zwrotnego przy użyciu funkcji anonimowych; obsługa wskaźnika „this”.



Opis zagadnienia zawiera wiele elementów, które na pierwszy rzut oka mogą wydawać się zupełnie różne, ale to co ich łączy, to mechanizm callback. Jest to tak zwana funkcja zwrotna, która rejestrowana jest przez autora kodu do wykonania przez bibliotekę, której używa. Callback wykonywany jest w odpowiednim czasie przez bibliotekę, która nie wie nic o tym co się wydarzy. Wszystko zależy od ciała zarejestrowanej funkcji.

HTML5 WebSocket API

Egzamin 70-480 wymaga od nas znajomości API WebSocket, które pojawiło się wraz z HTML5. Specyfikacja definiuje założenia komunikacji full-duplex, czyli w dwie strony serwer <-> przeglądarka <-> serwer.

Zakładane są cztery typy obsługiwanych callback:

  • onopen – połączenie jest gotowe i można wysyłać wiadomości
  • onmessage – w momencie otrzymania wiadomości z serwera
  • onclose – w momencie zamknięcia połączenia
  • onerror – w momencie wystąpienia błędu

Pierwszym krokiem jest oczywiście utworzenie połączenia:

var connection = new WebSocket('ws://localhost/');

Następnie możemy zdefiniować odpowiednie callback:

connection.onopen = function () {
  connection.send('Ping');
};

connection.onmessage = function (e) {
  console.log('Server: ' + e.data);
};

connection.onclose = function(e) {
  console.log('Closed!');
};

connection.onerror = function (error) {
  console.log('WebSocket Error ' + error);
};

Każdy z callbacków wykona się w ściśle zdefiniowanym momencie. Decyduje o tym API WebSocket oraz komunikacja z serwerem.

Staram się skupiać na podstawach, a zarazem materiale, który w pełni pokrywa założenia egzaminu 70-480. Jeżeli ktoś chce dowiedzieć się nieco więcej na temat HTML5 WebSocket API, to odsyłam do artykułu na html5rocks.com.

jQuery – get, post, ajax

Biblioteki jQuery przedstawiać nikomu nie trzeba. Zyskała ona ogromną popularność społeczności, a to za sprawą wielu ułatwień i funkcji, które zwyczajnie ułatwiają życie. jQuery również świetnie radzi sobie z asynchronicznością oraz obsługą requestów typu AJAX.

Polegają one na wykonywaniu operacji komunikacji z serwerem w tle. Tym samym możemy pobierać dane oraz zmieniać interfejs strony bez przeładowania okna przeglądarki.

Przedstawię niżej trzy najbardziej popularne metody do zapytań typu AJAX, które znajdziemy w bibliotece jQuery.

$.get

Pozwala wykonywać asynchroniczne zapytania typu GET. Sygnatura metody wygląda następująco:

$.get(url [,data] [,success] [,dataType])

, gdzie poszczególne parametry to:

  • url – [String] – url pod jaki ma zostać wysłane zapytanie typu GET
  • data – [PlainObject | String] – obiekt lub string, które wysłane zostają razem z zapytaniem do serwera
  • success – [Function(data, textStatus, jqXHR)] – funkcja i zarazem nasz callback, który wykonany zostanie w momencie poprawnego obsłużenia odpowiedzi. Parametry, które zostaną przekazane do wywołania funkcji to:
    • data – [PlainObject] – zwrócone dane
    • textStatus – [String] – status odpowiedzi
    • jqXHR – [jqXHR] – odpowiednik XMLHTTPRequest
  • dataType – [String] – typ zwracanych danych – xml, json, script, html. Domyślnie ustalany na podstawie zwróconych danych.

Funkcja $.get jest odpowiednikiem (skrótem) dla funkcji $.ajax:

$.ajax({
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

$.post

W zasadzie od funkcji $.get różni się tym, że tym razem wysyła na serwer asynchroniczne żądanie typu POST. Zestaw parametrów jest tutaj identyczny, a odpowiednikiem funkcji $.ajax jest:

$.ajax({
  type: "POST",
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

$.ajax

Funkcja, która zawsze wywoływana jest pod spodem. Jest to najbardziej ogólna funkcja pozwalająca na definiowanie asynchronicznych zapytań. Jej pełna specyfikacja znajduje się na stronie jQuery. Posiada pokaźną liczbę parametrów i opisywanie ich wszystkich tutaj byłoby sporym nadmiarem materiału do przyswojenia.

callback i słowo kluczowe this

W zasadzie o czymś podobnym pisałem już we wpisie o wywoływaniu i obsłudze zdarzeń. Chodzi mianowicie o dostęp do kontekstu this wywołanej funkcji. Spójrzmy na przykład funkcji, która wykonuje callback na sam koniec swojego działania:

function callbackFn() {
  console.log(this);
}

function invoke(x, y, callback) {
  var xy = x + y;
  callback();  
}

invoke(1, 2, callbackFn);

Co będzie wynikiem console.log(this)? Na konsoli otrzymamy obiekt window. Jak przekazać kontekst funkcji wywołującej callback? No to kolejny przykład:

function callbackFn() {
  console.log(this);
}

function invoke(x, y, callback) {
  var xy = x + y;
  callback.call(xy);  
}

invoke(1, 2, callbackFn);

Zmiana nastąpiła w linii 6 poprzez użycie call. Tym razem wyliczona wartość xy została przekazana do funkcji callbackFn i stanowi jej kontekst (this).

70-480 – Wdrażanie narzędzi obsługi wyjątków

70-480

Ustawianie i reagowanie na kody błędów; rzucanie wyjątku; żądanie sprawdzeń „null check”; wdrażanie bloków try-catch-finally.



Przedświąteczny wpis będzie krótki bo i temat nietrudny, a zacznę od tego, że w JavaScript posiadamy dwie możliwości określenia, że zmienna nie jest zdefiniowana:

  • null
  • undefined

Różnica polega na tym, że wartośc null jest obiektem i oznacza po prostu, że obiekt istnieje, ale nie posiada on jeszcze przypisanej wartości. undefined natomiast oznacza, że obiekt jeszcze nigdzie nie istnieje, nie został zadeklarowany i nie została przypisana mu żadna wartość.

Dobre przykłady znajdziecie na stronie: http://saladwithsteve.com/2008/02/javascript-undefined-vs-null.html

try .. catch .. finally

Jest to konstrukcja znana z wielu języków programowania, więc spójrzmy od razu na przykład:

function doSmth() {
    throw new Error('Some error!');
};

function doSmthElse() {
    console.log('Something else :)');
};

try {
    doSmth();
} catch(e) {
    console.log(e.message); // Some error!
} finally {
    doSmthElse(); // Something else :)
}

Zdefiniowane zostały dwie funkcje doSmth() oraz doSmthElse(). Pierwsze rzuca wyjątkiem w momencie wywołania, dlatego umieszczona została w sekcji try .. catch, której zadaniem jest przechwycenie wyjątku. I tak jak try .. catch jest wymagane i jedno słowo kluczowe nie może być używane bez drugiego, to opcja finally jest opcjonalna. Jej użycie powoduje wywołanie kodu niezależnie od tego, czy wystąpił wyjątek w sekcji try .. catch. Wykona się on na samym końcu – albo po poprawnym wywołaniu funkcji znajdującej się w bloku try albo po obsłużeniu wyjątku w bloku catch.

W przypadku powyższego przykładu na konsoli przeglądarki zobaczymy najpierw wiadomość pobraną z rzucanego wyjątku ‘Some error!‘, a następnie wykonanie bloku finally i funkcji doSmthElse ‘Something else :)‘.

Należy nadmienić, że obsługa wyjątków w JavaScript nie powala na kolana, a na dodatek jej wykonywanie odbywa się bardzo powoli. Jeżeli zależy nam na czasie, to z pewnością zastanowimy się dwukrotnie nad użyciem konstrukcji try .. catch.

Rzucanie wyjątków

Ich obsługę już znamy, sposób tworzenia i rzucania w zasadzie również. W funkcji doSmth() rzuciłem wyjątkiem poprzez użycie słowa kluczowego throw oraz utworzenie nowego obiektu Error. Nie ma tutaj żadnego ‘rocket science’. Całość znów wygląda bardzo podobnie w innych językach programowania.

Przeglądarka Internet Explorer pozwala nam ponad to podać w konstruktorze obiektu Error kod błędu. Konstrukcja wygląda następująco:

function login() {
	throw new Error(401, 'Not authorized!');
};

try {
    login();
} catch(e) {
    console.log('Error NO: ' + e.number + ' , message: ' + e.message);
}

Pierwszym argumentem obiektu Error w tym przypadku jest właśnie kod błędu. Na konsoli otrzymamy tekst: Error NO: 401 , message: Not authorized!

Pamiętajcie, że powyższy przykład zadziała poprawnie tylko na przeglądarce Internet Explorer. Jako, że twórcą egzaminu 70-480 jest Microsoft, to należało o tym wspomnieć.

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.

70-480 – Wdrażanie przepływu programu

70-480

Tworzenie iteracji elementów kolekcji i tablicy; zarządzanie decyzjami programu przy użyciu instrukcji switch, instrukcji if/then i operatorów; ocena wyrażeń.



Wraz z kolejną sekcją jaką jest Wdrażanie przepływu programu rozpoczynam serię poświęconą sterowaniem przepływu w naszym kodzie. Stanowi ona około 25% pytań, które pojawić się mogą na egzaminie.

Przejdźmy od razu do konkretów i pierwszego podrozdziału dzisiejszego wpisu.

Iterowanie po tablicach (for)

Standardowa pęta for nie różni się praktycznie niczym od innych języków programowania:

for (var i = 1; i <= 10; i++) {
  console.log('i: ' + i);
}

Równie proste jest iterowanie po elementach jakieś kolekcji, a konkretniej tablicy:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

for (var i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}

Kolejno odwołujemy się w tym wypadku do elementów w tablicy numbers, po czym wyświetlamy na konsoli wartość znajdującą się pod danym indeksem.

Istnieje także inna forma zapisu pętli for, która przy każdym wywołaniu inkrementuje indeks pozwalający odwołać się do wartości kolejnego elementu z tablicy:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

for (var i in numbers) {
  console.log(numbers[i]);
}

Zapis ten możemy utożsamiać sobie z pętlami forEach lub each, które dostępne są w innych językach programowania. Metoda forEach występuje także natywnie w obiekcie Array:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

numbers.forEach(function (item, i) {
  console.log('i: ' + i + ' item: ' + item);
});

Jako argument funkcji forEach podajemy funkcję, która wykorzystana zostanie jako callback i w parametrach przyjmować będzie odwołanie do wartości elementu (item) oraz indeks (i) z kolekcji. Sam często korzystam z biblioteki Lo-Dash, która również posiada własną implementację _.forEach dla dowolnych kolekcji.

Konstrukcje warunkowe if / else oraz switch

Instrukcje warunkowe występują w praktycznie wszystkich językach programowania. Najważniejsze to zapamiętanie, że:

  • if – wykonanie kodu w bloku nastąpi, gdy wyrażenie zwróci true
  • else – wykonanie kodu następuje w momencie, gdy wyrażenie zdefiniowane przy if zwróci false
  • if else – definicja kolejnego wyrażenia, gdy poprzednie zwróci false
if (time < 10) {
  greeting = 'Good morning';
} else if (time < 20) {
  greeting = 'Good day';
} else {
  greeting = 'Good evening';
}

Warto również wiedzieć, że porównanie z użyciem podwójnego znaku == wykonuje wykonanie instrukcji warunkowej z porównaniem co do wartości, ale nie typu. W przypadku użycia potrójnego znaku === porównanie wykonywane jest zarówno co do typu wartości po obu stronach wyrażenia. Tym samym warto używać wersji z trzema znakami =.

var number = '42';

console.log(42 == number); // true!
console.log(42 === number); // false!

Inną formą instrukcji warunkowych jest switch. Wykorzystujemy ją momencie, gdy mamy wiele bloków kodu do wykonania, a wszystkie zależne są konkretnej wartości wyrażenia. Jej użycie w JavaScript jest wręcz identyczne w porównaniu znów do innych języków programowania. Warto zapamiętać:

  • instrukcja switch wykonywana jest raz
  • wartość wyrażenia porównywana jest z sekcjami case
  • w momencie, gdy dana instrukcja case pokrywa się z wartością wyrażenia wykonywany jest blok kodu
  • polecenie break przerywa wykonywanie instrukcji switch
  • sekcja default wykonywana jest w momencie niedopasowania wartości wyrażenia do jakiejkolwiek sekcji case
  • możemy grupować sekcje case z różnymi wartościami, ale tym samym blokiem kodu do wykonania

Przykład z w3schools pokazujący wszystkie powyższe punkty:

switch (new Date().getDay()) {
  case 1:
  case 2:
  case 3:
  default: 
    text = "Looking forward to the Weekend";
    break; 
  case 4:
  case 5:
    text = "Soon it is Weekend";
    break; 
  case 0:
  case 6:
    text = "It is Weekend";
}

Funkcja eval

Ostatnim tematem wpisu jest funkcja eval. W zasadzie nie powinienem o niej pisać z racji tego, że jej wykorzystywanie jest niebezpieczne, utrudnia debuggowanie kodu oraz powoduje jego wolniejsze wykonywanie ze względu na brak optymalizacji, ale funkcja ta występuje w obiekcie window i dostępna jest z każdego miejsca w naszym kodzie. Stanowi także element, który pojawić się może w formie pytania na egzaminie 70-480.

Funkcja eval powoduje wykonanie kodu JavaScript przekazanego w formie argumentu będącego wartością typu String.

var x = 10,
	y = 20,
	a = eval('x * y');
	
console.log(a); // 200

70-480 – Tworzenie i wdrażanie obiektów i metod

70-480

Wdrażanie obiektów natywnych; tworzenie obiektów i właściwości niestandardowych dla obiektów natywnych przy użyciu prototypów i funkcji; dziedziczenie z obiektu; wdrażanie metod macierzystych i tworzenie metod niestandardowych.



To już ostatnia część sekcji Wdrażanie i edycja struktur i obiektów dokumentu, która stanowi około 24% całego materiału do opanowania. Przygotowania do egzaminu 70-480 można uznać za zaawansowane.

Zajmę się dzisiaj obiektami natywnymi w JavaScript, tworzeniem własnych obiektów oraz ich rozszerzaniem w ramach dziedziczenia. Pokażę także wzorzec tworzenia modułu, z którego często korzystam pisząc aplikacje w AngularJS.

Natywnie w JavaScript

JavaScript dostarcza nam szereg natywnych obiektów i metod, które możemy wykorzystywać w naszym kodzie beż żadnych dodatkowych bibliotek. Często możemy sobie nawet nie zdawać sprawy, że przypisując do zmiennej liczbę, tworzony jest nowy obiekt new Number(liczba), czy przypisując wartość tekstową, tworzony jest obiekt new String(tekst). Wszystkie obiekty tworzone są ponad to na podstawie głównego obiektu Object. Jest to sposób na dziedziczenie wspólnych cech obiektów znane z wielu języków programowania.

Na stronie w3schools.com znajdziemy opisy wszystkich podstawowych obiektów oraz ich metody:

Poza tym, w JavaScript dostępne są także wartości specjalne (null, undefined oraz operator typeof).

Różnica pomiędzy null, a undefined przedstawia się następująco:

typeof undefined		// undefined
typeof null				// object
null === undefined		// false
null == undefined		// true
var person = null;

– wartość null, typ object

var person = undefined;

– wartość undefined, typ undefined

Rozszerzanie natywnych obiektów w JavaScript

Większość typów w JavaScript posiada natywne, wbudowane metody oraz dodatkowe pola, które budują domyślne zachowania. Spójrzmy choćby na typ Function:

Object.getOwnPropertyNames(Function.prototype)
// ["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply", "toMethod"]

Każda nowo tworzona funkcja posiada również te metody. Nie możemy ich edytować oraz zmieniać, ale możemy dodawać nowe metody oraz dodawać lub edytować istniejące pola do już istniejących typów. Służy do tego model prototype, w którym zawarte są wszystkie natywne oraz domyślne dla każdego tworzonego obiektu (new), metody oraz właściwości.

Stworzę w ten sposób nową (prostą i lekko naiwną) metodę dla typu Array, która usunie dany element z tablicy:

Array.prototype.remove = function(member) {
  var index = this.indexOf(member);
  if (index > -1) {
    this.splice(index, 1);
  }
  return this;
}

Metoda stara się znaleźć element w tablicy za pomocą funkcji indexOf, a następnie usuwa go za pomocą metody splice. W przeciwnym wypadku nie robi nic. Na koniec zwraca referencje do samej siebie, co pozwala wywoływać metody jedna po drugiej.

Jej użycie jest od tej chwili możliwe na zbiorze danych znajdujących się w tablicy:

['one', 'two', 'three'].remove('two'); // ["one", "three"]
['one', 'two', 'three'].remove('four'); // ["one", "two", "three"]

Dziedziczenie w JavaScript

Wiedząc już o własności prototype, możemy skupić się na jednej z najważniejszych cech programowania obiektowego, a mianowicie dziedziczeniu. Jest ono dostępne zarówno w JavaScript, jako w pełni obiektowym języku.

Poniżej przykład dziedziczenia opartego o prototypy. Tworząc obiekt, który dziedziczyć ma metody oraz właściwości swojego rodzica musimy przypisać do pola prototype klasy dziedziczącej referencję do obiektu rodzica.

function Parent() {}

Parent.prototype.setName = function (name) {
	this.name = 'Parent: ' + name;
};

Parent.prototype.getName = function () {
	console.log(this.name);
};
function Child() {}

Child.prototype = new Parent();

Child.prototype.setName = function (name) {
	this.name = 'Child: ' + name;
};
var parent = new Parent();
parent.setName('Mark');
parent.getName();

var child = new Child();
child.setName('Rob');
child.getName();

Na konsoli przeglądarki otrzymamy w ten sposób:

// Parent: Mark
// Child: Rob

Metoda setName w obiekcie Child została przysłonięta przez własną implementację. Metoda getName została natomiast w pełni odziedziczona z obiektu Parent. Jest to na tyle proste, że nie ma chyba sensu opisywać tego dalej.

Wzorzec modułu

Jak już wspomniałem we wstępie wpisu, pisząc aplikacje w AngularJS często korzystam ze wzorca modułu. Można go spotkać choćby w serwisach, o których pisałem we wpisie: Factory vs Service vs Provider.

Idea znów jest bardzo prosta. Chcemy stworzyć moduł, który posiadać będzie swoją implementację wewnętrzną (prywatne metody oraz zmienne) oraz metody publiczne, wystawione na świat. Spójrzmy od razu na przykład:

var UserModule = function () {
	
  var _username = '',
    _setUsername = function (username) {
      _username = username;
    },
    _sayHello = function () {
      return 'Hello, ' + _username;
    };
    
  return {
    setUsername: _setUsername,
    sayHello: _sayHello
  };
	
};

var UserModule = new UserModule();
UserModule.setUsername('mrzepinski');
console.log(UserModule.sayHello());

Dzięki takiej definicji możemy enkapsulować swój kod i tworzyć prywatne metody oraz zmienne. Poprzez użycie obiektu zwracanego za pomocą słowa return, wystawiamy tylko te metody, które zostały przez nas odpowiednio przygotowane. W tym wypadku zmienna _username oraz metody _setUsername i _sayHello dostępne są tylko wewnątrz implementacji modułu.