Po zapoznaniu się z podstawowymi funkcjonalnościami MooTools pora zająć się dogłębnie tym co tworzy w tym frameworku całą pokaźną grupę Native. Już w pliku Core.js mogliśmy znaleźć fragmenty kodu przeznaczone dla tego typu obiektów, a przez następne 4 części kursu będziemy poznawać kolejne rozszerzenia jakie oferuje nam MooTools dla tablic (o nich w tym wpisie), funkcji, ciągów znaków, liczb, obiektów oraz zdarzeń. Generalnie obiekty te poza Element, który będzie miał cały swój dział to chleb powszedni przy pisaniu skryptów, dlatego warto nie traktować po macoszemu tych części kursu :)

Podobnie jak w wypadku poprzedniej części kursu będziemy korzystali z konsoli JavaScript (albo tej z Firebuga/Firefoxa, albo tej).

Obiekt Array ma dzięki MooTools dodane metody, których nie obsługują natywnie wszystkie przeglądarki oraz metody do wykonywania najczęstszych operacji na tablicach. Zajmijmy się najpierw metodami, które występują natywnie w JavaScript 1.6, a których nie obsługuje każda przeglądarka. Te metody to every, filter, indexOf, map oraz some (poza nimi jeszcze forEach zaimplementowane w pliku Core.js i lastIndexOf, które w ogóle w MooTools nie jest zaimplementowane).

Metody z JavaScript 1.6

Metody every i some działają na podobnej zasadzie - badają wartości elementów w tablicy. Jeżeli elementy te spełniają dany warunek (w wypadku metody every KAŻDY element musi spełniać dany warunek, a w wypadku metody some CO NAJMNIEJ JEDEN element tablicy musi spełniać warunek) to zwracana jest wartość true, a w przeciwnym wypadku false.

Najprostrze przykłady użycia metod every i some - wykorzystamy w nim tablicę liczb naturalnych 1-5 i warunek mówiący, że elementy mają mieć wartości większe od 1:

var tablicaLiczb = [1,2,3,4,5];

tablicaLiczb.every(function(element){
    return element > 1;
});

var tablicaLiczb = [1,2,3,4,5];

tablicaLiczb.some(function(element){
    return element > 1;
});

W pierwszym wypadku zostanie zwrócona wartość false, bo jeden z elementów nie jest większy od 1 (jest równy 1), a w drugim wypadku otrzymamy true, gdyż 4 elementy spełniają podany warunek.

Jeszcze dla przykładu sprawdzimy czy tablica nazwisk składa się tylko z nazwisk na literę "B":

var tablicaNazwisk = ["Borowski","Borewicz","Bogusławski"];

tablicaNazwisk.every(function(element){
    var reg = /^B+/;
    return reg.exec(element);
});

Otrzymamy wartość true, ponieważ każde nazwisko zaczyna się dużą literą "B". Warto dodać, że funkcja wywoływana przy metodzie every może pobierać dwa argumenty - uchwyt do elementu i numer indeksu aktualnie przetwarzanego elementu. Dla przykładu wykonajmy na tablicy liczb metodę some, która sprawdzi czy jakaś wartość elementu tablicy pokrywa się z indeksem tegoż elementu:

var tablicaLiczb = [1,2,3,4,5];

tablicaLiczb.some(function(element,indeks){
    return element == indeks;
});

W tym wypadku zwrócona zostanie wartość false, ale już dla tablicy:

var tablicaLiczb = [0,1,2,3,4];

Nawet w wypadku metody every (i some zresztą też) zostanie zwrócona wartość true.

Przejdźmy teraz do metod filter i map. Mają one jedną wspólną cechę - tworzą nową tablicę na bazie starej - w wypadku metody filter nowa tablica zawiera elementy spełniające dany warunek, natomiast metoda map zwraca tablicę elementów, które są elementami starej tablicy z wartościami przetworzonymi według podanej jako argument metody funkcji.

Zatem gdybyśmy wzięli nasz pierwszy przykład i zmienili metodę na filter:

var tablicaLiczb = [1,2,3,4,5];

var nowaTablicaLiczb = tablicaLiczb.filter(function(element){
    return element > 1;
});

To otrzymamy tablicę postaci:

[2, 3, 4, 5]

Potrzebujemy wartości drugiej potęgi dla każdego elementu tablicy ? Zastosujmy metodę map:

var tablicaLiczb = [1,2,3,4,5];

var nowaTablicaLiczb = tablicaLiczb.map(function(element){
    return element * element;
});

