Tworzenie klas w Dojo (stary mechanizm < 1.6)

Opisany tu sposób tworzenia „klas” Dojo jest już wycofywany. Aktualnie w Dojo Toolkit zaleca się (a w przyszłości będzie to jedyne dostępne rozwiązanie), by tworzyć moduły zgodne z AMD. Zobacz nowszy wpis o tym jak tworzyć moduły Dojo zgodne z AMD.

Dojo jest bardzo rozbudowanym i jednocześnie przyjaznym toolkitem JavaScript. W JavaScript, jak pewnie wiesz nie ma klas. Dojo wprowadza jednak zarówno takie pojęcie, jak i zestaw mechanizmów pozwalających bardzo przyjaźnie tworzyć „klasy JavaScript”.

Na poczatek

Pierwsza klasa

Początkowy kod, jaki jest wymagany wygląda mniej – więcej tak:

[javascript]// informacja o tym, że ten plik dostarcza `yarpo.MyFirstClass’
dojo.provide("yarpo.MyFirstClass");

// miejsce na załączanie innych modułów
// tu nie stosuję – póki co

// definicja klasy
dojo.declare("yarpo.MyFirstClass", null,
{
constructor : function()
{
console.log("działam", arguments);
}
});[/javascript]

Aby wykorzystać tę klasę na stronie należy wykorzystać np. taki fragment kodu:

[html]<script type="text/javascript" src="../1.6.1/dojo/dojo.js">// <![CDATA[

// ]]></script>
<script type="text/javascript">// <![CDATA[
dojo.require(‚yarpo.MyFirstClass’);
dojo.ready(function()
{
var obj = new yarpo.MyFirstClass();
});
// ]]></script>[/html]

demo online

Jedynym co zrobi powyższy kod jest wyświetlenie w konsoli (np. Firebugu) komunikatu „działam []”. Nie przejmuj się zmienną `arguments’. Jest ona tu wprowadzona tylko po to, aby w przykładach z dalszej części wpisu pokazać coś bardzo ciekawego. Na razie możesz ją ignorować. W wyświetlonym komunikacie „[]” pochodzi właśnie od tej zmiennej.

Czy to jedyne, co można zrobić za pomocą mechanizmu klas wbudowanym w Dojo? Nie.

Co tu się dzieje?

Być może zastanawiasz się nad kodem dostępnym na powyższych listingach. Co tam się dzieje? Skupmy się na `dojo.declare’.

Metoda ta przyjmuje 3 parametry:

1. Nazwę tworzonego modułu (klasy). Nazwa ta zawiera także przestrzeń nazw, w której ma zostać umieszczona. Przestrzeń nazw z kolei ma duży wpływ na to skąd będzie odczytywana dana klasa. Zauważ fragment:

[html]<script type="text/javascript" src="../1.6.1/dojo/dojo.js">// <![CDATA[
[/html]

Mówi on, że „pakietu” `yarpo’ należy szukać w odpowiednim katalogu. Wartość tę należy ustalić tak, aby była prawdziwa z faktyczną strukturą katalogów. U mnie jest to katalog `class/yarpo`, a w nim plik `MyFirstClass.js`. Można zauważyć, że tak naprawdę ścieżka do pliku to nazwa modułu, w której kropkę zamienia się na ukośnik.

2. Tablica modułów, po którym dziedziczy deklarowana klasa. Dojo pozwala na dziedziczenie po wielu modułach. Tu ustawione na `null’. W dalszej części wpisu pokażę, jak to wykorzystywać w bardziej wyrafinowanych przypadkach.

3. Obiekt według którego będą tworzone wszystkie obiekty deklarowanej klasy. Należy pamiętać, że wszystkie pola będą publiczne.

Warto przeczytać:

Dziedziczenie

Dojo pozwala bardzo ułatwić dziedziczenie. Załóżmy, że chcemy stworzyć klasę, która dziedziczy po wcześniejszej `yarpo.MyFirstClass’. Będzie to `yarpo.MySecondClass’ (fizycznie plik `class/yarpo/MySecondClass.js`):

[javascript]dojo.provide(‚yarpo.MySecondClass’);

dojo.require(‚yarpo.MyFirstClass’);

dojo.declare(‚yarpo.MySecondClass’, [yarpo.MyFirstClass],
{
constructor : function()
{
console.log("działa MySecondClass", arguments);
}
});[/javascript]

demo online

