JavaScript几种形式的树结构菜单


1.悬浮层树(Tree)
这种树结构实现类似面包屑导航功能,监听的是节点鼠标移动的事件,然后在节点下方或右方显示子节点,依此递归显示子节点的子节点 。

用户首页博客设置文章相册留言评论系统
这里要注意几个小问题,其一这种树结构是悬浮层绝对定位的,在创建层的时候一定要直接放在body的下面,这样做的是确保在IE里面能遮掩住任何层,因为在IE里面是有stacking context这种东西的潜规则在里面的,另外当然还有一个select你能遮住我吗?老掉牙的问题,这里是采用在每个悬浮层后面加个iframe元素,当然同一级的菜单只产生一个iframe元素,菜单有几级将产生几个iframe遮掩,然后菜单显示和隐藏的时候同时显示和隐藏iframe 。

不过这种菜单并不合适前台,因为目前只支持在脚本里动态添加菜单节点,而不能从现有的html元素获取菜单节点,我们为了SEO等前台导航一般是在后台动态输出的,假如菜单有多级的话也建议不超过2层,对客户来说太多层也懒得去看,不过有个面包屑导航显示还是很不错的 。

menu.js
复制代码 代码如下:

/*
** Author : Jonllen
** Create : 2009-12-13
** Update : 2010-05-08
** SVN : 152
** WebSite: http://www.jonllen.com/
*/
var Menu = function (container) {
this.container = container;
return this;
}
Menu.prototype = {
list : new Array(),
active : new Array(),
iframes : new Array(),
settings : {
id : null,
parentId : 0,
name : null,
url : null,
level : 1,
parent : null,
children : null,
css : null,
element : null
},
push : function (item) {
var list = Object.prototype.toString.apply(item) === [object Array] ? item : [item];
for( var i=0; i< list.length; i++) {
var settings = list[i];
for( p in this.settings) {
if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
}
this.list.push(settings);
}
return this;
},
getChlid : function (id) {
var list = new Array();
for( var i=0;i < this.list.length; i++)
{
var item = this.list[i];
if( item.parentId == id)
{
list.push(item);
}
}
return list;
},
render : function (container) {
var _this = this;
var menuElem = container || this.container;
for( var i=0;i < this.list.length; i++)
{
var item = this.list[i];
if ( item.parentId != 0 ) continue;
var itemElem = document.createElement(div);
itemElem.innerHTML = <a href="+item.url+">+item.name+</a>;
itemElem.className = item;
if ( item.css ) itemElem.className += +item.css;
var disabled = ( +item.css+ ).indexOf( disabled )!=-1;
if ( disabled ) {
itemElem.childNodes[0].disabled = true;
itemElem.childNodes[0].className = disabled;
itemElem.childNodes[0].removeAttribute(href);
}
if ( ( +item.css+ ).indexOf( hidden )!=-1 ) {
itemElem.style.display = none;
}
itemElem.menu = item;
itemElem.menu.children = this.getChlid(item.id);
itemElem.onmouseover = function (e){
_this.renderChlid(this);
};
menuElem.appendChild(itemElem);
}
document.onclick = function (e){
e = window.event || e;
var target = e.target || e.srcElement;
if (!target.menu) {
var self = _this;
for( var i=1;i<_this.active.length;i++) {
var item = _this.active[i];
var menuElem = document.getElementById(menu+item.id);
if ( menuElem !=null)
menuElem.style.display = none;
}
for(var j=1;j<_this.iframes.length;j++){
_this.iframes[j].style.display = none;
}
}
};
},
renderChlid : function (target){
var self = this;
var item = target.menu;
var activeItem = self.active[item.level];
while(activeItem) {
var activeItemElem = activeItem.element;
if ( activeItemElem!= null ) activeItemElem.style.display = none;
activeItem = self.active[activeItem.level + 1];
}
self.active[item.level] = item;
var level = item.level;
while(this.iframes[level]) {
this.iframes[level].style.display = none;
level++;
}
var childElem = document.getElementById(menu+item.id);
if (childElem==null) {
var hasChild = false;
for( var j=0;j<item.children.length;j++) {
if( ( +item.children[j].css+ ).indexOf( hidden ) == -1) {
hasChild = true;
break;
}
}
if( hasChild) {
var xy = self.elemOffset(target);
var x = xy.x;
var y = target.offsetHeight + xy.y;
if ( item.level >= 2 )
{
x += target.offsetWidth - 1;
y -= target.offsetHeight;
}
childElem = document.createElement(div);
childElem.id = menu+item.id;
childElem.className = child;
childElem.style.position = absolute;
childElem.style.left = x + px;
childElem.style.top = y + px;
childElem.style.zIndex = 1000 + item.level;
for( var i=0;i < item.children.length; i++)
{
var childItem = item.children[i];
var childItemElem = document.createElement(a);
var disabled = ( +childItem.css+ ).indexOf(disabled)!=-1;
if ( disabled ) {
childItemElem.disabled = true;
childItemElem.className += +childItem.css;
}else {
childItemElem.href = childItem.url;
}
if ( ( +childItem.css+ ).indexOf( hidden )!=-1 ) {
childItemElem.style.display = none;
}
childItemElem.innerHTML = childItem.name;
childItemElem.menu = childItem;
childItemElem.menu.children = self.getChlid(childItem.id);
var hasChild = false;
for( var j=0;j<childItemElem.menu.children.length;j++) {
if( ( +childItemElem.menu.children[j].css+ ).indexOf( hidden ) == -1) {
hasChild = true;
break;
}
}
if( hasChild ) {
childItemElem.className += hasChild;
}
childItemElem.onmouseover = function (e) {
self.renderChlid(this)
};
childElem.appendChild(childItemElem);
}
document.body.insertBefore(childElem,document.body.childNodes[0]);
item.element = childElem;
}
}
if( childElem!=null) {
var iframeElem = this.iframes[item.level];
if ( iframeElem == null) {
iframeElem = document.createElement(iframe);
iframeElem.scrolling = no;
iframeElem.frameBorder = 0;
iframeElem.style.cssText = position:absolute; overflow:hidden;;
document.body.insertBefore(iframeElem,document.body.childNodes[0]);
this.iframes[item.level]=iframeElem;
}
childElem.style.display = block;
iframeElem.width = childElem.offsetWidth;
iframeElem.height = childElem.offsetHeight;
iframeElem.style.left = parseInt(childElem.style.left) + px;
iframeElem.style.top = parseInt(childElem.style.top) + px;
iframeElem.style.display = block;
}
},
elemOffset : function(elem){
if( elem==null) return {x:0,y:0};
var t = elem.offsetTop;
var l = elem.offsetLeft;
while( elem = elem.offsetParent) {
t += elem.offsetTop;
l += elem.offsetLeft;
}
return {x : l,y : t};
}
};

