zoukankan      html  css  js  c++  java
  • 简简单单右键菜单

    今天是给大家介绍一款在网页上使用的右键菜单,原作者的网址是: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控件,顺便分析一下代码。

    首先来看一下效果吧

    image

    ↑这是控件的效果

    image ←Windows Se7en 系统的邮件菜单

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

    首先还是来分析一下HTML吧

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

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

    image菜单项/菜单组    image 分割线

    这里一个要注意的地方就是多级菜单其实在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;
    }
    

    CSS中会用到的所有图片 m_arrow m_item m_splitLine menu_bg  注意有四张图片哦。。

    第三来看javascript了

    先来看个完整的吧

    ;(function($) {
        function returnfalse() { return false; };
        $.fn.contextmenu = function(option) {
            option = $.extend({ alias: "cmroot",  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",  150 }, option);
    默认参数只有两个,另外几个的只是没有默认值而已
    /*参数说明
    option: {Number, items:Array, onShow:Function, rule:JSON}
    成员语法(三种形式)    -- para.items
    -> {text:String, icon:String, type:String, alias:String, 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", int, items:Array,action:Funtion}

    其中:
    text:String 菜单项的文字说明 。
    icon: String 图标的Src地址,如果没有图标,如果item不需要图标,请设置成none.gif(在images/icons/中可以找到)。
    alias:String 唯一标识菜单项。
    type:"group"|"item"|"splitLine" 分别为组,项,分割线,当选择是"splitLine"则其他设置项无需设置。
    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

  • 相关阅读:
    C语言丨博客作业10
    C语言博客作业09
    C语言博客作业08
    闽江学院软件学院2019-2020《创新思维》课程预告(20200412)
    C语言和数据结构的书单-再次推荐
    2019-2020创新思维初探(IT | Design |Think)书单(更新ING)
    闽江学院软件学院2016级JAVA构建之法-学生自学兴趣小组招募通知
    2014软件工程专业-职业能力调查(作业)
    软件第二次结对作业
    软件第一次结对作业
  • 原文地址:https://www.cnblogs.com/xuanye/p/xuanye_jquery_contextmenu.html
Copyright © 2011-2022 走看看