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.glzy8.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.glzy8.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.glzy8.com/js/tree_json/TreeMenu.htm

  打包下载地址 JavaScript 多种树结构菜单效果

  本文转载自金龙博客:http://www.jonllen.com/jonllen/js/menu.aspx,转载请保留此段声明。