Blog webdeveloperski Patryk yarpo Jar

Dijit.Tree i lazy loading

Autor wiadomości Wrzesień 8, 2011

Lazy loading (z ang. późne ładowanie danych - w wolnym tłumaczeniu) pozwala na pobranie do aplikacji z serwera tylko tych danych, które są nam aktualnie niezbędne. Takie podejście oszczędza pamięć i łącze (rzadko kiedy potrzebujemy wczytać cały zbiór danych). Niestety jednocześnie sprawia, że mamy więcej połączeń do serwera - każde zwraca kolejną małą porcję danych.

Chciałbym zaprezentować możliwości lazy loadingu w Dojo Toolkit.

Na początek

Drzewo dijit.Tree

O samym drzewie dijit.Tree pisałem już wcześniej. W tamtym wpisie wykorzystałem `dojo.data.ItemFileReadStore'. Tu wykorzystam `dojox.data.JsonRestStore', który implementuje wzorzec REST web services.

Aby wstawić drzewo na stronę wykorzystam taki kod:

dojo.addOnLoad(function()
{
  var store = new dojox.data.JsonRestStore(
    {
      target : 'resource/',
      labelAttribute : 'name',
      idAttribute : 'id'
    })
    , model = new dijit.tree.ForestStoreModel({
      store: store,
      deferItemLoadingUntilExpand: true,
      query : 'root',
      childrenAttrs: ['children']
    })
    , tree = new dijit.Tree(
    {
      model: model,
      showRoot: false
    }, 'tree');
});

demo online

Data Store

Wybrany data store implementuje dojo.data API:

  • Read
  • Write
  • Notification
  • Identity

Dzięki czemu można dokonywać na nim wszelkich operacji (odczyt, zapis, edycja, usuwanie, identyfikacja). Data store sam wie, kiedy konieczna jest komunikacja z serwerem, dla zsynchronizowania danych. Oczywiście po stronie serwera trzeba to odpowiednio obsłużyć. Polecam wykonać testy wszystkich operacji ze zwróceniem szczególnej uwagi na dane wysyłane na serwer (bardzo ładnie pokazuje to zakładka "Konsola" w Firebugu).

Konstruktor

Do konstruktora przekazać należy następujące dane (w dokumentacji znaleźć można inne przydatne opcje):

