JavaScript几种形式的树结构菜单


Posted in Javascript onMay 10, 2010

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.3water.com/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.3water.com/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.3water.com/js/tree_json/TreeMenu.htm
打包下载地址 JavaScript 多种树结构菜单效果
本文转载自金龙博客:http://www.jonllen.com/jonllen/js/menu.aspx,转载请保留此段声明。

Javascript 相关文章推荐
jQuery的一些特性和用法整理小结
Jan 13 Javascript
关于onchange事件在IE和FF下的表现及解决方法
Mar 08 Javascript
JQuery 在文档中查找指定name的元素并移除的实现方法
May 19 Javascript
基于JS模仿windows文件按名称排序效果
Jun 29 Javascript
jQuery ajax方法传递中文时出现中文乱码的解决方法
Jul 25 Javascript
Javascript基于jQuery UI实现选中区域拖拽效果
Nov 25 Javascript
学习使用Bootstrap页面排版样式
May 11 Javascript
form表单数据封装成json格式并提交给服务器的实现方法
Dec 14 Javascript
angular6的响应式表单的实现
Oct 10 Javascript
js设计模式之单例模式原理与用法详解
Aug 15 Javascript
如何在Express4.x中愉快地使用async的方法
Nov 18 Javascript
echarts浮动显示单位的实现方法示例
Dec 04 Javascript
js function使用心得
May 10 #Javascript
javascript 模式设计之工厂模式详细说明
May 10 #Javascript
javascript 精粹笔记
May 09 #Javascript
javascript之通用简单的table选项卡实现(二)
May 09 #Javascript
javascript动态添加表格数据行(ASP后台数据库保存例子)
May 08 #Javascript
使用jQuery向asp.net Mvc传递复杂json数据-ModelBinder篇
May 07 #Javascript
javascript 通用简单的table选项卡实现
May 07 #Javascript
You might like
JAVA/JSP学习系列之二
2006/10/09 PHP
类的另类用法--数据的封装
2006/10/09 PHP
PHP zlib扩展实现页面GZIP压缩输出
2010/06/17 PHP
php 解析xml 的四种方法详细介绍
2016/10/26 PHP
IE8 浏览器Cookie的处理
2009/01/31 Javascript
javascript 动态调整图片尺寸实现代码
2009/12/28 Javascript
JQuery为textarea添加maxlength属性的代码
2010/04/07 Javascript
JavaScript 异步方法队列链实现代码分析
2010/06/05 Javascript
javascript 冒泡排序 正序和倒序实现代码
2010/12/14 Javascript
Jquery为单选框checkbox绑定单击click事件
2012/12/18 Javascript
JavaScript实现关键字高亮功能
2014/11/12 Javascript
jQuery EasyUI之DataGrid使用实例详解
2016/01/04 Javascript
JavaScript中解决多浏览器兼容性23个问题的快速解决方法
2016/05/19 Javascript
利用Vue.js指令实现全选功能
2016/09/08 Javascript
详解ES6中的let命令
2020/04/05 Javascript
jQuery上传多张图片带进度条样式(DEMO)
2017/03/02 Javascript
w3c编程挑战_初级脚本算法实战篇
2017/06/23 Javascript
vue使用axios上传文件(FormData)的方法
2019/04/14 Javascript
JavaScript实现捕获鼠标坐标
2020/04/12 Javascript
[01:43]倾听DOTA2英雄之声 魅惑魔女国服配音鉴赏
2013/06/06 DOTA
[36:54]Mineski vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
[01:01:52]完美世界DOTA2联赛PWL S2 GXR vs Magma 第二场 11.25
2020/11/26 DOTA
python实现进程间通信简单实例
2014/07/23 Python
Python操作MySQL数据库的示例代码
2020/07/13 Python
解决Python 写文件报错TypeError的问题
2020/10/23 Python
Python调用飞书发送消息的示例
2020/11/10 Python
STUBHUB日本:购买和出售全球活动门票
2018/07/01 全球购物
Python面试题:如何用Python来发送邮件
2016/03/15 面试题
平安家庭示范户事迹
2014/06/02 职场文书
国际商务英语专业求职信
2014/07/08 职场文书
运动会演讲稿300字
2014/08/25 职场文书
机动车登记业务委托书
2014/10/08 职场文书
工作失误检讨书(经典集锦版)
2014/10/17 职场文书
捐资助学感谢信
2015/01/21 职场文书
2019年个人工作总结范文
2019/03/25 职场文书
HTML5中的DOCUMENT.VISIBILITYSTATE属性详解
2023/05/07 HTML / CSS