User Actions

The problem is our tree does nothing but stand around looking beautiful. Nothing wrong with that. Normally, though, you'd want some kind of actions.

Events

At the very least, you probably want to do something when a user clicks or press [ENTER] on a node. To do this, you can use the onClick event.

Pick a Pop Tart Please
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
            djConfig="parseOnLoad: true">
</script>
    <script>
        dojo.require("dojo.data.ItemFileReadStore");
        dojo.require("dijit.Tree");
        dojo.require("dojo.parser");
   </script>
</head>
<body class="tundra">
        <div id="response">Pick a Pop Tart Please</div>
        <div dojoType="dojo.data.ItemFileReadStore"
             url="poptarts_direct.txt" jsid="popStore">
</div>
        <div dojoType="dijit.Tree" store="popStore" labelAttr="name"
             label="Pop Tarts">

             <script type="dojo/method" event="onClick" args="item">
                dojo.byId("response").innerHTML =
                   "You're a " + popStore.getLabel(item) + " fan, eh?";
            </script>
        </div>
</body>
</html>

Alternatively, you can use Dojo's publish/subscribe event system. When a node is clicked, the tree id is sent as the topic along with the message:

  • tree: the actual tree widget
  • event: "execute"
  • item: the dojo.data item selected
  • node: the DOM node selected

Drag and Drop

Dojo trees are great, and so is Dojo Drag And Drop (DnD). But together, they're unstoppable! Not being satisfied with our selection of Pop Tarts, we'll create a pool of Drag and Drop sources to add:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<ul dojoType="dojo.dnd.Source">
    <li class="dojoDndItem" id="Hot Chocolate">Hot Chocolate</li>
    <li class="dojoDndItem" id="Blueberry">Blueberry</li>
</ul>

The user should only be allowed to drop a Pop Tart on its home category - for example, Blueberry should only go under Fruit. To handle this, we use JavaScript regular expressions to make an intelligent guess. We add these to the data store:

{ label: 'name',
  identifier: 'name',
   items: [
     { name:'Fruit', type:'category', regexp:'.*erry$'},
     { name:'Cinammon', type: 'category', regexp:'[Cc]innamon',
       children: [
          { name:'Cinammon Roll', type:'poptart' }, 
          { name:'Brown Sugar Cinnamon', type:'poptart' }, 
          { name:'French Toast', type:'poptart' }
       ]
     },
     { name:'Chocolate', type: 'category', regexp:'([Cc]hocolate|Fudge)'}
  ]
}

You can download this file below. Next we wire in DnD to the Tree. This is as simple as specifying two attributes: the controller and the acceptance checker extension point, which we'll write shortly.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dijit.Tree" store="popStore" labelAttr="name"
     label="Pop Tarts" jsid="ptTree"
     dndController="dijit._tree.dndSource"
     checkItemAcceptance="poptartCheckItemAcceptance">

The checkItemAcceptance extension point function is called each time a drop target is entered. In Tree's case, every node is a drop target. How do we know it's valid? The function must check the dragged node to the regular expression of the drop node:

function poptartCheckItemAcceptance(node,source) {
     // Get the associated dojo.data item for the target
     item = dijit.getEnclosingWidget(node).item;

     // Need to check for item because when dropping on a root node,
     // item === null
     if (! item) return false;
            
     ptType = ptTree.store.getValue(item,"type"); 
            
     if (ptType == 'category') {

         // We make intelligent guesses about the correct folder
         re = new RegExp(ptTree.store.getValue(item,"regexp"));
         okToMove = true;
         for (var itemId in source.selection) {
              console.debug(itemId+" tested against "+re.toString());
              okToMove &= re.test(itemId);
         }
         return okToMove;
     }
     else 
         return false;
}

So now all the pieces are in place, yielding:

Drag a Pop Tart to Its Category
Hot Chocolate
Blueberry

And here's the full source code, which is downloadable below:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
            djConfig="parseOnLoad: true">
</script>
    <script>
        dojo.require("dojo.data.ItemFileWriteStore");
        dojo.require("dijit.Tree");
        dojo.require("dojo.parser");
        dojo.require("dojo.dnd.Source");
        dojo.require("dijit._tree.dndSource");
       
        function poptartCheckItemAcceptance(node,source) {
            // Get the associated dojo.data item for the target
            item = dijit.getEnclosingWidget(node).item;
            // Need to check for item because when dropping on a root node,
            // item === null
            if (! item) return false;
           
            ptType = ptTree.store.getValue(item,"type");
           
            if (ptType == 'category') {
                // We make intelligent guesses about the correct folder
                re = new RegExp(ptTree.store.getValue(item,"regexp"));
                okToMove = true;
                for (var itemId in source.selection) {
                   console.debug(itemId+" tested against "+re.toString());
                   okToMove &= re.test(itemId);
                }
               return okToMove;
            }
            else
                return false;
        }
   </script>
   
</head>
<body class="tundra">
        <div>Drag a Pop Tart to Its Category</div>
        <div dojoType="dojo.dnd.Source">
           <div class="dojoDndItem" id="Hot Chocolate">Hot Chocolate</div>
           <div class="dojoDndItem" id="Blueberry">Blueberry</div>
        </div>
        <div dojoType="dojo.data.ItemFileWriteStore"
             url="poptarts_dnd.txt" jsid="popStore" />

        <div dojoType="dijit.Tree" store="popStore" labelAttr="name"
             label="Pop Tarts" jsid="ptTree"
             dndController="dijit._tree.dndSource"
             checkItemAcceptance="poptartCheckItemAcceptance">

        </div>
</body>
</html>