W pierwszej "prawdziwej" części kursu MooTools 1.2 zajmę się opisem podstawowej części tego frameworka czyli plików Core.js i Browser.js . Te dwa pliki stanowią podstawę reszty MooTools, pierwszy z nich zawiera implementacje podstawowych funkcji, a drugi służy do jednej z podstawowych czynności w świecie JS - detekcji przeglądarki uzytkownika.

Konsola JavaScript

Zacznijmy od tego, że musimy mieć narzędzie do pracy - konsolę JS. W wypadku użytowników Firefoksa nie jest to problem - wystarczy z menu "Narzędzia" wybrać "Konsola błędów" i już mamy dostęp do konsoli JavaScript. Oczywiście jeszcze lepiej mieć rozszerzenie Firebug. Co jednak z użytkownikami innych przeglądarek ? Ponieważ nie chcę nikogo zmuszać do używania w celu testów Firefoksa wykorzystałem Firebug Lite i stworzyłem z jego użyciem stronkę z prostą konsolą JS, którą znajdziecie pod tym adresem. I tak oto uzbrojeni w to potężne narzędzie możemy ruszać w świat MooTools :)

Informacje o wersji MooTools

Zacznijmy od tego co zawiera plik Core.js. Oczywiście kod zaczyna się od znanego już niektórym obiektu MooTools, zawierającego informacje o wersji frameworka. Ma on następującą budowę:

var MooTools = {
    'version': wersja,
    'build': build
};

Gdyby ktoś się zastanawiał kiedyś do czego ów obiekt może się przydać - pierwsza i podstawowa sprawa - dzięki niemu możemy sprawdzić czy w ogóle jakaś wersja MooTools została na danej stronie załadowana. Dokonamy tego poprzez prosty kod wywołany w konsoli JavaScript:

try{
    MooTools
}catch(e){
    false
}

Jeżeli otrzymamy z niego false to znaczy, że obiekt MooTools nie jest zdefiniowany, a co za tym idzie framework nie został załadowany...

Oczywiście możliwości wykorzystania tego obiektu jest więcej jednak są one z reguły o wiele bardziej wyszukane niż ta podstawowa funkcja...

Informację o wersji wyświetlamy oczywiście poprzez odczytanie właściwości:

MooTools.version;
MooTools.build;

Obiekty natywne

Bardzo ważną częścią pliku Core.js jest funkcja Native, która porządkuje i rozszerza możliwości obiektów natywnych JavaScript takich jak String, Function, Number, Array, RegExp, Date, Boolean, Native, Object.

Właściwie nas z tego fragmentu kodu powinny zainteresować dwie metody - implement, która pozwala na dodanie własnych metod do obiektów natywnych oraz alias, która pozwala tworzyć kilka nazw dla tej samej metody danego obiektu natywnego...

Jeżeli chodzi o metodę implement to ma ona następującą składnię:

obiektNatywny.implement({
    nazwaMetody1: function(){
        // kod
    },
    nazwaMetody2: function(){
        // kod
    },

    ...

    nazwaMetodyN: function(){
        // kod
    }
});

Przy czym jeżeli chcemy zaimplementować te same metody w wielu obiektach naraz to stosujemy tablicę obiektów jako pierwszy argument metody:

Native.implement([obiektNatywny1, obiektNatywny2, obiektNatywny3],{
    nazwaMetody1: function(){
        // kod
    },
    nazwaMetody2: function(){
        // kod
    },

    ...

    nazwaMetodyN: function(){
        // kod
    }
});

Jak widać możemy do kilku obiektów dodać od razu kilka/kilkanaście metod.

Jeżeli chodzi o metodę alias to składnia wygląda następująco:

obiektNatywny.alias("pierwotnaNazwa","nowaNazwa");

Gdybyśmy chcieli na przykład zamiast Array.each stosować Array.e to zapiszemy:

Array.alias("each","e");

Na oswojenie się z łańcuchowym wywoływaniem metod obiektu dodam, że zamiast:

Array.alias("metoda1","m1");
Array.alias("metoda2","m2");

można stosować zapis:

