PDA

View Full Version : TreePanel with Context Menu


thameema
01-29-2007, 05:09 PM
I have been searching on the internet for a good example about making a TreeView or TreePanel with contextmenu and couldn't find any. I used 0.40 yui-ext and latest yui libraries and after 2 days of struggling finally got something working. Here is the detailed explanation with code.

First I made yuitest.html


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title> TreePanel</title>

<script type="text/javascript" src="lib/yahoo/yahoo.js"></script>
<script type="text/javascript" src="lib/event/event.js"></script>
<script type="text/javascript" src="lib/dom/dom.js"></script>
<script type="text/javascript" src="lib/container/container_core.js"></script>
<script type="text/javascript" src="lib/menu/menu.js" ></script>
<script type="text/javascript" src="lib/treeview/treeview.js" ></script>
<script type="text/javascript" src="lib/utilities/utilities.js"></script>

<script type="text/javascript" src="yui-ext-0.40/jscripts/yui-ext.js"></script>



<link rel="stylesheet" type="text/css" href="yui-ext-0.40/css/yui-ext.css" />
<link rel="stylesheet" type="text/css" href="css/menu.css" />
<link rel="stylesheet" type="text/css" href="examples.css" />
<link rel="stylesheet" type="text/css" href="lib/reset/reset.css">
<link rel="stylesheet" type="text/css" href="lib/fonts/fonts.css">

<script type="text/javascript">

// shorthand
var Tree = YAHOO.ext.tree;
var tree;
var root;

function init(){
tree = new Tree.TreePanel('tree-div', {
animate:true,

loader: new Tree.TreeLoader({dataUrl:"http://localhost/test.php",
baseParams: {}
}),
enableDD:true,
containerScroll: true
});

// set the root node
root = new Tree.AsyncTreeNode({
text: 'yui-ext',
draggable:false,
id:'mainportal'
});
tree.setRootNode(root);

// render the tree
tree.render();
root.expand();

};

function assignConmenu() {
if(root.isLoaded()){
var children = root.childNodes;
//alert("test:"+children);

for ( var i=0; i < children.length; i++ ) {

children[i].on("contextmenu", conMenu, children[i], true);
console.log("timesd:"+i);
}
}
};

function conMenu() {
var aMenuItems = [ {text: "Cut"},{text:"Copy"},{text:"Paste"} ];

//if( typeof oContextMenu != "undefined"){alert("contextmenu exist");oContextMenu.destroy;}
if ( typeof oContextMenu != "undefined"){
oContextMenu.destroy();
}

this.select(); //to make the right clicked menu node highlighted..remove if you don't need it.
oContextMenu = new YAHOO.widget.ContextMenu(
"otmenu",
{
trigger: this.ui.elNode.id,
itemdata: aMenuItems,
lazyload: true
}
);

oContextMenu.renderEvent.subscribe(onContextMenuRender, oContextMenu, true);


};

function onContextMenuRender(p_sType,p_aArgs, p_oMenu) {
//alert("inside context menu render");
this.clickEvent.subscribe(onContextMenuClick, this, true);
};

function onContextMenuClick(p_sType,p_aArgs, p_oMenu) {
alert("u have clicked");
};

YAHOO.ext.EventManager.onDocumentReady(init);
YAHOO.ext.EventManager.on(window,"load",assignConmenu, assignConmenu, true);
</script>

</head>
<body>

<div id="tree-div" style="overflow:auto; height:300px;width:250px;border:1px solid #c3daf9;"></div>

</body>
</html>


and test.php has the following contents, put this under apache document root and it will return the JSON object when XHR happens from the TreeLoader.



[{"text":"yui-ext.js","id":"yui-ext.js","leaf":true,"cls":"file"},{"text":"yui-ext-1125.php","id":"yui-ext-1125.php","leaf":true,"cls":"file"}]



The above example uses all yui-ext and yui libraries. In addition to that i am also copied the menu.css from yui example. Here is the menu.css code.