Ważne zmiany:

  1. dodanie dojo.require ładującego klasę `yarpo.MyFirstClass’
  2. dodanie tablicy z modułami, po których dziedziczy definiowana klasa

Aby móc wykorzystać nowy kod starczy taki fragment:

[html]<script type="text/javascript" src="../1.6.1/dojo/dojo.js" djConfig="modulePaths: {‚yarpo’: ‚../../class/yarpo’}">
// ]]></script>
<script type="text/javascript">// <![CDATA[
dojo.require(‚yarpo.MySecondClass’);
dojo.ready(function()
{
var obj = new yarpo.MySecondClass();
});
// ]]></script>[/html]

W wyniku działania powyższego kodu zostanie wyświetlone:

[text]działa []
działa MySecondClass [][/text]

Widać zatem, że konstruktory zostają wywoływane jeden po drugim – w kolejności od „najstarszej” klasy w łańcuchu dziedziczenia.

Przekazywanie parametrów do konstruktora

Oczywiście można przekazać do konstruktora parametry. Jak je przekazać i odebrać?

Przekazanie:

[javascript]var obj = new yarpo.MyThirdClass(1,2,3);[/javascript]

Odebranie w konstruktorze – sposób 1:

[javascript]dojo.provide(‚yarpo.MyThirdClass’);
dojo.require(‚yarpo.MySecondClass’);
dojo.declare(‚yarpo.MyThirdClass’, [yarpo.MySecondClass],
{
constructor : function()
{
console.log("działa MyThirdClass", arguments);
}
});[/javascript]

Sposób 2:

[javascript]dojo.provide(‚yarpo.MyThirdClass’);
dojo.require(‚yarpo.MySecondClass’);
dojo.declare(‚yarpo.MyThirdClass’, [yarpo.MySecondClass],
{
constructor : function(a, b, c)
{
console.log("działa MyThirdClass", a, b, c);
}
});[/javascript]

demo online

Oczywiście to, jakiego rodzaju wartości przekazujemy do konstruktora jest kwestią otwartą. Mogą to być liczby, ciągi znaków, czy też inne obiekty (funkcje czy tablice to także obiekty).

W wyniku uruchomienia powyższego kodu zostanie nam wyświetlone:

[text]działa [1, 2, 3]
działa MySecondClass [1, 2, 3]
działa MyThirdClass 1 2 3[/text]

Czyli każdy przekazany parametr do konstruktora `yarpo.MyThirdClass’ jest przekazywany wyżej. Nie zawsze jednak chcemy, aby tak się działo.

Załóżmy, że mamy klasę `geometria.Figura’, która nie przyjmuje żadnych parametrów. Z tej klasy dziedziczy `geometria.Prostokat’, która przyjmuje dwa parametry: x, y. Z kolei z `Prostokat’ dziedziczy `geometria.Kwadrat’ przyjmująca tylko jeden parametr. Gdzie należałoby się zająć liczbą parametrów przekazywanych „wyżej” skoro konstruktory wywoływane są automatycznie? Czytaj dalej… 🙂

Metoda preamble

Aby móc zmieniać zarówno liczbę przekazywanych parametrów, jak i ich wartości należy wykorzystać metodę `preamble’, która jest wywoływana przed `constructor’.

Na przykładzie wszystko powinno być bardziej jasne:

[javascript]dojo.provide(‚yarpo.MyFourthClass’);

dojo.require(‚yarpo.MyThirdClass’);
dojo.declare(‚yarpo.MyFourthClass’, [yarpo.MyThirdClass],
{
preamble : function(a, b)
{
console.log(‚preamble MyFourthClass’, arguments);
return [a, b, -3];
},
constructor : function(a, b)
{
console.log("działa MyFourthClass", a, b);
}
});[/javascript]

demo online

Chociaż do konstruktora przy tworzeniu obiektu przekazałem dwa parametry, to zostały one całkowicie zmienione w metodzie `preamble’. Jak widzisz atrybuty, które mają zostać przekazane dalej zostały zwrócone w postaci tablicy. W ten sposób można zarówno dodawać dodatkowe (domyślne) atrybuty, jak również usuwać ich część.

Oto komunikaty z konsoli:

[text]preamble MyFourthClass [1, 2]
działa [1, 2, -3]
działa MySecondClass [1, 2, -3]
działa MyThirdClass 1 2 -3
działa MyFourthClass 1 2[/text]

Stworzyłem także przykład 4 klas (dziedziczących po sobie tak jak powyższe), w którym każda posiada metodę `preamble’. Ostatnią klasą jest `MyFourthClassWithPreamble’ wyglądająca w ten sposób:

[javascript]dojo.provide(‚yarpo.MyFourthClassWithPreamble’);
dojo.require(‚yarpo.MyThirdClassWithPreamble’);
dojo.declare(‚yarpo.MyFourthClassWithPreamble’, [yarpo.MyThirdClassWithPreamble],
{
preamble : function(a, b, c)
{
console.log(‚preamble MyFourthClassWithPreamble’, arguments);
return [a, b, c, -4];
},
constructor : function(a, b, c, d)
{
console.log("działa MyFourthClassWithPreamble", arguments);
}
});[/javascript]

demo online

W linkowanym przykładzie można zobaczyć wszystkie klasy. W wyniku wywołania takiego kodu otrzymałem:

[text]preamble MyFourthClassWithPreamble [1, 2, 3, 4]
preamble MyThirdClassWithPreamble [1, 2, 3, -4]
preamble MySecondClassWithPreamble [1, 2, -3]
preamble MyFirstClassWithPreamble [1, -2]
działa MyFirstClassWithPreamble [1, -2]
działa MySecondClassWithPreamble [1, 2, -3]
działa MyThirdClassWithPreamble [1, 2, 3, -4]
działa MyFourthClassWithPreamble [1, 2, 3, 4][/text]

Wynika z tego tyle, że najpierw wywoływane są metody `preamble’, a następnie metody `constructor’ w odwrotnej kolejności.

Prócz możliwości „wstrzyknięcia” kodu wykonane przed `constructor’ istnieje także możliwość automatycznego wykonania jakiegoś kodu po konstruktorze.

