今天我主要讲3种不同展示的JavaScript树结构菜单,分别是悬浮层树(Tree)、右键菜单树(ContextMenu)和节点树(TreeMenu),目前都支持无限级层次。
1.悬浮层树(Tree)
这种树结构实现类似面包屑导航功能,监听的是节点鼠标移动的事件,然后在节点下方或右方显示子节点,依此递归显示子节点的子节点。
这里要注意几个小问题,其一这种树结构是悬浮层绝对定位的,在创建层的时候一定要直接放在body的下面,这样做的是确保在IE里面能遮掩住任何层,因为在IE里面是有stacking context这种东西的潜规则在里面的,另外当然还有一个select老掉牙的问题,这里是采用在每个悬浮层后面加个iframe元素,当然同一级的菜单只产生一个iframe元素,菜单有几级将产生几个iframe遮掩,然后菜单显示和隐藏的时候同时显示和隐藏iframe。
不过这种菜单并不合适前台,因为目前只支持在脚本里动态添加菜单节点,而不能从现有的html元素获取菜单节点,我们为了SEO等前台导航一般是在后台动态输出的,假如菜单有多级的话也建议不超过2层,对客户来说太多层也懒得去看,不过有个面包屑导航显示还是很不错的。
1/**//*
2** Author : Jonllen
3** Create : 2009-12-13
4** Update : 2010-05-08
5** SVN : 152
6** WebSite: http://www.jonllen.com/
7*/
8
9var Menu = function (container) ...{
10this.container = container;
11return this;
12}
13
14Menu.prototype = ...{
15list : new Array(),
16active : new Array(),
17iframes : new Array(),
18settings : ...{
19id : null,
20parentId : 0,
21name : null,
22url : null,
23level : 1,
24parent : null,
25children : null,
26css : null,
27element : null
28},
29push : function (item) ...{
30var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
31![]()
32for( var i=0; i< list.length; i++) ...{
33var settings = list[i];
34for( p in this.settings) ...{
35if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
36}
37this.list.push(settings);
38}
39return this;
40},
41getChlid : function (id) ...{
42var list = new Array();
43for( var i=0;i < this.list.length; i++)
44...{
45var item = this.list[i];
46if( item.parentId == id)
47...{
48list.push(item);
49}
50}
51return list;
52},
53render : function (container) ...{
54var _this = this;
55var menuElem = container || this.container;
56for( var i=0;i < this.list.length; i++)
57...{
58var item = this.list[i];
59if ( item.parentId != 0 ) continue;
60var itemElem = document.createElement('div');
61itemElem.innerHTML = '<a href="'+item.url+'">'+item.name+'</a>';
62itemElem.className = 'item';
63if ( item.css ) itemElem.className += ' '+item.css;
64var disabled = (' '+item.css+' ').indexOf(' disabled ')!=-1;
65if ( disabled ) ...{
66itemElem.childNodes[0].disabled = true;
67itemElem.childNodes[0].className = 'disabled';
68itemElem.childNodes[0].removeAttribute('href');
69}
70if ( (' '+item.css+' ').indexOf(' hidden ')!=-1 ) ...{
71itemElem.style.display = 'none';
72}
73itemElem.menu = item;
74itemElem.menu.children = this.getChlid(item.id);
75itemElem.onmouseover = function (e)...{
76_this.renderChlid(this);
77};
78menuElem.appendChild(itemElem);
79}
80document.onclick = function (e)...{
81e = window.event || e;
82var target = e.target || e.srcElement;
83if (!target.menu) ...{
84var self = _this;
85for( var i=1;i<_this.active.length;i++) ...{
86var item = _this.active[i];
87var menuElem = document.getElementById('menu'+item.id);
88if ( menuElem !=null)
89menuElem.style.display = 'none';
90}
91for(var j=1;j<_this.iframes.length;j++)...{
92_this.iframes[j].style.display = 'none';
93}
94}
95};
96},
97renderChlid : function (target)...{
98var self = this;
99var item = target.menu;
100var activeItem = self.active[item.level];
101while(activeItem) ...{
102var activeItemElem = activeItem.element;
103if ( activeItemElem!= null ) activeItemElem.style.display = 'none';
104activeItem = self.active[activeItem.level + 1];
105}
106self.active[item.level] = item;
107![]()
108var level = item.level;
109while(this.iframes[level]) ...{
110this.iframes[level].style.display = 'none';
111level++;
112}
113![]()
114var childElem = document.getElementById('menu'+item.id);
115if (childElem==null) ...{
116var hasChild = false;
117for( var j=0;j<item.children.length;j++) ...{
118if( (' '+item.children[j].css+' ').indexOf(' hidden ') == -1) ...{
119hasChild = true;
120break;
121}
122}
123if( hasChild) ...{
124![]()
125var xy = self.elemOffset(target);
126var x = xy.x;
127var y = target.offsetHeight + xy.y;
128if ( item.level >= 2 )
129...{
130x += target.offsetWidth - 1;
131y -= target.offsetHeight;
132}
133![]()
134childElem = document.createElement('div');
135childElem.id = 'menu'+item.id;
136childElem.className = 'child';
137childElem.style.position = 'absolute';
138childElem.style.left = x + 'px';
139childElem.style.top = y + 'px';
140childElem.style.zIndex = 1000 + item.level;
141for( var i=0;i < item.children.length; i++)
142...{
143var childItem = item.children[i];
144var childItemElem = document.createElement('a');
145var disabled = (' '+childItem.css+' ').indexOf('disabled')!=-1;
146if ( disabled ) ...{
147childItemElem.disabled = true;
148childItemElem.className += ' '+childItem.css;
149}else ...{
150childItemElem.href = childItem.url;
151}
152if ( (' '+childItem.css+' ').indexOf(' hidden ')!=-1 ) ...{
153childItemElem.style.display = 'none';
154}
155childItemElem.innerHTML = childItem.name;
156childItemElem.menu = childItem;
157childItemElem.menu.children = self.getChlid(childItem.id);
158var hasChild = false;
159for( var j=0;j<childItemElem.menu.children.length;j++) ...{
160if( (' '+childItemElem.menu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
161hasChild = true;
162break;
163}
164}
165if( hasChild ) ...{
166childItemElem.className += ' hasChild';
167}
168childItemElem.onmouseover = function (e) ...{
169self.renderChlid(this)
170};
171childElem.appendChild(childItemElem);
172}
173document.body.insertBefore(childElem,document.body.childNodes[0]);
174item.element = childElem;
175}
176}
177![]()
178if( childElem!=null) ...{
179var iframeElem = this.iframes[item.level];
180if ( iframeElem == null) ...{
181iframeElem = document.createElement('iframe');
182iframeElem.scrolling = 'no';
183iframeElem.frameBorder = 0;
184iframeElem.style.cssText = 'position:absolute; overflow:hidden;';
185document.body.insertBefore(iframeElem,document.body.childNodes[0]);
186this.iframes[item.level]=iframeElem;
187}
188childElem.style.display = 'block';
189iframeElem.width = childElem.offsetWidth;
190iframeElem.height = childElem.offsetHeight;
191iframeElem.style.left = parseInt(childElem.style.left) + 'px';
192iframeElem.style.top = parseInt(childElem.style.top) + 'px';
193iframeElem.style.display = 'block';
194}
195![]()
196},
197elemOffset : function(elem)...{
198if( elem==null) return ...{x:0,y:0};
199var t = elem.offsetTop;
200var l = elem.offsetLeft;
201while( elem = elem.offsetParent) ...{
202t += elem.offsetTop;
203l += elem.offsetLeft;
204}
205return ...{x : l,y : t};
206}
207};
2.右键菜单树(ContextMenu)
自定义右键菜单(ContextMenu)和悬浮层树(Tree)其实现上都大同小异,都是在脚本里动态添加节点,然后在生成一个绝对定位层,只不过右键菜单树(ContextMenu)触发的事件不一样。另外右键菜单还需要提供一个动态添加菜单项功能,以实现右击不同的元素可以显示不同的右键菜单,我这里提供一种"回调函数",使用见如下代码:
ContextMenu回调函数
1//ContextMenu
2![]()
3var contextmenu = new ContextMenu(...{ container : document.getElementById('treemenu') });
4contextmenu.push( ...{ html : 'Powered By: Jonllen', css : 'disabled'});
5contextmenu.push( ...{ html : '', css : 'line'});
6contextmenu.push( ...{ html : '刷新(<u>R</u>)', href : 'javascript:location.reload();'});
7for(var i=0;i<menu.length;i++) ...{
8contextmenu.push(...{
9id : menu[i].id,
10level : menu[i].level,
11parentId : menu[i].parentId,
12html : menu[i].name,
13href : menu[i].url
14});
15}
16contextmenu.render();
17![]()
18//原有回调函数
19var contextmenuOnShow = contextmenu.onShow;
20//设置新的回调函数
21contextmenu.onShow = function (target, _this)...{
22var item = target.treemenu || target.parentNode.treemenu;
23if( item ) ...{
24var html = '添加'+item.html+'“子节点'+(item.children.length+1)+'”';
25_this.push( ...{
26html : html,
27click : function (e)...{
28item.expand = false;
29var newItem = ...{
30id : item.id + '0'+ (item.children.length+1),
31level : item.level + 1,
32parentId : item.id,
33html : item.html+'子节点'+(item.children.length+1),
34href : '#',
35css : 'item',
36createExpand : true
37};
38item.children.push(newItem);
39treemenu.list.push(newItem);
40treemenu.renderChild(item);
41},
42clickClose : true,
43index : 1,
44type : 'dynamic'
45});
46_this.push( ...{
47html : '删除节点“'+item.html+'”',
48click : function (e)...{
49if( confirm('是否确认删除节点“'+item.html+'”?'))
50treemenu.remove(item);
51},
52clickClose : true,
53index : 2,
54type : 'dynamic'
55});
56}
57contextmenuOnShow(target, _this);
58};
那么"回调函数"如何来实现呢?其实很简单,就是函数运行到某一行代码时运行预先设置的"回调函数",有点像事件机制,如同绑定多个window.onload事件,由于之前可能有绑定函数,所以先记录之前的函数,再设置新绑定的函数,之后再调用之前绑定的函数。上面的所示代码实现右击元素如果为treemenu节点,则在右键里添加添加和删除treemenu节点菜单,效果见后面节点树(TreeMenu)示例。
回调函数里我们需要注意作用域,this指针指向当前回调函数对象,而不是在运行回调函数的上下里,不过我们也可以使用call方法来把回调函数在当前this上下文里运行。我这里是采用给回调函数传递2个参数的办法,这样在回调函数就能很方便的获取this对象和其他变量,这个在Ajax的Callback回调函数里普遍使用。
自定义右键菜单(ContextMenu)只适合一些辅助功能的快捷操作,如有一些业务功能复杂的OA系统等,下面我也将会结合节点树(TreeMenu)进行使用。如果可以话尽量不要使用右键菜单,其一是需要培训用户右击操作的习惯,其二自定义右键菜单丢失掉了原有右键菜单的一些功能,如查看源文件等。
- 这里右键菜单区域。
- 右击我你可以看属性哦。
- 你也可以选择我再右击复制。
1/**//*
2** Author : Jonllen
3** Create : 2010-05-01
4** Update : 2010-05-09
5** SVN : 153
6** WebSite: http://www.jonllen.com/
7*/
8
9var ContextMenu = function (settings) ...{
10
11for( p in this.settings)
12...{
13if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
14}
15this.settings = settings;
16![]()
17this.settings.menu = document.createElement('div');
18this.settings.menu.className = this.settings.css;
19this.settings.menu.style.cssText = 'position:absolute;display:none;';
20document.body.insertBefore(this.settings.menu,document.body.childNodes[0]);
21![]()
22return this;
23}
24
25ContextMenu.prototype = ...{
26list : new Array(),
27active : new Array(),
28iframes : new Array(),
29settings : ...{
30menu : null,
31excursionX : 0,
32excursionY : 0,
33css : 'contextmenu',
34container : null,
35locked : false
36},
37item : ...{
38id : null,
39level : 1,
40parentId : 0,
41html : '',
42title : '',
43href : 'javascript:;',
44target : '_self',
45css : null,
46element : null,
47childElement : null,
48parent : null,
49children : null,
50type : 'static',
51click : null,
52clickClose : false
53},
54push : function (item) ...{
55var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
56for( var i=0; i< list.length; i++) ...{
57var _item = list[i];
58for( p in this.item) ...{
59if( !_item.hasOwnProperty(p) ) _item[p] = this.item[p];
60}
61_item.element = null;
62if( _item.name ) _item.html = _item.name;
63if( _item.url ) _item.href = _item.url;
64if( _item.type == 'static') ...{
65this.list.push(_item);
66}else ...{
67if(this.dynamic == null) this.dynamic = new Array();
68this.dynamic.push(_item);
69}
70}
71return this;
72},
73bind : function ()...{
74var _this = this;
75for( var i=0; this.dynamic && i<this.dynamic.length; i++)
76...{
77var item = this.dynamic[i];
78var itemElem = document.createElement('div');
79itemElem.title = item.title;
80itemElem.innerHTML = '<a href="'+item.href+'" target="'+item.target+'">'+item.html+'</a>';
81itemElem.className = 'item ' + (item.css?' '+item.css:'');
82item.element = itemElem;
83![]()
84if( item.click ) ...{
85(function (item)...{
86item.element.childNodes[0].onclick = function (e)...{
87if( item.clickClose) _this.hidden();
88return item.click(e);
89};
90})(item);
91}
92![]()
93itemElem.contextmenu = item;
94itemElem.onmouseover = function (e)...{ _this.hidden(item.level);};
95![]()
96var index = item.index || 0;
97if( index >= this.settings.menu.childNodes.length)
98index = this.settings.menu.childNodes.length - 1;
99if( index < 0 )
100this.settings.menu.appendChild(itemElem);
101else
102this.settings.menu.insertBefore(itemElem, this.settings.menu.childNodes[index]);
103}
104},
105render : function ( container ) ...{
106![]()
107var _this = this;
108![]()
109container = container || this.settings.container;
110![]()
111this.settings.menu.innerHTML = '';
112![]()
113for( var i=0;i < this.list.length; i++)
114...{
115var item = this.list[i];
116if ( item.parentId != 0 ) continue;
117var itemElem = document.createElement('div');
118itemElem.title = item.title;
119itemElem.innerHTML = '<a href="'+item.href+'" target="'+item.target+'">'+item.html+'</a>';
120itemElem.className = 'item ' + (item.css?' '+item.css:'');
121var disabled = _this.hasClass(itemElem, 'disabled');
122if ( disabled ) ...{
123itemElem.childNodes[0].disabled = true;
124itemElem.childNodes[0].className = 'disabled';
125itemElem.childNodes[0].removeAttribute('href');
126}
127if ( _this.hasClass(itemElem, 'hidden') ) ...{
128itemElem.style.display = 'none';
129}
130![]()
131if( item.click ) ...{
132(function (item)...{
133item.element.childNodes[0].onclick = function (e)...{
134if( item.clickClose) _this.hidden();
135return item.click(e);
136};
137})(item);
138}
139![]()
140itemElem.contextmenu = item;
141itemElem.contextmenu.children = this.getChlid(item.id);
142if( itemElem.contextmenu.children.length > 0 )
143itemElem.childNodes[0].className += ' hasChild';
144itemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
145this.settings.menu.appendChild(itemElem);
146}
147![]()
148this.active[0] = ...{ element : _this.settings.menu };
149this.settings.menu.contextmenu = _this;
150container.oncontextmenu = function (e)...{
151e = window.event || e;
152var target = e.target || e.srcElement;
153if( e.preventDefault)
154e.preventDefault();
155var mouseCoords = _this.mouseCoords(e);
156_this.settings.menu.style.left = mouseCoords.x + _this.settings.excursionX + 'px';
157_this.settings.menu.style.top = mouseCoords.y + _this.settings.excursionY + 'px';
158_this.hidden();
159_this.show(0, target);
160return false;
161};
162this.addEvent(document, 'click', function (e)...{
163e = window.event || e;
164var target = e.target || e.srcElement;
165var isContextMenu = !!target.contextmenu;
166if( isContextMenu == false) ...{
167var parent = target.parentNode;
168while( parent!=null) ...{
169if( parent.contextmenu) ...{
170isContextMenu = true;
171break;
172}
173parent = parent.parentNode;
174}
175}
176if (isContextMenu == false) ...{
177_this.hidden();
178}
179});
180![]()
181},
182renderChlid : function ( target )...{
183![]()
184if(this.settings.locked) return;
185![]()
186var contextmenu = target.contextmenu;
187var currentLevel = contextmenu.level;
188this.hidden(currentLevel);
189![]()
190var hasChild = false;
191for( var j=0;j<contextmenu.children.length;j++) ...{
192if( (' '+contextmenu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
193hasChild = true;
194break;
195}
196}
197if( !hasChild) return;
198![]()
199var childElem = contextmenu.element;
200if (childElem == null) ...{
201![]()
202childElem = document.createElement('div');
203childElem.className = this.settings.css;
204childElem.style.position = 'absolute';
205childElem.style.zIndex = 1000 + contextmenu.level;
206![]()
207var _this = this;
208![]()
209for( var i=0;i < contextmenu.children.length; i++)
210...{
211var childItem = contextmenu.children[i];
212
213var childItemElem = document.createElement('div');
214childItemElem.title = childItem.title;
215childItemElem.innerHTML = '<a href="'+childItem.href+'" target="'+childItem.target+'">'+childItem.html+'</a>';
216childItemElem.className = 'item' + (childItem.css?' '+childItem.css : '');
217var disabled = this.hasClass(childItemElem, 'disabled');
218if ( disabled ) ...{
219childItemElem.childNodes[0].disabled = true;
220childItemElem.childNodes[0].removeAttribute('href');
221}
222if ( this.hasClass(childItemElem, 'hidden') ) ...{
223childItemElem.style.display = 'none';
224}
225![]()
226if( childItem.click ) ...{
227(function (childItem)...{
228childItem.element.childNodes[0].onclick = function (e)...{
229if( childItem.clickClose) _this.hidden();
230return childItem.click(e);
231};
232})(childItem);
233}
234![]()
235childItem.parent = contextmenu;
236childItemElem.contextmenu = childItem;
237childItemElem.contextmenu.children = this.getChlid(childItem.id);
238var hasChild = false;
239for( var j=0; j<childItemElem.contextmenu.children.length; j++) ...{
240if( (' '+childItemElem.contextmenu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
241hasChild = true;
242break;
243}
244}
245if( hasChild ) ...{
246childItemElem.childNodes[0].className += ' hasChild';
247}
248childItemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
249childElem.appendChild(childItemElem);
250}
251![]()
252document.body.insertBefore(childElem,document.body.childNodes[0]);
253contextmenu.element = childElem;
254
255}
256![]()
257this.active[currentLevel] = contextmenu;
258![]()
259var xy = this.elemOffset(target);
260var x = xy.x + target.offsetWidth + this.settings.excursionX;
261var y = xy.y + this.settings.excursionY;
262childElem.style.left = x + 'px';
263childElem.style.top = y + 'px';
264childElem.style.display = 'block';
265![]()
266this.show(currentLevel);
267},
268getChlid : function (id) ...{
269var list = new Array();
270for( var i=0;i < this.list.length; i++)
271...{
272var item = this.list[i];
273if( item.parentId == id)
274...{
275list.push(item);
276}
277}
278return list;
279},
280show : function (level, target) ...{
281![]()
282if(this.settings.locked) return;
283![]()
284level = level || 0;
285var item = this.active[level];
286![]()
287if ( level == 0 ) ...{
288for( var i=0;this.dynamic && i < this.dynamic.length; i++)
289...{
290var dynamicItemElem = this.dynamic[i].element;
291if( dynamicItemElem !=null) dynamicItemElem.parentNode.removeChild(dynamicItemElem);
292}
293if (this.dynamic) this.dynamic.length = 0;
294this.onShow(target, this);
295}
296![]()
297var menuElem = item.element;
298menuElem.style.display = 'block';
299var iframeElem = this.iframes[level];
300if ( iframeElem == null) ...{
301iframeElem = document.createElement('iframe');
302iframeElem.scrolling = 'no';
303iframeElem.frameBorder = 0;
304iframeElem.style.cssText = 'position:absolute; overflow:hidden;';
305document.body.insertBefore(iframeElem,document.body.childNodes[0]);
306this.iframes.push(iframeElem);
307}
308iframeElem.width = menuElem.offsetWidth;
309iframeElem.height = menuElem.offsetHeight;
310var menuElemOffset = this.elemOffset(menuElem);
311iframeElem.style.left = menuElemOffset.x + 'px';
312iframeElem.style.top = menuElemOffset.y + 'px';
313iframeElem.style.display = 'block';
314![]()
315},
316onShow : function (target, _this) ...{
317![]()
318if( target.nodeType == 1 && target.tagName == 'A' && target.innerHTML.indexOf('.rar') != -1 )...{
319//解压文件
320_this.push( ...{
321html : '解压缩到“'+target.innerHTML.substring(0,target.innerHTML.lastIndexOf('.'))+'\\”...',
322click : function (e)...{
323e = e || window.event;
324var srcElement = e.srcElement || e.target;
325srcElement.className = 'on';
326srcElement.innerHTML = '解压缩到“'+target.innerHTML.substring(0,target.innerHTML.lastIndexOf('.'))+'\\”...';
327var url = '/Ajax/FileZip.aspx?mode=unzip&files='+target.href.substring(target.href.replace('//','xx').indexOf('/'));
328if( typeof Ajax == 'undefined') return;
329Ajax.get(url, function (data, _this)...{
330_this.settings.locked = true;
331eval(data);
332if( rs.success ) ...{
333location.reload();
334}else...{
335alert(rs.error);
336_this.hidden();
337}
338}, _this);
339srcElement.onclick = null;
340_this.settings.locked = true;
341![]()
342},
343clickClose : false,
344index : 2,
345type : 'dynamic'
346});
347}
348else if( target.nodeType == 1 && target.title.indexOf('添加到') == 0) ...{
349//添加单个压缩文件
350_this.push( ...{
351html : target.title,
352title : target.title,
353click : function (e)...{
354var index = target.href.indexOf('?path=');
355if( index != -1)...{
356var fullName = target.href.substring(index+'?path='.length);
357}else ...{
358var fullName = target.href.substring(target.href.replace('//','xx').indexOf('/'));
359}
360e = e || window.event;
361var srcElement = e.srcElement || e.target;
362srcElement.className = 'on';
363srcElement.innerHTML = '正在添加到“'+fullName.substring(fullName.lastIndexOf('/')+1)+'.rar”...';
364var url = '/Ajax/FileZip.aspx?mode=zip&files='+fullName;
365if( typeof Ajax == 'undefined') return;
366Ajax.get(url, function (data, _this)...{
367_this.settings.locked = true;
368eval(data);
369if( rs.success ) ...{
370location.reload();
371}else...{
372alert(rs.error);
373_this.hidden();
374}
375}, _this);
376srcElement.onclick = null;
377_this.settings.locked = true;
378},
379clickClose : false,
380index : 2,
381type : 'dynamic',
382css : 'on'
383});
384}else ...{
385//添加多个压缩文件
386var fileName = '';
387var files = new Array();
388var ids = document.getElementsByName('ids');
389for( var i=0; i<ids.length; i++) ...{
390if( !ids[i].checked) continue;
391![]()
392var file = ids[i].value;
393files.push(file);
394if( files.length == 1) ...{
395fileName = file.substring(file.lastIndexOf('/')+1) + '.rar';
396}
397}
398if( files.length > 0 )...{
399_this.push( ...{
400html : '添加'+files.length+'个文件到压缩包“'+fileName+'”',
401click : function (e)...{
402![]()
403e = e || window.event;
404var srcElement = e.srcElement || e.target;
405srcElement.className = 'on';
406srcElement.innerHTML = '正在添加到“'+fileName+'”...';
407var url = '/Ajax/FileZip.aspx?mode=zip&files='+files.join('|');
408if( typeof Ajax == 'undefined') return;
409Ajax.get(url, function (data, _this)...{
410_this.settings.locked = true;
411eval(data);
412if( rs.success ) ...{
413location.reload();
414}else...{
415alert(rs.error);
416_this.hidden();
417}
418}, _this);
419srcElement.onclick = null;
420_this.settings.locked = true;
421![]()
422},
423clickClose : false,
424index : 2,
425type : 'dynamic'
426});
427}
428}
429![]()
430if( target.nodeType == 1 && target.tagName == 'A') ...{
431_this.push( ...{
432html : '属性“'+target.innerHTML+'”',
433href : target.href,
434click : function (e)...{
435prompt('属性“'+target.innerHTML+'”',target.href);
436return false;
437},
438clickClose : true,
439index : 3,
440type : 'dynamic'
441});
442}
443![]()
444var selection = window.getSelection ? window.getSelection().toString() : document.selection.createRange().text;
445if( selection ) ...{
446_this.push( ...{
447html : '复制“' + (selection.length > 15 ? selection.substring(0,12) + '...' : selection) +'”',
448title : '复制“' + selection + '”',
449click : function (e) ...{
450if(window.clipboardData) ...{
451window.clipboardData.clearData();
452window.clipboardData.setData("Text", selection);
453}else ...{
454netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
455var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard);
456var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
457if (!clip || !trans) return;
458
459trans.addDataFlavor('text/unicode');
460var len = new Object();
461var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
462str.data = selection;
463trans.setTransferData("text/unicode",str,selection.length*2);
464var clipid=Components.interfaces.nsIClipboard;
465if (!clip) return false;
466clip.setData(trans,null,clipid.kGlobalClipboard);
467}
468},
469clickClose : true,
470index : 0,
471type : 'dynamic'
472});
473}
474![]()
475_this.bind();
476},
477hidden : function (level) ...{
478
479level = level || 0;
480![]()
481for( var i = level; i<this.active.length; i++) ...{
482var item = this.active[i];
483
484var iframeElem = this.iframes[i];
485if ( iframeElem !=null)
486iframeElem.style.display = 'none';
487![]()
488if(this.settings.locked) return;
489![]()
490var menuElem = item.element;
491if ( menuElem !=null)
492menuElem.style.display = 'none';
493![]()
494}
495this.onHidden(level);
496},
497onHidden : function (level) ...{
498},
499hasClass : function (elem, name)
500...{
501return !!elem && (' '+elem.className+' ').indexOf(' '+name+' ') != -1;
502},
503elemOffset : function(elem)...{
504var left = 0;
505var top = 0;
506while (elem.offsetParent)...{
507left += elem.offsetLeft;
508top += elem.offsetTop;
509elem = elem.offsetParent;
510}
511left += elem.offsetLeft;
512top += elem.offsetTop;
513return ...{x:left, y:top};
514},
515mouseCoords : function (e)...{
516if (e.pageX && e.pageY) ...{
517return ...{
518x: e.pageX,
519y: e.pageY
520};
521}
522var d = (document.documentElement && document.documentElement.scrollTop) ? document.documentElement : document.body;
523return ...{
524x: e.clientX + d.scrollLeft,
525y: e.clientY + d.scrollTop
526};
527},
528addEvent : function(target,eventType,func)...{
529if(target.attachEvent)
530...{
531target.attachEvent("on" + eventType, func);
532![]()
533}else if(target.addEventListener)
534...{
535target.addEventListener(eventType == 'mousewheel' ? 'DOMMouseScroll' : eventType, func, false);
536}
537return this;
538},
539removeEvent : function(target,eventType,func)...{
540if(target.detachEvent)
541...{
542target.detachEvent("on" + eventType, func);
543![]()
544}else if(target.removeEventListener)
545...{
546target.removeEventListener(eventType == 'mousewheel' ? 'DOMMouseScroll' : eventType, func, false);
547}
548return this;
549}
550}
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还是什么其他的元素了,很方便。这样做的好处就是集中来处理一个事件,而不需要臃肿的给每一个元素添加事件,充分展示代码的优雅之美。
1/**//*
2** Author : Jonllen
3** Create : 2010-05-08
4** SVN : 152
5** WebSite: http://www.jonllen.com/
6*/
7
8var TreeMenu = function (settings)...{
9for( p in this.settings) ...{
10if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
11}
12this.settings = settings;
13}
14
15TreeMenu.prototype = ...{
16list : new Array(),
17settings : ...{
18indent : 20,
19container : null,
20recursion : false,
21checkbox : false,
22radio : false,
23name : 'ids',
24tree_expand_plus : '/style/default/tree_expand_plus.gif',
25tree_expand_minus : '/style/default/tree_expand_minus.gif',
26tree_expand_normal : '/style/default/tree_expand_normal.gif',
27tree_icon_file : '/style/default/tree_icon_file.gif',
28tree_icon_folder : '/style/default/tree_icon_folder.gif',
29tree_icon_folderopen : '/style/default/tree_icon_folderopen.gif'
30},
31item : ...{
32id : null,
33level : 1,
34parentId : 0,
35html : '',
36title : '',
37href : 'javascript:;',
38target : '_self',
39css : 'item',
40img : null,
41click : null,
42createExpand : true,
43expand : false,
44checked : false,
45disabled : false,
46children : null
47},
48push : function (item) ...{
49var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
50for( var i=0; i< list.length; i++) ...{
51var _item = list[i];
52for( p in this.item) ...{
53if( !_item.hasOwnProperty(p) ) _item[p] = this.item[p];
54}
55this.list.push(_item);
56}
57},
58render : function (container)...{
59var _this = this;
60![]()
61var container = container || this.settings.container;
62![]()
63while( container.lastChild) ...{
64container.removeChild( container.lastChild);
65}
66![]()
67for( var i=0; i<this.list.length; i++) ...{
68var item = this.list[i];
69if( item.parentId !=0) continue;
70![]()
71var itemElem = document.createElement('div');
72itemElem.className = item.css;
73itemElem.title = item.title;
74![]()
75var expandElem = null;
76if( item.createExpand) ...{
77expandElem = document.createElement('img');
78expandElem.src = this.settings.tree_expand_plus;
79itemElem.appendChild(expandElem);
80}
81![]()
82var iconElem = document.createElement('img');
83iconElem.src = item.img ? item.img : this.settings.tree_icon_folder;
84itemElem.appendChild(iconElem);
85![]()
86if( this.settings.checkbox || this.settings.radio)...{
87var inputTemp = document.createElement('div');
88inputTemp.innerHTML = '<input type="'+(this.settings.checkbox?'checkbox':'radio')+'" name="' + this.settings.name + '" />';
89var inputElem = inputTemp.childNodes[0];
90inputElem.value = item.id;
91inputElem.checked = item.checked;
92inputElem.disabled = item.disabled;
93![]()
94itemElem.appendChild(inputElem);
95item.inputElem = inputElem;
96}
97![]()
98var aElem = document.createElement('a');
99aElem.href = item.href;
100aElem.target = item.target;
101aElem.innerHTML = item.html;
102itemElem.appendChild(aElem);
103![]()
104var children = this.getChlid(item.id);
105if( children.length ==0 )...{
106expandElem ? expandElem.src = this.settings.tree_expand_normal : null;
107iconElem.src = item.img ? item.img : this.settings.tree_icon_file;
108}
109![]()
110item.children = children;
111item.itemElem = itemElem;
112item.iconElem = iconElem;
113item.expandElem = expandElem;
114item.aElem = aElem;
115![]()
116itemElem.treemenu = item;
117![]()
118container.appendChild(itemElem);
119}
120![]()
121container.onclick = this.onClick;
122container._this = this;
123},
124renderChild : function(item, checked) ...{
125![]()
126if( item.children.length == 0) return;
127![]()
128var _this = this;
129var expand = item.expand;
130![]()
131for( var i=0;i<item.children.length;i++) ...{
132![]()
133var childItem = item.children[i];
134![]()
135var childElem = childItem.itemElem;
136![]()
137if( childElem == null) ...{
138childElem = document.createElement('div');
139childElem.className = childItem.css;
140childElem.title = childItem.title;
141![]()
142var expandElem = null;
143if( childItem.createExpand) ...{
144expandElem = document.createElement('img');
145expandElem.src = this.settings.tree_expand_plus;
146childElem.appendChild(expandElem);
147}
148![]()
149var iconElem = document.createElement('img');
150iconElem.src = childItem.img ? childItem.img : this.settings.tree_icon_folder;
151childElem.appendChild(iconElem);
152![]()
153if( this.settings.checkbox || this.settings.radio)...{
154var inputTemp = document.createElement('div');
155inputTemp.innerHTML = '<input type="'+(this.settings.checkbox?'checkbox':'radio')+'" name="' + this.settings.name + '" />';
156var inputElem = inputTemp.childNodes[0];
157inputElem.value = childItem.id;
158inputElem.checked = childItem.checked;
159inputElem.disabled = childItem.disabled;
160![]()
161childElem.appendChild(inputElem);
162childItem.inputElem = inputElem;
163}
164![]()
165var aElem = document.createElement('a');
166aElem.href = childItem.href;
167aElem.target = childItem.target;
168aElem.innerHTML = childItem.html;
169childElem.appendChild(aElem);
170![]()
171var children = this.getChlid(childItem.id);
172if( children.length ==0 )...{
173expandElem ? expandElem.src = this.settings.tree_expand_normal : null;
174iconElem.src = childItem.img ? childItem.img : this.settings.tree_icon_file;
175}
176![]()
177childItem.level = item.level + 1;
178![]()
179childElem.style.paddingLeft = this.settings.indent+'px';
180childElem.style.display = 'none';
181![]()
182childItem.parent = item;
183childItem.children = children;
184childItem.itemElem = childElem;
185childItem.iconElem = iconElem;
186childItem.expandElem = expandElem;
187childItem.aElem = aElem;
188![]()
189childElem.treemenu = childItem;
190![]()
191item.itemElem.appendChild(childElem);
192}
193![]()
194if( this.settings.recursion ) ...{
195//递归
196childItem.expand = expand;
197this.renderChild(childItem, checked);
198}
199![]()
200if( childItem.inputElem && typeof checked != 'undefined') ...{
201childItem.inputElem.checked = checked;
202continue;
203}
204![]()
205childItem.expand = expand;
206childElem.style.display = childItem.expand ? 'none' : 'block';
207}
208
209if( typeof checked == 'undefined') ...{
210item.expandElem && (item.expandElem.src = expand ? this.settings.tree_expand_plus : this.settings.tree_expand_minus);
211item.iconElem.src = expand ? (item.img ? item.img : this.settings.tree_icon_folder) : this.settings.tree_icon_folderopen;
212![]()
213item.expand = !expand;
214}
215},
216onClick :function (e) ...{
217e = e || window.event;
218var target = e.target || e.srcElement;
219![]()
220var _this = this._this;
221var treemenu = target.treemenu || target.parentNode.treemenu || target.parentNode.parentNode.treemenu;
222![]()
223if(treemenu && target.nodeType == 1 && target.type == 'checkbox') ...{
224var recursion = false;
225if( _this.settings.recursion) ...{
226recursion = true;
227}
228_this.settings.recursion = true;
229_this.renderChild(treemenu, target.checked);
230_this.settings.recursion = recursion;
231return;
232}
233![]()
234if( treemenu && treemenu.aElem == target) ...{
235_this.selectItem ? _this.selectItem.aElem.className = '' : null;
236_this.selectItem = treemenu;
237target.className = 'selected';
238target.blur();
239if( treemenu.click) ...{
240return treemenu.click(treemenu);
241}
242alert('the selected item id is : '+treemenu.id);
243return false;
244}
245![]()
246if( target.nodeType == 1 && target.type == 'radio') return;
247![]()
248if( treemenu) ...{
249_this.renderChild(treemenu);
250}
251},
252expandAll : function (expand) ...{
253![]()
254expand = expand === false;
255![]()
256var recursion = false;
257if( this.settings.recursion) ...{
258recursion = true;
259}
260![]()
261this.settings.recursion = true;
262![]()
263for( var i=0; i<this.list.length; i++) ...{
264var item = this.list[i];
265item.expand = expand;
266if( item.parentId ==0) ...{
267this.renderChild(item);
268}
269}
270![]()
271this.settings.recursion = recursion;
272},
273getChlid : function (id) ...{
274var list = new Array();
275for( var i=0;i < this.list.length; i++)
276...{
277var item = this.list[i];
278if( item.parentId == id)
279...{
280list.push(item);
281}
282}
283return list;
284},
285remove : function (item, list)...{
286list = list || this.list;
287for( var i=0;i < list.length; i++)
288...{
289if( list[i].id == item.id)
290...{
291list[i].itemElem.parentNode.removeChild(list[i].itemElem);
292list.splice(i,1);
293if( item.parent ) ...{
294for ( var j=0; j<item.parent.children.length; j++) ...{
295if( item.parent.children[j].id == item.id) ...{
296item.parent.children.splice(j,1);
297break;
298}
299}
300item.parent.expand = false;
301this.renderChild(item.parent);
302}
303return list;
304}
305}
306return list;
307}
308}
4.结束语
如果有细心的童鞋查看原代码,发现我在实例化活动函数的时指定的参数很多地方都是构建成一个静态对象,如下面的代码段:
treemenu.push({
id : 9999,
parentId : 0,
html : '无限级树节点',
href : '#',
img : '/style/default/ico_profile.gif',
disabled : true,
createExpand : false,
click : function (treemenu) {
alert('您选择了'+treemenu.html);
}
});
那么为什么要这样做呢?这里我来解释一下我这么做的用意:看上去使用复杂了一点,其实用处可大着呢。指定一个静态对象的参数,可以获取它所有的属性和值,并能与预设的参数属性比较,指定缺失参数的默认值。并且有利于以后的扩展,比如新增一个属性直接在预设的参数对象里添加一个默认属性即可,这样也不用修改之前传递过来的参数,在有多个不定的arguments参数时非常实用。算是一个小技巧吧,像jQeruy插件等构造函数初始化都有用到。
//默认的参数对象
var item = {
id : null,
level : 1,
parentId : 0,
html : '',
title : '',
href : 'javascript:;',
target : '_self',
css : 'item',
img : null,
click : null,
createExpand : true,
expand : false,
checked : false,
disabled : false,
children : null
}
//初始化缺失属性参数默认值
for( p in item) {
if( !settings.hasOwnProperty(p) ) settings[p] = item[p];
}
本文转载自金龙博客:http://www.jonllen.com/jonllen/js/menu.aspx,转载请保留此段声明。