开发者问题收集

Bootstrap:单击 jQuery 时下拉菜单无法打开

2022-07-05
1392

我正在创建一个包含多行的表格,每行都有一个“选项”按钮,用于显示下拉上下文菜单。为了使代码更简短,我使用单个 div 以便将其重新用作上下文菜单的通用标记。

我使用的是 Bootstrap 5.1.3 和 jQuery 3.6.0。以下是我的代码:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Test Code</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</head>

<body>
  <table id="myTable" class="table table-hover">
    <thead>
      <tr>
        <th>#</th>
        <th>Document</th>
        <th>Reference</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>General Policies</td>
        <td>GP-01-2022</td>
        <td>
          <div class="dropdown">
            <a href="#" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">Options</a>
          </div>
        </td>
      </tr>
      <tr>
        <td>2</td>
        <td>Training Material</td>
        <td>GP-02-2022</td>
        <td>
          <div class="dropdown">
            <a href="#" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">Options</a>
          </div>
        </td>
      </tr>
    </tbody>
  </table>

  <ul id="contextMenu" class="dropdown-menu">
    <li><a tabindex="-1" href="#" class="dropdown-item downloadLink">Download</a></li>
    <li><a tabindex="-1" href="#" class="dropdown-item propertiesLink">Properties</a></li>
  </ul>

  <script>
    //save the selector so you don't have to do the lookup everytime
    var $dropdown = $('#contextMenu');

    $('.optionsButton').click(function(event) {

      //get document ID
      var id = this.id;

      //move dropdown menu
      $(this).after($dropdown);

      //update links
      $dropdown.find(".downloadLink").attr("href", "/data/download?id=" + id);
      $dropdown.find(".propertiesLink").attr("href", "/data/viewproperties?id=" + id);

      //show dropdown
      $(this).dropdown();
    });
  </script>


</body>

</html>

在此代码中,我面临两种类型的问题。首先,下拉菜单未打开。当我在开发人员模式下检查代码时,我可以看到 jQuery 脚本已成功将 contextmenu DIV 传输到“选项”按钮下方,以便它按照 Bootstrap 的要求进行嵌套。但是 $(this).dropdown(); 并没有打开菜单。

第二个错误是,在开发者模式控制台中,每次单击“选项”按钮时都会看到此错误:

dropdown.js:285 Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.

并且此错误的堆栈跟踪指向 dropdown.js ,并未指定错误位于我的代码中的哪个位置。
需要帮助来尝试诊断此处的问​​题。我对 Bootstrap 和 jQuery 还不太熟悉。谢谢。

3个回答

TL;DR: 不要 移动 #contextMenu 到任何地方。阅读: 解决方案*


您收到的错误

dropdown.js:285 Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.

Bootstrap (v5.1.3) 相关: dropdown.js 代码:

// @l62:
const SELECTOR_MENU = '.dropdown-menu'

// @l103:
  constructor(element, config) {
    super(element);
    //...
    this._menu = this._getMenuElement()
    //...
  }

// @l269:
  _getMenuElement() {
    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
  }

// @l285:
   _getPlacement() {
    // ...
    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'
    // ...
  }