Array.alias("metoda1","m1").alias("metoda2","m2");

Obiekty i tablice

Plik Core.js zawiera także podstawowe implementacje obiektów Array i Hash. W wypadku Array została od razu zaimplementowana metoda forEach wraz z aliasem each. Oczywiście nie zabrakło funkcji $A, która zamienia kolekcję elementów na tablicę. W wypadku obiektu Hash, który jest swoistą nakładką na obiekt natywny Object, mamy zaimplementowaną także metodę forEach z aliasem each oraz skrót $H, który daje taki sam efekt jak zapis:

var zmienna = new Hash({obiekt});

Skoro już się tak obracamy w otoczeniu obiektów, które można iteracyjnie przetwarzać to od razu wspomnę o funkcji $each, która służy do wykonania danej funkcji na każdym elemencie iterowalnym (argumenty, tablica, kolekcja elementów). Składnia wygląda następująco:

$each(obiektIterowalny, funkcja, bind);

Zmienna obiektIterowalny już wiadomo czym może być, funkcja to oczywiście kod funkcji, która wykona się dla każdego elementu obiektu, a zmienna bind definiuje do czego odnosi się operator this w ciele funkcji (domyślnie odnosi się on do samej funkcji).

Dla wyklarowania sytuacji z Hash i Array kilka prostych przykładów :)

Obiekt daneUzytkownika możemy zdefiniować na dwa sposoby:

var daneUzytkownika = new Hash({nick: "dziudek", imie: "Tomek"});

lub:

var daneUzytkownika = $H({nick: "dziudek", imie: "Tomek"});

Informacje z takiego obiektu możemy odczytać na co najmniej trzy sposoby:

daneUzytkownika.forEach(function(el, key){alert(el);});

daneUzytkownika.each(function(el, key){alert(el);});

$each(daneUzytkownika, function(el, key){alert(el);});

Wszystkie te trzy zapisy są równoważne i spowodują wyświetlenie alertów z tekstami "dziudek" i "Tomek" (zastąpienie el wartością key spowoduje wyświetlenie nazw kluczy obiektu).

Oczywiście dostęp do tych danych otrzymamy także poprzez zapisy:

daneUzytkownika["imie"];
daneUzytkownika["nick"];

oraz:

daneUzytkownika.imie;
daneUzytkownika.nick;

W wypadku tablic sytuacja ma się podobnie z tym, że charakter przechowywanych danych jest inny - nie posiadają one nazwanego odnośnika, a jedynie indeks w tablicy. Aby przy wyświetlaniu danych takim samym sposobem jak w wypadku obiektu Hash, otrzymać te same wyniki należy zmienną daneUzytkownika zdefiniować następująco:

var daneUzytkownika = ["dziudek","Tomek"];

Zostały nam jeszcze dwie metody obiektu Hash - getClean, getLength. Pierwsza z nich zwraca obiekt z usuniętymi pustymi właściwościami (tj. obiekt bez właściwości, które nie miały przypisanej wartości), a druga metoda zwraca nam "długość obiektu" tj. ilość właściwości w obiekcie. Obie stosujemy tak samo jak każdą zwykłą metodę w MooTools:


var obiekt = new Hash({"testowy":"test"});

obiekt.getClean();
obiekt.getLength();

Pozostałe funkcje

Jeżeli chodzi o resztę funkcji pliku Core.js to mają one jedną wspólną cechę - zaczynają się od znaku $:

  • $time
  • $clear
  • $chk
  • $defined
  • $pick
  • $type
  • $empty
  • $try
  • $arguments
  • $lambda
  • $extend
  • $merge
  • $unlink
  • $splat
  • $random

Po odstępach możecie zauważyć, że pozwoliłem sobie je troszkę pogrupować tematycznie :)

Funkcje związane z czasem

Funkcje $time i $clear są związane z czasem - pierwsza zwraca aktualny czas w postaci ilości milisekund jakie upłynęły od 1 stycznia 1970 roku od godziny 00:00:00. Funkcja $clear natomiast służy do przerywania okresowego wykonania funkcji lub przerwania odliczania czasu do wykonania danej funkcji (nieodłącznie związane z metodami delay i periodical obiektu natywnego Function).

