JavaScript几种形式的树结构菜单
JavaScript几种形式的树结构菜单
发布时间:2016-12-30 来源:查字典编辑
摘要:1.悬浮层树(Tree)这种树结构实现类似面包屑导航功能,监听的是节点鼠标移动的事件,然后在节点下方或右方显示子节点,依此递归显示子节点的子...

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,转载请保留此段声明。

推荐文章
猜你喜欢
附近的人在看
推荐阅读
拓展阅读
相关阅读
网友关注
最新Javascript教程学习
热门Javascript教程学习
编程开发子分类