这个右键菜单已经非常优秀,不过呢。却是IE Only,而且在DTD模式下

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "​连IE显示都是有问题的,所以呢只有自己动手了,另外就顺便改造成jQuery控件,顺便分析一下代码。

首先来看一下效果吧

打造html右键菜单_css

↑这是控件的效果

打造html右键菜单_右键菜单_02​ ←Windows Se7en 系统的邮件菜单

插一句吧,其实我最终的目标是提供一个ASP.NET MVC 框架前台UI Controls解决方案,因为后面的控件会用到这个右键菜单所以就讲一下。

首先还是来分析一下HTML吧

1:一级菜单(每一组菜单)即是一个独立的div容器

2:每一项又是div,嵌套一个nobr(可用div代替不过要额外写个class)的标签,里面是图标和span包裹的位置内容

打造html右键菜单_ico_03​菜单项/菜单组    ​打造html右键菜单_右键菜单_04​ 分割线

这里一个要注意的地方就是多级菜单其实在HTMl结构是分离的,只是通过显示的位置在视觉上给人连载一起(另外就是箭头图标了)

第二接着是CSS了(是修改过的)

CSS非常简单,因为HTML结构本身也不复杂




​+ View Code​​​​?​



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;​


​}​



CSS中会用到的所有图片 ​打造html右键菜单_菜单项_05​ ​打造html右键菜单_菜单项_06​ ​打造html右键菜单_菜单项_07​ ​打造html右键菜单_ico_08​  注意有四张图片哦。。

第三来看javascript了

先来看个完整的吧




​+ View Code​​​​?​



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]; ​​​​//否则就用这个了​


​}​


这个代码很玄乎,好像做了些什么好像有什么都没做,其实只有来看下buildGroup方法和addItems才知道到底干了什么



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​

你的支持是我继续写作的动力。

这个右键菜单已经非常优秀,不过呢。却是IE Only,而且在DTD模式下

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "​连IE显示都是有问题的,所以呢只有自己动手了,另外就顺便改造成jQuery控件,顺便分析一下代码。

首先来看一下效果吧

打造html右键菜单_css

↑这是控件的效果

打造html右键菜单_右键菜单_02​ ←Windows Se7en 系统的邮件菜单

插一句吧,其实我最终的目标是提供一个ASP.NET MVC 框架前台UI Controls解决方案,因为后面的控件会用到这个右键菜单所以就讲一下。

首先还是来分析一下HTML吧

1:一级菜单(每一组菜单)即是一个独立的div容器

2:每一项又是div,嵌套一个nobr(可用div代替不过要额外写个class)的标签,里面是图标和span包裹的位置内容

打造html右键菜单_ico_03​菜单项/菜单组    ​打造html右键菜单_右键菜单_04​ 分割线

这里一个要注意的地方就是多级菜单其实在HTMl结构是分离的,只是通过显示的位置在视觉上给人连载一起(另外就是箭头图标了)

第二接着是CSS了(是修改过的)

CSS非常简单,因为HTML结构本身也不复杂




​+ View Code​​​​?​



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;​


​}​



CSS中会用到的所有图片 ​打造html右键菜单_菜单项_05​ ​打造html右键菜单_菜单项_06​ ​打造html右键菜单_菜单项_07​ ​打造html右键菜单_ico_08​  注意有四张图片哦。。

第三来看javascript了

先来看个完整的吧




​+ View Code​​​​?​



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]; ​​​​//否则就用这个了​


​}​


这个代码很玄乎,好像做了些什么好像有什么都没做,其实只有来看下buildGroup方法和addItems才知道到底干了什么



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​