$time nie pobiera żadnego argumentu, a $clear pobiera zmienną przypisaną do funkcji wykonywanej okresowo/z opóźnieniem. Zatem jej wywołanie w wypadku gdy mamy następujący kod w skrypcie:

var czasomierz = (function(){alert("buum!");}).delay(5000);

Wygląda następująco:

$clear(czasomierz);

Funkcje badające zmienne

Przejdźmy teraz do funkcji związanych z typem zmiennych i tym czy są one zdefiniowane.

Funkcje $chk i $defined działają podobnie - pierwsza zwraca wartość logiczną true gdy dana zmienna istnieje lub jest równa 0, a druga z nich zwraca taką wartość gdy dana zmienna nie jest pusta (czyli ma zdefiniowaną wartość). W wypadku niespełnienia warunku funkcje te zwracają wartość logiczną false. Oczywiście sens ma zastosowanie tych funkcji w ciele innej funkcji do sprawdzenia argumentów, ponieważ w innym wypadku nie obędzie się bez błędu jeżeli dana zmienna nie istnieje... Obie funkcje pobierają jeden argument - zmienną do sprawdzenia.

Przykłady:

function test(zmienna){
    return $chk(zmienna);
}

test();

Zwróci nam false, natomiast kod:

function test(zmienna){
    return $chk(zmienna);
}

test("testowy tekst");

zwróci nam wartość true.

Jeżeli chodzi o funkcję $defined:

function test(zmienna){
    return $defined(zmienna);
}

test("testowy tekst");

Powyższy kod zwróci nam oczywiście wartość true, natomiast false otrzymamy w każdym z poniższych wypadków:

function test(zmienna){
    return $defined(zmienna);
}

test();


function test(zmienna){
    return $defined(zmienna);
}

test(null);

function test(zmienna){
    return $defined(zmienna);
}

var zmienna;

test(zmienna);

Funkcja $pick służy do "grupowego" sprawdzania czy zmienne są zdefiniowane zwracając przy tym pierwszą zdefiniowaną zmienną z podanych lub wartość null jeżeli żadna ze zmiennych podanych jako argumenty tej funkcji nie jest zdefiniowana.

Stwórzmy zatem przykładową funkcję:

function test(a,b,c){
    return $pick(a,b,c);
}

test();

Zwróci ona nam null, bo przy wywołaniu nie podaliśmy żadnych argumentów. Gdybyśmy funkcję test wywołali w taki sposób:

test(10);

To zostanie zwrócone właśnie 10.

Gdy wywołanie będzie wyglądać tak:

test(null,null,3);

To zostanie zwrócona wartość 3, a wywołanie:


test(null,null,null);

Zwróci oczywiście wartość null.

Pora na coś bardziej złożonego czyli funkcję $type - zwraca ona typ argumentu. Ponieważ istnieje wiele wartości jakie może ona zwrócić podam tylko nazwy wartości przez nią zwracanych bez przykładów, by nie wydłużać i tak już długiego wpisu :)

Wartości zwracane przez funkcje $type:

  • 'element' - gdy argument jest elementem DOM,
  • 'textnode' - gdy argument jest węzłem tekstowym DOM,
  • 'whitespace' - gdy argument jest węzłem zawierającym wyłącznie "białe znaki",
  • 'arguments' - gdy argument jest tablicą argumentów (w funkcji)
  • 'array' - gdy argument jest tablicą,
  • 'object' - gdy argument jest obiektem,
  • 'string' - gdy argument jest ciągiem znaków,
  • 'number' - gdy argument jest liczbą,
  • 'boolean' - gdy argument jest wartością logiczną,
  • 'function' - gdy argument jest funkcją,
  • 'regexp' - gdy argument jest wyrażeniem regularnym,
  • 'class' - gdy argument jest klasą (stworzona przez Class lub rozszerzenie instancji Class),
  • 'collection' - gdy argument jest kolekcją elementów DOM (na przykład zwracaną przez funkcję $$),
  • 'window' - gdy argument to obiekt window,
  • 'document' - gdy argument to obiekt document,
  • false - gdy argument jest niezdefiniowany lub nie jest jednym z wyżej wymienionych elementów.

