Blog webdeveloperski Patryk yarpo Jar

Tworzenie klas w Dojo (stary mechanizm < 1.6)

Autor wiadomości Październik 11, 2011

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:

// 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);
    }
});

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

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

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:

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

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`):

dojo.provide('yarpo.MySecondClass');

dojo.require('yarpo.MyFirstClass');

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

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:

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

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

działa []
działa MySecondClass []

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:

var obj = new yarpo.MyThirdClass(1,2,3);

Odebranie w konstruktorze - sposób 1:

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

Sposób 2:

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);
    }
});

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:

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

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:

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);
    }
});

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:

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

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:

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);
    }
});

demo online

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

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]

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:

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);
    }
});

demo online

Zwróci taki oto wynik:

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]

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:

dojo.provide("yarpo.A");

dojo.declare("yarpo.A", null,
{
    beMyExample : function()
    {
        console.log('Jestem metodą A::beMyExample',
        this.varDefinedByClassB);
    }
});

Klasa B:

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);
    }
});

demo online

Wynik działania:

Jestem metodą B::beMyExample zmienna zdefiniowana w klasie B
Jestem metodą A::beMyExample zmienna zdefiniowana w klasie B

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.

Komentarze (2) Trackbacks (0)
  1. Dla mnie top i tak czarna magia. Podziwiam

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

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


Leave a comment

 

Brak trackbacków.