Otrzymamy oczywiście tablicę postaci:

[1, 4, 9, 16, 25]

Można też wykorzystać drugą zmienną funkcji i stworzyć tablicę kolejnych potęg (począwszy od zerowej potęgi) dla liczb z tablicy:

var tablicaLiczb = [1,2,3,4,5];

var nowaTablicaLiczb = tablicaLiczb.map(function(element,indeks){
    return Math.pow(element,indeks);
});

Otrzymamy:

[1, 2, 9, 64, 625]

Z tej grupy metod pozostała nam jeszcze metoda indexOf - jak sama nazwa wskazuje chodzi o zwrócenie wartości indeksu dla elementu o danej wartości. Metoda indexOf pobiera dwa argumenty - szukaną wartość elementu oraz opcjonalnie numer indeksu od którego ma być rozpoczęte przeszukiwanie. W wypadku gdy element o danej wartości nie istnieje w tablicy (lub w jej fragmencie, którego początek definiuje drugi parametr metody) zostanie zwrócona wartość -1.

Dla przykładu weźmy tablicę będącą efektem działania ostatniego kodu:

var tablicaLiczb = [1, 2, 9, 64, 625];

tablicaLiczb.indexOf(9);

Powinniśmy otrzymać wartość 2.

Gdybyśmy zastosowali taki zapis:

var tablicaLiczb = [1, 2, 9, 64, 625];

tablicaLiczb.indexOf(9,3);

Zostanie zwrócona wartość -1, bo wyszukiwanie zacznie się od fragmentu tablicy [64, 625] gdzie elementu o wartości 9 nie ma.

Pora omówić metody, dodane przez programistów MooTools.

Nowe metody obiektu Array

Takich metod jest sporo: associate, clean, link, contains, extend, getLast, getRandom, include, merge, remove, empty, flatten, hexToRgb, rgbToHex.

Metody associate i link służą do tworzenia z tablic obiektów. W wypadku pierwszej z nich obiekt powstaje na bazie dwóch tablic (elementy jednej tablicy tworzą klucze, a elementy drugiej tworzą wartości), a w drugim wypadku do obiektu są przypisywane elementy danego typu (typ jest określony w każdym kluczu).

Stwórzmy zatem obiekt na bazie dwóch tablic:

var Oprogramowanie = ["Przeglądarka", "Komunikator", "Program pocztowy"];
var Programy = ["Firefox", "Konnekt", "GMail"];

Programy.associate(Oprogramowanie);

Otrzymamy obiekt postaci:

{
    "Przeglądarka":"Firefox",
    "Komunikator": "Konnekt",
    "Program pocztowy": "GMail"
}

Przy okazji dodam, że w dokumentacji i to zarówno dla wersji 1.2 beta jaki i najnowszej wersji z SVN jest błąd. Znajdziemy tam taki przykład:

var animals = ['Cow', 'Pig', 'Dog', 'Cat']; var sounds = ['Moo', 'Oink', 'Woof', 'Miao']; animals.associate(sounds); //returns {'Cow': 'Moo', 'Pig': 'Oink', 'Dog': 'Woof', 'Cat': 'Miao'}

Problem w tym, że nie zwróci on takiego obiektu jak podano tylko podobny obiekt ale z zamienionymi parami klucz-wartość. Aby to naprawić musielibyśmy wykonać poniższy kod:

Array.prototype.associate = function(keys){
    var obj = {}, length = Math.min(this.length, keys.length);
    for (var i = 0; i < length; i++) obj[this[i]] = keys[i];
    return obj;
};

Lub jak kto woli - zamienić w pliku Array.js linijkę:

for (var i = 0; i < length; i++) obj[keys[i]] = this[i];

na:

for (var i = 0; i < length; i++) obj[this[i]] = keys[i];

Warto jeszcze dodać, że gdy nasze tablice mają nierówną długość, to obiekt będzie miał długość tablicy o mniejszej długości.

Pora na ciekawszą metodę link - do obiektu o danej strukturze przypisuje elementy danego typu z tablicy. Przy czym liczy się kolejność elementów w tablicy, a nie w obiekcie. Dla przykładu - mamy tablicę zawierającą kilka ciągów znaków i liczb i chcemy stworzyć obiekt gdzie klucze będą miały przypisane wartości będące dwoma pierwszymi ciągami znaków z tej tablicy:

var wartosci = ["Test",1,2,"abc",3,4,"efg"];