/*
Copyright (c) 2006, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.txt
Version: 0.12.1
*/



/* Menu styles */

div.yuimenu {

background-color:#f6f7ee;
border:solid 1px #c4c4be;
padding:1px;

}

/* Submenus are positioned absolute and hidden by default */

div.yuimenu div.yuimenu,
div.yuimenubar div.yuimenu {

position:absolute;
visibility:hidden;

}

/* MenuBar Styles */

div.yuimenubar {

background-color:#f6f7ee;

}

/*
Applying a width triggers "haslayout" in IE so that the module's
body clears its floated elements
*/
div.yuimenubar div.bd {

width:100%;

}

/*
Clear the module body for other browsers
*/
div.yuimenubar div.bd:after {

content:'.';
display:block;
clear:both;
visibility:hidden;
height:0;

}


/* Matches the group title (H6) inside a Menu or MenuBar instance */

div.yuimenu h6,
div.yuimenubar h6 {

font-size:100%;
font-weight:normal;
margin:0;
border:solid 1px #c4c4be;
color:#b9b9b9;

}

div.yuimenubar h6 {

float:left;
display:inline; /* Prevent margin doubling in IE */
padding:4px 12px;
border-width:0 1px 0 0;

}

div.yuimenu h6 {

float:none;
display:block;
border-width:1px 0 0 0;
padding:5px 10px 0 10px;

}


/* Matches the UL inside a Menu or MenuBar instance */

div.yuimenubar ul {

list-style-type:none;
margin:0;
padding:0;

}

div.yuimenu ul {

list-style-type:none;
border:solid 1px #c4c4be;
border-width:1px 0 0 0;
margin:0;
padding:10px 0;

}


div.yuimenu ul.first-of-type,
div.yuimenu ul.hastitle,
div.yuimenu h6.first-of-type {

border-width:0;

}


/* MenuItem and MenuBarItem styles */

div.yuimenu li,
div.yuimenubar li {

font-size:85%;
cursor:pointer;
cursor:hand;
white-space:nowrap;
text-align:left;

}

div.yuimenu li.yuimenuitem {

padding:2px 24px;

}

div.yuimenu li li,
div.yuimenubar li li {

font-size:100%;

}


/* Matches the help text for a menu item */

div.yuimenu li em {

font-style:normal;
margin:0 0 0 40px;

}

div.yuimenu li a em {

margin:0;

}

div.yuimenu li a,
div.yuimenubar li a {

/*
"zoom:1" triggers "haslayout" in IE to ensure that the mouseover and
mouseout events bubble to the parent LI in IE.
*/
zoom:1;
color:#000;
text-decoration:none;

}

div.yuimenu li.hassubmenu,
div.yuimenu li.hashelptext {

text-align:right;

}

div.yuimenu li.hassubmenu a.hassubmenu,
div.yuimenu li.hashelptext a.hashelptext {

float:left;
display:inline; /* Prevent margin doubling in IE */
text-align:left;

}


/* Matches focused and selected menu items */

div.yuimenu li.selected,
div.yuimenubar li.selected {

background-color:#8c8ad0;

}

div.yuimenu li.selected a.selected,
div.yuimenubar li.selected a.selected {

text-decoration:underline;

}

div.yuimenu li.selected a.selected,
div.yuimenu li.selected em.selected,
div.yuimenubar li.selected a.selected {

color:#fff;

}


/* Matches disabled menu items */

div.yuimenu li.disabled,
div.yuimenubar li.disabled {

cursor:default;

}

div.yuimenu li.disabled a.disabled,
div.yuimenu li.disabled em.disabled,
div.yuimenubar li.disabled a.disabled {

color:#b9b9b9;
cursor:default;

}

div.yuimenubar li.yuimenubaritem {

float:left;
display:inline; /* Prevent margin doubling in IE */
border-width:0 0 0 1px;
border-style:solid;
border-color:#c4c4be;
padding:4px 24px;
margin:0;

}

