Rafał Wrzeszcz - Wrzasq.pl

Przegląd frameworków JavaScript: dojo

Wednesday, 10 June 2009, 03:51

Frameworki JavaScript

JavaScript jest obowiązkowym elementem każdej nowoczesnej aplikacji webowej. Ja sam jestem w nim powiedzmy średnio-zaawansowany: tego co jest potrafię używać do czego potrzeba, ale poezji w nim nie stworzę. We własnych aplikacjach piszę kod dość łopatologicznie, natomiast kiedy tworzę coś, co musi zadziałać i to sprawnie, wykorzystuję framework, do którego akurat jest dostępne przystępne case study projektów podobnych do tego, co właśnie muszę wyposażyć w interfejs wygodniejszy niż czysty XHTML+CSS. Jednak z czasem stało się to niewygodne, bo zamiast przyzwyczajać się chociaż do jednego frameworka, za każdym razem zaczynałem pracę od nowa. Postanowiłem wstawić w swój silnik webowy jakiś konkretny framework. Postanowiłem przejrzeć 4 popularne frameworki - dojo, jQuery, MooTools i Prototype. Początkowo brałem też pod uwagę YUI, jednak po przejrzeniu dokumentacji i przykładów doszedłem do wniosku, że jest on nieadekwatny do moich potrzeb. To czego potrzebowałem nie jest tam wcale uproszczone (na przykład brak wsparcia dla przesyłania danych za pośrednictwem XHR wykorzystując JSON, a sam framework jest niewątpliwie stworzony z myślą o wysoce skalowalnych, dużych serwisach - ja JavaScript wykorzystuję na mniejszą skalę. Postanowiłem się więc podzielić swoimi spostrzeżeniami na temat wspomnianych bibliotek. Nie będą to tutoriale, ale moje subiektywne oceny, może pomogą komuś w podjęciu decyzji.

Metodologia

Wspomniane frameworki realizują zazwyczaj jeden z modeli - albo rozszerzają istniejące prototypy natywnych obiektów (tak działają MooTools i Prototype), albo tworzą nakładki jako oddzielne środowiska (tak z kolei działają dojo i jQuery). Pierwszy sposób ma pewną wadę - niestety, ale Internet Explorer dopiero w wersji ósmej udostępnia natywnie prototypy obiektów DOM, dlatego, aby korzystać z dobrodziejstw dwóch pierwszych toolkitów w IE należy inicjować każdy węzeł drzewa DOM. Ma to jednak tę zaletę, że kod jest bardziej "naturalny". Drugi rodzaj frameworków ma tę przewagę, że w dużym stopniu uniezależnia aplikację nie tylko od przeglądarki, ale wręcz od całego środowiska pracy - im bardziej framework zastępuje zewnętrzną i domyślną funkcjonalność, tym bardziej aplikacja jest niezależna od pozostałych czynników.

Ja preferuję pierwszy model - kiedy piszę w JavaScripcie, to chcę pisać w nim i tyle. Nie chcę się uczyć kolejnego języka, jakim niemal nieraz stają się frameworki (szczególnie ma to miejsce w przypadku jQuery). Jednak najgorsze jest mieszanie tych stylów. Nieraz korzystałem na przykład z dojo i mimo iż przeszkadzał mi trochę styl kodu, to najważniejsze, że był on spójny - nie musiałem uzupełniać funkcjonalności frameworka domyślnymi metodami udostępnianymi przez przeglądarkę.

Moje kryteria

Tak jak wspomniałem nie wykorzystuję JavaScriptu na potęgę. Używam go tam, gdzie potrzebuję. Dlatego wydajność frameworku nie jest dla mnie tak krytyczna - moje aplikacje nie obciążają przeglądarki tak mocno, aby nie mogła czegoś przełknąć. Ważniejszy jest dla mnie mój własny czas - nie chcę tracić długich godzin na wynajdywanie koła po raz drugi. Jeśli ktoś potrzebuje testów wydajnościowych, to tutaj jest wynik testów SlickSpeed. Oprócz tego tak jak napisałem w poprzednim punkcie, preferuję frameworki spójne i przejrzyste - nie chcę w co drugiej linijce skakać jakimś dolarkiem w zupełnie nie znany mi zasięg bo nie wiem nawet co dany framework robi - chcę, żeby praca, którą zadam była zrobiona i nic ponadto. Nie chcę tracić czasu na tworzenie czegoś, co już jest dawno zrobione, ale chcę wiedzieć co się dzieje. Innymi słowy framework ma robić to co ja chcę, a jak się przekonałem nie zawsze jest to takie oczywiste.

Wybrałem 4 jak mi się wydaje najpopularniejsze biblioteki (plus YUI, ale jak już napisałem postanowiłem go odrzucić). To również jest dla mnie ważne. Niszowe produkty są niepewne: jeśli nie zaczną być popularne - upadną, a ja nie mam zamiaru zmieniać silnika aplikacji JavaScript znowu co pół roku. Wszystkie wybrane przeze mnie mają długą historię, są zakorzenione na rynku, mają dużą społeczność i wszystko to gwarantuje, że będą dalej rozwijane, a potencjalne błędy naprawiane.

dojo

Pierwszym frameworkiem z listy, którego przerobiłem był dojo. To bodaj najstarszy ze wszystkich wymienionych z YUI włącznie. Korzystałem z niego już kilka razy. Jest to dojrzałe rozwiązanie, rozwijane według bardzo zdefiniowanych wzorców i zapewniające kompleksową funkcjonalność. Wszystko co dostarcza toolkit jest zawarte wewnątrz przestrzeni nazw dojo., tak więc nie następuje kolizja z żadną inną biblioteką. Co ważne dojo posiada wrappery na bardzo wiele standardowych wywołań typu dojo.byId(), dojo.addOnLoad(), dojo.create() tak więc jeśli stworzymy kod dobrze, nie będziemy musieli korzystać z niczego poza dojo.

Jednak sposób w jaki biblioteka realizuje pewne sprawy nieco odbiega od powszechnie znanego schematu projektowania obiektowego. Niestety aby wykonać tutaj operacje na bazowych typach musimy odwoływać się do metod dojo, a nie do metod obiektów tych typów, na przykład:

// zwraca [2, 3, 4, 5]
dojo.map([1, 2, 3, 4], function(item) { return item + 1; } );

// we frameworkach typu Prototype/MooTools:
//[1, 2, 3, 4].map( function(item) { return item + 1; } );

Integracja z kodem

Biblioteka bardzo dobrze integruje się z kodem. Zazwyczaj nie wymaga żadnych modyfikacji na stronie aby dodać do niej warstwę JavaScript. Niestety jednak nie zawsze. W przeciwieństwie do pozostałych frameworków w niektórych przypadkach (szczególnie w przypadku widgetów) potrzebna jest edycja strony i to do formy niezgodnej ze standardami W3C (dodawanie własnych atrybutów do elementów). Szkoda, że nie wykorzystano przy tym przestrzeni nazw XML.

Ładowanie dojo na stronie jest bardzo proste. Wystarczy dodać plik główny (dojo.js). Zależności zazwyczaj ładują się na bierząco. W razie potrzeby możemy również jawnie załadować bibliotekę, którą będziemy chcieli dodatkowo użyć w naszym kodzie dzięki dojo.require():

dojo.require("dijit.Dialog");
var dialog = new dijit.Dialog();

Praca z drzewem DOM

Oczywiście podstawowa sprawa, którą silniki JavaScript stron muszą wykonywać to modyfikacja i przeszukiwanie drzewa dokumentu. W przypadku dojo mamy do dyspozycji pełen serwis - od wrapperów na najbardziej podstawowe metody jak dojo.byId(), czy dojo.create(), aż po pełną paletę metod do pracy z drzewem przy pomocy dojo.NodeList. Klasa ta implementuje tak zwany fluent interface, co umożliwia łańcuchowanie metod (każda metoda obiektu tej klasa zwraca ten obiekt, dzięki czemu można podpiąć następne wywołanie bezpośrednio pod poprzednie zamiast odwoływać się ciągle do obiektu).

dojo.query()

Na pewno najczęściej używaną funkcją jest dojo.query(). Funkcja ta zwraca listę elementów (wspomniane dojo.NodeList). To właśnie dzięki niej możemy szybko wyszukiwać elementy w drzewie i wykonywać na nich operacje. Możemy wyszukiwać korzystając niemal z każdego selektora CSS3. Niestety w trakcie mojej pracy nieraz zdarzały się w niektórych momentach dziwne rzeczy - na przykład:

// to nie chciało działać
// mimo, że masksTable.tHead jak najbardziej jest tym, czym powinno
dojo.query("a", masksTable.tHead);
// a to zadziałało
dojo.query("#masksTable thead a");

To zazwyczaj detale, jednak dość denerwujące szczególnie, że po dojo spodziewałem się niezawodności na poziomie bliskim zupełnemu. Kiedy już korzystam z frameworka, to chcę, żeby robił to co mu mówię (piszę).

DomNode, NodesList, i inni

Inny problem to rozbieżności wynikające z drogi, jaką podążają deweloperzy dojo. Ponieważ tworzą zupełnie odrębne wrappery dla swojej funkcjonalności, elementy samego drzewa DOM są od tej funkcjonalności całkiem oderwane. Powoduje to zazwyczaj konieczność niemal w nieskończoność od nowa zamykania elementów wewnątrz funkcjonalności dostarczanej przez bibliotekę. Na przykład tworząc element musimy i tak wywołać na nim potem dojo.query(), a potem jeszcze raz dla na przykład rodzica. Również wykonując operacje bezpośrednio na elementach listy dojo.NodeList pobieramy pojedyncze elementy DOM i żeby korzystać z dobrodziejstw naszego toolkitu musimy ponownie na każdym z tych elementów wywołać rzeczoną funkcję. W efekcie zamiast uproszczenia kod staje się nieczytelny i pogmatwany:

dojo.query("#masksTable thead a").forEach( function(node) {
    dojo.query(node)/* tutaj operacje na pojedynczym elemencie */;
} );

Jest to szczególnie uciążliwe, kiedy korzystamy z dojo.byId() i/lub dojo.create() - szkoda, że elementy, które zwraca samo dojo nie są w żaden sposób domyślnie obudowane w jego funkcjonalność.

Obsługa zdarzeń

Ten element dojo stoi na bardzo wysokim poziomie. Ogólnie rzecz biorąc zdarzenia obsługujemy bindując je przy pomocy dojo.connect(). Pomijając bardzo szerokie możliwości różnych wariantów wywołania, oczywiście oprócz tego mamy do dyspozycji helpery dla podstawowych zdarzeń takie jak click().

Każda metoda obsługi zdarzenia otrzymuje jako argument obiekt samego zdarzenia, dzięki czemu możemy w pełni decydować o działaniu przeglądarki. W szczególności możemy zablokować wykonywanie domyślnej akcji (czyli na przykład przekierowania pod adres łącza po kliknięciu) wywołując metodę preventDefault() zdarzenia.

XHR/AJAX

Obok pracy z drzewem dokumentu, najważniejszą funkcjonalnością, jaką musi wykazać się biblioteka JavaScript jest obsługa obiektu XHR, czyli tego, co potocznie zazwyczaj jest nazywane AJAX, mimo iż zazwyczaj nie jest to tak na prawdę AJAX (przeważnie zamiast XML wykorzystuje się format JSON, a zapytanie wcale nie jest asynchroniczne, ale tak już się przyjęło - nie pytajcie). Tutaj również dojo jest niezwykle wygodne, proste i intuicyjne. Mamy do dyspozycji szeroką gamę ułatwień w stylu dojo.xhrGet(), dojo.xhrPost(), dojo.xhrPut() i dojo.xhrDelete(), albo też bardziej elastyczne dojo.xhr(). Całość obsługi zapytania jest wysoce zautomatyzowana - przekazujemy po prostu obiekt z ustawionymi odpowiednimi polami odpowiadającymi parametrom zapytania. Nasz toolkit potrafi automatycznie pobrać zawartość całego formularza jako zapytanie, sam potrafi przetworzyć odpowiedź w zależności od typu danych (potrzebne jest do tego przesyłanie odpowiednich nagłówków). Przeszkadzać może jedynie brak możliwości nadpisywania pewnych danych, gdy korzystamy z przesyłania całego formularza - moim zdaniem w takim wypadku dane z formularza powinny mieć niższy priorytet niż ustawienia przekazywane bezpośrednio do obiektu XHR. No cóż, kolejny nieco irytujący szkopuł, ale nie zmienia to faktu, że obsługa XHR jest bardzo łatwa:

dojo.xhrGet( {
    url: "/ajax.php",
    content: {
        action: "ManageMasks",
        page: masksTable.pagination.page,
        by: masksTable.by,
        order: masksTable.order
    },
    handleAs: "json",
    load: function(data) {
        masksTable.update(data.MasksList.data);
    }
} );

Widgety i dodatki

Obok bardzo bogatej i rozbudowanej funkcjonalności dotyczącej standardowych operacji dojo oferuje dużo więcej - posiada gotową bibliotekę widgetów (dijit), oraz rozszerzenia do pracy z zewnętrznymi mediami i zasobami (dojox). Myślę, że nie ma sensu nawet próbować ich tutaj przedstawić, gdyż dostępne są przeróżne rozszerzenia i jest ich na prawdę sporo. Od zwykłego okienka dialogowego, przez "upiękrzacze" formularzy, aż po gotowe widgety do generowania drzewek i datagridów. To wszystko sprawia, że dojo z marszu gotowe jest do pracy z wieloma usługami, nadaje się do tworzenia nawet złożonych konstrukcji wymagających aplikacji. Żaden inny framework nie jest dostarczany z taką ilością gotowych rozwiązań out of the box.

Moja opinia

Moim zdaniem dojo to bardzo dobry produkt. Jest kompletny i przemyślany, całość jest spójna. Ma ogromne możliwości - z branych przeze mnie pod uwagę czterech frameworków największe. Bogate wyposażenie standardowe sprawia, że można przy jego pomocy szybko zdziałać prawdziwe cuda. Bez wątpienia do średnio i bardzo zaawansowanych projektów nadaje się bardzo dobrze, a ze wszystkich wymienionych tutaj bibliotek jest moim zdaniem najlepszy. Tyle, że tak jak powiedziałem, mi bardziej pasują małe biblioteki, które idą inną drogą - rozszerzają standardowy model danych dostępny w przeglądarce. Do codziennej pracy dojo jest dla mnie zbyt przytłaczający - przy pomocy MooTools, czy Prototype jestem w stanie zrobić proste rzeczy znacznie szybciej.

O pozostałych frameworkach napiszę niedługo.

MooTools
Prototype

Tags: Web, Teksty, JavaScript, AJAX, DOM