Menu kontekstowe na drzewie dijit.Tree
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:
- znać podstawy dojo
- wiedzieć coś na temat widgetu dijit.tree
- mieć 10 minut wolnego czasu
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>
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>
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):
- http://dojotoolkit.org/reference-guide/dijit/Declaration.html#dojo-connect
- http://www.sitepen.com/blog/2007/09/21/dojo-09-power-tools-script-typedojomethod/
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>
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:
- zmiana data store - dodanie itema "Continents"
- wskazanie wszystkich kontynentów jako dzieci tego itema w data store
- dodanie do widgeta drzewa parametru `showRoot="false"'
- 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ć:
- http://dojotoolkit.org/reference-guide/dijit/Tree.html w szczególności do akapitu o menu
Strony
Kategorie
- client-side
- ie6
- java
- marketing
- OOP
- public
- recenzje
- server-side
- w3c
- warsztat
- web-usability
- youthcoders
Blogroll
Archiwum
- Maj 2013
- Marzec 2013
- Luty 2013
- Styczeń 2013
- Grudzień 2012
- Listopad 2012
- Sierpień 2012
- Lipiec 2012
- Czerwiec 2012
- Maj 2012
- Marzec 2012
- Luty 2012
- Styczeń 2012
- Grudzień 2011
- Listopad 2011
- Październik 2011
- Wrzesień 2011
- Sierpień 2011
- Lipiec 2011
- Czerwiec 2011
- Maj 2011
- Kwiecień 2011
- Marzec 2011
- Luty 2011
- Styczeń 2011
- Grudzień 2010
- Listopad 2010
- Październik 2010