div.yuimenubar li.yuimenubaritem.first-of-type {

border-width:0;

}


/* Matches the submenu indicator for menu items */

div.yuimenubar li.yuimenubaritem img {

height:8px;
width:8px;
margin:0 0 0 10px;
vertical-align:middle;

}

div.yuimenu li.yuimenuitem img {

height:8px;
width:8px;
margin:0 -16px 0 0;
padding-left:10px;
border:0;

}

div.yuimenu li.checked {

position:relative;

}

div.yuimenu li.checked img.checked {

height:8px;
width:8px;
margin:0;
padding:0;
border:0;
position:absolute;
left:6px;
_left:-16px; /* Underscore hack b/c this is for IE 6 only */
top:.5em;

}


Run the sample code and post your comments. I do have few questions/bugs from the above example.


1. Context menu appears when I right click the menu node. But it appears even if i right click next to the menu item, ie, the div tag area which holds the menu item. I am not sure how to prevent this. I even noticed the same in yahoo examples.

2. I have to find out a way to customize the context menu per menu item by adding removing the contextmenu items. I think it should be possible.



I followed the alpha documentation for yui-ext 0.40 from

http://www.yui-ext.com/deploy/yui-ext.0.40-alpha/docs/


I forgot to mention that the latest yui-ext-0.40 has a bug in yui-ext.js. The bug is instead of "contextmenu" it has "contentmenu" in two places. Please change it before you run this code otherwise the contextmenu will not appear. (I think this is corrected in svn).


Cee ya,
Thameem[/b][/quote]

kyriakos
01-30-2007, 02:14 AM
thanks! this is exactly what i was looking for :)

kyriakos
01-31-2007, 02:44 AM
ok i've got a very strange problem with it. First of all if i used the code as was provided. The weird thing is that when i load the page in a new browser window (firefox 1.5) and right click on a menu option it does nothing (firebug doesnt show an error either). If I hit the browser refresh button and then right click it works. I couldnt get it to work at all in IE. I dont know if i'm doing something wrong here but has anyone else tested this?

regarding..

2. I have to find out a way to customize the context menu per menu item by adding removing the contextmenu items. I think it should be possible.

Isnt it just a matter of using this keyword to identify which node got clicked?

thameema
01-31-2007, 04:52 AM
I do faced that problem. I am using my linux firefox 2.0 to develop the tree and it worked. But it didn't work in windows IE and Firefox. I am also trying to find out why its not working in windows. As per the code I am not doing anything special to display in linux firefox.

I got the second part also working...will post the code once I get the windows thing figured out.

Cee yaa.
Thameem

