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 – 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.

70-480 – Określanie zakresu obiektów i zmiennych

70-480

Definiowanie okresu istnienia zmiennych; utrzymywanie obiektów poza globalną przestrzenią nazw; stosowanie słowa kluczowego „this” w celu odwołania się do obiektu, który wyzwolił zdarzenie; lokalne i globalne ustalanie zakresu zmiennych.



Kolejnym przystankiem na drodze do certyfikatu 70-480 jest znajomość zakresu zmiennych (scope) w JavaScript oraz słowo kluczowe “this”.

Scope i zakres zmiennych

W JavaScript jest nieco inaczej.. a jak! Mam na myśli sytuację, gdzie w wielu innych językach programowania zasięgiem zmiennej jest po prostu zasięg blokowy (metoda, klasa, pętla). W JavaScript zasięg zmiennej definiowany jest poprzez zakres funkcji.

Dlatego żaden inny zakres blokowy {} nie powoduje utworzenia lokalnej zmiennej. Spójrzmy na na naiwny przykład pętli while:

var invoke = true;

while (invoke) {
    var n = 10;
    invoke = false;    
}

console.log(n);

Wykona się ona dokładnie raz i utworzy w swoim bloku zmienną n i przypisze do niej wartość 10. Jaki będzie wynik na konsoli? Otóż 10! Zmienna zadeklarowana została globalnie, a nie lokalnie.

Dopiero utworzenie zmiennej wewnątrz ciała funkcji stworzy na scope lokalny i tym samym zmienne zadeklarowana zostanie lokalnie:

function invoke() {
    var n = 10;
    console.log('1: ' + n);
}

invoke();
console.log('2: ' + n);

Na konsoli ukarze się nam:

> 1: 10
> Uncaught ReferenceError: n is not defined

Zmienna n nie jest widoczna poza zakresem funkcji!

Ciekawy jest przypadek, którego używam na rozmowach kwalifikacyjnych i męczę nim nieco kandydatów:

var n = 1;
 
(function printSomething() {
    console.log('1: ' + n);
    var n = 2;
    console.log('2: ' + n);
})();

console.log('3: ' + n);

Potraficie powiedzieć, co oraz w jakiej kolejności pojawi się na konsoli przeglądarki bez uruchamiania tego kodu? Zostawiam to Wam, a dodam tylko, że spowodowane jest to poprzez: JavaScript Declarations are Hoisted.

Istnieje również zmodyfikowana wersja tego przypadku, ale tutaj analizę również zostawiam Wam:

var n = 1;
 
(function printSomething() {
    n = 2;
    console.log('1: ' + n);
    var n = 3;
    console.log('2: ' + n);
})();

console.log('3: ' + n);

“this” is this!

Skoro już wiemy czym jest scope i jak jest definiowany, to możemy przejść do definicji słowa kluczowego “this”. Definiujemy this globalne oraz zależne od scope, ale zawsze ważny jest tutaj kontekst i miejsce wywołania.

Kontekst globalny

console.log(this.document === document); // true

console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

Kontekst globalny odwołuje się bezpośrednio do obiektu window. Nasze this w tym wypadku to tak naprawdę referencja do tego obiektu. Tym samym przypisując coś do this w scope globalnym, przypisujemy tą wartość do obiektu window.

Kontekst funkcji

Spójrzmy na dwa przykłady i zastanówmy się nad wynikiem uruchomienia poszczególnych kawałków kodu:

function f1() {
  return this;
}

f1() === window; // true
function f2() {
  'use strict';
  return this;
}

f2() === undefined; // true

this w kontekście funkcji zależy od sposobu i miejsca w jakim dana funkcja zostanie wykonana. Używanie tak zwanego strict mode pozwala nam uniknąć problemu z niewłaściwym użyciem this w takim wypadku.

By this miało w powyższym przykładzie jakieś znacznie, powinniśmy stworzyć nowy obiekt za pomocą konstruktora i słowa new.

function f1() {
  this.echo = 'Hello World!';
  return this;
}

console.log(new f1()); // { echo: "Hello World!" }

Kontekst obiektu

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // 37

W tym wypadku this odnosi się do tego samego scope ograniczonego przez nawiasy klamrowe. Idealnie zilustruje to kolejny przykład:

var o = {
  prop: 37,
  p: {
    prop: 38,
    f: function() {
      return this.prop;
    }
  }
};

console.log(o.p.f()); // logs 38

W wyniku otrzymujemy 38, które zdefiniowane w tym samym scope, co funkcja f.

