Blog webdeveloperski Patryk yarpo Jar

Menu kontekstowe na drzewie dijit.Tree

Autor wiadomości Lipiec 23, 2011

W niedawnym wpisie pokazałem, jak stworzyć drzewo za pomocą Dojo Toolkit. Konkretniej widgetu dijit.tree.

W tym wpisie pokażę jak dodać do niego menu kontekstowe, aktywowane, gdy ktoś kliknie PPM na dowolnym węźle drzewa.

Na początek:

Menu kontekstowe na drzewie

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html dir="ltr">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js" djConfig="parseOnLoad: true"></script>
    <script type="text/javascript">
        dojo.require("dijit.Menu");
        dojo.require("dojo.data.ItemFileReadStore");
        dojo.require("dijit.Tree");

        dojo.addOnLoad(function() {
            // łączę widget menu z drzewem
            dijit.byId('treeMenu').bindDomNode(dijit.byId('tree').domNode);
        });
    </script>
    <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/claro/claro.css" />
</head>
<body>
    <ul dojoType="dijit.Menu" id="treeMenu" style="display: none;">
        <li dojoType="dijit.MenuItem" onClick="alert('Kliknąłeś ' + this.label);">Menu 1</li>
        <li dojoType="dijit.MenuItem">Menu 2</li>
    </ul>
    <div dojoType="dojo.data.ItemFileReadStore" jsId="continentStore" url="/download/examples/dojo/tree/countries-small.json">
    </div>
    <div dojoType="dijit.tree.ForestStoreModel" jsId="continentModel" store="continentStore"
        query="{type:'continent'}" rootId="continentRoot" rootLabel="Continents"
        childrenAttrs="children">
    </div>
    <div dojoType="dijit.Tree" id="tree" model="continentModel" openOnClick="true"></div>
</body>
</html>

demo online

Myślę, że deklaratywna metoda umieszczania widgetów w kodzie jest na tyle czytelna, że jesteś w stanie sam wskazać miejsce deklaracji menu. Z rzeczy istotonych - atrybut `onClick' jest zapisany prawidłowo. To nie odnosi się do zdarzenia `click' (w XHTML atrybut `onclick') modelu zdarzeń DOM. Jest to zdarzenie dijit. Aby nie być gołosłownym, odsyłam do dokumentacji dijit. Co istotne funkcja obsługi zdarzenia przypisana do atrybutu `onClick' zostanie wywołana nie tylko na kliknięcie, ale na aktywację danego widgetu za pomocą klawiatury także.

Inny sposób połączenia

Pokazany wyżej sposób jest poprawny. Na stronach Dojo w przykładach robią to jednak inaczej. Warto wiedzieć, że istnieje taka możliwość:

<div dojoType="dijit.Tree" id="tree" model="continentModel" openOnClick="true">
    <script type="dojo/connect">
    // połącz menu z tym drzewem
    dijit.byId("treeMenu").bindDomNode(this.domNode);
    </script>
</div>

demo online

Wow! Co to jest?

<script type="dojo/connect">

Jest to specjalny sposób wplatania kodu JavaScript obsługującego widget w kod HTML. Przyznasz, że całkiem czytelne:). Operator `this' jest w tym kodzie widgetem drzewa. Gdybyś chciał poczytać więcej na ten temat, odsyłam do zewnętrznych źródeł (anglojęzycznych):

Różne menu do różnych węzłów

We wcześniejszym przykładzie pokazałem, jak dodać menu do całego drzewa. Czasem jednak chcielibyśmy móc dodać różne menu do różnych węzłów, np. inne menu dla kontynentów i inne dla państw. Istnieje na to sposób:

<div dojoType="dijit.Tree" id="tree" model="continentModel" openOnClick="true">
<script type="dojo/connect">
    var menus = {
        continent : dijit.byId('treeMenuContinent'),
        country : dijit.byId('treeMenuCountry'),
        city : dijit.byId('treeMenuCity')
    };

    this.onOpen = function(item, node)
    {
        function bindProperMenu( node, item )
        {
            var m, type;
            item = item || {};
            for(m in menus)
            {
                menus[m].unBindDomNode(node);
            }

            type = continentStore.getValue(item, 'type');

            if (type)
            {
                menus[type].bindDomNode(node);
            }
        }

        // obiekt store posiada pole `root'
        // dla store nie chcemy wywolywac przypisywania menu
        if (!item.root)
        {
            var children = node.containerNode.childNodes,
                n = children.length,
                thisWidget;
            while(n--)
            {
                // dodaj odpowiednie menu dla dzieci
                thisWidget = dijit.getEnclosingWidget(children[n]);
                bindProperMenu(thisWidget.domNode, thisWidget.item);
            }
            // przypis menu dla aktualnego węzła
            bindProperMenu(node.domNode, item);
        }
        else
        {
            // jesteśmy na węźle "Continents", dodaj sztucznie
            menus['continent'].bindDomNode(this.domNode);
        }
    }