演示地址 http://demo.jb51.net/js/tree_json/menu.htm
打包下载地址

2.右键菜单树(ContextMenu)
自定义右键菜单(ContextMenu)和悬浮层树(Tree)其实现上都大同小异,都是在脚本里动态添加节点,然后在生成一个绝对定位层,只不过右键菜单树(ContextMenu)触发的事件不一样 。另外右键菜单还需要提供一个动态添加菜单项功能,以实现右击不同的元素可以显示不同的右键菜单,我这里提供一种"回调函数",使用见如下代码:
ContextMenu回调函数
复制代码 代码如下:

//ContextMenu
var contextmenu = new ContextMenu(...{ container : document.getElementById(treemenu) });
contextmenu.push( ...{ html : Powered By: Jonllen, css : disabled});
contextmenu.push( ...{ html : , css : line});
contextmenu.push( ...{ html : 刷新(<u>R</u>), href : javascript:location.reload();});
for(var i=0;i<menu.length;i++) ...{
contextmenu.push(...{
id : menu[i].id,
level : menu[i].level,
parentId : menu[i].parentId,
html : menu[i].name,
href : menu[i].url
});
}
contextmenu.render();
//原有回调函数
var contextmenuOnShow = contextmenu.onShow;
//设置新的回调函数
contextmenu.onShow = function (target, _this)...{
var item = target.treemenu || target.parentNode.treemenu;
if( item ) ...{
var html = 添加+item.html+“子节点+(item.children.length+1)+”;
_this.push( ...{
html : html,
click : function (e)...{
item.expand = false;
var newItem = ...{
id : item.id + 0+ (item.children.length+1),
level : item.level + 1,
parentId : item.id,
html : item.html+子节点+(item.children.length+1),
href : #,
css : item,
createExpand : true
};
item.children.push(newItem);
treemenu.list.push(newItem);
treemenu.renderChild(item);
},
clickClose : true,
index : 1,
type : dynamic
});
_this.push( ...{
html : 删除节点“+item.html+”,
click : function (e)...{
if( confirm(是否确认删除节点“+item.html+”?))
treemenu.remove(item);
},
clickClose : true,
index : 2,
type : dynamic
});
}
contextmenuOnShow(target, _this);
};