wartosci.link({
    "Tekst1":String.type,
    "Tekst2":String.type
});

Otrzymamy obiekt postaci:

{
    Tekst1:"Test"
    Tekst2:"abc"
}

Warto zauważyć, że typ możemy zdefiniować poprzez zapis:

ObiektNatywny.type

Zatem jeżeli chcemy by do danego klucza był przypisany element będący liczbą zapiszemy:

Number.type

Na przykład:

var wartosci = ["Test",1,2,"abc",3,4,"efg"];

wartosci.link({
    "Tekst1":String.type,
    "Tekst2":String.type,
    "Liczba1":Number.type,
    "Liczba2":Number.type
});

Warto zauważyć, że w tym wypadku zostanie zwrócony obiekt postaci:

{
    Tekst1:"Test",
    Liczba1:"1",
    Liczba2:"2",
    Tekst2:"abc"
}

Jak widać kolejność elementów w tablicy zdeterminowała kolejność właściwości obiektu.

Jeżeli byśmy zastosowali zapis:

var wartosci = ["Test",1,2,"abc",3,4,"efg"];

wartosci.link({
    "Tekst1":String.type,
    "Tekst2":String.type,
    "Liczba1":Number.type,
    "Liczba2":Number.type,
    "Element1":Element.type
});

To nadal otrzymamy obiekt tej samej postaci co poprzednio, bo jeżeli tablica nie zawiera elementu o typie "wymaganym" przez jedną z właściwości obiektu to owa właściwość nie istnieje w zwracanym obiekcie.

Gdybyśmy chcieli "posprzątać" w naszej tablicy, to warto wykorzystać metodę clean - usuwa ona wszelkie wartości typu 0, null, undefined, false z tablicy.

[0,0,null].clean();

Powyższy kod zwróci nam po "posprzątaniu" pustą tablicę, bo wszystkie wartości są równe 0 lub jego logicznym odpowiednikom.

Zamijmy się teraz metodami contains, getLast i getRandom - pierwsza z tych metod zwraca wartość logiczną zależną od tego czy element o danej wartości istnieje w tablicy (true) czy też nie (false). Metody getLast i getRandom jak same nazwy wskazują, zwracają odpowiednio ostatni i losowy element tablicy.

Myślę, że funkcje te są na tyle proste, że trzy proste przykłady użycia wystarczą :

[1,2,3,4,5].contains(1);

zwróci true, a zapis:

[1,2,3,4,5].contains(0);

zwróci false.

Zapis:

[].getLast();

Zwróci wartość null - to samo stanie się w przypadku użycia dla pustej tablicy metody getRandom.

Pora na omówienie metod, które potrafią dodać co nieco do naszej tablicy - extend i merge. Teoretycznie robią to samo - łączą dwie tablice w jedną. W praktyce w wypadku metody extend następuje takowe połączenie bez względu na wartości elementów tablic, a w wypadku merge nie zostają dodane do pierwotnej tablicy elementy, które już w niej występują. Tradycyjne już przykłady z liczbami:

[1,2,3].extend([3,4,5]);

Powyższy kod zwróci tablicę postaci:

[1, 2, 3, 3, 4, 5]

Natomiast w wypadku użycia metody merge:

[1,2,3].merge([3,4,5]);

Otrzymamy tablicę postaci:

[1, 2, 3, 4, 5]

Tak jak w wypadku poprzednich metod - wielkiej filozofii tutaj nie ma, tak samo zresztą jak w wypadku dwóch kolejnych metod - include i remove. Pierwsza z nich dodaje nowy element o podanej wartości do tablicy jeżeli on jeszcze w niej nie istnieje, a metoda remove usunie dany element (o podanej wartości) z tablicy (o ile istnieje).

Jak widać metody te to typowe uproszczenia najczęściej wykonywanych operacji na tablicach - zamiast paru linijek (w wypadku remove musimy pętlą przeszukać tablicę, usunąć element jeżeli istnieje i zwrócić element) piszemy jedną (Jak to w wielu wypadkach w MooTools bywa ;) ).

Zapis:

[1,2,3,4,5].include(5);

Zwróci nam tablicę postaci:

[1, 2, 3, 4, 5]

a zapis:

[1,2,3,4,5].include(6);

zwróci tablicę postaci:

[1, 2, 3, 4, 5, 6]

W wypadku metody remove ostatni przykład zwróci tablicę:

[1, 2, 3, 4, 5]

a przedostatni przykład zwróci tablicę postaci:

[1, 2, 3, 4]