Przejdźmy teraz do funkcji związanych z... funkcjami :)

Funkcje operujące na funkcjach

Funkcje $lambda i $arguments są dość ciekawe zważywszy na możliwości jakie nam oferują - $lambda pozwala nam stworzyć ze zmiennej funkcję zwracającą ją samą (zmienną), a $arguments pozwala stworzyć funkcję zwracającą argument z określonej pozycji w liście argumentów.

$lambda pobiera jako argument zmienną lub funkcję, natomiast funkcja $arguments pobiera numer argumentu jaki ma zostać później zwrócony (przy czym argumenty są numerowane jak w tablicy t.j. od zera).

Zapis:

var d = $arguments(1);

d(1,2,3);

Spowoduje zwrócenie wartości 2.

Funkcja $lambda pozwala nam często skrócić kod - weźmy przykład gdy mamy w kodzie obiektu zapis:

metoda: function(){
    return false;
}

Powyższy kod możemy zamienić na:

metoda: $lambda(false);

Apropo metod obiektów - funkcja $empty oznacza po prostu pusta funkcję. Zatem zamiast w definicji klasy pisać:

onZdarzenie: function(){}

Piszemy po prostu:

onZdarzenie: $empty

Może taka sztuka dla sztuki, ale według mnie jest to czytelniejsze :)

Przydatną funkcją jest z pewnością $try - pozwala ona sprawdzić czy dana funkcja nie powoduje błędów, ma ona trochę bardziej złożoną składnię niż poznane przez nas już funkcje:

$try(funkcja,this,argumenty);

Zmienna funkcja to oczywiście funkcja jaką chcemy przetestować, zmienna this to obiekt do jakiego odnosi się operator this w ciele funkcji, a zmienna argumenty to zmienne przekazywane do funkcji (gdy argumentów jest więcej niż jeden wtedy przekazujemy je jako tablicę).

Kod z początku wpisu:

try{
    MooTools
}catch(e){
    false
}

Możemy zapisać jako:

$try($lambda(MooTools));

Choć akurat przykład z obiektem MooTools może się wydać średnio udany (bo zakładamy, że MooTools jest załadowany :D) to warto zapamiętać, że możemy tak sprawdzić każdy obiekt/zmienną :)

Funkcje operujące na obiektach

Funkcja $extend pozwala nam rozszerzyć dany obiekt o nowe właściwości, a $merge pozwala złączyć kilka obiektów w jeden. Ponadto $extend można zastosować na dwa sposoby:

$extend(obiektPodstawowy,obiektRozszerzenie);

lub:

Obiekt.extend = $extend;
Obiekt.extend(obiektRozszerzenie);

Podobieństwo tych funkcji polega na tym, że w obu wypadkach następuje nadpisanie właściwości obiektu jeżeli się powtarzają w podawanych jako argumenty obiektach.

Dla przykładu przyjmijmy, że mamy trzy obiekty:

przegladarka = {
    nazwa: "Lisek",
    wersja: 3
}

oprogramowanie = {
    wersja: 5,
    wlasciciel: "Dziudek"
}


system = {
    wersja: 6
}

Jeżeli teraz wykonamy operację:

$extend(przegladarka,oprogramowanie);

To otrzymamy obiekt postaci:

{
    nazwa: "Lisek",
    wersja: 5,
    wlasciciel: "Dziudek"
}

Jak widać nie występujące w obiekcie przegladarka właściwości zostały dodane, a właściwość wersja została zmieniona na wartość z obiektu oprogramowanie.

W wypadku funkcji $merge:

$merge(przegladarka,oprogramowanie);

Otrzymalibyśmy taki sam obiekt, ale już po wykonaniu kodu:

$merge(przegladarka,oprogramowanie,system);

Otrzymamy:

{
    nazwa: "Lisek",
    wersja: 6,
    wlasciciel: "Dziudek"
}