Następny przykład ilustruje jak ważny jest kontekst wywołania funkcji, o czym była już mowa wyżej.

var o = {
    prop: 37
};

function independent() {
  return this.prop;
}

console.log(independent()); // undefined

window.prop = 36;

console.log(independent()); // 36

o.f = independent;

console.log(o.f()); // 37

Funkcje call i apply

By ściśle zdefiniować kontekst wywołania funkcji, a tym samym this w ciele funkcji możemy wykorzystać funkcje call lub apply. Spójrzmy na przykład:

function add(c, d) {
  return this.a + this.b + c + d;
}

var o = {
    a: 1,
    b: 3
};

console.log(add.call(o, 5, 7)); // 1 + 3 + 5 + 7 = 16

console.log(add.apply(o, [10, 20])); // 1 + 3 + 10 + 20 = 34

Pierwszym argumentem obu funkcji jest obiekt, który w ciele funkcji użyty będzie jako this. Różnica w obu funkcjach polega na przekazywaniu pozostałych parametrów. W przypadku funkcji call parametry podawane są po prostu po przecinku, a funkcja apply przyjmuje jako drugi argument tablicę, w której znajdują się argumenty wywołania funkcji.

Funkcja bind

Kolejnym sposobem na przypisanie kontekstu this do ciała funkcji jest metoda bind (wprowadzona w ECMAScript 5). Jej wywołanie powoduje, że kontekst this przypisywany jest do danej funkcji na stałe. I kolejny przykład obrazujący tą zależność.

function f() {
  return this.a;
}

var g = f.bind({
    a: 'Hello World!'
});

console.log(g()); // Hello World!

var o = {
    a: 37,
    f: f,
    g: g
};

console.log(o.f(), o.g()); // 37, "Hello World!"

Elementy DOM

Ostatni już przykład wykorzystania słowa kluczowego this. Odwołam się w tym miejscu do ciała funkcji wywołanej w ramach zdarzenia DOM.

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

Funkcja echo posiada thiw postaci referencji do klikniętego boxa. Możemy w ten prosty sposób manipulować obiektem DOM.

Część przykładów została zaczerpnięta z MDN.

70-480 – Programowe stosowanie stylów do elementów HTML

70-480

Zmiana lokalizacji elementu; stosowanie przekształcenia; pokazywanie i ukrywanie elementów.



Po raz kolejny, pod krótkim opisem ze strony Microsoft kryje się kilka tematów do omówienia. Tym razem będą to:

  • pozycjonowanie elementów w CSS3
  • pokazywanie i ukrywanie elementów za pomocą CSS i JavaScript
  • transformacje 2D z wykorzystaniem CSS3

Pozycjonowanie elementów w CSS3 (CSS Positioning & HTML DOM Style Object)

Pozycjonowanie elementów w strukturze HTML może wydawać się skomplikowane, ale tylko na początku. Z pomocą przychodzą nam style CSS(3), które udostępniają kilka opcji sterowania warstwami, ułożeniem elementów oraz ich nachodzeniem na siebie.

Najważniejsze, to zrozumienie czterech wartości atrybutu position:

  • static – domyślna wartość każdego elementu. Jest to normalny przepływ w strukturze dokumentu HTML (od góry do dołu). Atrybuty: left, top, right, bottom nie wpływają w tym wypadku na elementy, nawet jeśli zostały zdefiniowane.
  • fixed – jak mówi sama nazwa – jest to pozycjonowanie stałe, a więc związane z oknem przeglądarki. Atrybuty left, top, right, bottom pozwalają nam ustalić pozycję elementu względem okna przeglądarki. Często możemy się spotkać z efektem przyklejonego górnego menu podczas scrollowania. To własnie efekt pozycjonowania stałego. Pozostałe elementy zachowują się tak, jakby elementów typu fixed w ogóle nie było w strukturze HTML.
  • relative – w zasadzie różni się od pozycjonowania statycznego tym, że tym razem atrybuty left, top, right, bottom mają realne odzwierciedlenie i ustawione dla danego elementu pozwalają przesuwać go względem jego oryginalnej pozycji. Pozostałe elementy traktują taki element jakby był on pozycjonowany statycznie i nieprzesunięty względem swojego oryginalnego położenia. Elementy mogą na siebie zachodzić, a warstwy definiować możemy za pomocą atrybutu: z-index.
  • absolute – pozycjonowanie bezwzględne, które w zasadzie posiada dwa stany:
    • jeżeli dla elementów (w górę) nie ustawiono pozycjonowania innego niż statyczne, to w zasadzie można to przyrównać to pozycjonowania stałego
    • jeżeli któryś element (pierwszy w górę) ma ustawione pozycjonowanie inne niż statyczne, to elementy ustawione jako absolute będą pozycjonowane względem tego elementu

    Co warto dodać, to informacja, że elementy pozycjonowane bezwzględnie nie zabierają miejsca i są traktowane przez pozostałe elementy jakby nie istniały w strukturze dokumentu HTML.