Poza metodami konwertującymi pozostały nam jeszcze dwie metody, które dość mocno potrafią ingerować w strukturę tablicy - empty i flatten. Pierwsza z nich czyści tablicę:

[1,2,3,4,5].empty();

Zwróci nam po prostu:

[]

Natomiast metoda flatten "spłaszcza" tablice wielowymiarowe do jednowymiarowych. Parę przykładów w wymiarach od pierwszego do trzeciego :

[1,2,3,4,5].flatten();

Powyższy kod zwróci nam po prostu tą samą tablicę bo sama w sobie jest już jednowymiarowa, ale jeżeli będziemy mieli tablicę dwuwymiarową 3x3:

[[1,2,3],[1,2,3],[1,2,3]].flatten();

To zostanie zwrócona tablica postaci:

[1, 2, 3, 1, 2, 3, 1, 2, 3]

Weźmy jeszcze tablicę trójwymiarową 3x3x3:

[[[1,2,3],[1,2,3],[1,2,3]],[[1,2,3],[1,2,3],[1,2,3]],[[1,2,3],[1,2,3],[1,2,3]]].flatten();

Otrzymamy z tego tablicę :

[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

Można łatwo zauważyć, że jeżeli rozpiszemy sobie nasza wielowymiarową tablicę to metoda flatten zwróci nam postać bez zbędnych nawiasów kwadratowych ;)

Na koniec pozostały nam do omówienia metody konwertujące zapis heksadecymalny na RGB i na odwrót - hexToRgb i rgbToHex.

Metoda hexToRgb konwertuje zapis heksadecymalny na zapis RGB, przy czym warto uważać bo zapis:

"#FFFFFF".hexToRgb();

Nie oznacza wcale użycia metody hexToRgb z pliku Array.js tylko z pliku String.js. Aby użyć metody z pliku Array.js musimy zastosować zapis:

["FF","FF","FF"].hexToRgb();

choć w wypadku powtarzających się wartości "F" wystarczy nawet zapis:

["F","F","F"].hexToRgb();

Metoda hexToRgb pobiera jeden argument będący wartością logiczną, która decyduje o tym czy zwracana wartość również jest tablicą (true) czy ciągiem postaci rgb(liczba,liczba,liczna) (false).

Jeżeli chodzi o metodę rgbToHex to także musimy wykorzystać zapis RGB w postaci tablicy, a zapis heksadecymalny zostanie zwrócony jako tablica gdy argument metody rgbToHex ma wartość true. W przeciwnym wypadku zostanie zwrócony ciąg postaci #XXXXXX, gdzie XXXXXX to dowolna wartość w zapisie heksadecymalnym...

I to wszystko co miałem do napisania o obsłudze tablic w MooTools 1.2 . W następnej części kursu zajmiemy się obiektami Function i Event.

Komentarze do wpisu "MooTools 1.2 - obiekty natywne - tablice":

1. blue napisał(a):
05 stycznia 2008, 21:21:22

Mały błąd się wkradł:
Jak widać kolejność elementów w tablicy zdeterminowała kolejność właściwości obiektu.

Właściwości obiektu nie mają żadnej kolejności ;) jak sobie wypiszesz wszystkie przy pomocy for(... in …), to każda przeglądarka zwróci inną kolejność. Nie jest to po prostu ustandaryzowane.

2. Dziudek napisał(a):
05 stycznia 2008, 21:35:11

@blue – i tak i nie ;)

Oczywiście właściwości obiektu nie mają żadnej kolejności, bo to nie tablica, ale z drugiej strony jak sobie odpalisz w konsoli JS:

var wartosci = ["Test",1,2,"abc",3,4,"efg"];objt = wartosci.link({"Tekst1":String.type,"Tekst2":String.type,"Liczba1":Number.type,"Liczba2":Number.type,"Element1":Element.type});

a potem:

for(key in objt){alert(key);}

To kolejność będzie taka jak podałem (w Operze, Fx, IE6/7, Safari) :) Można powiedzieć tak – kolejność jest i jej nie ma – z jednej strony dzięki odpowiedniej budowie obiektu możemy ją ustalić, ale nie możemy w nią później ingerować tj. operacja typu wstawienie właściwości przed inną właściwość jest przynajmniej z tego co mi wiadomo niemożliwa, no chyba, że przepiszemy cały obiekt na nowo ;)

3. Łukasz napisał(a):
21 stycznia 2008, 23:11:04

Dobra robota. Tak trzymaj!

Dodaj komentarz:

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