今天我主要讲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) ...{
10 this.container = container;
11 return this;
12}
13
14Menu.prototype = ...{
15 list : new Array(),
16 active : new Array(),
17 iframes : new Array(),
18 settings : ...{
19 id : null,
20 parentId : 0,
21 name : null,
22 url : null,
23 level : 1,
24 parent : null,
25 children : null,
26 css : null,
27 element : null
28 },
29 push : function (item) ...{
30 var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
31
32 for( var i=0; i< list.length; i++) ...{
33 var settings = list[i];
34 for( p in this.settings) ...{
35 if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
36 }
37 this.list.push(settings);
38 }
39 return this;
40 },
41 getChlid : function (id) ...{
42 var list = new Array();
43 for( var i=0;i < this.list.length; i++)
44 ...{
45 var item = this.list[i];
46 if( item.parentId == id)
47 ...{
48 list.push(item);
49 }
50 }
51 return list;
52 },
53 render : function (container) ...{
54 var _this = this;
55 var menuElem = container || this.container;
56 for( var i=0;i < this.list.length; i++)
57 ...{
58 var item = this.list[i];
59 if ( item.parentId != 0 ) continue;
60 var itemElem = document.createElement('div');
61 itemElem.innerHTML = '<a href="'+item.url+'">'+item.name+'</a>';
62 itemElem.className = 'item';
63 if ( item.css ) itemElem.className += ' '+item.css;
64 var disabled = (' '+item.css+' ').indexOf(' disabled ')!=-1;
65 if ( disabled ) ...{
66 itemElem.childNodes[0].disabled = true;
67 itemElem.childNodes[0].className = 'disabled';
68 itemElem.childNodes[0].removeAttribute('href');
69 }
70 if ( (' '+item.css+' ').indexOf(' hidden ')!=-1 ) ...{
71 itemElem.style.display = 'none';
72 }
73 itemElem.menu = item;
74 itemElem.menu.children = this.getChlid(item.id);
75 itemElem.onmouseover = function (e)...{
76 _this.renderChlid(this);
77 };
78 menuElem.appendChild(itemElem);
79 }
80 document.onclick = function (e)...{
81 e = window.event || e;
82 var target = e.target || e.srcElement;
83 if (!target.menu) ...{
84 var self = _this;
85 for( var i=1;i<_this.active.length;i++) ...{
86 var item = _this.active[i];
87 var menuElem = document.getElementById('menu'+item.id);
88 if ( menuElem !=null)
89 menuElem.style.display = 'none';
90 }
91 for(var j=1;j<_this.iframes.length;j++)...{
92 _this.iframes[j].style.display = 'none';
93 }
94 }
95 };
96 },
97 renderChlid : function (target)...{
98 var self = this;
99 var item = target.menu;
100 var activeItem = self.active[item.level];
101 while(activeItem) ...{
102 var activeItemElem = activeItem.element;
103 if ( activeItemElem!= null ) activeItemElem.style.display = 'none';
104 activeItem = self.active[activeItem.level + 1];
105 }
106 self.active[item.level] = item;
107
108 var level = item.level;
109 while(this.iframes[level]) ...{
110 this.iframes[level].style.display = 'none';
111 level++;
112 }
113
114 var childElem = document.getElementById('menu'+item.id);
115 if (childElem==null) ...{
116 var hasChild = false;
117 for( var j=0;j<item.children.length;j++) ...{
118 if( (' '+item.children[j].css+' ').indexOf(' hidden ') == -1) ...{
119 hasChild = true;
120 break;
121 }
122 }
123 if( hasChild) ...{
124
125 var xy = self.elemOffset(target);
126 var x = xy.x;
127 var y = target.offsetHeight + xy.y;
128 if ( item.level >= 2 )
129 ...{
130 x += target.offsetWidth - 1;
131 y -= target.offsetHeight;
132 }
133
134 childElem = document.createElement('div');
135 childElem.id = 'menu'+item.id;
136 childElem.className = 'child';
137 childElem.style.position = 'absolute';
138 childElem.style.left = x + 'px';
139 childElem.style.top = y + 'px';
140 childElem.style.zIndex = 1000 + item.level;
141 for( var i=0;i < item.children.length; i++)
142 ...{
143 var childItem = item.children[i];
144 var childItemElem = document.createElement('a');
145 var disabled = (' '+childItem.css+' ').indexOf('disabled')!=-1;
146 if ( disabled ) ...{
147 childItemElem.disabled = true;
148 childItemElem.className += ' '+childItem.css;
149 }else ...{
150 childItemElem.href = childItem.url;
151 }
152 if ( (' '+childItem.css+' ').indexOf(' hidden ')!=-1 ) ...{
153 childItemElem.style.display = 'none';
154 }
155 childItemElem.innerHTML = childItem.name;
156 childItemElem.menu = childItem;
157 childItemElem.menu.children = self.getChlid(childItem.id);
158 var hasChild = false;
159 for( var j=0;j<childItemElem.menu.children.length;j++) ...{
160 if( (' '+childItemElem.menu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
161 hasChild = true;
162 break;
163 }
164 }
165 if( hasChild ) ...{
166 childItemElem.className += ' hasChild';
167 }
168 childItemElem.onmouseover = function (e) ...{
169 self.renderChlid(this)
170 };
171 childElem.appendChild(childItemElem);
172 }
173 document.body.insertBefore(childElem,document.body.childNodes[0]);
174 item.element = childElem;
175 }
176 }
177
178 if( childElem!=null) ...{
179 var iframeElem = this.iframes[item.level];
180 if ( iframeElem == null) ...{
181 iframeElem = document.createElement('iframe');
182 iframeElem.scrolling = 'no';
183 iframeElem.frameBorder = 0;
184 iframeElem.style.cssText = 'position:absolute; overflow:hidden;';
185 document.body.insertBefore(iframeElem,document.body.childNodes[0]);
186 this.iframes[item.level]=iframeElem;
187 }
188 childElem.style.display = 'block';
189 iframeElem.width = childElem.offsetWidth;
190 iframeElem.height = childElem.offsetHeight;
191 iframeElem.style.left = parseInt(childElem.style.left) + 'px';
192 iframeElem.style.top = parseInt(childElem.style.top) + 'px';
193 iframeElem.style.display = 'block';
194 }
195
196 },
197 elemOffset : function(elem)...{
198 if( elem==null) return ...{x:0,y:0};
199 var t = elem.offsetTop;
200 var l = elem.offsetLeft;
201 while( elem = elem.offsetParent) ...{
202 t += elem.offsetTop;
203 l += elem.offsetLeft;
204 }
205 return ...{x : l,y : t};
206 }
207};
2.右键菜单树(ContextMenu)
自定义右键菜单(ContextMenu)和悬浮层树(Tree)其实现上都大同小异,都是在脚本里动态添加节点,然后在生成一个绝对定位层,只不过右键菜单树(ContextMenu)触发的事件不一样。另外右键菜单还需要提供一个动态添加菜单项功能,以实现右击不同的元素可以显示不同的右键菜单,我这里提供一种"回调函数",使用见如下代码:
ContextMenu回调函数1 //ContextMenu
2
3 var contextmenu = new ContextMenu(...{ container : document.getElementById('treemenu') });
4 contextmenu.push( ...{ html : 'Powered By: Jonllen', css : 'disabled'});
5 contextmenu.push( ...{ html : '', css : 'line'});
6 contextmenu.push( ...{ html : '刷新(<u>R</u>)', href : 'javascript:location.reload();'});
7 for(var i=0;i<menu.length;i++) ...{
8 contextmenu.push(...{
9 id : menu[i].id,
10 level : menu[i].level,
11 parentId : menu[i].parentId,
12 html : menu[i].name,
13 href : menu[i].url
14 });
15 }
16 contextmenu.render();
17
18 //原有回调函数
19 var contextmenuOnShow = contextmenu.onShow;
20 //设置新的回调函数
21 contextmenu.onShow = function (target, _this)...{
22 var item = target.treemenu || target.parentNode.treemenu;
23 if( item ) ...{
24 var html = '添加'+item.html+'“子节点'+(item.children.length+1)+'”';
25 _this.push( ...{
26 html : html,
27 click : function (e)...{
28 item.expand = false;
29 var newItem = ...{
30 id : item.id + '0'+ (item.children.length+1),
31 level : item.level + 1,
32 parentId : item.id,
33 html : item.html+'子节点'+(item.children.length+1),
34 href : '#',
35 css : 'item',
36 createExpand : true
37 };
38 item.children.push(newItem);
39 treemenu.list.push(newItem);
40 treemenu.renderChild(item);
41 },
42 clickClose : true,
43 index : 1,
44 type : 'dynamic'
45 });
46 _this.push( ...{
47 html : '删除节点“'+item.html+'”',
48 click : function (e)...{
49 if( confirm('是否确认删除节点“'+item.html+'”?'))
50 treemenu.remove(item);
51 },
52 clickClose : true,
53 index : 2,
54 type : 'dynamic'
55 });
56 }
57 contextmenuOnShow(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
11 for( p in this.settings)
12 ...{
13 if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
14 }
15 this.settings = settings;
16
17 this.settings.menu = document.createElement('div');
18 this.settings.menu.className = this.settings.css;
19 this.settings.menu.style.cssText = 'position:absolute;display:none;';
20 document.body.insertBefore(this.settings.menu,document.body.childNodes[0]);
21
22 return this;
23}
24
25ContextMenu.prototype = ...{
26 list : new Array(),
27 active : new Array(),
28 iframes : new Array(),
29 settings : ...{
30 menu : null,
31 excursionX : 0,
32 excursionY : 0,
33 css : 'contextmenu',
34 container : null,
35 locked : false
36 },
37 item : ...{
38 id : null,
39 level : 1,
40 parentId : 0,
41 html : '',
42 title : '',
43 href : 'javascript:;',
44 target : '_self',
45 css : null,
46 element : null,
47 childElement : null,
48 parent : null,
49 children : null,
50 type : 'static',
51 click : null,
52 clickClose : false
53 },
54 push : function (item) ...{
55 var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
56 for( var i=0; i< list.length; i++) ...{
57 var _item = list[i];
58 for( p in this.item) ...{
59 if( !_item.hasOwnProperty(p) ) _item[p] = this.item[p];
60 }
61 _item.element = null;
62 if( _item.name ) _item.html = _item.name;
63 if( _item.url ) _item.href = _item.url;
64 if( _item.type == 'static') ...{
65 this.list.push(_item);
66 }else ...{
67 if(this.dynamic == null) this.dynamic = new Array();
68 this.dynamic.push(_item);
69 }
70 }
71 return this;
72 },
73 bind : function ()...{
74 var _this = this;
75 for( var i=0; this.dynamic && i<this.dynamic.length; i++)
76 ...{
77 var item = this.dynamic[i];
78 var itemElem = document.createElement('div');
79 itemElem.title = item.title;
80 itemElem.innerHTML = '<a href="'+item.href+'" target="'+item.target+'">'+item.html+'</a>';
81 itemElem.className = 'item ' + (item.css?' '+item.css:'');
82 item.element = itemElem;
83
84 if( item.click ) ...{
85 (function (item)...{
86 item.element.childNodes[0].onclick = function (e)...{
87 if( item.clickClose) _this.hidden();
88 return item.click(e);
89 };
90 })(item);
91 }
92
93 itemElem.contextmenu = item;
94 itemElem.onmouseover = function (e)...{ _this.hidden(item.level);};
95
96 var index = item.index || 0;
97 if( index >= this.settings.menu.childNodes.length)
98 index = this.settings.menu.childNodes.length - 1;
99 if( index < 0 )
100 this.settings.menu.appendChild(itemElem);
101 else
102 this.settings.menu.insertBefore(itemElem, this.settings.menu.childNodes[index]);
103 }
104 },
105 render : function ( container ) ...{
106
107 var _this = this;
108
109 container = container || this.settings.container;
110
111 this.settings.menu.innerHTML = '';
112
113 for( var i=0;i < this.list.length; i++)
114 ...{
115 var item = this.list[i];
116 if ( item.parentId != 0 ) continue;
117 var itemElem = document.createElement('div');
118 itemElem.title = item.title;
119 itemElem.innerHTML = '<a href="'+item.href+'" target="'+item.target+'">'+item.html+'</a>';
120 itemElem.className = 'item ' + (item.css?' '+item.css:'');
121 var disabled = _this.hasClass(itemElem, 'disabled');
122 if ( disabled ) ...{
123 itemElem.childNodes[0].disabled = true;
124 itemElem.childNodes[0].className = 'disabled';
125 itemElem.childNodes[0].removeAttribute('href');
126 }
127 if ( _this.hasClass(itemElem, 'hidden') ) ...{
128 itemElem.style.display = 'none';
129 }
130
131 if( item.click ) ...{
132 (function (item)...{
133 item.element.childNodes[0].onclick = function (e)...{
134 if( item.clickClose) _this.hidden();
135 return item.click(e);
136 };
137 })(item);
138 }
139
140 itemElem.contextmenu = item;
141 itemElem.contextmenu.children = this.getChlid(item.id);
142 if( itemElem.contextmenu.children.length > 0 )
143 itemElem.childNodes[0].className += ' hasChild';
144 itemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
145 this.settings.menu.appendChild(itemElem);
146 }
147
148 this.active[0] = ...{ element : _this.settings.menu };
149 this.settings.menu.contextmenu = _this;
150 container.oncontextmenu = function (e)...{
151 e = window.event || e;
152 var target = e.target || e.srcElement;
153 if( e.preventDefault)
154 e.preventDefault();
155 var 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);
160 return false;
161 };
162 this.addEvent(document, 'click', function (e)...{
163 e = window.event || e;
164 var target = e.target || e.srcElement;
165 var isContextMenu = !!target.contextmenu;
166 if( isContextMenu == false) ...{
167 var parent = target.parentNode;
168 while( parent!=null) ...{
169 if( parent.contextmenu) ...{
170 isContextMenu = true;
171 break;
172 }
173 parent = parent.parentNode;
174 }
175 }
176 if (isContextMenu == false) ...{
177 _this.hidden();
178 }
179 });
180
181 },
182 renderChlid : function ( target )...{
183
184 if(this.settings.locked) return;
185
186 var contextmenu = target.contextmenu;
187 var currentLevel = contextmenu.level;
188 this.hidden(currentLevel);
189
190 var hasChild = false;
191 for( var j=0;j<contextmenu.children.length;j++) ...{
192 if( (' '+contextmenu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
193 hasChild = true;
194 break;
195 }
196 }
197 if( !hasChild) return;
198
199 var childElem = contextmenu.element;
200 if (childElem == null) ...{
201
202 childElem = document.createElement('div');
203 childElem.className = this.settings.css;
204 childElem.style.position = 'absolute';
205 childElem.style.zIndex = 1000 + contextmenu.level;
206
207 var _this = this;
208
209 for( var i=0;i < contextmenu.children.length; i++)
210 ...{
211 var childItem = contextmenu.children[i];
212
213 var childItemElem = document.createElement('div');
214 childItemElem.title = childItem.title;
215 childItemElem.innerHTML = '<a href="'+childItem.href+'" target="'+childItem.target+'">'+childItem.html+'</a>';
216 childItemElem.className = 'item' + (childItem.css?' '+childItem.css : '');
217 var disabled = this.hasClass(childItemElem, 'disabled');
218 if ( disabled ) ...{
219 childItemElem.childNodes[0].disabled = true;
220 childItemElem.childNodes[0].removeAttribute('href');
221 }
222 if ( this.hasClass(childItemElem, 'hidden') ) ...{
223 childItemElem.style.display = 'none';
224 }
225
226 if( childItem.click ) ...{
227 (function (childItem)...{
228 childItem.element.childNodes[0].onclick = function (e)...{
229 if( childItem.clickClose) _this.hidden();
230 return childItem.click(e);
231 };
232 })(childItem);
233 }
234
235 childItem.parent = contextmenu;
236 childItemElem.contextmenu = childItem;
237 childItemElem.contextmenu.children = this.getChlid(childItem.id);
238 var hasChild = false;
239 for( var j=0; j<childItemElem.contextmenu.children.length; j++) ...{
240 if( (' '+childItemElem.contextmenu.children[j].css+' ').indexOf(' hidden ') == -1) ...{
241 hasChild = true;
242 break;
243 }
244 }
245 if( hasChild ) ...{
246 childItemElem.childNodes[0].className += ' hasChild';
247 }
248 childItemElem.onmouseover = function (e)...{ _this.renderChlid(this);};
249 childElem.appendChild(childItemElem);
250 }
251
252 document.body.insertBefore(childElem,document.body.childNodes[0]);
253 contextmenu.element = childElem;
254
255 }
256
257 this.active[currentLevel] = contextmenu;
258
259 var xy = this.elemOffset(target);
260 var x = xy.x + target.offsetWidth + this.settings.excursionX;
261 var y = xy.y + this.settings.excursionY;
262 childElem.style.left = x + 'px';
263 childElem.style.top = y + 'px';
264 childElem.style.display = 'block';
265
266 this.show(currentLevel);
267 },
268 getChlid : function (id) ...{
269 var list = new Array();
270 for( var i=0;i < this.list.length; i++)
271 ...{
272 var item = this.list[i];
273 if( item.parentId == id)
274 ...{
275 list.push(item);
276 }
277 }
278 return list;
279 },
280 show : function (level, target) ...{
281
282 if(this.settings.locked) return;
283
284 level = level || 0;
285 var item = this.active[level];
286
287 if ( level == 0 ) ...{
288 for( var i=0;this.dynamic && i < this.dynamic.length; i++)
289 ...{
290 var dynamicItemElem = this.dynamic[i].element;
291 if( dynamicItemElem !=null) dynamicItemElem.parentNode.removeChild(dynamicItemElem);
292 }
293 if (this.dynamic) this.dynamic.length = 0;
294 this.onShow(target, this);
295 }
296
297 var menuElem = item.element;
298 menuElem.style.display = 'block';
299 var iframeElem = this.iframes[level];
300 if ( iframeElem == null) ...{
301 iframeElem = document.createElement('iframe');
302 iframeElem.scrolling = 'no';
303 iframeElem.frameBorder = 0;
304 iframeElem.style.cssText = 'position:absolute; overflow:hidden;';
305 document.body.insertBefore(iframeElem,document.body.childNodes[0]);
306 this.iframes.push(iframeElem);
307 }
308 iframeElem.width = menuElem.offsetWidth;
309 iframeElem.height = menuElem.offsetHeight;
310 var menuElemOffset = this.elemOffset(menuElem);
311 iframeElem.style.left = menuElemOffset.x + 'px';
312 iframeElem.style.top = menuElemOffset.y + 'px';
313 iframeElem.style.display = 'block';
314
315 },
316 onShow : function (target, _this) ...{
317
318 if( target.nodeType == 1 && target.tagName == 'A' && target.innerHTML.indexOf('.rar') != -1 )...{
319 //解压文件
320 _this.push( ...{
321 html : '解压缩到“'+target.innerHTML.substring(0,target.innerHTML.lastIndexOf('.'))+'\\”...',
322 click : function (e)...{
323 e = e || window.event;
324 var srcElement = e.srcElement || e.target;
325 srcElement.className = 'on';
326 srcElement.innerHTML = '解压缩到“'+target.innerHTML.substring(0,target.innerHTML.lastIndexOf('.'))+'\\”...';
327 var url = '/Ajax/FileZip.aspx?mode=unzip&files='+target.href.substring(target.href.replace('//','xx').indexOf('/'));
328 if( typeof Ajax == 'undefined') return;
329 Ajax.get(url, function (data, _this)...{
330 _this.settings.locked = true;
331 eval(data);
332 if( rs.success ) ...{
333 location.reload();
334 }else...{
335 alert(rs.error);
336 _this.hidden();
337 }
338 }, _this);
339 srcElement.onclick = null;
340 _this.settings.locked = true;
341
342 },
343 clickClose : false,
344 index : 2,
345 type : 'dynamic'
346 });
347 }
348 else if( target.nodeType == 1 && target.title.indexOf('添加到') == 0) ...{
349 //添加单个压缩文件
350 _this.push( ...{
351 html : target.title,
352 title : target.title,
353 click : function (e)...{
354 var index = target.href.indexOf('?path=');
355 if( index != -1)...{
356 var fullName = target.href.substring(index+'?path='.length);
357 }else ...{
358 var fullName = target.href.substring(target.href.replace('//','xx').indexOf('/'));
359 }
360 e = e || window.event;
361 var srcElement = e.srcElement || e.target;
362 srcElement.className = 'on';
363 srcElement.innerHTML = '正在添加到“'+fullName.substring(fullName.lastIndexOf('/')+1)+'.rar”...';
364 var url = '/Ajax/FileZip.aspx?mode=zip&files='+fullName;
365 if( typeof Ajax == 'undefined') return;
366 Ajax.get(url, function (data, _this)...{
367 _this.settings.locked = true;
368 eval(data);
369 if( rs.success ) ...{
370 location.reload();
371 }else...{
372 alert(rs.error);
373 _this.hidden();
374 }
375 }, _this);
376 srcElement.onclick = null;
377 _this.settings.locked = true;
378 },
379 clickClose : false,
380 index : 2,
381 type : 'dynamic',
382 css : 'on'
383 });
384 }else ...{
385 //添加多个压缩文件
386 var fileName = '';
387 var files = new Array();
388 var ids = document.getElementsByName('ids');
389 for( var i=0; i<ids.length; i++) ...{
390 if( !ids[i].checked) continue;
391
392 var file = ids[i].value;
393 files.push(file);
394 if( files.length == 1) ...{
395 fileName = file.substring(file.lastIndexOf('/')+1) + '.rar';
396 }
397 }
398 if( files.length > 0 )...{
399 _this.push( ...{
400 html : '添加'+files.length+'个文件到压缩包“'+fileName+'”',
401 click : function (e)...{
402
403 e = e || window.event;
404 var srcElement = e.srcElement || e.target;
405 srcElement.className = 'on';
406 srcElement.innerHTML = '正在添加到“'+fileName+'”...';
407 var url = '/Ajax/FileZip.aspx?mode=zip&files='+files.join('|');
408 if( typeof Ajax == 'undefined') return;
409 Ajax.get(url, function (data, _this)...{
410 _this.settings.locked = true;
411 eval(data);
412 if( rs.success ) ...{
413 location.reload();
414 }else...{
415 alert(rs.error);
416 _this.hidden();
417 }
418 }, _this);
419 srcElement.onclick = null;
420 _this.settings.locked = true;
421
422 },
423 clickClose : false,
424 index : 2,
425 type : 'dynamic'
426 });
427 }
428 }
429
430 if( target.nodeType == 1 && target.tagName == 'A') ...{
431 _this.push( ...{
432 html : '属性“'+target.innerHTML+'”',
433 href : target.href,
434 click : function (e)...{
435 prompt('属性“'+target.innerHTML+'”',target.href);
436 return false;
437 },
438 clickClose : true,
439 index : 3,
440 type : 'dynamic'
441 });
442 }
443
444 var selection = window.getSelection ? window.getSelection().toString() : document.selection.createRange().text;
445 if( selection ) ...{
446 _this.push( ...{
447 html : '复制“' + (selection.length > 15 ? selection.substring(0,12) + '...' : selection) +'”',
448 title : '复制“' + selection + '”',
449 click : function (e) ...{
450 if(window.clipboardData) ...{
451 window.clipboardData.clearData();
452 window.clipboardData.setData("Text", selection);
453 }else ...{
454 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
455 var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard);
456 var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
457 if (!clip || !trans) return;
458
459 trans.addDataFlavor('text/unicode');
460 var len = new Object();
461 var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
462 str.data = selection;
463 trans.setTransferData("text/unicode",str,selection.length*2);
464 var clipid=Components.interfaces.nsIClipboard;
465 if (!clip) return false;
466 clip.setData(trans,null,clipid.kGlobalClipboard);
467 }
468 },
469 clickClose : true,
470 index : 0,
471 type : 'dynamic'
472 });
473 }
474
475 _this.bind();
476 },
477 hidden : function (level) ...{
478
479 level = level || 0;
480
481 for( var i = level; i<this.active.length; i++) ...{
482 var item = this.active[i];
483
484 var iframeElem = this.iframes[i];
485 if ( iframeElem !=null)
486 iframeElem.style.display = 'none';
487
488 if(this.settings.locked) return;
489
490 var menuElem = item.element;
491 if ( menuElem !=null)
492 menuElem.style.display = 'none';
493
494 }
495 this.onHidden(level);
496 },
497 onHidden : function (level) ...{
498 },
499 hasClass : function (elem, name)
500 ...{
501 return !!elem && (' '+elem.className+' ').indexOf(' '+name+' ') != -1;
502 },
503 elemOffset : function(elem)...{
504 var left = 0;
505 var top = 0;
506 while (elem.offsetParent)...{
507 left += elem.offsetLeft;
508 top += elem.offsetTop;
509 elem = elem.offsetParent;
510 }
511 left += elem.offsetLeft;
512 top += elem.offsetTop;
513 return ...{x:left, y:top};
514 },
515 mouseCoords : function (e)...{
516 if (e.pageX && e.pageY) ...{
517 return ...{
518 x: e.pageX,
519 y: e.pageY
520 };
521 }
522 var d = (document.documentElement && document.documentElement.scrollTop) ? document.documentElement : document.body;
523 return ...{
524 x: e.clientX + d.scrollLeft,
525 y: e.clientY + d.scrollTop
526 };
527 },
528 addEvent : function(target,eventType,func)...{
529 if(target.attachEvent)
530 ...{
531 target.attachEvent("on" + eventType, func);
532
533 }else if(target.addEventListener)
534 ...{
535 target.addEventListener(eventType == 'mousewheel' ? 'DOMMouseScroll' : eventType, func, false);
536 }
537 return this;
538 },
539 removeEvent : function(target,eventType,func)...{
540 if(target.detachEvent)
541 ...{
542 target.detachEvent("on" + eventType, func);
543
544 }else if(target.removeEventListener)
545 ...{
546 target.removeEventListener(eventType == 'mousewheel' ? 'DOMMouseScroll' : eventType, func, false);
547 }
548 return 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)...{
9 for( p in this.settings) ...{
10 if( !settings.hasOwnProperty(p) ) settings[p] = this.settings[p];
11 }
12 this.settings = settings;
13}
14
15TreeMenu.prototype = ...{
16 list : new Array(),
17 settings : ...{
18 indent : 20,
19 container : null,
20 recursion : false,
21 checkbox : false,
22 radio : false,
23 name : 'ids',
24 tree_expand_plus : '/style/default/tree_expand_plus.gif',
25 tree_expand_minus : '/style/default/tree_expand_minus.gif',
26 tree_expand_normal : '/style/default/tree_expand_normal.gif',
27 tree_icon_file : '/style/default/tree_icon_file.gif',
28 tree_icon_folder : '/style/default/tree_icon_folder.gif',
29 tree_icon_folderopen : '/style/default/tree_icon_folderopen.gif'
30 },
31 item : ...{
32 id : null,
33 level : 1,
34 parentId : 0,
35 html : '',
36 title : '',
37 href : 'javascript:;',
38 target : '_self',
39 css : 'item',
40 img : null,
41 click : null,
42 createExpand : true,
43 expand : false,
44 checked : false,
45 disabled : false,
46 children : null
47 },
48 push : function (item) ...{
49 var list = Object.prototype.toString.apply(item) === '[object Array]' ? item : [item];
50 for( var i=0; i< list.length; i++) ...{
51 var _item = list[i];
52 for( p in this.item) ...{
53 if( !_item.hasOwnProperty(p) ) _item[p] = this.item[p];
54 }
55 this.list.push(_item);
56 }
57 },
58 render : function (container)...{
59 var _this = this;
60
61 var container = container || this.settings.container;
62
63 while( container.lastChild) ...{
64 container.removeChild( container.lastChild);
65 }
66
67 for( var i=0; i<this.list.length; i++) ...{
68 var item = this.list[i];
69 if( item.parentId !=0) continue;
70
71 var itemElem = document.createElement('div');
72 itemElem.className = item.css;
73 itemElem.title = item.title;
74
75 var expandElem = null;
76 if( item.createExpand) ...{
77 expandElem = document.createElement('img');
78 expandElem.src = this.settings.tree_expand_plus;
79 itemElem.appendChild(expandElem);
80 }
81
82 var iconElem = document.createElement('img');
83 iconElem.src = item.img ? item.img : this.settings.tree_icon_folder;
84 itemElem.appendChild(iconElem);
85
86 if( this.settings.checkbox || this.settings.radio)...{
87 var inputTemp = document.createElement('div');
88 inputTemp.innerHTML = '<input type="'+(this.settings.checkbox?'checkbox':'radio')+'" name="' + this.settings.name + '" />';
89 var inputElem = inputTemp.childNodes[0];
90 inputElem.value = item.id;
91 inputElem.checked = item.checked;
92 inputElem.disabled = item.disabled;
93
94 itemElem.appendChild(inputElem);
95 item.inputElem = inputElem;
96 }
97
98 var aElem = document.createElement('a');
99 aElem.href = item.href;
100 aElem.target = item.target;
101 aElem.innerHTML = item.html;
102 itemElem.appendChild(aElem);
103
104 var children = this.getChlid(item.id);
105 if( children.length ==0 )...{
106 expandElem ? expandElem.src = this.settings.tree_expand_normal : null;
107 iconElem.src = item.img ? item.img : this.settings.tree_icon_file;
108 }
109
110 item.children = children;
111 item.itemElem = itemElem;
112 item.iconElem = iconElem;
113 item.expandElem = expandElem;
114 item.aElem = aElem;
115
116 itemElem.treemenu = item;
117
118 container.appendChild(itemElem);
119 }
120
121 container.onclick = this.onClick;
122 container._this = this;
123 },
124 renderChild : function(item, checked) ...{
125
126 if( item.children.length == 0) return;
127
128 var _this = this;
129 var expand = item.expand;
130
131 for( var i=0;i<item.children.length;i++) ...{
132
133 var childItem = item.children[i];
134
135 var childElem = childItem.itemElem;
136
137 if( childElem == null) ...{
138 childElem = document.createElement('div');
139 childElem.className = childItem.css;
140 childElem.title = childItem.title;
141
142 var expandElem = null;
143 if( childItem.createExpand) ...{
144 expandElem = document.createElement('img');
145 expandElem.src = this.settings.tree_expand_plus;
146 childElem.appendChild(expandElem);
147 }
148
149 var iconElem = document.createElement('img');
150 iconElem.src = childItem.img ? childItem.img : this.settings.tree_icon_folder;
151 childElem.appendChild(iconElem);
152
153 if( this.settings.checkbox || this.settings.radio)...{
154 var inputTemp = document.createElement('div');
155 inputTemp.innerHTML = '<input type="'+(this.settings.checkbox?'checkbox':'radio')+'" name="' + this.settings.name + '" />';
156 var inputElem = inputTemp.childNodes[0];
157 inputElem.value = childItem.id;
158 inputElem.checked = childItem.checked;
159 inputElem.disabled = childItem.disabled;
160
161 childElem.appendChild(inputElem);
162 childItem.inputElem = inputElem;
163 }
164
165 var aElem = document.createElement('a');
166 aElem.href = childItem.href;
167 aElem.target = childItem.target;
168 aElem.innerHTML = childItem.html;
169 childElem.appendChild(aElem);
170
171 var children = this.getChlid(childItem.id);
172 if( children.length ==0 )...{
173 expandElem ? expandElem.src = this.settings.tree_expand_normal : null;
174 iconElem.src = childItem.img ? childItem.img : this.settings.tree_icon_file;
175 }
176
177 childItem.level = item.level + 1;
178
179 childElem.style.paddingLeft = this.settings.indent+'px';
180 childElem.style.display = 'none';
181
182 childItem.parent = item;
183 childItem.children = children;
184 childItem.itemElem = childElem;
185 childItem.iconElem = iconElem;
186 childItem.expandElem = expandElem;
187 childItem.aElem = aElem;
188
189 childElem.treemenu = childItem;
190
191 item.itemElem.appendChild(childElem);
192 }
193
194 if( this.settings.recursion ) ...{
195 //递归
196 childItem.expand = expand;
197 this.renderChild(childItem, checked);
198 }
199
200 if( childItem.inputElem && typeof checked != 'undefined') ...{
201 childItem.inputElem.checked = checked;
202 continue;
203 }
204
205 childItem.expand = expand;
206 childElem.style.display = childItem.expand ? 'none' : 'block';
207 }
208
209 if( typeof checked == 'undefined') ...{
210 item.expandElem && (item.expandElem.src = expand ? this.settings.tree_expand_plus : this.settings.tree_expand_minus);
211 item.iconElem.src = expand ? (item.img ? item.img : this.settings.tree_icon_folder) : this.settings.tree_icon_folderopen;
212
213 item.expand = !expand;
214 }
215 },
216 onClick :function (e) ...{
217 e = e || window.event;
218 var target = e.target || e.srcElement;
219
220 var _this = this._this;
221 var treemenu = target.treemenu || target.parentNode.treemenu || target.parentNode.parentNode.treemenu;
222
223 if(treemenu && target.nodeType == 1 && target.type == 'checkbox') ...{
224 var recursion = false;
225 if( _this.settings.recursion) ...{
226 recursion = true;
227 }
228 _this.settings.recursion = true;
229 _this.renderChild(treemenu, target.checked);
230 _this.settings.recursion = recursion;
231 return;
232 }
233
234 if( treemenu && treemenu.aElem == target) ...{
235 _this.selectItem ? _this.selectItem.aElem.className = '' : null;
236 _this.selectItem = treemenu;
237 target.className = 'selected';
238 target.blur();
239 if( treemenu.click) ...{
240 return treemenu.click(treemenu);
241 }
242 alert('the selected item id is : '+treemenu.id);
243 return false;
244 }
245
246 if( target.nodeType == 1 && target.type == 'radio') return;
247
248 if( treemenu) ...{
249 _this.renderChild(treemenu);
250 }
251 },
252 expandAll : function (expand) ...{
253
254 expand = expand === false;
255
256 var recursion = false;
257 if( this.settings.recursion) ...{
258 recursion = true;
259 }
260
261 this.settings.recursion = true;
262
263 for( var i=0; i<this.list.length; i++) ...{
264 var item = this.list[i];
265 item.expand = expand;
266 if( item.parentId ==0) ...{
267 this.renderChild(item);
268 }
269 }
270
271 this.settings.recursion = recursion;
272 },
273 getChlid : function (id) ...{
274 var list = new Array();
275 for( var i=0;i < this.list.length; i++)
276 ...{
277 var item = this.list[i];
278 if( item.parentId == id)
279 ...{
280 list.push(item);
281 }
282 }
283 return list;
284 },
285 remove : function (item, list)...{
286 list = list || this.list;
287 for( var i=0;i < list.length; i++)
288 ...{
289 if( list[i].id == item.id)
290 ...{
291 list[i].itemElem.parentNode.removeChild(list[i].itemElem);
292 list.splice(i,1);
293 if( item.parent ) ...{
294 for ( var j=0; j<item.parent.children.length; j++) ...{
295 if( item.parent.children[j].id == item.id) ...{
296 item.parent.children.splice(j,1);
297 break;
298 }
299 }
300 item.parent.expand = false;
301 this.renderChild(item.parent);
302 }
303 return list;
304 }
305 }
306 return 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];
}
怎么样加载时展开指定节点