【问题标题】:Use Bootstrap 3 dropdown menu as context menu使用 Bootstrap 3 下拉菜单作为上下文菜单
【发布时间】:2013-09-11 01:47:53
【问题描述】:

使用 Bootstrap 3,如何将下拉菜单放在光标处并从代码中打开它?

我需要在表格上使用它作为其行的上下文菜单。

【问题讨论】:

标签: jquery twitter-bootstrap twitter-bootstrap-3


【解决方案1】:

我只是想改进 letiagoalves 的好答案并提供更多建议。
以下是如何将上下文菜单添加到任何 html 元素的演练。

Let's start off with a working demo in jsFiddle

标记:

首先,让我们从bootstrap dropdown control 添加一个菜单。将它添加到您的 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 代码添加为一个名为 contextMenu 的 jQuery 扩展。

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

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

插件模板:

基于jQuery boilerplate plugin template,我们将使用Immediately-Invoked Function Expression,这样我们就不会混淆全局命名空间。由于我们依赖于 jQuery 并且需要访问窗口,因此我们将它们作为变量传递,这样我们就可以在缩小过程中存活下来。它看起来像这样:

(function($, window){

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

})(jQuery, window);

好的,不用再安装管道了。这是函数的核心:

处理右键事件:

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

  • 我们将获取e.target 属性并将其存储为名为@9​​87654348@ 的数据属性,以便我们稍后可以识别引发上下文菜单的元素。
  • 我们将使用.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;
});

修复菜单边缘情况:

这将打开打开它的光标右下角的菜单。但是,如果光标指向far right of the screen,则菜单应向左打开。同样,如果光标在底部,菜单应该打开到顶部。区分bottom of the 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 noted,我们不想记录对菜单项以外的任何内容的点击,因此我们通过将选择器传递给on 函数来设置delegated handler,该函数将“过滤所选元素的后代触发事件”。

到目前为止,函数应该是这样的:

$(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);

点击关闭时隐藏:

最后,与大多数上下文菜单一样,我们也希望在用户单击菜单时关闭菜单。为此,我们将监听 body 上的任何点击事件,如果它像这样打开,则关闭上下文菜单:

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

注意Thanks to Sadhir's comment,Firefox linux 在右键点击时会触发document 的点击事件,所以你必须在body 上设置监听器。

示例语法:

扩展程序将返回引发上下文菜单和单击的菜单项的原始对象。您可能必须使用 jQuery traverse the 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;

【讨论】:

  • 如果单击屏幕右/左端附近,我们如何避免菜单创建水平滚动条。假设我们点击的距离屏幕右端太近,当菜单打开时,它会在左侧打开,检测到它靠近屏幕端,应该在其他方向打开。我的意思是智能地添加左拉或右拉?
  • @Simon,完成!问题在于,当每个传入contextMenu 插件的元素会立即自动在菜单弹出窗口中添加它自己的点击处理程序。单击元素后,每个事件处理程序都会背靠背触发。为了解决这个问题,现在在处理右键单击事件时动态添加处理程序,因此只有一个元素负责处理菜单单击。
  • @KyleMit:谢谢,我正在为我的项目偷这个。我注意到了一个小问题。顶部和左侧位置的计算使用窗口的高度和宽度——只要没有滚动就可以了。如果向表格添加更多行,以便滚动,您会注意到对于最初出现在底部的行,上下文菜单出现在光标上方,即使向下滚动时底部有足够的空间一点。修复很简单 - 只需将 $(window).height() 替换为 $(document).height()
  • @Sadhir,感谢您的评论。看起来document 有它自己的问题,因为如果不向下滚动,文档仍然有足够的空间,所以菜单会下拉到窗口底部。 Here's an example of that issue in fiddle。看来您需要考虑$(window).scrollTop()。我会更新我的答案。
  • 再次感谢@KyleMit,它解决了所有滚动问题。顺便说一句,关于这在 Firefox 中不起作用的 cmets - 这似乎是 Firefox 中的一个错误,其中在右键单击时触发了文档上的单击事件。因此右键单击上下文菜单会立即隐藏。使用 $('body').on('click'.. 而不是 $(document).click() 似乎已经为我解决了这个问题
【解决方案2】:

这是可能的。我给你做了一个工作演示,让你有个好的开始。

Working demo (右键单击任何表格行以查看它的实际效果)

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

#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();
});

【讨论】:

  • +1,很好的答案。我的一个建议是,通常在页面上的任何位置单击上下文菜单都会消失。我在下面的回答中添加了您的示例。
  • 谢谢 :) 我注意到了,但我选择不假设比 OP 要求的更多,尽管他可能会想要那个
  • 在你可以使用的回调中:var $row = $(this) 或者如果你只想要它的 id 就不需要创建一个 jQuery 对象,只需执行 var id = this.id
  • 哦,好像恢复了。我的错。
  • 如何在单击行右侧时阻止菜单出现在页面之外?例如,Google Docs/Drive 将始终以最佳方式定位菜单。定位有解决办法吗?
【解决方案3】:

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

  • 将“on document click”处理程序从“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/

【讨论】:

  • 伟大的补充!喜欢添加视觉指示器onMenuShow 的想法。另请注意,在$.fn.contextMenu = function (settings) {} 的末尾,您应该调用return this;,因为期望jQuery 方法通常是可链接的。
【解决方案4】:

我发现了这个简单且有效的上下文菜单。我正在使用这个库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;
        }
        },

【讨论】:

    猜你喜欢
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 2014-09-10
    相关资源
    最近更新 更多