Zanim ktoś powie, że te funkcje działają w sumie tak samo poza liczbą argumentów, dodam jeszcze, że w wypadku funkcji $merge operacja rozszerzania/aktualizowania właściwości następuje także wtedy gdy jedna z właściwości jest obiektem (tj. mamy zagnieżdżone obiekty). Zatem weźmy dwa nowe obiekty:

strona = {
    title: "test",
    body: {
        menu: "opcje",
        stopka: "copyright"
    }
}

nowaStrona = {
    title: "drugi test",
    body: {
        tresc: "tekst",
        stopka: "All right reserved"
    }
}

W wypadku funkcji $extend:

$extend(strona,nowaStrona);

Otrzymamy obiekt postaci:

{
    title: "drugi test",
    body: {
        tresc: "tekst",
        stopka: "All right reserved"
    }
}

Natomiast w wypadku $merge:

$merge(strona,nowaStrona);

Zostanie zwrócony taki obiekt:

{
    title: "drugi test",
    body: {
        menu: "opcje",
        tresc: "tekst",
        stopka: "All right reserved"
    }
}

Jak widać w $extend właściwość body nie została rozszerzona tylko zamieniona, natomiast takie działanie miało miejsce w przypadku funkcji $merge.

Kwestię sprawdzenia czy mam rację pozostawiam Wam :) Jest to dobre ćwiczenie na wykazanie się znajomością obsługi obiektu Hash zaimplementowanej w pliku Core.js :)

Funkcja $unlink powoduje wyczyszczenie obiektu lub tablicy:

$unlink(['a','b','c']);
$unlink({"test":25});

Powyższy kod zwróci wartości:

[]
{}

Pozostała nam jeszcze funkcja $splat, która zamienia wszystko co nie jest tablicą w tablicę. Oczywiście nie w taki sposób jak funkcja $A - po prostu jeżeli jako argument tej funkcji podamy obiekt, to zostanie zwrócona tablica jednoelementowa zawierająca tenże obiekt:

$splat(10);

Zwróci tablicę postaci:

[10]

Natomiast kod:

$splat({1:"a",2:"b"});

Zwróci tablicę:

[{1:"a",2:"b"}]

itd.

Funkcja związana z liczbami

Jak już sam tytuł tej części wskazuje - jest tylko jedna taka funkcja (przynajmniej w opisywanych plikach) - $random pobiera dwa argumenty, które tworzą przedział liczbowy z jakiego są generowane liczby pseudolosowe:

$random(start,koniec);

powyższy kod zwróci nam liczbę pseudolosową z przedziału <start,koniec>

Na przykład:

$random(2,8);

Zwróci liczbę z przedziału <2,8>

Detekcja systemu operacyjnego i przeglądarki

Jak już pewnie zauważyliście MooTools 1.2 dorobił się osobnego pliku odpowiedzialnego za detekcję przeglądarki i systemu operacyjnego użytkownika.

Od teraz mamy zdefiniowany obiekt Browser posiadający trzy właściwości - Engine, Platform i Features. Pierwsza z nich z pewnością najbardziej nas zainteresuje, bo jest związana z detekcją przeglądarki.

To jakiej przeglądarki/systemu używa użytkownik sprawdzimy poprzez zbadanie jaką ma wartość logiczną właściwość:

  • Browser.Engine.trident - Internet Explorer (dowolny),
  • Browser.Engine.trident4 - Internet Explorer 6,
  • Browser.Engine.trident5 - Internet Explorer 7,
  • Browser.Engine.gecko - Mozilla/Gecko,
  • Browser.Engine.webkit - Safari/Konqueror,
  • Browser.Engine.webkit419 - Safari 2,
  • Browser.Engine.webkit420 - Safari 3,
  • Browser.Engine.presto925 - Opera w wersji < 9.5
  • Browser.Engine.presto950 - Opera w wersji 9.5

Dodatkowo właściwość Browser.Engine.name przechowuje nazwę przeglądarki.

W wypadku detekcji systemu badamy właściwości:

  • Browser.Platform.mac - MacOS,
  • Browser.Platform.win - Windows,
  • Browser.Platform.linux - Linux,
  • Browser.Platform.nix - systemy z rodziny *nix,
  • Browser.Platform.other - inny system operacyjny

