Kwestia wyglądu aplikacji internetowych zawsze pozostawała niedoceniana przez twórców różnych technologii programistycznych. To jednak powoli się zmienia. Powstają frameworki JavaScript tworzone specjalnie z myślą o prostym tworzeniu efektownych interfejsów. Jednym z nich jest Ext JS. Będziesz zachwycony jego możliwościami!
Jakiś czas temu można było odnieść wrażenie,
że technologia JavaScript okres dynamicznego
rozkwitu ma już za sobą. Znane
i popularne przed laty skrypty DHTML zostały
zmarginalizowane, a sam JavaScript sprowadzony
do roli jednego z elementów AJAX-a. Okazuje się
jednak, że JavaScript wciąż żyje i stanowi podstawę
gotowych do użycia frameworków.
Ext JS jest jednym z nich. Został przygotowany
jako narzędzie do tworzenia interfejsów dla
wszelakich systemów internetowych. W mniejszym
stopniu przyda się on podczas tworzenia klasycznych
stron internetowych. Jego domeną są właśnie
systemy – rozmaite panele administracyjne, CMS-y
oraz aplikacje internetowe. Wykorzystanie w nich
Ext JS może okazać się strzałem w dziesiątkę.
Użytkownicy pokochają wygląd dostarczany przez
framework, nawiązujący sposobem obsługi do
standardowych aplikacji desktopowych.
Dla webmasterów tworzących programy w modelu
MVC jest to też dobry sposób na oddzielenie
warstwy prezentacji od warstwy logiki biznesowej.
Ext JS zajmuje się wyłącznie wizualizacją danych,
podczas gdy właściwe działania realizowane są
w tle, po stronie serwera. Ext JS potrafi na bieżąco
komunikować się z serwerem, bez przeładowywania
całej strony, wykorzystując do tego celu
wbudowaną implementację XML oraz AJAX-a. Jest
też niezależny od platformy systemowej dostępnej
na serwerze i od wykorzystywanego języka
programowania.
W tym artykule prezentujemy przegląd możliwości
systemu Ext JS wraz z przykładami obsługi
kilku podstawowych komponentów i kontrolek.
Pokażemy, w jaki sposób możesz tworzyć aplikacje
z wykorzystaniem tego interesującego frameworka.
Rozpoczynanie pracy z Ext JS
Aby rozpocząć pracę z Ext JS, wystarczy wejść
na stronę http://www.extjs.com i pobrać aktualną
wersję frameworka w wersji SDK. Najnowszą
dostępną w chwili zamykania numeru wersję 2.1
znajdziesz na naszej płycie CD.
Pobraną paczkę rozpakuj w katalogu domowym
tworzonego projektu (w naszych przykładach
wgraliśmy je do podkatalogu ext-2.1), a następnie
utwórz ramową stronę HTML, załączając potrzebne
pliki JS:
Definicje wyglądu i zachowania interfejsu dla
Ext JS zostały w powyższym przykładzie wyłączone
do osobnego pliku skrypty.js, a ewentualne
dodatkowe style do style.css. Te pliki będziesz za
chwilę edytować.
Zauważ ponadto, że załączony został skrypt
ext-all-debug.js. Nie stosuj go jednak w gotowych
aplikacjach. Jest to skrypt przydatny podczas
tworzenia oprogramowania (dostarcza dodatkowych
możliwości wyłapywania błędów). Jednak
w środowisku produkcyjnym warto podmienić tę
linię na odpowiednik – załączając plik ext-all.js.
Ponieważ przygotowałeś już ramową stronę
HTML, czas przejść do właściwej obsługi frameworka.
Layout, czyli rama interfejsu
Tworząc pełnookienkową aplikację, podstawową
decyzją jest wybór odpowiedniego do projektu
układu zawartości, czyli tak zwanego layoutu.
Osoby, które miały już wcześniej do czynienia
z tworzeniem okienkowych interfejsów w środowiskach
typu Visual Studio, NetBeans, Eclipse
i podobnych, powinny definicję layoutów z Ext JS
przyjąć niemal intuicyjnie.
Mówiąc najkrócej, layout definiuje układ
treści w oknie. Dostarcza kontenerów, w których
będziesz mógł umieścić właściwe komponenty.
Oto krótki przegląd kilku najważniejszych układów
treści dostępnych w Ext JS:
Fit Layout – jest najprostszym dostępnym układem.
Znajdzie zastosowanie tam, gdzie zamierzasz
umieścić tylko jeden komponent. Kontener \”fit\”
dostosuje się automatycznie do rozmiarów okna
lub innego elementu, wewnątrz którego został
umieszczony.
Border Layout – jest to bardzo popularny
układ, zgodnie z którym poszczególne komponenty
są umieszczane na obrzeżach okna oraz w jego
centralnej części. Programista ma do dyspozycji
cztery kontenery na każdym z brzegów (nazywanych
zgodnie ze stronami świata – północnym,
wschodnim, południowym i zachodnim) oraz
wspomniany już kontener znajdujący się w centrum.
Występowanie każdego z kontenerów brzegowych
jest opcjonalne. Razem daje to możliwość
umieszczenia do pięciu komponentów w ramach
pojedynczego Border Layout.
Tab Panels Layout – układ, w którym treść
umieszczana jest na poszczególnych kartach.
Użytkownik, przełączając się pomiędzy kartami,
uzyskuje dostęp do kolejnych odsłon zawartości
takiego układu.
Card Layout – podobnie jak Tab Panels
Layout pozwala umieszczać treść na kilku kartach.
W przypadku Card Layout nie występuje jednak
nawigacja pomiędzy kartami w postaci widocznych
zakładek. Widoczną kartę można zmienić programowo
lub korzystając z kontrolek typu \”następny\”
czy \”poprzedni\”. Ten typ layoutu jest często wykorzystywany
do tworzenia rozmaitych kreatorów.
Form Layout – układ dedykowany do umieszczania
wewnątrz elementów formularzy (takich
jak pola tekstowe, pola wyboru i inne). Przydaje
się tam, gdzie będziesz chciał szybko stworzyć
formularz przyjmujący określone dane.
Oprócz tego istnieje jeszcze kilka innych
układów, które przydają się w specyficznych
okolicznościach. Pominiemy w tym miejscu ich
charakterystykę.
Border Layout w trybie wypełniającym całą
dostępną przestrzeń (tzw. viewport). Jako wypełnienie
kontenerów wstawiona została treść tekstowa
Poszczególne układy mogą być zagnieżdżane
jedne w drugich. Łatwo można wyobrazić sobie
sytuację, w której jako element nadrzędny znajduje
się Border Layout, w jednym z jego kontenerów
umiejscowiony został Tab Panel, w innym z kolei
Form Layout. Podobne kombinacje można stosować
niemal dowolnie, dlatego jeśli jesteś zaznajomiony
z tworzeniem interfejsów w nowoczesnych
środowiskach zintegrowanych, to powinieneś dość
swobodnie czuć się w świecie Ext JS. A nawet jeśli
temat ten jest dla ciebie nowinką, to jego zgłębienie
i tak nie powinno sprawić ci dużych trudności.
Tworzenie layoutu
Skoro już wiemy, czym są layouty, czas
zastosować tę wiedzę w praktyce. Całość obsługi
frameworka dokonywana będzie z poziomu odpowiednich
instrukcji JavaScript, dlatego kluczowym
elementem tworzonego systemu jest plik skrypty.js
dołączony do projektu.
Większość zachowań będziemy definiować wewnątrz
zdarzenia Ext.onReady. Jest to chyba najważniejsze
zdarzenie dostarczane przez silnik frameworka.
Jest ono wywoływane w sytuacji, gdy biblioteka
Ext JS została poprawnie załadowana i zainicjowana,
czyli jest gotowa i oczekuje na twoje instrukcje.
Oto najprostszy przykład obsługi tego zdarzenia.
Dzięki poniższym instrukcjom będziesz mógł
zweryfikować, czy dotychczasowe kroki zostały
wykonane poprawnie:
Ext.onReady(function() {
alert(\"To działa!\");
});
A oto jak można zrobić to samo, ale
bardziej w stylu Ext JS:
Ext.onReady(function() {
Ext.MessageBox.show({
title: \'Okno testowe\',
msg: \'Zawartość okna\',
width: 300,
buttons: Ext.MessageBox.OK
});
});
Czas na bardziej ambitny przykład. Wstawimy
Border Layout w taki sposób, aby wypełnił całe
okno przeglądarki. Na początek musisz stworzyć
tablicę obiektów definiujących layout – będą
to obiekty klasy Panel. Definicję wpisz do Ext.
OnReady.
var panel1;
var panel2;
var panel3;
var borders = [new Ext.Panel({
layout:\'fit\',
title: \'Poludnie\',
region: \'south\',
margins: \'0 5 5 5\',
height: 100,
minSize: 75,
maxSize: 250,
collapsible: true,
split:true,
items: panel1
}),
new Ext.Panel({
layout:\'fit\',
title:\'Zachod\',
region:\'west\',
margins: \'5 0 0 5\',
cmargins: \'5 5 0 5\',
width: 200,
minSize: 150,
maxSize: 300,
split:true,
items: panel2
}),
new Ext.Panel({
layout:\'fit\',
title: \'Centrum\',
region: \'center\',
margins: \'5 5 0 0\',
items: panel3
})
];
Do szczegółowego omówienia powyższego
kodu wrócimy za chwilę. Wyświetlenie zawartości
layoutu w całym oknie przeglądarki można zrealizować
na kilka sposobów. Najprościej poprzez
klasę ViewPort dostarczaną przez bibliotekę Ext JS.
Reprezentuje ona cały widoczny obszar przeglądarki.
Poniżej stworzonych już instrukcji dodaj nowy
kod tworzący obiekt klasy Ext.ViewPort:
var vp = new Ext.Viewport({
layout:\'border\',
items: borders
});
Na koniec tak powstały obiekt należy jeszcze
wyrenderować. Służy do tego metoda render(): vp.render();
W tym momencie powinieneś już widzieć utworzony
kontener w przeglądarce. W kilku słowach
postaramy się go scharakteryzować.
Każdy z regionów (kontenerów) jest w rzeczywistości
obiektem klasy Ext.Panel. Tablicę obiektów dołączamy do Viewport poprzez atrybut items.
Ponieważ ViewPort nadaliśmy layout o nazwie
\”border\”, system ułoży poszczególne komponenty
w układ charakterystyczny dla \”Border Layout\”.
Jednakże już dla poszczególnych obiektów wewnątrz
Border Layout został użyty layout o nazwie
\”fit\”. To po to, aby całkowicie wypełniły dostępną
przestrzeń. Możesz domyślać się, że w ten
sposób zagnieździliśmy dwa layouty wewnątrz
siebie – przy czym drugi layout powstał w sposób
mniej jawny. W rzeczywistości Ext JS w większości
przypadków tworzy layouty automatycznie, tobie
pozostaje możliwość kontrolowania ich ręcznie
tylko w nielicznych przypadkach.
Przejdźmy dalej. Poszczególne obiekty zapisane
w zmiennej borders mają ustawionych kilka dodatkowych
atrybutów. Większość z nich jest dość
intuicyjna. Przyjrzyjmy się im po kolei:
title – definiuje tytuł widoczny na samej górze
danego komponentu.
region – jest to atrybut charakterystyczny dla
\”Border Layout\”, określa położenie komponentu
(np. \”west\” – oznacza lewą część okna).
margins – określa marginesy wewnątrz
komponentu.
height – domyślna wysokość komponentu
w pikselach (może ulegać zmianie).
minSize, maxSize – minimalny i maksymalny
rozmiar przewidywany dla danego komponentu.
W przypadku komponentów wschodniego
i zachodniego jest to w rzeczywistości szerokość,
a dla północnego i południowego – wysokość.
Framework będzie pilnować, aby nie wykroczyć
poza zadeklarowane ramy.
collapsible – jeśli ustawiony na true, da możliwość
\”zwinięcia\” komponentu w taki sposób, aby
widoczna pozostała tylko ramka z tytułem.
split – jeśli ustawiony na true, użytkownik
otrzyma możliwość samodzielnej zmiany rozmiaru
komponentu poprzez przeciąganie jego ramki.
Zwróć uwagę na to, że system nie pozwoli zmienić
rozmiaru poza ramy określone przez minSize
i maxSize.
items – w to miejsce możesz wpisać kolejne
kompenenty, które zostaną zagnieżdżone w kontenerze.
W zależności od wybranego dla komponentu
układu (layout), należy podać pojedynczy
komponent lub tablicę komponentów. W przykładzie
wybraliśmy układ \”fit\”, który przyjmuje tylko
jeden komponent. Na razie podane komponenty
są puste.
Fantastyczną cechą Ext JS są jego niesamowicie
szerokie możliwości konfiguracyjne. Powyżej
wymieniliśmy jedynie kilka spośród całej gamy
atrybutów dostępnych dla komponentu Ext.
Panel. A należy w tym miejscu zaznaczyć, że każdy
z pozostałych komponentów ma mnóstwo własnych
opcji, które możesz ustawiać! Aby poznać
je precyzyjnie, warto szczegółowo zapoznać się
z dokumentacją.
Wstawianie tekstu
Najprostszym sposobem wypełnienia zawartości
stworzonych kontenerów jest wstawienie do
nich tekstu. Żeby tego dokonać, wklej poniższy kod
w miejsce zadeklarowanych wcześniej zmiennych
panel1, panel2 oraz panel3:
var panel1 = new Ext.Panel({
layout:\'fit\',
html: \'To jest poludniowy komponent
\',
border: false
});
var panel2 = new Ext.Panel({
layout:\'fit\',
html: \'To jest zachodni komponent
\',
border: false
});
var panel3 = new Ext.Panel({
layout:\'fit\',
html: \'To jest centralny komponent
\',
border: false
});
W tej chwili zawartość wszystkich kontenerów
powinna być już wypełniona tekstem. Czas poznać
kolejny komponent dostępny w ExtJS.
Gridy, czyli tabele z dodatkową funkcjonalnością
Będą to tzw. gridy, czyli najogólniej mówiąc
tabele. Dalece odbiegają one jednak od tabel
znanych ze specyfikacji HTML. W zależności
od ustawień, gridy mogą przypominać bądź to
bardzo prosty układ tabelkowy, bądź zaawansowany
system przypominający nieco arkusz
kalkulacyjny, a nawet obsługiwać grupowania
wierszy. Jeśli dołączyć do tego możliwość dostosowania
wyglądu gridu po stronie użytkownika,
otrzymujemy bardzo potężne narzędzie do
prezentacji danych.
Jednak jeszcze przed utworzeniem gridu
warto zapoznać się ze sposobem tworzenia tzw.
składnic danych (ang. store), które stanowią
podstawę do funkcjonowania właściwych gridów,
przechowując dla nich dane. Najbardziej podstawowa
składnica dostępna w Ext JS może zostać
stworzona na podstawie danych dostarczonych
w formie tablicy JavaScript. Oto jak możesz ją
zaimplementować:
// Definiowanie wartosci w tablicy
var wartosci = [
[\'Cukier\', 3.50, 30,\'20-04-2008\'],
[\'Maly chleb\', 2.70, 15,\'23-04-2008\']
];
// Tworzenie skladnicy (store\'a)
var store = new Ext.data.SimpleStore({
fields: [
{name: \'produkt\'},
{name: \'cena\', type:\'float\'},
{name: \'ilosc\', type:\'float\'},
{name: \'dostawa\', type:\'date\', dateFormat: \'d-m-Y\'}
]
});
// Ladowanie danych do skladnicy
store.loadData(wartosci);
Powyższy kod najpierw stworzy tablicę
z wartościami (ważna jest kolejność elementów).
Następnie tworzony jest właściwy obiekt uproszczonej
klasy SimpleStore. Możesz tutaj zdefiniować
pola oraz ich typy. Nazwy pól będą używany przy
podłączaniu danych do gridów. Natomiast typ
będzie mieć znaczenie dla zachowania się określonego
pola (np. przy sortowaniu). Na koniec musisz
jeszcze załadować dane do składnicy, używając
metody loadData().
Czas na dodanie właściwego gridu:
var grid = new Ext.grid.GridPanel({
title: \'Magazyn\',
store: store,
columns: [
{id:\'produkt\',header: \"Produkt\", width: 160, sortable: true, dataIndex: \'produkt\'},
{header: \"Cena\", width:75, sortable: true, renderer: \'usMoney\',dataIndex: \'cena\'},
{header: \"Ilosc\",width: 75, sortable: true, dataIndex:\'ilosc\'},
{header: \"Ostatniadostawa\", width: 100, sortable: true,renderer: Ext.util.Format.dateRenderer(\'dm-Y\'), dataIndex: \'dostawa\'}
], autoExpandColumn: \'produkt\'
});
Znów otrzymujesz sporo rozmaitych atrybutów
do ustawienia. Najważniejszy jest store,
wskazujący zmienną, w której przechowywana
jest składnica danych, oraz columns, zawierający
tablicę z parametrami określającymi poszczególne
kolumny.
Same kolumny w powyższym przykładzie
charakteryzują się nazwą nagłówka (header), ustawioną
na stałe szerokością (width), możliwością
sortowania (sortable) oraz w pierwszym przypadku
wyraźnym wskazaniem identyfikatora pierwszej
kolumny (przydaje się przy podawaniu wartości
autoExpandColumn). Ponadto za pomocą parametru
dataIndex następuje powiązanie kolumny
z odpowiednią komórką w składnicy danych. Występuje
również element \”render\”, dzięki któremu
możesz poinformować framework, jak ma zostać
wyświetlona określona informacja.
Dodatkowy atrybut gridu o nazwie autoExpand-
Column informuje silnik Ext JS, że wskazaną kolumnę
ma poszerzyć tak, jak to tylko możliwe z zachowaniem
rozmiarów pozostałych kolumn. Jest to autodopasowanie
do szerokości całego komponentu.
Warto dodać, że ponownie system Ext JS
dostarcza znacznie większą liczbę atrybutów, pozwalając
na dużo dokładniejszą konfigurację. Aby
jednak poznać szczegóły, sięgnij po dokumentację.
Jeśli chcesz, by stworzony właśnie grid pojawił
się w przeglądarce, powinieneś jeszcze podpiąć go
w jakiś sposób do kontenera zagnieżdżonego już
w interfejsie. Proponujemy zamienić wystąpienie
panel3 w definicji obiektu borders na zmienną
\”grid\”, aby w ten sposób umieścić tabelę w środkowej
części okna.
Renderowanie komponentów do warstw HTML
W tym miejscu warto na moment się zatrzymać.
Ext JS to nie tylko system do tworzenia aplikacji
wypełniających całe okno przeglądarki. Poszczególne
komponenty możesz wplatać do środka zwykłej
strony HTML. Wystarczy stworzyć na takiej stronie
odpowiedni element (najlepiej warstwę), do wnętrza
którego wrzucony zostanie komponent.
Możliwość tę zademonstrujemy na przykładzie
stworzonego już gridu. Utwórz stronę HTML, która
będzie wyglądać mniej więcej w taki sposób:
Następnie w pliku skrypty.js, poniżej miejsca,
gdzie stworzony został obiekt w zmiennej grid, wywołaj
metodę render(), jako parametr przekazując
ID elementu w dokumencie, w którym ma zostać
umieszczony komponent. Oto przykład:
grid.render(\'warstwa\');
Jednocześnie usuń wszystkie instrukcje dotyczące
obiektu klasy ViewPort. W efekcie w przeglądarce
powinieneś zobaczyć stworzony wcześniej
grida, który znajduje się wewnątrz warstwy DIV.
Co więcej, dostosował się on do rozmiarów tej
warstwy. W podobny sposób możesz wstawić
dowolny inny komponent Ext JS na swoją stronę
internetową.
Dynamiczne pobieranie danych
Jedną z tych ciekawych możliwości, które
stanowią o potędze Ext JS, jest jego wbudowana
obsługa danych w formacie XML. Jak się za chwilę
przekonasz, możesz uczynić dynamiczne wprowadzanie
danych do gridów niezwykle prostym
w implementacji, szybkim i wygodnym. Podobne
zastosowanie XML znajdzie w formularzach. Przyjrzyjmy
się temu bliżej.
Stworzony dotychczas grid bazuje na danych
w postaci tablicy Java Script. Wadą tego rozwiązania
jest jego statyczność. Dane wprowadzone
zostały na stałe do kodu skryptu, więc aby je
zmienić, musiałbyś przeładować całą stronę. Dzięki
wykorzystaniu dynamicznego pobierania danych
w postaci XML, silnik frameworka odpyta podany
adres skryptu po stronie serwera, a następnie
sparsuje otrzymany dokument XML. Czynność
tę będziesz mógł powtarzać tak często, jak tylko
chcesz, za każdym razem odświeżając dane w tabeli
bez przeładowania całej strony!
Aby wdrożyć obsługę XML, będziemy potrzebowali
tylko drobnych zmian w obrębie definicji
składnicy danych.
var store = new Ext.data.Store({
url: \'produkty.xml\',
autoLoad: true,
reader: new Ext.data.XmlReader({
record: \'pozycja\'
},[\'produkt\',\'cena\',\'ilosc\',\'dostawa\'])
});
Powyższy kod pobierze dokument XML spod
adresu produkty.xml (adres względny w stosunku
do ścieżki, z której wywoływana jest strona). Ponieważ
dodatkowo użyliśmy właściwości \”autoLoad\”
ustawionej na \”true\”, dane zostaną załadowane od
razu po stworzeniu elementu. Aby móc wykorzystać
pobrane dane, należy zdefiniować obiekt klasy
XmlReader. Na wstępie wskazujemy, jak nazwane
zostały rekordy w pliku, a także jakie dane w obrębie
rekordu należy odczytać.
Oto jak może wyglądać przykładowy dokument
XML, który zostanie poprawnie odczytany przez
powyższy store:
Cukier
3.50
30
20-04-2008
Maly chleb
2.70
15
23-04-2008
Oczywiście sam XML może zostać wygenerowany
po stronie serwera dynamicznie. Użyta
technologia nie ma tutaj znaczenia.
Struktura drzewa
Poznałeś już elementy tabelowe typu grid. Innym
ciekawym komponentem dostarczanym przez
Ext JS są struktury drzewiaste. Przydają się one do
konstruowania rozmaitych zagnieżdżonych menu
oraz przeglądania drzewa folderów. Tworzenie
komponentu drzewa (ang. tree) nie jest bardziej
skomplikowane od tworzenia gridu.
var tree = new Ext.tree.TreePanel({
fitToFrame: true,
animate:true,
containerScroll: true,
autoScroll:true,
loader: new Ext.tree.TreeLoader({
dataUrl:\'menu.json\'
})
});
var root = new Ext.tree.AsyncTreeNode({
text: \'Produkty\'
});
tree.setRootNode(root);
root.expand(false, false);
Powyższy kod przygotuje obiekt, który będzie
odpowiadać za ładowanie drzewa. W rzeczywistości
są to dwa powiązane ze sobą obiekty. Pierwszy
to właściwy panel drzewa, natomiast drugi to
obiekt klasy AsyncTreeNode, który przechowuje
jego strukturę. Mógłbyś w tym miejscu zastosować
również klasę TreeNode. Różnica polega na tym, że
jedna pracuje w sposób asynchroniczny, a druga
standardowo.
W drzewie ustawionych zostało kilka właściwości:
animate – odpowiada za ustawienie animacji
obiektu. Jeśli wartość zostanie ustawiona na
true, gałęzie drzewa będą się płynnie rozwijać
i zwijać. W przeciwnym razie nastąpi to skokowo.
Drugi sposób warto stosować przy drzewach
zarządzanych programowo i na wolniejszych
komputerach, pierwszy – gdy drzewami steruje
sam użytkownik.
containerScroll – uruchamia możliwość
przewijania zawartości, jeśli wykracza poza ekran.
Warto stosować containerScroll razem z autoScroll.
autoScroll – opcja ta ma analogiczne działanie
do \”overflow: auto\” w CSS. Właściwość ta
odpowiada za ukrycie przewijania, gdy nie jest
potrzebne. Jeśli wartość jest ustawiona na true, to
w przypadku gdy treść tego komponentu wykroczy
poza standardowy rozmiar, wyświetlone zostaną
suwaki służące do przewijania. W przeciwnym
razie suwaki będą niewidoczne.
Na szczególną uwagę zasługuje właściwość
loader, w której podany został obiekt klasy TreeLoader.
O ile gridy uzupełniane były dokumentami
generowanymi w postaci XML, o tyle do wysyłania
dynamicznej zawartości do drzewa należy użyć
JSON. Format ten wysyła wprost JavaScriptową
tablicę spod wskazanego adresu URL. Plik JSON
może wyglądać na przykład tak:
[
{\"text\": \"Spożywcze\", \"leaf\": true},
{\"text\": \"Przemysłowe\", \"leaf\": true}
]
Co ważne, przed wyrenderowaniem drzewa
zawsze należy jeszcze wypełnić jego tzw.
rootNode, czyli główny węzeł struktury danych
(w terminologii struktur drzewiastych będzie to
korzeń). W tym celu zainicjowany został obiekt
AsyncTreeNode – tworzy on taki pierwotny i nadrzędny
węzeł struktury, nadając mu właściwość
\”text\” (widoczny napis) o treści \”Produkty\”. Dane
pobrane z JSON zostaną dołączone jako potomkowie
tego korzenia.
Powiązania powstałej struktury danych
z drzewem dokonuje się poprzez użycie metody
setRootNode(). Oprócz załadowania zawartości
do struktury danych, warto ją jeszcze rozwinąć,
aby już w czasie uruchamiania komponentu była
ona widoczna. W tym celu wywołana została
metoda expand() klasy TreeNode. Jako parametry
dwukrotnie podana została wartość false.
Pierwszy parametr odpowiada za zastosowanie rekurencyjnego
rozwijania lub nie – jeśli wartość byłaby
pozytywna (co jest domyślne), rozwinięci zostaliby
także potomkowie elementów z obiektu JSON (jeśli
istnieją), aż do samego końca.
Drugi parametr
wskazuje, czy rozwinięcia dokonać w sposób
animowany. Ponieważ dzieje się to podczas
uruchamiania programu, stosowanie takich ozdób
kosztem czasu jego całkowitego uruchomienia
wydaje się zbędne – stąd podanie wartości false.
Wygląd powyższego drzewa w praktyce zaprezentowano
na rysunku 8.
Aby wyświetlić drzewo w samej strukturze
strony, musisz dołączyć je do określonego kontenera.
Na przykład zmieniając odpowiednio definicję
zachodniego panelu w utworzonym uprzednio
Border Layout:
[...]
new Ext.Panel({
layout:\'fit\',
title:\'Zachod\',
region:\'west\',
margins: \'5 0 0 5\',
width: 200,
minSize: 150,
maxSize: 300,
split:true,
items: tree
}),
[...]
Wiesz już także, że można zastosować dalsze
elementy rozwijalne, poprzez wprowadzenie do
JSON definicji potomków (atrybut children). Oto
w jaki sposób może wyglądać przykładowy plik
zawierający potomków:
[
{ \"text\": \"Spożywcze\", \"leaf\": false,\"expanded\": true, \"children\":
[
{\"text\": \"Pieczywo\", \"leaf\": true},
{\"text\": \"Nabiał\", \"leaf\": true},
{\"text\": \"Słodycze\", \"leaf\": true}
]
},
{ \"text\": \"Przemysłowe\", \"leaf\": false,\"expanded\": true, \"children\":
[
{\"text\": \"Kosmetyki\", \"leaf\": true},
{\"text\": \"Proszki do prania\",\"leaf\": true}
]
}
]
Powyższy dokument JSON spowoduje wyświetlenie
drzewa pokazanego na rysunku 9.
Warto w tym miejscu wyjaśnić jeszcze kilka
drobnych kwestii. W dokumencie JSON pojawiały
się dwa dodatkowe, nieopisane dotąd atrybuty:
leaf – wskazuje, czy określony węzeł jest
liściem drzewa, czyli czy ma on potomków (jeśli
false, może mieć potomków).
expanded – jeśli ustawiony na true, gałąź
będzie rozwinięta. Zauważ, że skoro w samym
skrypcie JS podczas wywoływania metody expand()
podana została wartość false do pierwszego parametru
(brak rekurencyjnego rozwijania potomków),
to zadbaliśmy o komfort zarządzania rozwijaniem
z poziomu dokumentu JSON.
Dodatkową cechą wizualną odróżniającą liść
od struktur drzewa posiadających potomków
(parametr leaf) jest inna ikonka. Gdy zagłębisz się
w meandry Ext JS, poznasz sposoby na zmianę
domyślnych ikonek na własne.
Na koniec tego zagadnienia jeszcze jedna mała
ciekawostka świadcząca o potędze Ext JS. Wystarczy,
że dopiszesz tylko jedną linię:
new Ext.tree.TreeSorter(tree,{
folderSort:true
});
aby zawartość drzewa przechowywanego
w zmiennej tree została automatycznie posortowana
(łącznie z folderami, na co wskazuje wartość
folderSort). Czyż ten system nie jest wygodny?
Obsługa zdarzeń
W przypadku każdego frameworka programista
dociera do momentu, w którym standardowe
zachowania dostarczonych mechanizmów przestają
wystarczać. Wówczas należy je rozbudować
o własne. Podstawową możliwość w tym zakresie
daje potężny system zdarzeń, szeroko zaimplementowany
w Ext JS.
Można byłoby w tym miejscu długo wymieniać
listę obsługiwanych zdarzeń. Byłaby ona tym dłuższa,
im więcej komponentów zostałoby wziętych
pod uwagę – każdy dostarcza zdarzenia ściśle
wyspecjalizowane pod kątem własnej funkcjonalności.
Jako przykłady można podać zdarzenia
załadowania komponentu, kliknięcia, najechania
myszką, ale również bardziej zaawansowane, takie
jak rozwinięcie struktury drzewa, posortowanie
elementów, czy nawet bardzo wygodna obsługa
zdarzeń typu \”przeciągnij i upuść\”.
Implementowanie zdarzeń jest niezwykle
proste. Dla przykładu opracujemy prostą funkcję
obsługującą zdarzenie kliknięcia na element zdefiniowanego
uprzednio drzewa danych. W rzeczywistości
będziemy obsługiwać po prostu kliknięcie na
cały komponent. Zdarzenie to, zgodnie z terminologią
używaną w innych środowiskach programowania,
nosi nazwę \”onClick\”. Jako parametr funkcji
obsługującej to zdarzenie przekazywana jest
referencja do węzła drzewa, który został kliknięty.
tree.on(\'click\', function(node) {
alert(\'Kliknięto drzewo\');
});
Prawda, że proste? Rozbudujmy zdarzenie o realną
funkcjonalność. Chcemy, aby w zależności od
tego, jaki element drzewa został kliknięty, wyświetlona
została inna zawartość w naszym gridzie.
Wymaga to zmiany adresu URL w składnicy danych
zgodnie z informacją o tym, który węzeł został
kliknięty, a następnie przeładowania tej struktury
danych. Oto realizujący tę funkcjonalność kod:
tree.on(\'click\', function(node) {
if (node.attributes[\'text\'] ==\'Kosmetyki\')
newUrl = \'kosmetyki.xml\';
else if (node.attributes[\'text\'] ==\'Pieczywo\')
newUrl = \'pieczywo.xml\';
else return;
store.proxy = new Ext.data.HttpProxy({url: newUrl, method:\'get\'});
store.reload();
});
Po analizie kodu możesz zacząć zastanawiać
się, dlaczego jawnie utworzyliśmy tutaj obiekt klasy HttpProxy, podczas gdy wcześniej,
podczas definiowania składnicy danych, nie
było to konieczne. Otóż użyliśmy wówczas
właściwości \”url\” obiektu klasy Store. Ext JS
w trakcie inicjowania tego komponentu utworzył
HttpProxy za nas – w tle. Sztuczka ta już się nie
powiedzie, jeśli dokonujesz zmian URL ręcznie
– musisz samodzielnie zadbać o dostarczenie
właściwego obiektu klasy DataProxy (HttpProxy
po niej dziedziczy). Warto dodać, że ładowany
dokument XML musi istnieć – w przeciwnym razie
przeładowywanie składnicy danych zakończy
się porażką.
Inna ciekawa sztuczka, o której powinieneś
wiedzieć, to możliwość odwołania się poprzez
tablicę attributes do dowolnego argumentu dla
danego elementu drzewa z pliku JSON. Oznacza
to, że możesz tworzyć własne argumenty, a następnie
się do nich odwoływać. Oto przykładowy
plik JSON:
[
{\"text\": \"Spożywcze\", \"leaf\":true, \"url\": \"spozywcze.xml\"},
{\"text\": \"Przemysłowe\", \"leaf\":true, \"url\": \"przemyslowe.xml\"}
]
Oraz odpowiednio zmodyfikowane zdarzenie,
ładujące dane do składnicy na podstawie przekazanego
parametru o nazwie \”url\”:
tree.on(\'click\', function(node) {
newUrl = node.attributes[\'url\'];
store.proxy = new Ext.data.HttpProxy({url: newUrl, method:\'get\'});
store.reload();
});
Pozostała funkcjonalność
W jednym artykule nie sposób szczegółowo
opisać wszystkich możliwości, komponentów,
metod i zdarzeń dostarczanych przez tak rozbudowany
framework. Mamy jednak nadzieję, że udało
nam się zademonstrować prostotę tworzenia interfejsów
z wykorzystaniem Ext JS. Na zakończenie
prezentujemy krótki opis pozostałych dostępnych
możliwości.
Ext JS posiada własną obsługę formularzy,
zupełnie niezależną od języka HTML. Do formularzy
dołączona jest wygodna obsługa przesyłania
informacji na serwer, a nawet ciekawe funkcje
do natychmiastowej weryfikacji wprowadzanych
danych. Mało tego – formularz możesz uzupełniać,
korzystając z pobranego z serwera dokumentu XML!
Jeśli w dowolnym momencie pracy z systemem
ExtJS dojdziesz do wniosku, że standardowe
mechanizmy synchronizacji danych z serwerem nie
są wystarczające, w każdej chwili możesz wprost
skorzystać z wbudowanej obsługi AJAX-a.
Otrzymujesz również do dyspozycji wiele
innych niewymienionych w artykule komponentów
i kontrolek. Są wśród nich m.in.: wbudowane
okienka Ext JS wraz z możliwością ich wypełniania
dowolną zawartością (cały czas pozostając wewnątrz
właściwego okna przeglądarki), kontrolka
kalendarza pomagająca wybierać daty, rozmaite
menu rozwijalne, w tym menu kontekstowe,
własne paski narzędziowe, obsługa paska statusu
oraz suwaków.
Także wymienione już komponenty mają dużo
szerszą funkcjonalność. W szczególności gridy
dostarczają wielu rozmaitych układów treści
i zachowań. Zaczynając na wyglądzie klasycznej
tabelki, poprzez różne formy zwijania i grupowania
elementów, paginację (podział treści na kilka podstron),
filtrowanie czy sortowanie.
Część spośród wymienionej funkcjonalności jest dostarczana
bezpośrednio użytkownikowi w postaci kontrolek
– może on sam sortować dane, filtrować je wedle
określonych kryteriów, a nawet zmieniać kolejność
kolumn lub część z nich ukrywać. Oczywiście
każdym dostępnym zachowaniem komponentu
można sterować.
Jak widzisz, Ext JS jest frameworkiem o naprawdę
potężnych możliwościach. Dlatego mamy
nadzieję, że już wkrótce w polskim internecie
zobaczymy wiele ciekawych systemów bazujących
na tym interfejsie.