Metoda postscript

Służy do podania kodu, który ma zostać wykonany po konstruktorze. W tym wypadku jednak nie będziemy świadkami wykonania n metod. Metoda `postscript’ nadpisuje wcześniejsze. Stąd też taki przykładowy kod:

[javascript]dojo.provide(‚yarpo.MyFourthClassWithPreambleAndPostscript’);
dojo.require(‚yarpo.MyThirdClassWithPreambleAndPostscript’);
dojo.declare(‚yarpo.MyFourthClassWithPreambleAndPostscript’, [yarpo.MyThirdClassWithPreambleAndPostscript],
{
preamble : function(a, b, c)
{
console.log(‚preamble MyFourthClassWithPreambleAndPostscript’, arguments);
return [a, b, c, -4];
},
constructor : function(a, b, c, d)
{
console.log("działa MyFourthClassWithPreambleAndPostscript", arguments);
},
postscript: function()
{
console.log(‚postscript MyFourthClassWithPreambleAndPostscript’, arguments);
}
});[/javascript]

demo online

Zwróci taki oto wynik:

[text]preamble MyFourthClassWithPreambleAndPostscript [1, 2, 3, 4]
preamble MyThirdClassWithPreambleAndPostscript [1, 2, 3, -4]
preamble MySecondClassWithPreambleAndPostscript [1, 2, -3]
preamble MyFirstClassWithPreambleAndPostscript [1, -2]
działa MyFirstClassWithPreambleAndPostscript [1, -2]
działa MySecondClassWithPreambleAndPostscript [1, 2, -3]
działa MyThirdClassWithPreambleAndPostscript [1, 2, 3, -4]
działa MyFourthClassWithPreambleAndPostscript [1, 2, 3, 4]
postscript MyFourthClassWithPreambleAndPostscript [1, 2, 3, 4][/text]

Mimo że każda klasa (1, 2, 3, 4) miała zdefiniowaną metodę `postscript’.

Jawne wywołanie metody z klasy nadrzędnej

Mechanizmy tworzące klasy w Dojo pozwalają także na jawne wywołanie metody z klasy nadrzędnej. Służy do tego metoda `this.inherited’.

Brzmi jak czary? No, może trochę. Jest to bardzo ciekawy mechanizm, o którym warto pamiętać. Nie wchodząc w szczegóły jak to jest rozwiązane (ma to związek z obiektem [array-like object] `arguments’, który posiada pole `callee’ – zobacz w kodzie frameworka), wykorzystuje się to w ten sposób:

Klasa A:

[javascript]dojo.provide("yarpo.A");

dojo.declare("yarpo.A", null,
{
beMyExample : function()
{
console.log(‚Jestem metodą A::beMyExample’,
this.varDefinedByClassB);
}
});[/javascript]

Klasa B:

[javascript]dojo.provide("yarpo.B");
dojo.require("yarpo.A");
dojo.declare("yarpo.B", [yarpo.A],
{
varDefinedByClassB : ‚zmienna zdefiniowana w klasie B’,
beMyExample : function()
{
console.log(‚Jestem metodą B::beMyExample’,
this.varDefinedByClassB);
this.inherited(arguments);
}
});[/javascript]

demo online

Wynik działania:

[text]Jestem metodą B::beMyExample zmienna zdefiniowana w klasie B
Jestem metodą A::beMyExample zmienna zdefiniowana w klasie B[/text]

Widać zatem, iż metoda z klasy bazowej zostaje wywołana w kontekście klasy, z której jest wywoływana – dzięki czemu posiada dostęp do zmiennych i metod tej klasy (w powyższym przypadku metoda `A::beMyExample’ posiada dostęp do zmiennej `this.varDefinedByClassB’, któej nie ma w normalnych warunkach w klasie `yarpo.A’).

Warto przeczytać:

Zmiany w wersji 1.6

W wersji 1.6 Dojo Toolkit został wykorzystany inny mechanizm tworzenia klas, wykorzystujący AMD. Więcej na ten temat można przeczytać w innym wpisie oraz  dokumentacji Dojo:

Póki co jednak (chyba będzie tak do wersji 2.0) opisany sposób tworzenia klas jest w pełni poprawny i nadal wspierany.

2 odpowiedzi do “Tworzenie klas w Dojo (stary mechanizm < 1.6)”

  1. Ale co konkretnie jest czarną magią 🙂 ?

    Myślę, że analizując spokojnie krok po kroku okaząłoby się, że to nie jest takie trudne 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *