简简单单右键菜单
今天是给大家介绍一款在网页上使用的右键菜单,原作者的网址是: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结构本身也不复杂
.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了
先来看个完整的吧
;(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控件那么自然还是老架子
;(function($) { $.fn.contextmenu = function(option) { } })(jQuery);接着是默认参数了哦
//alias:"唯一标示"(这个标示很重要哦,可以实现多次调用只生成一个菜单哦), //width菜单宽度 option = $.extend({ alias: "cmroot", width: 150 }, option);默认参数只有两个,另外几个的只是没有默认值而已
/*参数说明 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个模板临时变量
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的部分
//获取菜单的跟 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才知道到底干了什么
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; //返回菜单本身 };
有了容器就可以往里面添加菜单项了,我在代码中加了详细的注释了,应该可以很好的理解了
//添加菜单项 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方法就比较简单,就不详细描述了,接着我们还是继续往下看主流程了哦
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