kyriakos
01-31-2007, 05:08 AM
I swithced to normal Menu rather than Context menu and it works without the "refresh" issue but of course the menu appears at the bottom of the tree (instead of next to the treeitem) as described in this topic ( http://www.yui-ext.com/forum/viewtopic.php?t=2335 ). I'm trying to set the X / Y cfg property manually but doesnt seem to work at the moment - still working on it though.

kyriakos
01-31-2007, 05:23 AM
another question here is.. instead of using AssignConmenu to assign to each treenode why dont you just assign it to the contextmenu event of the tree itself and then use the node event parameter to differentiate between nodes?

function assignConmenu() {
if(root.isLoaded()){
var children = root.childNodes;
//alert("test:"+children);

for ( var i=0; i < children.length; i++ ) {

children[i].on("contextmenu", conMenu, children[i], true);
console.log("timesd:"+i);
}
}
};

kyriakos
01-31-2007, 06:39 AM
I found out why it doesnt work unless the page is reloaded. The assignConmenu is not being called unless I either open it in a fresh browser session or hit reload. Can't understand why but thats the problem with it. So i guess there must be something wrong with:
YAHOO.ext.EventManager.on(window,"load",assignConmenu, assignConmenu, true);

Animal
01-31-2007, 06:51 AM
What do you mean? That code fragment is only supposed to call assignConmenu when the window loads, so it will only run when you open or reload that page.

kyriakos
01-31-2007, 07:04 AM
Exactly thats what it should do, but in my case it only loads when i reload the page - thats what I cannot understand.

Animal
01-31-2007, 07:16 AM
You mean on first page load, it's not firing, but it is when you press "reload"????

kyriakos
01-31-2007, 07:25 AM
yes

Animal
01-31-2007, 07:35 AM
That's weird because that just adds a load listener directly to the window. Are you sure it's not called at all? Have you put a logging statement in to confirm that beyond doubt?

kyriakos
01-31-2007, 08:21 AM
yes. it shows no console output on the first load.

anyway i think the ideal way of doing this is by assiging the contextmenu event for the tree itself rather than each node individually, I believe it saves a lot of trouble, especially when nodes get loaded in dynamically.

So i tried using:

tree.on('contextmenu', function(node){
conMenu(node);
});

then i use the node parameter in the conMenu function to customize the menu. the only problem i have with this approach is that i can't get the contextmenu to appear next to the tree node, i tried setting the xy value from YAHOO.util.Dom.getRegion(node.ui.getAnchor()) or even manually and it still appears at the bottom of the tree no matter what.

alexvm
01-31-2007, 09:13 AM
hi! i did the the similar contextmenu. but it doesnt appears. my contextmenu event handler fires, but there is no any menu showed. could you describe, how to show it? here is my code:

// this is init
tree = new Tree.TreePanel('tree-div', ..... );
... blah-blah-blah

// setup contextevent handler
tree.addListener('contextmenu', onTreeNodeContextmenu);



function onContextMenuRender(p_sType, p_aArgs, p_oMenu) {
alert("inside context menu render");
this.clickEvent.subscribe(onContextMenuClick, this, true);
};

function onContextMenuClick(p_sType, p_aArgs, p_oMenu) {
alert("u have clicked");
};

////////////////////////////////////////////////////////////////////////////
// events handlers
var onTreeNodeContextmenu = function(node_, e_){
var aMenuItems = [ {text: "Cut"},{text:"Copy"},{text:"Paste"} ];

if ( typeof oContextMenu != "undefined"){
oContextMenu.destroy();
}

this.select(); //to make the right clicked menu node highlighted..remove if you don't need it.
oContextMenu = new YAHOO.widget.ContextMenu(
"otmenu",
{
trigger: this.ui.elNode.id,
itemdata: aMenuItems,
lazyload: true
}
);

oContextMenu.renderEvent.subscribe(onContextMenuRender, oContextMenu, true);
}


onContextMenuRender is not fired. can you say me what is wrong in my app?

Animal
01-31-2007, 09:18 AM
yes. it shows no console output on the first load.

anyway i think the ideal way of doing this is by assiging the contextmenu event for the tree itself rather than each node individually, I believe it saves a lot of trouble, especially when nodes get loaded in dynamically.

So i tried using:

tree.on('contextmenu', function(node){
conMenu(node);
});

then i use the node parameter in the conMenu function to customize the menu. the only problem i have with this approach is that i can't get the contextmenu to appear next to the tree node, i tried setting the xy value from YAHOO.util.Dom.getRegion(node.ui.getAnchor()) or even manually and it still appears at the bottom of the tree no matter what.

Yuo are definitely correct to have the context menu listener on the tree rather than one on every node. That's the way I did it on my hack of the YUI Tree.

Don't you get the event passed to the event handler?

The TreeNodeUI code says:


onContextMenu : function(e){
e.preventDefault();
this.focus();
this.fireEvent('contextmenu', this.node, e);
},


so you should be able to get the X,Y from the event.

alexvm
01-31-2007, 09:36 AM
i found a problem of not showing my menu. i replaced node_.ui.elNode.id to tree.id, and now my tree triggers the menu.

oContextMenu = new YAHOO.widget.ContextMenu(
"otmenu",
{
trigger: tree.id, //node_.ui.elNode.id,
itemdata: aMenuItems,
lazyload: true
}
);


But there is another trouble, menu appears under leyout. I'm using layout from yui-ext help.
http://experts.pereslavl.ru/~alexvm/yui-menu.jpg
Any idea?

Animal
01-31-2007, 09:47 AM
YUI menus have very low z-index values. Try bumping your menu's z-index up to above what the layout elements have.

alexvm
01-31-2007, 09:55 AM
YUI menus have very low z-index values. Try bumping your menu's z-index up to above what the layout elements have.
Yes, it was zindex. Thank you. Now menu show in right position.
Do you know, how can i turn off focus from menu items? when you move mouse over menu items each item gets focus frame when mouse over it.

Animal
01-31-2007, 10:01 AM
No I don't know. That's a feature of YUI menus. I think it's for keyboard navigation. Once a menu is focussed, you can use keys to navigate. So if not using a mouse, then some kind of visual indication of which menu item is active must be shown.

thameema
01-31-2007, 03:57 PM
yes. it shows no console output on the first load.

anyway i think the ideal way of doing this is by assiging the contextmenu event for the tree itself rather than each node individually, I believe it saves a lot of trouble, especially when nodes get loaded in dynamically.

So i tried using:

tree.on('contextmenu', function(node){
conMenu(node);
});

then i use the node parameter in the conMenu function to customize the menu. the only problem i have with this approach is that i can't get the contextmenu to appear next to the tree node, i tried setting the xy value from YAHOO.util.Dom.getRegion(node.ui.getAnchor()) or even manually and it still appears at the bottom of the tree no matter what.

The reason why I added the context menu to the every menuitem is, as per my requirement I should be able to change the context menu by checking the type of menuitem. I am not sure this is possibe if we assign the context menu to the tree. I have to experiment that..btw, did it solve your IE issue??

thameema
02-01-2007, 07:33 AM
I tried your suggestion about assigining the context menu to the tree and it fixed the problem with IE and it shows in every browser...

But it produces another issue...the context menu is appearing wherever you right click in the treepanel area. I think thats the reason I was assigning that to every menuitem. Please let me know if you fix this..

I can customize the context menu per menuitem by passing the reference of the node to conMenu function and it also works fine.

Thanks for trying out more...Here are the corrections to the initial code:

In function init(),


root.expand();
tree.on('contextmenu', function(node){
conMenu(node);
});


Change conMenu() function to

function conMenu(fnode)

In conMenu function,


fnode.select(); //highlight the node right clicked.

oContextMenu = new YAHOO.widget.ContextMenu(
"otmenu",
{
trigger: tree.id,
itemdata: aMenuItems,
lazyload: true

}
);



Thanks,
Thameem

kyriakos
02-01-2007, 07:36 AM
I still cannot get it to show the menu at the right position. It simply appears at the bottom of the page tree even when i set the XY manually. I can post the complete code if anyone wants to check it out.

Animal
02-01-2007, 07:39 AM
Even when you do


oContextMenu.cfg.setProperty("xy", [x, y]);


:?:

kyriakos
02-01-2007, 07:49 AM
here's the script code:

<script>
var root;

var TreeTest = function(){
var Tree = YAHOO.ext.tree;



return {
init : function(){
tree = new Tree.TreePanel('tree-div', {
animate:true,
loader: new Tree.TreeLoader({dataUrl:'get-nodes.php'}),
enableDD:true,
containerScroll: true
});


tree.on('contextmenu', function(node){
alert(node);
conMenu(node,tree);
});

var root = new Tree.AsyncTreeNode({
text: 'root',
draggable:true,
id:'source'
});
tree.setRootNode(root);

root.appendChild(
new Tree.TreeNode({text:'title1', id:'a1',cls:'album-node', leaf:false, allowDrag:true}),
new Tree.TreeNode({text:'title2', id:'a2',cls:'album-node', leaf:false, allowDrag:true}),
new Tree.TreeNode({text:'title3', id:'a3',cls:'album-node', leaf:false, allowDrag:true}),
new Tree.AsyncTreeNode({text:'title4', id:'a4',cls:'album-node', leaf:false, allowDrag:true})
);


tree.render();

root.expand();
}
};
}();


function conMenu(node,tree) {
var Anchor = YAHOO.util.Dom.getRegion(node.ui.getAnchor());
var aMenuItems = [ {text: "Create item"},{text:"Remove"},{text:"Edit.."} ];

if ( typeof oContextMenu != "undefined"){
oContextMenu.destroy();
}


oContextMenu = new YAHOO.widget.Menu(
"otmenu",
{
/* trigger: this.ui.elNode.id, */
trigger: tree.id,
itemdata: aMenuItems,
lazyload: true
}
}
);

oContextMenu.renderEvent.subscribe(onContextMenuRender, oContextMenu, true);
oContextMenu.cfg.setProperty ( "xy" , [5, 5]);
oContextMenu.show();



};
function onContextMenuRender(p_sType,p_aArgs, p_oMenu) {
alert("inside context menu render");
};