Oczywiście we właściwości Browser.Platform.name mamy przechowywaną nazwę systemu operacyjnego użytkownika.

To czy przeglądarka natywnie wspiera XPath i XMLHttp lub na stronie wykorzystywany jest framework Adobe Air sprawdzimy poprzez właściwości:

  • Browser.Features.xpath
  • Browser.Features.xhr
  • Browser.Features.air

Jak widać obiekt Browser dostarcza nam wielu potrzebnych informacji. Warto jeszcze zwrócić uwagę na pozostałą zawartość pliku Browser.js.

eval nie jest dobry na wszystko

Gdyby się kogoś spytać jak wykonać tekst jako kod JavaScript to z pewnością większość odpowie, że wystarczy użyć funkcji eval dostępnej natywnie w JS. Ta odpowiedź jest oczywiście dobra, ale nie w 100% wypadków.

Weźmy dla przykładu taki oto kod:

function dodajZmienna() {
  var kod = 'var zmienna = "Tekst testowy"';
  eval(kod);
}

dodajZmienna();
alert(zmienna);

Powyższy kod nie zadziała, ponieważ nowa zmienna nie istnieje poza funkcją. Gdybyśmy zastosowali taki kod:

function dodajZmienna() {
  var kod = 'var zmienna = "Tekst testowy"';
  eval(kod);
  alert(zmienna);
}

dodajZmienna();

Wszystko będzie w porządku. Zatem jak widać problem polega na tym, że kod nie jest wykonywany przez funkcję eval globalnie. Lekarstwem na to w IE jest window.execScript, a uniwersalnym rozwiązaniem jakie oferuje MooTools jest funkcja $exec - zmieńmy pierwszy przykład, a dokładniej 3 linijkę:


function dodajZmienna() {
  var kod = 'var zmienna = "Tekst testowy"';
  $exec(kod);
}

dodajZmienna();
alert(zmienna);

Powyższy kod powinien zadziałać bez zarzutu. Podsumowując - jeżeli potrzebujemy GLOBALNIE wykonać kod JS zapisany w postaci ciągu znaków, to stosujemy funkcję $exec zamiast eval.

Obiekt document

Na koniec słów kilka o obiekcie document - MooTools dodaje do niego kilka właściwości, które mogą się przydać:

  • document.head - zwraca uchwyt do sekcji head dokumentu,
  • document.html - zwraca uchwyt do elementu html dokumentu,
  • document.window - zwraca uchwyt do obiektu okna dla danego dokumentu (to samo co document.defaultView i document.parentWindow tylko w wersji uniwersalnej)

I to by było na tyle w kwestii omawiania rdzenia MooTools - jak mogliśmy zauważyć opisywane pliki dają nam już całkiem sporą grupę narzędzi ułatwiających pisanie kodu w JavaScript, a to dopiero początek :)

Komentarze do wpisu "MooTools 1.2 - rdzeń frameworka":

1. mruwek napisał(a):
04 czerwca 2008, 14:16:33

Czołem Dziudku!
Właśnie zacząłem czytać tego tutka i język całkiem przystępny, ale na samym początku namierzyłem błąd. Gdy demonstrujesz użycie forEach, mylisz kolejność argumentów funkcji. Po wywołaniu:
daneUzytkownika.each(function(el, key){alert(key);});
nie dodstaniemy alertów z wartościami obiektu: „dziudek” i „Tomek” tylko dostaniemy alerty z kluczami czyli odpowiednio: „nick” i „imie”. Aby dostać „dziudka” i „Tomka” należy poprawić powyższy kod na:
daneUzytkownika.each(function(el, key){alert(el);});

Aby jeszcze lepiej zilustrować klucz i wartość można w sumie zrobić tak:
daneUzytkownika.each(function(el, key){alert("Na " + key + " mam " + el);});

2. Dziudek napisał(a):
04 czerwca 2008, 18:46:19

@mruwek – poprawione ;) Dzięki za wnikliwą lekturę – z powodu obszerności wpisu troszkę się rozpędziłem po prostu ;)

Dodaj komentarz:

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