</script>
</div>

demo online

Jak być może zauważyłeś w tym kodzie odwołuję się do 3 różnych menu. Zajrzyj do kodu przykładu online, aby zobaczyć jak to wygląda w całości.

Ciekaw jestem, czy dostrzegłeś pewien problem. Otóż, przypisujemy menu do węzłów. W naszym drzewie węzeł o labelu "Continents" nie istnieje (tzn. nie ma go w data store). Nie ma itema odpowiadajcego tej pozycji w drzewie. Stąd też potrzebny był ten hack ze sztywnym przypisaniem:

menus['continent'].bindDomNode(this.domNode);

Innym rozwiązaniem, jest:

  1. zmiana data store - dodanie itema "Continents"
  2. wskazanie wszystkich kontynentów jako dzieci tego itema w data store
  3. dodanie do widgeta drzewa parametru `showRoot="false"'
  4. zmiana w modelu wartości atrybutu `query="{'name':'Continents'}"`

Działający przykład, wraz ze zmianami opisanymi wyżej można zobaczyć w kolejnym przykładzie online:

Operacje na węźle za pomocą menu

Jako, że menu kontekstowe nie jest częścią drzewa, trzeba trochę się napracować, aby móc za pomocą odpowiedniego pozycji w menu zrobić coś w drzewie, np. usunąć węzeł. Przede wszystkim musimy zapamiętać jaki węzeł został kliknięty. Do tego celu dodajmy kawałek kodu:

<script type="dojo/connect">
    var menus = {
        continent : dijit.byId('treeMenuContinent'),
        country : dijit.byId('treeMenuCountry'),
        city : dijit.byId('treeMenuCity')
    }, m;

    for(m in menus)
    {
        dojo.connect(menus[m], '_openMyself', this, function(e)
        {
            lastClicked = dijit.getEnclosingWidget(e.target);
            console.debug(lastClicked);
        });
    }
...

demo online (kliknij na pierwszą pozycję menu kontekstowego kontynentu)

Zmienna `lastClicked' musi być tu zmienną globalną (a więc zadeklarowaną poza tym blokiem kodu w globalnej przestrzeni nazw), chyba, że chcemy jej używać tylko w tym bloku. Zajrzyj do kodu przykładu online, aby zrozumieć, co mam na myśli.

Taki kod pozwala nam na uzyskanie widgetu konkretnego węzła drzewa (konkretnie Widget dijit._TreeNode). Zatem, mamy także dostęp do itema tego węzła!

lastClicked.item;

Aby pobrać dowolne pole itema należy używać:

continentStore.getValue(lastClicked.item, 'type'); // typ
continentStore.getValue(lastClicked.item, 'name'); // name
continentStore.getValue(lastClicked.item, 'id'); // id

Dlaczego nie można tego zrobić wprost:

lastClicked.item.name

Otóż dojo przetwarza dane zawarte w tablicy items (kod JSON). Jeśli chcielibyśmy się odwołać do odpowiedniego pola bezpośrednio musimy zrobić to tak:

lastClicked.item.name[0]

Jest to sposób wydajniejszy niż wykorzystywanie specjalnych metod. Sądzę jednak, że jeśli nie musisz optymalizować swojej aplikacji, to lepiej jest używać wbudowanych akcesorów.

Skoro masz już itema, to nic nie stoi na przeszkodzie, aby wykorzystać opisane przeze mnie wcześniej sposoby operowania na data storze i odpowiednio zmieniać drzewo:

Warto też wiedzieć, że w dojo każdy widget może być tworzony albo tak jak pokazałem w tym wpisie - deklaratywnie, albo programistycznie. Odsyłam do odpowiednich stron dokumentacji (podane niżej). Powodzenia :)

Warto zajrzeć:

Komentarze (0) Trackbacks (0)

Brak komentarzy.


Leave a comment

 

Brak trackbacków.