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

Far*_*har 4 css jquery bootstrap-5

我正在创建一个包含多行的表,所有行都有一个“选项”按钮,该按钮应该显示下拉上下文菜单。为了使代码更短,我使用单个代码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/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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>
Run Code Online (Sandbox Code Playgroud)

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

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

dropdown.js:285 未捕获类型错误:无法在“Window”上执行“getCompulatedStyle”:参数 1 不是“Element”类型。

并且此错误的堆栈跟踪指向dropdown.js,并没有指定错误在我的代码中的位置。
在尝试诊断问题时需要帮助。我对 Bootstrap 和 jQuery 相当陌生。谢谢。

Rok*_*jan 5

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

\n
\n

您收到的错误

\n
\n

dropdown.js:285 Uncaught TypeError: 无法在“Window”上执行“getCompulatedStyle”:参数 1 不是“Element”类型。

\n
\n

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

\n
// @l62:\nconst SELECTOR_MENU = \'.dropdown-menu\'\n\n// @l103:\n  constructor(element, config) {\n    super(element);\n    //...\n    this._menu = this._getMenuElement()\n    //...\n  }\n\n// @l269:\n  _getMenuElement() {\n    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]\n  }\n\n// @l285:\n   _getPlacement() {\n    // ...\n    const isEnd = getComputedStyle(this._menu).getPropertyValue(\'--bs-position\').trim() === \'end\'\n    // ...\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