此处 SelectorEngine.next(this._element,

如您所见,除了 BS 硬编码的元素 .menu-dropdown 之外,没有办法将另一个元素传递给构造函数,而这是方法中的 下一个兄弟元素 _getMenuElement() ~line285.

BS 使用 data-bs-toggle="dropdown" 为每个按钮分配一个“click”事件,并盲目地期望切换 下一个同级元素 下拉菜单 — 实际上还不存在(目前)!

我会:

  • 手动扩展类 Dropdown ,或者
  • 提出问题并创建 pull-request 到相关的 Bootstrap 模块

作为将任何所需元素作为 this._menu 传递的方式;类似于:

constructor(element, config) {
    //...
    // Fix: allow for custom reusable menu Element
    this._menu = config.menuElement || this._getMenuElement()
    //...
  }

免责声明: 主分支 中,关于上述剥离的代码有一些变化,我不确定在撰写本文时这些问题是否已得到解决。


与此同时,您可以简单地做,不使用“mousedown”事件(领先BS的 “click” 事件一步 - 就像在这个 重复问题的答案 ),也不使用愚蠢的 Event.stopPropagation() (永远都不要使用,除非您真的非常清楚自己在做什么,或者仅用于调试目的)— 是:

解决方案:

不要使用 .after() 或(使用 JS) .insertAdjacentElement() 移动 UL#contextMenu ,而是在扩展的 Popper 实例实例化时,更改预期的 Bootstrap this._menu 属性以指向所需的可重用元素 — 您的 in-body “#contextMenu”类似:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Test Code</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>

<body>
  <table id="myTable" class="table table-hover">
    <thead>
      <tr>
        <th>#</th>
        <th>Document</th>
        <th>Reference</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>General Policies</td>
        <td>GP-01-2022</td>
        <td>
          <div class="dropdown">
            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">Options</button>
          </div>
        </td>
      </tr>
      <tr>
        <td>2</td>
        <td>Training Material</td>
        <td>GP-02-2022</td>
        <td>
          <div class="dropdown">
            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">Options</button>
          </div>
        </td>
      </tr>
    </tbody>
  </table>

  <ul id="contextMenu" class="dropdown-menu">
    <li><button type="button" tabindex="-1" class="dropdown-item downloadLink">Download</button></li>
    <li><button type="button" tabindex="-1" class="dropdown-item propertiesLink">Properties</button></li>
  </ul>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

  <script>
    // DOM utility functions:
    const el = (sel, par) => (par || document).querySelector(sel);
    const els = (sel, par) => (par || document).querySelectorAll(sel);

    // Task: BS5 Popper fix for single static dropdown menu:
    const elDropdown = el('#contextMenu');
    const elsBtns = els(".optionsButton");
    const dropdownList = [...elsBtns].map(function(elBtn) {
      const instance = new bootstrap.Dropdown(elBtn);
      instance._menu = elDropdown;
      return instance;
    });
    // console.log(dropdownList); 
  </script>

</body>

</html>

上述 好处 在于 DOM 中没有任何会触发重排的更改。 Popper 代码将计算浮动 contextMenu 的最佳位置,然后完成任务。
不太好 的事情 是,在动态向 Table 添加 TR 元素的情况下,应特别小心;这意味着每个新添加的按钮都应在创建时实例化为 new bootstrap.Dropdown(elBtn)

使用“mousedown”

另一个(不太好的)解决方案是( 不必要地 在 DOM 中移动下拉菜单 。可以使用 “mousedown” 事件来实现,以便“提前”移动下拉菜单 — 在 BS 的 “click” 事件触发之前(如 此相关问题的答案 中建议的那样)。但这样无法正常工作。单击一个又一个按钮,可以看到内容闪烁/故障(实际下拉菜单)。可能有方法可以缓解此问题……但是,为什么呢。无论如何,FYEO 这里是代码:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Test Code</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>

<body>
  <table id="myTable" class="table table-hover">
    <thead>
      <tr>
        <th>#</th>
        <th>Document</th>
        <th>Reference</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>General Policies</td>
        <td>GP-01-2022</td>
        <td>
          <div class="dropdown">
            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">Options</button>
          </div>
        </td>
      </tr>
      <tr>
        <td>2</td>
        <td>Training Material</td>
        <td>GP-02-2022</td>
        <td>
          <div class="dropdown">
            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">Options</button>
          </div>
        </td>
      </tr>
    </tbody>
  </table>

  <ul id="contextMenu" class="dropdown-menu">
    <li><button type="button" tabindex="-1" class="dropdown-item downloadLink">Download</button></li>
    <li><button type="button" tabindex="-1" class="dropdown-item propertiesLink">Properties</button></li>
  </ul>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

  <script>
    // DOM utility functions:
    const el = (sel, par) => (par || document).querySelector(sel);
    const els = (sel, par) => (par || document).querySelectorAll(sel);

    // Task: BS5 Popper fix for single static dropdown menu:
    const elDropdown = el('#contextMenu');
    const elsBtns = els(".optionsButton");

    const prepareDropdown = (evt) => {
      const elBtn = evt.currentTarget;
      elBtn.insertAdjacentElement("afterend", elDropdown);
    };

    elsBtns.forEach(elBtn => elBtn.addEventListener("mousedown", prepareDropdown));
  </script>


</body>

</html>

PS:如果您不需要导航,而只是一个简单的 UI 交互按钮元素,请始终使用 <button type="button"> 而不是 Anchors(如上例所示)。

BS 使用和实现弹出窗口、选择等的方式有点 无论如何都是坏的 。如果已经通过单击另一个按钮打开了弹出窗口(或模式),则应 立即 显示第二个(或相同)弹出窗口(而不是在第二次单击后)。这是设计中的 UI/UX 缺陷。 Bootstrap 经常会以相当奇怪的方式实现想法,但不要忘记,你始终可以通过提供 Pull Request 来帮助开源社区。


如果你对如何使用 JavaScript 从头开始​​创建(类似的)弹出窗口感兴趣 — — 你可以在这里找到更多信息: 在鼠标位置显示自定义弹出窗口

Roko C. Buljan
2022-07-11

我已经注释了您对 bootstrap cdn 的加载,并且只处理了您的实际 Javascript。

您只有一个显示下拉菜单的逻辑,尽管我将其从 $(this).dropdown(); 更改为 $dropdown.show() ,因为您的下拉菜单已使用此变量进行跟踪。

我还实现了切换效果,也就是说,如果您的下拉菜单已打开,那么我们将有以下场景:

  • 您单击表中的其他位置,在这种情况下下拉菜单将被隐藏
  • 您单击相同的按钮,在这种情况下下拉菜单将被隐藏
  • 您单击另一行中的类似按钮,在这种情况下下拉菜单将使用适当的参数正确移动到适当的位置
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Test Code</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</head>
<body>
    <table id="myTable" class="table table-hover" onclick="hideall()">
        <thead>
            <tr>
                <th>#</th>
                <th>Document</th>
                <th>Reference</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>General Policies</td>
                <td>GP-01-2022</td>
                <td>
                    <div class="dropdown">
                        <a href="#" class="btn btn-primary optionsButton" aria-expanded="false" id="doc1">
                            Options
                        </a>
                    </div>
                </td>
            </tr>
            <tr>
                <td>2</td>
                <td>Training Material</td>
                <td>GP-02-2022</td>
                <td>
                    <div class="dropdown">
                        <a href="#" class="btn btn-primary optionsButton" aria-expanded="false" id="doc2">
                            Options
                        </a>
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
    


    <ul id="contextMenu" class="dropdown-menu">
        <li><a tabindex="-1" href="#" class="dropdown-item downloadLink">Download</a></li>
        <li><a tabindex="-1" href="#" class="dropdown-item propertiesLink">Properties</a></li>
    </ul>
    
    <script>
        //save the selector so you don't have to do the lookup everytime
        var $dropdown = $('#contextMenu');
        var activeOptionsButton;

        $('.optionsButton').click(function(event) {
        
            event.stopPropagation();
            if (activeOptionsButton === this) {
                hideall();
                activeOptionsButton = undefined;
                return;
            }
            activeOptionsButton = this;
            //get document ID
            var id = this.id;
            
            //move dropdown menu
            $(this).after($dropdown);

            //update links
            $dropdown.find(".downloadLink").attr("href", "/data/download?id="+id);
            $dropdown.find(".propertiesLink").attr("href", "/data/viewproperties?id="+id);
            
            //show dropdown
            $dropdown.show();
        });
        
        function hideall() {
            $dropdown.hide();
        }
    
    </script>


</body>
</html>
Lajos Arpad
2022-07-09

试试这个..

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Test Code</title>
  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
  
</head>
<body>
    <table id="myTable" class="table table-hover">
        <thead>
            <tr>
                <th>#</th>
                <th>Document</th>
                <th>Reference</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>General Policies</td>
                <td>GP-01-2022</td>
                <td>
                    <div class="dropdown">
                        <a href="#" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">
                            Options
                        </a>
                    </div>
                </td>
            </tr>
            <tr>
                <td>2</td>
                <td>Training Material</td>
                <td>GP-02-2022</td>
                <td>
                    <div class="dropdown">
                        <a href="#" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">
                            Options
                        </a>
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
    


    <ul id="contextMenu" class="dropdown-menu">
        <li><a tabindex="-1" href="#" class="dropdown-item downloadLink">Download</a></li>
        <li><a tabindex="-1" href="#" class="dropdown-item propertiesLink">Properties</a></li>
    </ul>
    
    <script>
        //save the selector so you don't have to do the lookup everytime
        var $dropdown = $('#contextMenu');

        $('.optionsButton').click(function(event) {
            
            //get document ID
            var id = this.id;
            
            //move dropdown menu
            $(this).after($dropdown);

            //update links
            $dropdown.find(".downloadLink").attr("href", "/data/download?id="+id);
            $dropdown.find(".propertiesLink").attr("href", "/data/viewproperties?id="+id);
            
            //show dropdown
            $(this).dropdown();
        });
    
    </script>


</body>
</html>
Mithun Deshan
2022-07-05