那么"回调函数"如何来实现呢?其实很简单,就是函数运行到某一行代码时运行预先设置的"回调函数",有点像事件机制,如同绑定多个window.onload事件,由于之前可能有绑定函数,所以先记录之前的函数,再设置新绑定的函数,之后再调用之前绑定的函数 。上面的所示代码实现右击元素如果为treemenu节点,则在右键里添加添加和删除treemenu节点菜单,效果见后面节点树(TreeMenu)示例 。
回调函数里我们需要注意作用域,this指针指向当前回调函数对象,而不是在运行回调函数的上下里,不过我们也可以使用call方法来把回调函数在当前this上下文里运行 。我这里是采用给回调函数传递2个参数的办法,这样在回调函数就能很方便的获取this对象和其他变量,这个在Ajax的Callback回调函数里普遍使用 。
自定义右键菜单(ContextMenu)只适合一些辅助功能的快捷操作,如有一些业务功能复杂的OA系统等,下面我也将会结合节点树(TreeMenu)进行使用 。如果可以话尽量不要使用右键菜单,其一是需要培训用户右击操作的习惯,其二自定义右键菜单丢失掉了原有右键菜单的一些功能,如查看源文件等 。
这里右键菜单区域 。
右击我你可以看属性哦 。
你也可以选择我再右击复制 。
你能遮住我吗?
ContextMenu.js
复制代码 代码如下:

/**//*
** Author : Jonllen
** Create : 2010-05-01
** Update : 2010-05-09
** SVN : 153
** WebSite: http://www.jonllen.com/
*/
var ContextMenu = function (settings) ...{
for( p in this.settings)
...{
if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
}
this.settings = settings;
this.settings.menu = document.createElement(div);
this.settings.menu.className = this.settings.css;
this.settings.menu.style.cssText = position:absolute;display:none;;
document.body.insertBefore(this.settings.menu,document.body.childNodes[0]);
return this;
}
ContextMenu.prototype = ...{
list : new Array(),
active : new Array(),
iframes : new Array(),
settings : ...{
menu : null,
excursionX : 0,
excursionY : 0,
css : contextmenu,
container : null,
locked : false
},
item : ...{
id : null,
level : 1,
parentId : 0,
html : ,
title : ,
href : javascript:;,
target : _self,
css : null,
element : null,
childElement : null,
parent : null,
children : null,
type : static,
click : null,
clickClose : false
},
push : function (item) ...{
var list = Object.prototype.toString.apply(item) === [object Array] ? item : [item];
for( var i=0; i< list.length; i++) ...{
var _item = list[i];
for( p in this.item) ...{
if( !_item.hasOwnProperty(p) ) _item[p] = this.item[p];
}
_item.element = null;
if( _item.name ) _item.html = _item.name;
if( _item.url ) _item.href = _item.url;
if( _item.type == static) ...{
this.list.push(_item);
}else ...{
if(this.dynamic == null) this.dynamic = new Array();
this.dynamic.push(_item);
}
}
return this;
},
bind : function ()...{
var _this = this;
for( var i=0; this.dynamic && i<this.dynamic.length; i++)
...{
var item = this.dynamic[i];
var itemElem = document.createElement(div);
itemElem.title = item.title;
itemElem.innerHTML = <a href="+item.href+" target="+item.target+">+item.html+</a>;
itemElem.className = item + (item.css? +item.css:);
item.element = itemElem;
if( item.click ) ...{
(function (item)...{
item.element.childNodes[0].onclick = function (e)...{
if( item.clickClose) _this.hidden();
return item.click(e);
};
})(item);
}
itemElem.contextmenu = item;
itemElem.onmouseover = function (e)...{ _this.hidden(item.level);};
var index = item.index || 0;
if( index >= this.settings.menu.childNodes.length)
index = this.settings.menu.childNodes.length - 1;
if( index < 0 )
this.settings.menu.appendChild(itemElem);
else
this.settings.menu.insertBefore(itemElem, this.settings.menu.childNodes[index]);
}
},
render : function ( container ) ...{
var _this = this;
container = container || this.settings.container;
this.settings.menu.innerHTML = ;
for( var i=0;i < this.list.length; i++)
...{
var item = this.list[i];
if ( item.parentId != 0 ) continue;
var itemElem = document.createElement(div);
itemElem.title = item.title;
itemElem.innerHTML = <a href="+item.href+" target="+item.target+">+item.html+</a>;
itemElem.className = item + (item.css? +item.css:);
var disabled = _this.hasClass(itemElem, disabled);
if ( disabled ) ...{
itemElem.childNodes[0].disabled = true;
itemElem.childNodes[0].className = disabled;
itemElem.childNodes[0].removeAttribute(href);
}
if ( _this.hasClass(itemElem, hidden) ) ...{
itemElem.style.display = none;
}
if( item.click ) ...{
(function (item)...{
item.element.childNodes[0].onclick = function (e)...{
if( item.clickClose) _this.hidden();
return item.click(e);
};
})(item);
}
itemElem.contextmenu = item;
itemElem.contextmenu.children = this.getChlid(item.id);
if( itemElem.contextmenu.children.length > 0 )
itemElem.childNodes[0].className += hasChild;
itemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
this.settings.menu.appendChild(itemElem);
}
this.active[0] = ...{ element : _this.settings.menu };
this.settings.menu.contextmenu = _this;
container.oncontextmenu = function (e)...{
e = window.event || e;
var target = e.target || e.srcElement;
if( e.preventDefault)
e.preventDefault();
var mouseCoords = _this.mouseCoords(e);
_this.settings.menu.style.left = mouseCoords.x + _this.settings.excursionX + px;
_this.settings.menu.style.top = mouseCoords.y + _this.settings.excursionY + px;
_this.hidden();
_this.show(0, target);
return false;
};
this.addEvent(document, click, function (e)...{
e = window.event || e;
var target = e.target || e.srcElement;
var isContextMenu = !!target.contextmenu;
if( isContextMenu == false) ...{
var parent = target.parentNode;
while( parent!=null) ...{
if( parent.contextmenu) ...{
isContextMenu = true;
break;
}
parent = parent.parentNode;
}
}
if (isContextMenu == false) ...{
_this.hidden();
}
});
},
renderChlid : function ( target )...{
if(this.settings.locked) return;
var contextmenu = target.contextmenu;
var currentLevel = contextmenu.level;
this.hidden(currentLevel);
var hasChild = false;
for( var j=0;j<contextmenu.children.length;j++) ...{
if( ( +contextmenu.children[j].css+ ).indexOf( hidden ) == -1) ...{
hasChild = true;
break;
}
}
if( !hasChild) return;
var childElem = contextmenu.element;
if (childElem == null) ...{
childElem = document.createElement(div);
childElem.className = this.settings.css;
childElem.style.position = absolute;
childElem.style.zIndex = 1000 + contextmenu.level;
var _this = this;
for( var i=0;i < contextmenu.children.length; i++)
...{
var childItem = contextmenu.children[i];
var childItemElem = document.createElement(div);
childItemElem.title = childItem.title;
childItemElem.innerHTML = <a href="+childItem.href+" target="+childItem.target+">+childItem.html+</a>;
childItemElem.className = item + (childItem.css? +childItem.css : );
var disabled = this.hasClass(childItemElem, disabled);
if ( disabled ) ...{
childItemElem.childNodes[0].disabled = true;
childItemElem.childNodes[0].removeAttribute(href);
}
if ( this.hasClass(childItemElem, hidden) ) ...{
childItemElem.style.display = none;
}
if( childItem.click ) ...{
(function (childItem)...{
childItem.element.childNodes[0].onclick = function (e)...{
if( childItem.clickClose) _this.hidden();
return childItem.click(e);
};
})(childItem);
}
childItem.parent = contextmenu;
childItemElem.contextmenu = childItem;
childItemElem.contextmenu.children = this.getChlid(childItem.id);
var hasChild = false;
for( var j=0; j<childItemElem.contextmenu.children.length; j++) ...{
if( ( +childItemElem.contextmenu.children[j].css+ ).indexOf( hidden ) == -1) ...{
hasChild = true;
break;
}
}
if( hasChild ) ...{
childItemElem.childNodes[0].className += hasChild;
}
childItemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
childElem.appendChild(childItemElem);
}
document.body.insertBefore(childElem,document.body.childNodes[0]);
contextmenu.element = childElem;
}
this.active[currentLevel] = contextmenu;
var xy = this.elemOffset(target);
var x = xy.x + target.offsetWidth + this.settings.excursionX;
var y = xy.y + this.settings.excursionY;
childElem.style.left = x + px;
childElem.style.top = y + px;
childElem.style.display = block;
this.show(currentLevel);
},
getChlid : function (id) ...{
var list = new Array();
for( var i=0;i < this.list.length; i++)
...{
var item = this.list[i];
if( item.parentId == id)
...{
list.push(item);
}
}
return list;
},
show : function (level, target) ...{
if(this.settings.locked) return;
level = level || 0;
var item = this.active[level];
if ( level == 0 ) ...{
for( var i=0;this.dynamic && i < this.dynamic.length; i++)
...{
var dynamicItemElem = this.dynamic[i].element;
if( dynamicItemElem !=null) dynamicItemElem.parentNode.removeChild(dynamicItemElem);
}
if (this.dynamic) this.dynamic.length = 0;
this.onShow(target, this);
}
var menuElem = item.element;
menuElem.style.display = block;
var iframeElem = this.iframes[level];
if ( iframeElem == null) ...{
iframeElem = document.createElement(iframe);
iframeElem.scrolling = no;
iframeElem.frameBorder = 0;
iframeElem.style.cssText = position:absolute; overflow:hidden;;
document.body.insertBefore(iframeElem,document.body.childNodes[0]);
this.iframes.push(iframeElem);
}
iframeElem.width = menuElem.offsetWidth;
iframeElem.height = menuElem.offsetHeight;
var menuElemOffset = this.elemOffset(menuElem);
iframeElem.style.left = menuElemOffset.x + px;
iframeElem.style.top = menuElemOffset.y + px;
iframeElem.style.display = block;
},
onShow : function (target, _this) ...{
if( target.nodeType == 1 && target.tagName == A && target.innerHTML.indexOf(.rar) != -1 )...{
//解压文件
_this.push( ...{
html : 解压缩到“+target.innerHTML.substring(0,target.innerHTML.lastIndexOf(.))+\\”...,
click : function (e)...{
e = e || window.event;
var srcElement = e.srcElement || e.target;
srcElement.className = on;
srcElement.innerHTML = 解压缩到“+target.innerHTML.substring(0,target.innerHTML.lastIndexOf(.))+\\”...;
var url = /Ajax/FileZip.aspx?mode=unzip&files=+target.href.substring(target.href.replace(//,xx).indexOf(/));
if( typeof Ajax == undefined) return;
Ajax.get(url, function (data, _this)...{
_this.settings.locked = true;
eval(data);
if( rs.success ) ...{
location.reload();
}else...{
alert(rs.error);
_this.hidden();
}
}, _this);
srcElement.onclick = null;
_this.settings.locked = true;
},
clickClose : false,
index : 2,
type : dynamic
});
}
else if( target.nodeType == 1 && target.title.indexOf(添加到) == 0) ...{
//添加单个压缩文件
_this.push( ...{
html : target.title,
title : target.title,
click : function (e)...{
var index = target.href.indexOf(?path=);
if( index != -1)...{
var fullName = target.href.substring(index+?path=.length);
}else ...{
var fullName = target.href.substring(target.href.replace(//,xx).indexOf(/));
}
e = e || window.event;
var srcElement = e.srcElement || e.target;
srcElement.className = on;
srcElement.innerHTML = 正在添加到“+fullName.substring(fullName.lastIndexOf(/)+1)+.rar”...;
var url = /Ajax/FileZip.aspx?mode=zip&files=+fullName;
if( typeof Ajax == undefined) return;
Ajax.get(url, function (data, _this)...{
_this.settings.locked = true;
eval(data);
if( rs.success ) ...{
location.reload();
}else...{
alert(rs.error);
_this.hidden();
}
}, _this);
srcElement.onclick = null;
_this.settings.locked = true;
},
clickClose : false,
index : 2,
type : dynamic,
css : on
});
}else ...{
//添加多个压缩文件
var fileName = ;
var files = new Array();
var ids = document.getElementsByName(ids);
for( var i=0; i<ids.length; i++) ...{
if( !ids[i].checked) continue;
var file = ids[i].value;
files.push(file);
if( files.length == 1) ...{
fileName = file.substring(file.lastIndexOf(/)+1) + .rar;
}
}
if( files.length > 0 )...{
_this.push( ...{
html : 添加+files.length+个文件到压缩包“+fileName+”,
click : function (e)...{
e = e || window.event;
var srcElement = e.srcElement || e.target;
srcElement.className = on;
srcElement.innerHTML = 正在添加到“+fileName+”...;
var url = /Ajax/FileZip.aspx?mode=zip&files=+files.join(|);
if( typeof Ajax == undefined) return;
Ajax.get(url, function (data, _this)...{
_this.settings.locked = true;
eval(data);
if( rs.success ) ...{
location.reload();
}else...{
alert(rs.error);
_this.hidden();
}
}, _this);
srcElement.onclick = null;
_this.settings.locked = true;
},
clickClose : false,
index : 2,
type : dynamic
});
}
}
if( target.nodeType == 1 && target.tagName == A) ...{
_this.push( ...{
html : 属性“+target.innerHTML+”,
href : target.href,
click : function (e)...{
prompt(属性“+target.innerHTML+”,target.href);
return false;
},
clickClose : true,
index : 3,
type : dynamic
});
}
var selection = window.getSelection ? window.getSelection().toString() : document.selection.createRange().text;
if( selection ) ...{
_this.push( ...{
html : 复制“ + (selection.length > 15 ? selection.substring(0,12) + ... : selection) +”,
title : 复制“ + selection + ”,
click : function (e) ...{
if(window.clipboardData) ...{
window.clipboardData.clearData();
window.clipboardData.setData("Text", selection);
}else ...{
netscape.security.PrivilegeManager.enablePrivilege(UniversalXPConnect);
var clip = Components.classes[@mozilla.org/widget/clipboard;1].createInstance(Components.interfaces.nsIClipboard);
var trans = Components.classes[@mozilla.org/widget/transferable;1].createInstance(Components.interfaces.nsITransferable);
if (!clip || !trans) return;
trans.addDataFlavor(text/unicode);
var len = new Object();
var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
str.data = selection;
trans.setTransferData("text/unicode",str,selection.length*2);
var clipid=Components.interfaces.nsIClipboard;
if (!clip) return false;
clip.setData(trans,null,clipid.kGlobalClipboard);
}
},
clickClose : true,
index : 0,
type : dynamic
});
}
_this.bind();
},
hidden : function (level) ...{
level = level || 0;
for( var i = level; i<this.active.length; i++) ...{
var item = this.active[i];
var iframeElem = this.iframes[i];
if ( iframeElem !=null)
iframeElem.style.display = none;
if(this.settings.locked) return;
var menuElem = item.element;
if ( menuElem !=null)
menuElem.style.display = none;
}
this.onHidden(level);
},
onHidden : function (level) ...{
},
hasClass : function (elem, name)
...{
return !!elem && ( +elem.className+ ).indexOf( +name+ ) != -1;
},
elemOffset : function(elem)...{
var left = 0;
var top = 0;
while (elem.offsetParent)...{
left += elem.offsetLeft;
top += elem.offsetTop;
elem = elem.offsetParent;
}
left += elem.offsetLeft;
top += elem.offsetTop;
return ...{x:left, y:top};
},
mouseCoords : function (e)...{
if (e.pageX && e.pageY) ...{
return ...{
x: e.pageX,
y: e.pageY
};
}
var d = (document.documentElement && document.documentElement.scrollTop) ? document.documentElement : document.body;
return ...{
x: e.clientX + d.scrollLeft,
y: e.clientY + d.scrollTop
};
},
addEvent : function(target,eventType,func)...{
if(target.attachEvent)
...{
target.attachEvent("on" + eventType, func);
}else if(target.addEventListener)
...{
target.addEventListener(eventType == mousewheel ? DOMMouseScroll : eventType, func, false);
}
return this;
},
removeEvent : function(target,eventType,func)...{
if(target.detachEvent)
...{
target.detachEvent("on" + eventType, func);
}else if(target.removeEventListener)
...{
target.removeEventListener(eventType == mousewheel ? DOMMouseScroll : eventType, func, false);
}
return this;
}
}

演示地址 http://demo.jb51.net/js/tree_json/ContextMenu.htm

3.节点树(TreeMenu)
节点树(TreeMenu)是我们实际项目中运用得最多了,网上很著名的有梅花雪的MzTreeVew,听说对大数据量时做了一些优化,效率很高 。但我不太喜欢拿来主义,有些东西既然我看不懂或还不明白它为什么要这么做,所以就想尝试着自己来"造轮子" 。当然功能肯定是没有MzTreeVew的那么强大,大数据量时我也没有做效率测试,图片先借MzTreeVew的 。

无限级节点树

要实现无限级的功能,如果没有什么小技巧,好象就只能递归了 。不过需要注意一定要有个正确条件判断来return,避免死循环 。从数据的存放结构来说,一般我们数据库里保存有id、name、parentId字段,树结构里仍然保存这种结构,在展开树节点的时候我们需要根据id获取它所有的子节点,并保存起来,避免第二次重复遍历 。

层次关系结构

我这里是想说,呈现出来的HTML具有层次关系,每一个树节点对象有层次关系 。HTML层次关系表现为子节点的元素一定是父节点的元素的子节点,本来我觉得这并不是必须的,后来我发现只有这样做才能保持子子节点的状态,比如我点击一级节点只需要展开所有的二级节点,三级或四级节点的状态不需要改变,HTML结构有这种层次关系支持就很容易实现 。与之相对应的是树节点对象,它保存着父节点对象、子节点集合对象、引用元素等等,以方便递归调用,这些信息都被附加到对应的dom元素上 。

带checkbox和radio选择

实际项目的需求都是复杂多变的,有时候我们需要提供radio单选功能,有时候可能需要提供checkbox多选功能,为了能在后台直接获取选择的值,提供带checkbox和radio选择功能也是必须的 。当然,是否创建checkbox或radio我们可以在实例化时配置指定,每一个节点初始化时是否选中也可设置指定,这里需要注意的是我们直接创建checkbox和radio是不能指定name属性的,转个弯换种思路来实现即可 。
复制代码 代码如下:

var inputTemp = document.createElement(div);
inputTemp.innerHTML = <input type="radio" name="ids" />;
var inputElem = inputTemp.childNodes[0];

只绑定一个click事件

看似较复杂的树结构,其实我只给最外面的容器元素绑定了一个click事件而已,另外点击checkbox的联动也是在这个click事件里处理的,因为元素的事件是会向父元素冒泡触发的,并且很容易使用事件对象event获取触发源元素,因此我就能获取你点击是checkbox还是什么其他的元素了,很方便 。这样做的好处就是集中来处理一个事件,而不需要臃肿的给每一个元素添加事件,充分展示代码的优雅之美 。

演示效果: http://demo.jb51.net/js/tree_json/TreeMenu.htm
打包下载地址 JavaScript 多种树结构菜单效果
本文转载自金龙博客:http://www.jonllen.com/jonllen/js/menu.aspx,转载请保留此段声明 。