这里SelectorEngine.next(this._element,

\n

.menu-dropdown正如您所看到的,除了 BS 硬编码的元素之外,没有办法向构造函数传递另一个元素,而该元素是方法 ~line285 中的下一个同级元素_getMenuElement()

\n

BS 为每个按钮分配一个“单击”事件,并data-bs-toggle="dropdown"盲目地期望切换下一个同级元素下拉列表 \xe2\x80\x94 ,而该下拉列表实际上并不存在(尚)!

\n

我会:

\n\n

this._menu作为将任何所需元素作为;传递的一种方式 就像是:

\n
  constructor(element, config) {\n    //...\n    // Fix: allow for custom reusable menu Element\n    this._menu = config.menuElement || this._getMenuElement()\n    //...\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

与此同时,您可以简单地做些什么,而不使用“mousedown”事件(比 BS 事件领先一步"click"- 就像在这个重复问题的答案中一样),并且不使用愚蠢的Event.stopPropagation()(这永远不应该)使用,除了你真的,真的知道你在做什么,或者仅用于调试目的) \xe2\x80\x94 是:

\n

解决方案:

\n

不要移动UL#contextMenuusing .after()or (with JS) .insertAdjacentElement(),而是在扩展 Popper 实例的实例化时更改预期的 Bootstrapthis._menu属性以指向所需的可重用元素 \xe2\x80\x94 你的体内“#contextMenu”,例如:

\n

\r\n
\r\n
<!doctype html>\n<html lang="en">\n\n<head>\n  <meta charset="utf-8">\n  <meta name="viewport" content="width=device-width, initial-scale=1">\n  <title>Test Code</title>\n  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">\n</head>\n\n<body>\n  <table id="myTable" class="table table-hover">\n    <thead>\n      <tr>\n        <th>#</th>\n        <th>Document</th>\n        <th>Reference</th>\n        <th>Action</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <td>1</td>\n        <td>General Policies</td>\n        <td>GP-01-2022</td>\n        <td>\n          <div class="dropdown">\n            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">Options</button>\n          </div>\n        </td>\n      </tr>\n      <tr>\n        <td>2</td>\n        <td>Training Material</td>\n        <td>GP-02-2022</td>\n        <td>\n          <div class="dropdown">\n            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">Options</button>\n          </div>\n        </td>\n      </tr>\n    </tbody>\n  </table>\n\n  <ul id="contextMenu" class="dropdown-menu">\n    <li><button type="button" tabindex="-1" class="dropdown-item downloadLink">Download</button></li>\n    <li><button type="button" tabindex="-1" class="dropdown-item propertiesLink">Properties</button></li>\n  </ul>\n\n  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>\n\n  <script>\n    // DOM utility functions:\n    const el = (sel, par) => (par || document).querySelector(sel);\n    const els = (sel, par) => (par || document).querySelectorAll(sel);\n\n    // Task: BS5 Popper fix for single static dropdown menu:\n    const elDropdown = el(\'#contextMenu\');\n    const elsBtns = els(".optionsButton");\n    const dropdownList = [...elsBtns].map(function(elBtn) {\n      const instance = new bootstrap.Dropdown(elBtn);\n      instance._menu = elDropdown;\n      return instance;\n    });\n    // console.log(dropdownList); \n  </script>\n\n</body>\n\n</html>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

上面的好处是 DOM 中没有任何会触发回流的更改Popper 代码将计算浮动 contextMenu 的最佳位置并称其为工作完成。
\n不太好的事情是,在动态添加 TR 元素到表的情况下应该特别小心;这意味着每个新添加的 Button 应在创建时实例化为new bootstrap.Dropdown(elBtn)

\n

使用“鼠标按下”

\n

针对您最初想法的另一个(不太好的)解决方案是(不必要地移动 DOM 中的下拉菜单。它可以使用"mousedown"事件来实现,以便在 BS 的事件触发之前“提前”移动下拉列表 \xe2\x80\x94 "click"如此相关问题的答案中所建议的)。但这样将无法正常工作。单击一个又一个按钮,可以看到一闪而过的内容/故障(实际的下拉列表)。可能有一些方法可以缓解这个问题\xe2\x80\xa6 但是,为什么。无论如何,FYEO 这是代码:

\n

\r\n
\r\n
<!doctype html>\n<html lang="en">\n\n<head>\n  <meta charset="utf-8">\n  <meta name="viewport" content="width=device-width, initial-scale=1">\n  <title>Test Code</title>\n\n  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">\n</head>\n\n<body>\n  <table id="myTable" class="table table-hover">\n    <thead>\n      <tr>\n        <th>#</th>\n        <th>Document</th>\n        <th>Reference</th>\n        <th>Action</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <td>1</td>\n        <td>General Policies</td>\n        <td>GP-01-2022</td>\n        <td>\n          <div class="dropdown">\n            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc1">Options</button>\n          </div>\n        </td>\n      </tr>\n      <tr>\n        <td>2</td>\n        <td>Training Material</td>\n        <td>GP-02-2022</td>\n        <td>\n          <div class="dropdown">\n            <button type="button" class="btn btn-primary optionsButton" data-bs-toggle="dropdown" aria-expanded="false" id="doc2">Options</button>\n          </div>\n        </td>\n      </tr>\n    </tbody>\n  </table>\n\n  <ul id="contextMenu" class="dropdown-menu">\n    <li><button type="button" tabindex="-1" class="dropdown-item downloadLink">Download</button></li>\n    <li><button type="button" tabindex="-1" class="dropdown-item propertiesLink">Properties</button></li>\n  </ul>\n\n  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>\n\n  <script>\n    // DOM utility functions:\n    const el = (sel, par) => (par || document).querySelector(sel);\n    const els = (sel, par) => (par || document).querySelectorAll(sel);\n\n    // Task: BS5 Popper fix for single static dropdown menu:\n    const elDropdown = el(\'#contextMenu\');\n    const elsBtns = els(".optionsButton");\n\n    const prepareDropdown = (evt) => {\n      const elBtn = evt.currentTarget;\n      elBtn.insertAdjacentElement("afterend", elDropdown);\n    };\n\n    elsBtns.forEach(elBtn => elBtn.addEventListener("mousedown", prepareDropdown));\n  </script>\n\n\n</body>\n\n</html>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

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

\n

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

\n
\n

如果您对如何使用 JavaScript \xe2\x80\x94 从头开始​​创建(类似)弹出窗口感兴趣,您可以在此处了解更多信息:在鼠标位置显示自定义弹出窗口

\n