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几种形式的树结构菜单
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@