使用Bootstrap 3下拉菜单作为上下文菜单


Answers:


93

有可能的。我为您制作了一个工作演示,为您提供了良好的开端。

工作演示 (右键单击任何表行以查看实际操作)

首先创建您的下拉菜单,将其隐藏并更改positionabsolute

#contextMenu {
  position: absolute;
  display:none;
}

然后将一个contextmenu事件绑定到您的表行,以便显示下拉菜单/上下文菜单并将其定位在光标处:

var $contextMenu = $("#contextMenu");

$("body").on("contextmenu", "table tr", function(e) {
   $contextMenu.css({
      display: "block",
      left: e.pageX,
      top: e.pageY
   });
   return false;
});

然后,当用户选择一个选项时,隐藏下拉菜单/上下文菜单:

$contextMenu.on("click", "a", function() {
   $contextMenu.hide();
});

5
+1,好答案。我的一个建议是,通常情况下,页面上任何位置的单击都会使上下文菜单消失。我在下面的答案中添加了您的示例。
KyleMit 2013年

谢谢:)我注意到了,但我选择不承担OP提出的更多要求,尽管他可能会想
要这样做

1
在回调中可以使用:var $row = $(this)或如果只需要其ID,则无需创建jQuery对象,只需执行var id = this.id
letiagoalves 2013年

哦,看起来好像备份了。我的错。
安倍·米斯勒

单击行右侧时,如何停止菜单在页面外显示?例如,Google文档/驱动器将始终以最佳方式放置菜单。定位有解决方案吗?
Redwall 2014年

151

我只是想通过几个建议来改善letiagoalves的出色答案。
这是有关如何向任何html元素添加上下文菜单的演练。

让我们从jsFiddle中的工作演示开始

标记:

首先,让我们从bootstrap下拉控件中添加一个菜单。将其添加到HTML的任何位置,最好是在正文的根目录级别。该.dropdown-menu课程将设置display:none所以它的最初不可见的。
它看起来应该像这样:

<ul id="contextMenu" class="dropdown-menu" role="menu">
    <li><a tabindex="-1" href="#">Action</a></li>
    <li><a tabindex="-1" href="#">Another action</a></li>
    <li><a tabindex="-1" href="#">Something else here</a></li>
    <li class="divider"></li>
    <li><a tabindex="-1" href="#">Separated link</a></li>
</ul>

扩展程序设置:

为了保持我们的设计模块化,我们将JavaScript代码作为jQuery扩展添加,称为 contextMenu

调用时$.contextMenu,我们将传入具有2个属性的设置对象:

  1. menuSelector 使用我们之前在HTML中创建的菜单的jQuery选择器。
  2. menuSelected 单击上下文菜单操作时将调用。
$("#myTable").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        // context menu clicked
    });
});

插件模板:

基于jQuery样板插件模板,我们将使用立即调用函数表达式,因此我们不会混淆全局名称空间。由于我们依赖jQuery并且需要访问窗口,因此我们将它们作为变量传递,这样我们就可以在缩小中幸存下来。它看起来像这样:

(function($, window){

    $.fn.contextMenu = function(settings) {  
        return this.each(function() {  
            // Code Goes Here
        }  
    };

})(jQuery, window);

好吧,再也没有管道了。这是函数的内容:

处理右键单击事件:

我们将contextmenu在调用扩展的对象上处理鼠标事件。事件触发时,我们将抓取一开始添加的下拉菜单。我们将在初始化函数时使用设置中传入的选择器字符串来查找它。我们将通过执行以下操作来修改菜单:

  • 我们将获取该e.target属性并将其存储为名为invokedOn,以便稍后可以标识引发上下文菜单的元素。
  • 我们将使用切换菜单显示为可见 .show()
  • 我们将使用定位元素.css()
    • 我们需要确保将其position设置为absolute
    • 然后,我们将使用事件的pageXpageY属性设置左侧和顶部位置。
  • 最后,为防止右键单击操作打开其自己的菜单,我们将return false停止javascript处理其他任何事情。

它看起来像这样:

$(this).on("contextmenu", function (e) {
    $(settings.menuSelector)
        .data("invokedOn", $(e.target))
        .show()
        .css({
            position: "absolute",
            left: e.pageX,
            top: e.pageY
        });

    return false;
});

修复菜单边缘案例:

这将打开打开菜单的光标右下角的菜单。但是,如果光标位于屏幕最右侧,则菜单应向左打开。同样,如果光标在底部,则菜单应在顶部打开。区分包含物理框架的底部和底部的底部window也很重要document代表整个html DOM并可以滚动到窗口之外。

为此,我们将使用以下功能设置位置:

我们将这样称呼他们:

.css({
    left: getMenuPosition(e.clientX, 'width', 'scrollLeft'),
    top: getMenuPosition(e.clientY, 'height', 'scrollTop')
});

它将调用此函数以返回适当的位置:

function getMenuPosition(mouse, direction, scrollDir) {
    var win = $(window)[direction](),
        scroll = $(window)[scrollDir](),
        menu = $(settings.menuSelector)[direction](),
        position = mouse + scroll;

    // opening menu would pass the side of the page
    if (mouse + menu > win && menu < mouse) 
        position -= menu;

    return position
}

在菜单元素上绑定单击事件:

显示上下文菜单后,我们需要添加一个事件处理程序以侦听其上的单击事件。我们将删除可能已添加的所有其他绑定,以免再次触发同一事件。这些可以在打开菜单的任何时间发生,但是由于单击没有选择任何内容。然后,我们可以在click事件上添加新的绑定,我们将在下一部分中处理逻辑。

valepu所述,除了菜单项外,我们不想注册其他任何点击,因此我们通过将选择器传递到函数中来设置委托处理程序,该选择器on将“过滤触发事件的选定元素的后代”。

到目前为止,该函数应如下所示:

$(settings.menuSelector)
    .off('click')
    .on( 'click', "a", function (e) {
        //CODE IN NEXT SECTION GOES HERE
});

处理菜单点击

知道在菜单上单击之后,我们将执行以下操作:我们将使用从屏幕上隐藏菜单.hide()。接下来,我们要保存最初调用菜单的元素以及当前菜单中的选择。最后,我们将通过.call()在属性上使用并将事件目标作为参数传递来触发传递给扩展的函数选项。

$menu.hide();

var $invokedOn = $menu.data("invokedOn");
var $selectedMenu = $(e.target);

settings.menuSelected.call($(this), $invokedOn, $selectedMenu);

单击关闭时隐藏:

最后,与大多数上下文菜单一样,我们也希望在用户单击菜单时也将其关闭。为此,我们将监听主体上的所有click事件,并关闭上下文菜单(如果打开),如下所示:

$('body').click(function () {
    $(settings.menuSelector).hide();
});

注意由于Sadhir的评论,Firefox linux在右键单击时触发了click事件document,因此您必须在上设置侦听器body

语法示例:

该扩展名将与引发上下文菜单的原始对象和被单击的菜单项一起返回。您可能必须使用jQuery遍历dom才能从事件目标中找到有意义的内容,但这应该提供良好的基础功能层。

这是一个返回所选项目和操作信息的示例:

$("#myTable").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        var msg = "You selected the menu item '" + 
                  selectedMenu.text() +
                  "' on the value '" + 
                  invokedOn.text() + "'";
        alert(msg);
    }
});

屏幕截图:

上下文菜单截图

更新说明:

通过将其包装在jQuery扩展方法中,此答案已得到实质性更新。如果您想查看我的原著,则可以查看帖子的历史记录,但是我相信此最终版本将采用更好的编码实践。

奖励功能

如果要为高级用户或您自己添加一些不错的功能来开发功能,则可以基于右键单击时按住的任何组合键来绕过上下文菜单。例如,如果要在握住时允许显示原始浏览器上下文菜单Ctrl,则可以将其添加为contextMenu处理程序的第一行:

// return native menu if pressing control
if (e.ctrlKey) return;

