今天我主要讲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
9
var Menu = function (container) ...{
10
this.container = container;
11
return this;
12
}
13
14
Menu.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
9
var 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
25
ContextMenu.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
8
var 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
15
TreeMenu.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