今天是給大家介紹一款在網頁上使用的右鍵菜單,原作者的網址是:http://51jsr.javaeye.com/blog/305517
這個右鍵菜單已經非常優秀,不過呢。卻是IE Only,而且在DTD模式下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd%22>
連IE顯示都是有問題的,所以呢只有自己動手了,另外就順便改造成jQuery控件,順便分析一下代碼。
首先來看一下效果吧
↑這是控件的效果
插一句吧,其實我最終的目標是提供一個ASP.NET MVC 框架前台UI Controls解決方案,因為后面的控件會用到這個右鍵菜單所以就講一下。
首先還是來分析一下HTML吧
1:一級菜單(每一組菜單)即是一個獨立的div容器
2:每一項又是div,嵌套一個nobr(可用div代替不過要額外寫個class)的標簽,里面是圖標和span包裹的位置內容
這里一個要注意的地方就是多級菜單其實在HTMl結構是分離的,只是通過顯示的位置在視覺上給人連載一起(另外就是箭頭圖標了)
第二接着是CSS了(是修改過的)
CSS非常簡單,因為HTML結構本身也不復雜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
.b-m-mpanel {
background: #F0F0F0 url(images/contextmenu/menu_bg.gif) left repeat-y;
border: 1px solid #718BB7;
padding: 2px 0;
position: absolute;
z-index: 99997;
}
.b-m-split {
height: 6px;
background: url(images/contextmenu/m_splitLine.gif) center repeat-x;
font-size: 0px;
margin: 0 2px;
}
.b-m-item, .b-m-idisable
{
padding: 4px 2px;
margin: 0 2px 0 3px;
cursor: normal;
line-height:100%;
}
.b-m-idisable
{
color:#808080;
}
.b-m-ibody, .b-m-arrow {
overflow: hidden;
text-overflow: ellipsis;
}
.b-m-arrow {
background: url(images/contextmenu/m_arrow.gif) right no-repeat;
}
.b-m-idisable .b-m-arrow
{
background:none;
}
.b-m-item img, .b-m-ifocus img, .b-m-idisable img {
margin-right: 8px;
}
.b-m-ifocus {
background: url(images/contextmenu/m_item.gif) repeat-x bottom;
border: 1px solid #AACCF6;
padding: 3px 1px ;
margin: 0 2px 0 3px;
cursor: normal;
line-height:100%;
}
.b-m-idisable img {
visibility:hidden;
}
|
第三來看javascript了
先來看個完整的吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
;(
function
($) {
function
returnfalse() {
return
false
; };
$.fn.contextmenu =
function
(option) {
option = $.extend({ alias:
"cmroot"
, width: 150 }, option);
var
ruleName =
null
, target =
null
,
groups = {}, mitems = {}, actions = {}, showGroups = [],
itemTpl =
"<div class='b-m-$[type]' unselectable=on><nobr unselectable=on><img src='$[icon]' align='absmiddle'/><span unselectable=on>$[text]</span></nobr></div>"
;
var
gTemplet = $(
"<div/>"
).addClass(
"b-m-mpanel"
).attr(
"unselectable"
,
"on"
).css(
"display"
,
"none"
);
var
iTemplet = $(
"<div/>"
).addClass(
"b-m-item"
).attr(
"unselectable"
,
"on"
);
var
sTemplet = $(
"<div/>"
).addClass(
"b-m-split"
);
//創建菜單組
var
buildGroup =
function
(obj) {
groups[obj.alias] =
this
;
this
.gidx = obj.alias;
this
.id = obj.alias;
if
(obj.disable) {
this
.disable = obj.disable;
this
.className =
"b-m-idisable"
;
}
$(
this
).width(obj.width).click(returnfalse).mousedown(returnfalse).appendTo($(
"body"
));
obj =
null
;
return
this
;
};
var
buildItem =
function
(obj) {
var
T =
this
;
T.title = obj.text;
T.idx = obj.alias;
T.gidx = obj.gidx;
T.data = obj;
T.innerHTML = itemTpl.replace(/\$\[([^\]]+)\]/g,
function
() {
return
obj[arguments[1]];
});
if
(obj.disable) {
T.disable = obj.disable;
T.className =
"b-m-idisable"
;
}
obj.items && (T.group =
true
);
obj.action && (actions[obj.alias] = obj.action);
mitems[obj.alias] = T;
T = obj =
null
;
return
this
;
};
//添加菜單項
var
addItems =
function
(gidx, items) {
var
tmp =
null
;
for
(
var
i = 0; i < items.length; i++) {
if
(items[i].type ==
"splitLine"
) {
//菜單分隔線
tmp = sTemplet.clone()[0];
}
else
{
items[i].gidx = gidx;
if
(items[i].type ==
"group"
) {
//菜單組
buildGroup.apply(gTemplet.clone()[0], [items[i]]);
arguments.callee(items[i].alias, items[i].items);
items[i].type =
"arrow"
;
tmp = buildItem.apply(iTemplet.clone()[0], [items[i]]);
}
else
{
//菜單項
items[i].type =
"ibody"
;
tmp = buildItem.apply(iTemplet.clone()[0], [items[i]]);
$(tmp).click(
function
(e) {
if
(!
this
.disable) {
if
($.isFunction(actions[
this
.idx])) {
actions[
this
.idx].call(
this
, target);
}
hideMenuPane();
}
return
false
;
});
}
//Endif
$(tmp).bind(
"contextmenu"
, returnfalse).hover(overItem, outItem);
}
//Endif
groups[gidx].appendChild(tmp);
tmp = items[i] = items[i].items =
null
;
}
//Endfor
gidx = items =
null
;
};
var
overItem =
function
(e) {
//如果菜單項不可用
if
(
this
.disable)
return
false
;
hideMenuPane.call(groups[
this
.gidx]);
//如果是菜單組
if
(
this
.group) {
var
pos = $(
this
).offset();
var
width = $(
this
).outerWidth();
showMenuGroup.apply(groups[
this
.idx], [pos, width]);
}
this
.className =
"b-m-ifocus"
;
return
false
;
};
//菜單項失去焦點
var
outItem =
function
(e) {
//如果菜單項不可用
if
(
this
.disable )
return
false
;
if
(!
this
.group) {
//菜單項
this
.className =
"b-m-item"
;
}
//Endif
return
false
;
};
//在指定位置顯示指定的菜單組
var
showMenuGroup =
function
(pos, width) {
var
bwidth = $(
"body"
).width();
var
bheight = document.documentElement.clientHeight;
var
mwidth = $(
this
).outerWidth();
var
mheight = $(
this
).outerHeight();
pos.left = (pos.left + width + mwidth > bwidth) ? (pos.left - mwidth < 0 ? 0 : pos.left - mwidth) : pos.left + width;
pos.top = (pos.top + mheight > bheight) ? (pos.top - mheight + (width > 0 ? 25 : 0) < 0 ? 0 : pos.top - mheight + (width > 0 ? 25 : 0)) : pos.top;
$(
this
).css(pos).show();
showGroups.push(
this
.gidx);
};
//隱藏菜單組
var
hideMenuPane =
function
() {
var
alias =
null
;
for
(
var
i = showGroups.length - 1; i >= 0; i--) {
if
(showGroups[i] ==
this
.gidx)
break
;
alias = showGroups.pop();
groups[alias].style.display =
"none"
;
mitems[alias] && (mitems[alias].className =
"b-m-item"
);
}
//Endfor
//CollectGarbage();
};
function
applyRule(rule) {
if
(ruleName && ruleName == rule.name)
return
false
;
for
(
var
i
in
mitems)
disable(i, !rule.disable);
for
(
var
i = 0; i < rule.items.length; i++)
disable(rule.items[i], rule.disable);
ruleName = rule.name;
};
function
disable(alias, disabled) {
var
item = mitems[alias];
item.className = (item.disable = item.lastChild.disabled = disabled) ?
"b-m-idisable"
:
"b-m-item"
;
};
/** 右鍵菜單顯示 */
function
showMenu(e, menutarget) {
target = menutarget;
showMenuGroup.call(groups.cmroot, { left: e.pageX, top: e.pageY }, 0);
$(document).one(
'mousedown'
, hideMenuPane);
}
var
$root = $(
"#"
+ option.alias);
var
root =
null
;
if
($root.length == 0) {
root = buildGroup.apply(gTemplet.clone()[0], [option]);
root.applyrule = applyRule;
root.showMenu = showMenu;
addItems(option.alias, option.items);
}
else
{
root = $root[0];
}
var
me = $(
this
).each(
function
() {
return
$(
this
).bind(
'contextmenu'
,
function
(e) {
var
bShowContext = (option.onContextMenu && $.isFunction(option.onContextMenu)) ? option.onContextMenu.call(
this
, e) :
true
;
if
(bShowContext) {
if
(option.onShow && $.isFunction(option.onShow)) {
option.onShow.call(
this
, root);
}
root.showMenu(e,
this
);
}
return
false
;
});
});
//設置顯示規則
if
(option.rule) {
applyRule(option.rule);
}
gTemplet = iTemplet = sTemplet = itemTpl = buildGroup = buildItem =
null
;
addItems = overItem = outItem =
null
;
//CollectGarbage();
return
me;
}
})(jQuery);
|
那接着就一步一步來分析唄,首先既然改造成jQuery控件那么自然還是老架子
1
2
3
4
|
;(
function
($) {
$.fn.contextmenu =
function
(option) {
}
})(jQuery);
|
1
2
3
|
//alias:"唯一標示"(這個標示很重要哦,可以實現多次調用只生成一個菜單哦),
//width菜單寬度
option = $.extend({ alias:
"cmroot"
, width: 150 }, option);
|
1
2
3
4
5
6
|
/*參數說明
option: {width:Number, items:Array, onShow:Function, rule:JSON}
成員語法(三種形式) -- para.items
-> {text:String, icon:String, type:String, alias:String, width:Number, items:Array} -- 菜單組
-> {text:String, icon:String, type:String, alias:String, action:Function } -- 菜單項
-> {type:String} --分割線*/
|
詳細描述下:
items:Array 右鍵菜單的內容定義,數組的元素格式如下所示:
{text: String, icon: String, alias: String, type: "group"|"item"|"splitLine", width:int, items:Array,action:Funtion}
其中:
text:String 菜單項的文字說明 。
icon: String 圖標的Src地址,如果沒有圖標,如果item不需要圖標,請設置成none.gif(在images/icons/中可以找到)。
alias:String 唯一標識菜單項。
type:"group"|"item"|"splitLine" 分別為組,項,分割線,當選擇是"splitLine"則其他設置項無需設置。
width:int 當且僅當type="group"時有效,設置新組容器的寬度。
items:Array 子元素可無限層次。
action:Function 當菜單項被點擊時被使用
alias: String (可選參數)唯一標識,當頁面上只有一種右鍵菜單時可以省略
width : Number (可選參數) 右鍵菜單根的寬度, 默認值:150px。
onContextMenu: Function (可選參數) 當右鍵菜單觸發時預先調用的函數,返回參數為Boolean指示是否顯示菜單
onShow: Function (可選參數) 當菜單顯示時觸發,一般在該函數中應用規則
rule : Json (可選參數) 默認規則,設置哪些項默認為禁用,格式如下所示 { name:String, disable: Boolean, items:Array}
name:String 規則名稱 disable:Boolean 規則是禁用還是啟用 items:Array 需要應用規則的item alias的集合
有點復雜哈,如果還有不明白看示例哈。
定義一堆臨時變量,還有4個模板臨時變量
1
2
3
4
5
6
7
|
var
ruleName =
null
, target =
null
,
groups = {}, mitems = {}, actions = {}, showGroups = [],
//定義內部的臨時變量。用到的地方再來分析
//一個菜單項的模板哦 ,容器和項,分割線的模板
itemTpl =
"<div class='b-m-$[type]' unselectable=on><nobr unselectable=on><img src='$[icon]' align='absmiddle'/><span unselectable=on>$[text]</span></nobr></div>"
;
var
gTemplet = $(
"<div/>"
).addClass(
"b-m-mpanel"
).attr(
"unselectable"
,
"on"
).css(
"display"
,
"none"
);
var
iTemplet = $(
"<div/>"
).addClass(
"b-m-item"
).attr(
"unselectable"
,
"on"
);
var
sTemplet = $(
"<div/>"
).addClass(
"b-m-split"
);
|
接着我們要跳過一些函數的定義,直接來看創建HTML的部分
1
2
3
4
5
6
7
8
9
10
11
12
|
//獲取菜單的跟
var
$root = $(
"#"
+ option.alias);
var
root =
null
;
if
($root.length == 0) {
//如果頂級不存在,這創建頂級菜單哦
root = buildGroup.apply(gTemplet.clone()[0], [option]);
root.applyrule = applyRule;
//把一個方法注冊到dom上
root.showMenu = showMenu;
//另外一個方法注冊的該dom上
addItems(option.alias, option.items);
//添加菜單項
}
else
{
root = $root[0];
//否則就用這個了
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
buildGroup =
function
(obj) {
//創建菜單容器
groups[obj.alias] =
this
;
//菜單項注冊到臨時變量中
this
.gidx = obj.alias;
this
.id = obj.alias;
if
(obj.disable) {
//如果是禁用狀態
this
.disable = obj.disable;
this
.className =
"b-m-idisable"
;
}
//設置菜單寬度,設置事件的阻止事件冒泡,並添加到body中
$(
this
).width(obj.width).click(returnfalse).mousedown(returnfalse).appendTo($(
"body"
));
obj =
null
;
return
this
;
//返回菜單本身
};
|
有了容器就可以往里面添加菜單項了,我在代碼中加了詳細的注釋了,應該可以很好的理解了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
//添加菜單項
var
addItems =
function
(gidx, items) {
var
tmp =
null
;
for
(
var
i = 0; i < items.length; i++) {
if
(items[i].type ==
"splitLine"
) {
//如果是分割線
//菜單分隔線
tmp = sTemplet.clone()[0];
}
else
{
items[i].gidx = gidx;
//把group的標識賦給item上
if
(items[i].type ==
"group"
) {
//菜單組
buildGroup.apply(gTemplet.clone()[0], [items[i]]);
//每個菜單組都是獨立的div哦,所以頂級一樣調用生產組的方法
arguments.callee(items[i].alias, items[i].items);
//遞歸生成菜單項
items[i].type =
"arrow"
;
//如果是group生成箭頭
tmp = buildItem.apply(iTemplet.clone()[0], [items[i]]);
//生成菜單項的html
}
else
{
//菜單項
items[i].type =
"ibody"
;
tmp = buildItem.apply(iTemplet.clone()[0], [items[i]]);
//生成菜單項的html
$(tmp).click(
function
(e) {
//如果菜單項那么注冊click事件
if
(!
this
.disable) {
if
($.isFunction(actions[
this
.idx])) {
actions[
this
.idx].call(
this
, target);
}
hideMenuPane();
}
return
false
;
});
}
//Endif
//把菜單項的右鍵事件屏蔽,同時注冊hover的效果
$(tmp).bind(
"contextmenu"
, returnfalse).hover(overItem, outItem);
}
//Endif
groups[gidx].appendChild(tmp);
//把菜單項添加到group的中
tmp = items[i] = items[i].items =
null
;
}
//Endfor
gidx = items =
null
;
};
|
builditem方法就比較簡單,就不詳細描述了,接着我們還是繼續往下看主流程了哦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var
me = $(
this
).each(
function
() {
//給元素添加右鍵事件了哦
return
$(
this
).bind(
'contextmenu'
,
function
(e) {
//如果(option.onContextMenu 存在則調用並判斷返回值是否顯示菜單,可以利用這個在特定情況下禁用菜單
var
bShowContext = (option.onContextMenu && $.isFunction(option.onContextMenu)) ? option.onContextMenu.call(
this
, e) :
true
;
if
(bShowContext) {
//觸發onShow事件,這個事件中可以執行修改rule,禁用某幾項菜單項哦
if
(option.onShow && $.isFunction(option.onShow)) {
option.onShow.call(
this
, root);
}
root.showMenu(e,
this
);
//調用顯示菜單
}
//阻止冒泡
return
false
;
});
});
//設置顯示規則,第一次執行時的規則,同時也可以onshow中動態設置rule
if
(option.rule) {
applyRule(option.rule);
}
|
基本就OK了,另外幾個方法就比較簡單了,還有亮點是邊緣的處理,這個前面的datepicker中也有相應的說明邏輯差不多就不在描述了,同樣還是來看下demo吧。關於打包下載,大家可以把demo的網頁完整的另存為即可
http://jscs.cloudapp.net/ControlsSample/CM
你的支持是我繼續寫作的動力。
歡迎轉載,但是請保留原鏈接:http://www.cnblogs.com/xuanye/archive/2009/10/29/1592585.html