YAHOO.ext.EventManager.onDocumentReady(TreeTest.init, TreeTest, true);
</script>

Animal
02-01-2007, 07:52 AM
You are not controlling when that's shown because you're using the "trigger" property. You set the xy on startup, but it positions itself when autotriggered.

Hook the contextmenu result of the tree, and manually show the menu in the handler.

thameema
02-01-2007, 10:42 AM
Ok. Finally got everything working as I needed. Here is the main corrections I made. Kyriakos you should do this to make the menu appearing properly next to the clicked point.


tree.on('contextmenu', function(node,e){
conMenu(node,e);
});



function conMenu(fnode,e) {
var aMenuItems = [ {text: "Cut"},{text:"Copy"},{text:"Paste"} ];



oContextMenu = new YAHOO.widget.ContextMenu(
"otmenu",
{
//trigger: tree.id, //as per Animal's suggestion
itemdata: aMenuItems,
lazyload: true

}
);


The following is important and if you switch the order or comment out something the menu will not work properly.

//oContextMenu.renderEvent.subscribe(onContextMenuRender, oContextMenu, true);
oContextMenu.render(document.body);
oContextMenu.cfg.setProperty ( "xy" , [e.getPageX(), e.getPageY()]);
oContextMenu.show();


Thanks,
Thameem

Animal
02-01-2007, 11:01 AM
Is it right that you are creating and initializing the context menu on every right click?

thameema
02-02-2007, 04:39 AM
Yes. I am destroying the context menu and re-initializing it for every right click. I am sure this is not the right way to do it. If I remove that destroy part, the menu is keep getting added and the context menu is growing for every right click. I am not sure whether removing the menuitem every rightclick and adding them again will be efficient way to do it. Thats the main reason I am destroying it and re-creating it. You or someone can shed some light on this.

Thanks,
Thameem

kyriakos
02-02-2007, 08:05 AM
Finally, it works. Thanks thameema and Animal, for all the help!

I'm facing another problem now, a css issue, the background div of the menu appears to be smaller than it should be, i noticed this happening when i set oContextMenu.render(document.body); if i comment this out the menu appears in full size but at the bottom of the page again.

here's a screenshot:
http://img266.imageshack.us/img266/8676/untitled1hh9.png

thameema
02-02-2007, 08:40 AM
Finally, it works. Thanks thameema and Animal, for all the help!

I'm facing another problem now, a css issue, the background div of the menu appears to be smaller than it should be, i noticed this happening when i set oContextMenu.render(document.body); if i comment this out the menu appears in full size but at the bottom of the page again.

here's a screenshot:
http://img266.imageshack.us/img266/8676/untitled1hh9.png

I had the same issue and I don't remember how I fixed it..but its an easy fix by swapping the order of the javascript calls...

Cee ya.