var store = new dojox.data.JsonRestStore({
    target : 'resource/',
    labelAttribute : 'name',
    idAttribute : 'id'
});
  • `target' wskazuje zasób, do którego ma się odwoływać data store. Tu jest to `resource/`. W dalszej części wpisu o kwestiach związanych z serwerem będzie więcej.
  • `labelAttribute' określa jakie pole z przesyłanych danych ma zostać wyświetlona jako etykieta węzła drzewa.
  • `idAttribute' określa jakie pole ma być identyfikatorem itema.

Format

Przesyłane dane powinny być poprawnym JSONem. Struktura jest prosta:

{
    idAtrribute : unikalna wartość,
    labelAtrribute : wartość,
    children : true lub tablica
}

Przykładowo (kod skopiowany z dema online dla żądania `resource/3`):

{
    "id":3,
    "name":"Zdzisiu",
    "children": [
         {"$ref":5,"name":"Józio"},
         {"$ref":6,"name":"Stefan"}
    ]
}

Zgodnie z danymi przekazanymi do konstruktora identyfikatorem jest pole `id', etykietą (label) jest `name'. Pole `children' wypełnione jest kolejnymi obiektami. Widać, że w nich zamiast `id' występuje pole `$ref' (są to namiastki prawdziwych itemów, dlatego są okrojone). W dzieciach nie występuje pole `children'.

Ważne: Pole `children' powinno być ustawiane na wartość `true' (w namiastce itema) lub powinno być tablicą namiastek (itemów z polami `$ref' odnoszącymi się do wartości ich `id').

W przypadku pierwszego wczytania zostaje wysłane żądanie `resource/{wartość atrybutu query}`, czyli w przypadku powyższego przykładu: `resource/root`. Jest to przydatna opcja, gdyż korzeń (korzenie, w końcu model mówi o lesie - ForesStoreModel) drzewa musi być zwrócony w postaci tablicy (nawet jeśli jest to tylko jeden element):

[
  {
    "id":1,"name":"Patryk yarpo Jar","children": [
        {"$ref":2,"name":"Jan"},
        {"$ref":3,"name":"Zdzisiu","children":true},
        {"$ref":4,"name":"Mietek","children":true}
     ]
  }
]

Struktura jest dokładnie taka sama, jak w przypadku `resource/1`, z tą różnicą, że całość opakowana jest w tablice.

Ustawienie lazy loading

Aby nasz data store rzeczywiście czekał na odpowiedni moment z wczytaniem danych do modelu należy przekazać opcję:

deferItemLoadingUntilExpand: true

Operacje na danych

Z poziomu JavaScript operacje na danych wyglądają identycznie jak w przypadku innych data store'ów. Polecam lekturę:

W przypadku edycji warto pamiętać, iż nie można zmieniać wartości pola określonego jako id.

Odświeżanie

Skoro trzymamy dane na serwerze, istnieje czasem konieczność ponownego wczytania węzła lub całej gałęzi. W takim wypadku może okazać się pomocna specjalna funkcja do odświeżania gałęzi dijit.Tree.refresh:

Po stronie serwera

Ja po stronie serwera korzystałem z PHP. Język ten posiada zestaw bardzo przydatnych funkcji dla obsługi JSON.

<?php
header( "Content-type: application/json; charset=utf-8" );
$database = [
  [], // pusty wiersz, aby indeksować od 1
  ['id' =>; 1, 'name' =>; 'Patryk yarpo Jar', 'children' => [
      [ '$ref' => 2, 'name' => 'Jan' ],
      [ '$ref' => 3, 'name' => 'Zdzisiu', 'children'=> true ],
      [ '$ref' => 4, 'name' => 'Mietek', 'children'=> true ]
    ]
  ],
  ['id' => 2, 'name' => 'Jan'],
  ['id' => 3, 'name' => 'Zdzisiu', 'children' => [
      [ '$ref' => 5, 'name' => 'Józio' ],
      [ '$ref' => 6, 'name' => 'Stefan']
    ]
  ],
  ['id' => 4, 'name' => 'Mietek'],
  ['id' => 5, 'name' => 'Józio'],
  ['id' => 6, 'name' => 'Stefan']
];

function getParamFromPath( $input )
{
  $path = $input['REQUEST_URI'];
  $parts = explode('/', $path);

  return $parts[count($parts) - 1];
}

$id = getParamFromPath( $_SERVER );
$result = ('root' == $id) ? array($database[1]) : $database[$id];
$json = json_encode( $result );
die( $json );

Jak widać na powyższym kawałku dane trzymam w tablicy, ale nic nie szkodzi aby umieścić je w bazie danych (jest to wręcz sposób zalecany, tu chodziło o prostotę przykładu) i odczytywać odpowiednie porcje.

Warto zwrócić uwagę na funkcje:

  • json_encode - zwraca ciąg znaków będący poprawną reprezentacją danych podanych na wejściu. W skrócie dajesz tablicę / tablicę asocjacyjną, a otrzymujesz ciąg znaków będący poprawnym JSONem.
  • getParamFromPath - moja autorska funkcja, odcinająca z końca adresu identyfikator żądanego węzła.

W przykładzie online odwołuję się nie do pliku `resource/` (jak opisuję cały wpis), ale do `resource.php/`. Nie zmienia to zbyt wiele. Można jednak odwoływać się do pliku z kodem php poprawnie parsowanym przez serwer jeśli skorzysta się z odpowiedniego pliku .htaccess. Należy w nim umieścić taką treść:

<Files resource>
ForceType application/x-httpd-php
</Files>

Taki wpis określa, że plik o nazwie `resource' ma być traktowany jak skrypt PHP. Na serwerze, na którym stoi mój blog plik ten nie działał. Jeśli chcesz spróbować uruchomić to lokalnie (bądź na innym serwerze) oto paczka z kodem:

Obsługa metody PUT

W przypadku wstawiania nowych węzłów do drzewa na serwer zostaje wysłane żądanie metodą PUT. O ile dla POST i GET w PHP mamy bardzo wygodne tablice `$_POST', `$_GET' to w przypadku PUT nie istnieje nic podobnego.

Nie jest to jednak sytuacja bez wyjścia :). Zawsze można to odczytać samemu:

function getPutRequest()
{
    /* PUT data comes in on the stdin stream */
    $putdata = fopen("php://input", "r");
    $jsonStr= '';

    while ($data = fread($putdata, 1024))
    {
        $jsonStr .= $data;
    }

    fclose($putdata);

    return json_decode($jsonStr);
}

W wyniku działania tej funkcji (jeśli coś zostało na serwer w tym żądaniu wysłane metodą PUT) zostaje to umieszczone w obiekcie zwracanym przez funkcję. Skąd obiekt? Zostaje on wysłany w ciele żądania. Następnie (pętla while) sklejony w ciąg znaków, a następnie za pomocą funkcji `json_decode' sparsowany do obiektu (w ciele żądania był zserializowany obiekt JSON).

Jeśli chciałbyś poczytać więcej na pewno znajdziesz w google. Ja powyższy kod napisałem po lekturze:

Warto wiedzieć

1.5 vs 1.6

Twórcy Dojo Toolkit dali trochę ciała w kwestii wersji 1.6 swojego dzieła. W przykładach pokazanych wyżej wykorzystywałem wersję 1.5. Z nieznanych mi przyczyn w wersji 1.6 KAŻDE wczytanie węzła jest dodatkowym żądaniem do serwera o roota. Wydaje mi się błędnym kierunkiem. Wersja Dojo Toolkit 1.5 może być pobrana z:

Zmieniając wersję w tym linku można pobrać (załączyć od razu do strony) także wersję 1.6. Na listach dyskusyjnych znalazłem o tym wątek. Mam jednak wrażenie, że nie ma tam odpowiedzi dlaczego przy _odczycie_ potrzebne są kolejne żądania - przy dodawaniu jestem w stanie zrozumieć (że tak ma być, bo ogólnie to też nie czaję, czemu akurat root).

dojo.data.JsonRest

Jeszcze nietestowana przeze mnie nowość z Dojo 1.6 dojo.data.JsonRest. Warto tu wspomnieć, że DojoX jest biblioteką eksperymentów i rozszerzeń. Dojo oraz Dijit są bibliotekami modułów stabilnych i kompletnych. Może jednak warto się przyjrzeć nowince 🙂

Lazy loading inaczej

Dlaczego nie spróbować samemu stworzyć klasę data store'a, która pozwalałaby otrzymać w efekcie późne ładowanie?

Jeśli miałbyś na to ochotę, przydatne mogą okazać się:

Ja mam jeden obiekt własnego autorstwa to realizujący, jednak trzeba by jeszcze nad nim popracować. Jeśli stworzę coś ciekawego, na pewno dam znać.

Przydatne narzędzia

Nie tylko do dijit.Tree, ale do webdeveloperki ogólnie:

Komentarze (2) Trackbacks (0)
  1. Witam. Nie chciało mi sie czytać. Wystarczy że zobaczyłem tablice w php’ie i jestem pewien ze to była dobra decyzja. Ide rzygać, papa !

  2. Twoja decyzja. W artykule jest prawie 10 listingow i wiele przykladow online, ale jesli Tobie najbardziej przeszkadza fake’owy serwis w PHP (ktory mial byc najprostszy jak sie da) to juz Twoj problem.

    Mam nadzieje, ze wymioty owocne.


Leave a comment

 

Brak trackbacków.