Pokazywanie i ukrywanie elementów za pomocą CSS i JavaScript (CSS Display and Visibility)

Widocznością elementów możemy sterować z poziomu CSS za pomocą dwóch atrybutów:

  • display
  • visibility

Różnicę pomiędzy nimi pokażę Wam na dwóch prostych przykładach. Pierwszy z nich prezentować będzie użycie display, a drugi visibility. Oba przykłady zawierać będą zdefiniowane trzy boxy z różnymi kolorami, gdzie dla środkowego boxa ustawiony zostanie odpowiedni atrybut widoczności.

display

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

visibility

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

Różnicę bardzo łatwo jest zauważyć. W przypadku użycia display: none ukrywamy całkowicie środkowy box i znika on ze struktury HTML. Gdy używamy visibility: hidden, to wprawdzie element również znika, ale nie znika ze struktury HTML i zajmuje w pełni swoje miejsce.

Widocznością sterować możemy także z poziomu JavaScript:

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

Pobieramy referencje do środkowego boxa oraz przycisku, a następnie tworzymy funkcję, która sprawdza, czy środkowy box jest widoczny lub nie i przypisuje odwrotną wartość do atrybutu display. Na końcu deklarujemy obsługę zdarzenia click dla przycisku i możemy się cieszyć możliwością ukrywania / pokazywania zielonego boxa.

Po więcej zapraszam oczywiście do materiałów na stronie w3schools. Zachęcam również do zapoznania się z biblioteką jQuery, która pozwala na dużo prostsze manipulowanie elementami HTML.

Transformacje 2D z wykorzystaniem CSS3 (CSS3 2D Transforms)

Ostatnim tematem wpisu są transformacje 2D, które możemy wykonać przy użyciu CSS3. Transformacje 2D wykonywane są w dwóch wymiarach i pozwalają zmieniać położenie, rozmiar oraz kształt elementów. Transformacje działają we wszystkich nowych wersjach przeglądarek, ale wymagają odpowiednich przedrostków dla atrybutu: transform:

  • Chrome, Safari, nowa Opera (15+) -webkit-
  • Firefox -moz-
  • Internet Explorer (9) -ms-
  • starsza Opera (< 15) -o-

Do dyspozycji mamy w zasadzie pięć funkcji, które przyjmowane są jako parametr dla transform (przykłady ze strony w3schools):

  • translate – przesunięcie względem osi X i Y
    [wp-js-fiddle url="https://jsfiddle.net/mrzepinski/as7ssb43/3/" style="width:100%;height:400px;border:solid #4173A0 1px;"]Przesunięcie o 50px w prawo i 100px w dół.
  • rotate – obracanie elementu wokół własnej osi o zadaną liczbę stopni
    [wp-js-fiddle url="https://jsfiddle.net/mrzepinski/as7ssb43/4/" style="width:100%;height:400px;border:solid #4173A0 1px;"]Obrót o 30 stopni względem wskazówek zegara. Podanie ujemnych wartości powoduje obrót przeciwny do wskazówek zegara.
  • scale – zmiana wielkości elementu względem wielokrotności wartości X i Y
    [wp-js-fiddle url="https://jsfiddle.net/mrzepinski/as7ssb43/5/" style="width:100%;height:400px;border:solid #4173A0 1px;"]Powiększenie 2 razy w poziomie i 4 razy w pionie.
  • scew – odchylenie od osi X i Y wyrażane w stopniach
    [wp-js-fiddle url="https://jsfiddle.net/mrzepinski/as7ssb43/6/" style="width:100%;height:400px;border:solid #4173A0 1px;"]Odchylenie o 30 stopni od osi X i o 20 stopni od osi Y.
  • matrix – kombinacja pozostałych czterech metod w jedną, gdzie ozostałe funkcje są w zasadzie jakąś wersją funkcji matrix i zarazem aliasem do niej
    [wp-js-fiddle url="https://jsfiddle.net/mrzepinski/as7ssb43/7/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Zapraszam do własnych eksperymentów!