1
如果单击屏幕右/左端附近,如何避免菜单创建水平滚动条。假设我们单击的位置离屏幕的右端太近,当菜单打开时,它在左侧打开,表明它靠近屏幕的右端,应该向其他方向打开。通过智能地添加向左拉或向右拉,我的意思是?
django 2014年

1
@Simon,完成!问题在于,当传递到contextMenu插件中的每个元素都将立即在菜单弹出窗口中自动添加其自己的单击处理程序。单击该元素后,每个事件处理程序都会立即返回。为了解决此问题,现在在处理右键单击事件时会动态添加处理程序,因此只有单个元素负责处理菜单单击。
KyleMit 2014年

1
@KyleMit:谢谢,我是在为我的项目偷东西。我注意到一个小问题。顶部和左侧位置的计算使用窗口的高度和宽度-只要没有滚动,就可以了。如果向表中添加更多行以便进行滚动,则会注意到,对于最初出现在底部的行,上下文菜单显示在光标上方,即使向下滚动时底部也有足够的空间一点。修复很简单-只需用$(document).height()替换$(window).height()
Sadhir 2015年

1
@Sadhir,感谢您的评论。看起来这document是它自己的问题,因为如果不向下滚动文档,仍然有足够的空间,因此菜单将下拉到窗口底部下方。 这是摆弄这个问题的一个例子。看来您需要考虑到这一点$(window).scrollTop()。我将更新我的答案。
KyleMit 2015年

2
再次感谢@KyleMit,它解决了滚动问题。顺便说一句,关于此注释在firefox中不起作用的问题-似乎是firefox中的错误,右键单击时会触发文档的click事件。因此,右键单击将立即隐藏上下文菜单。使用$('body')。on('click'..代替$(document).click()似乎已经为我解决了这个问题
Sadhir 2015年

8

KyleMit的代码进行了一些修改:

  • 将“单击文档时”处理程序从“ foreach”中移出
  • 删除“ foreach”,只需将事件添加到选择器
  • 隐藏“文档上下文菜单”上的菜单
  • 传递事件

    $("#myTable tbody td").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        var msg = "You selected the menu item '" + selectedMenu.text() +
            "' on the value '" + invokedOn.text() + "'";
        alert(msg);
    },
    onMenuShow: function(invokedOn) {
        var tr = invokedOn.closest("tr");
        $(tr).addClass("warning");
    },
    onMenuHide: function(invokedOn) {
        var tr = invokedOn.closest("tr");
        $(tr).removeClass("warning");
    } });
    

http://jsfiddle.net/dmitry_far/cgqft4k3/


1
很棒的补充!喜欢添加视觉指示器的想法onMenuShow。还要注意,在最后$.fn.contextMenu = function (settings) {}您应该调用,return this;因为人们期望jQuery方法通常是可链接的。
KyleMit

-3

我发现这个简单而有效的上下文菜单。我正在使用这个库http://swisnl.github.io/jQuery-contextMenu/index.html。希望能帮助到你

表:

<table id="ppmpsupplies" class="table table-bordered table-hover" cellspacing="0" width="100%">
          <thead>
            <tr>
              <th>Code</th>
              <th>General Description</th>
              <th>Unit</th>
              <th>Quantity</th>
              <th>Estimated Budget</th>
              <th>Mode of Procurement</th>                 

            </tr>
          </thead>
          <tbody>
            <?php foreach($items as $item){?>
            <tr>
             <td><?php echo $item->id;?></td>
             <td><?php echo $item->description;?></td>
             <td><?php echo $item->unit;?></td>
             <td><?php echo $item->quantity;?></td>
             <td><?php echo $item->budget;?></td>
             <td><?php echo $item->mode;?></td>                     
          </tr>
          <?php }?>

        </tbody>
        <tfoot>
          <td colspan="3"></td>
          <td>Total</td>
          <td></td>
        </tfoot>
      </table>

上下文菜单:

    "edit": {
        name: "Edit",
        icon: "fa-pencil-square-o",
        callback: function(item, id) {

        return true;
        }
        },
"delete": {
        name: "Delete",
        icon: "fa-trash-o",
        callback: function(item, id) {

        return true;
        }
        },
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.