[英]How to append a single dropdown menu to body in Bootstrap
我已经看到下拉菜单的文档作为组件并单独使用 javascript 。
我想知道是否可以在网站正文中添加一个下拉菜单(相对于可点击按钮元素的绝对定位)。
为什么?
因为如果我有一个包含 500 行的表,我不想将 10 个项目的相同列表添加 500 次,从而在处理 JS 时使生成的 HTML 变得更大更慢。
因为可以隐藏父元素,但我仍然希望下拉菜单可见,直到他们在它外面单击以取消聚焦。
我发现更多人要求使用此功能,但在文档中找不到任何相关内容。
正如引导文件所说,下拉菜单没有选项......这很可悲,但这意味着目前没有您想要的功能的“引导”解决方案。 但是,如果您正在使用Angular-UI/Bootstrap套件,现在有一个解决方案。 您引用的工单已关闭,因为它最终于2015 年 7 月 15 日添加到 Angular-UI中。
您所要做的就是“将 dropdown-append-to-body 添加到下拉元素以附加到正文的内部下拉菜单。 当下拉按钮位于带有溢出:隐藏的 div 内时,这很有用,否则菜单将被隐藏。 (参考)
<div class="btn-group" dropdown dropdown-append-to-body>
<button type="button" class="btn btn-primary dropdown-toggle" dropdown-toggle>Dropdown on Body <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</div>
希望这可以帮助!
编辑
为了回答另一个 SO 问题,我找到了一个解决方案,如果您不使用 Angular-UI,它会非常有效。 它可能是“hacky”,但它不会破坏引导菜单功能,而且它似乎在我使用过的大多数用例中都能很好地发挥作用。
所以我会留下一些小提琴以防其他人看到这个并且感兴趣。 第一个说明了为什么使用正文附加菜单可能会很好,第二个显示了工作解决方案:
问题:面板主体中的选择下拉列表
<div class="panel panel-default">
<div class="panel-body">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span data-bind="label">Select One</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#">Item 1</a></li>
<li><a href="#">Another item</a></li>
<li><a href="#">This is a longer item that will not fit properly</a></li>
</ul>
</div>
</div>
</div>
(function () {
// hold onto the drop down menu
var dropdownMenu;
// and when you show it, move it to the body
$(window).on('show.bs.dropdown', function (e) {
// grab the menu
dropdownMenu = $(e.target).find('.dropdown-menu');
// detach it and append it to the body
$('body').append(dropdownMenu.detach());
// grab the new offset position
var eOffset = $(e.target).offset();
// make sure to place it where it would normally go (this could be improved)
dropdownMenu.css({
'display': 'block',
'top': eOffset.top + $(e.target).outerHeight(),
'left': eOffset.left
});
});
// and when you hide it, reattach the drop down, and hide it normally
$(window).on('hide.bs.dropdown', function (e) {
$(e.target).append(dropdownMenu.detach());
dropdownMenu.hide();
});
})();
对于像我这样在使用 Angular 6+ 和 Bootstrap 4+ 时遇到同样问题的人,我写了一个小指令将下拉列表附加到正文:
事件.ts
/**
* Add a jQuery listener for a specified HTML event.
* When an event is received, emit it again in the standard way, and not using jQuery (like Bootstrap does).
*
* @param event Event to relay
* @param node HTML node (default is body)
*
* https://stackoverflow.com/a/24212373/2611798
* https://stackoverflow.com/a/46458318/2611798
*/
export function eventRelay(event: any, node: HTMLElement = document.body) {
$(node).on(event, (evt: any) => {
const customEvent = document.createEvent("Event");
customEvent.initEvent(event, true, true);
evt.target.dispatchEvent(customEvent);
});
}
下拉body.directive.ts
import {Directive, ElementRef, AfterViewInit, Renderer2} from "@angular/core";
import {fromEvent} from "rxjs";
import {eventRelay} from "../shared/dom/events";
/**
* Directive used to display a dropdown by attaching it as a body child and not a child of the current node.
*
* Sources :
* <ul>
* <li>https://getbootstrap.com/docs/4.1/components/dropdowns/</li>
* <li>https://stackoverflow.com/a/42498168/2611798</li>
* <li>https://github.com/ng-bootstrap/ng-bootstrap/issues/1012</li>
* </ul>
*/
@Directive({
selector: "[appDropdownBody]"
})
export class DropdownBodyDirective implements AfterViewInit {
/**
* Dropdown
*/
private dropdown: HTMLElement;
/**
* Dropdown menu
*/
private dropdownMenu: HTMLElement;
constructor(private readonly element: ElementRef, private readonly renderer: Renderer2) {
}
ngAfterViewInit() {
this.dropdown = this.element.nativeElement;
this.dropdownMenu = this.dropdown.querySelector(".dropdown-menu");
// Catch the events using observables
eventRelay("shown.bs.dropdown", this.element.nativeElement);
eventRelay("hidden.bs.dropdown", this.element.nativeElement);
fromEvent(this.element.nativeElement, "shown.bs.dropdown")
.subscribe(() => this.appendDropdownMenu(document.body));
fromEvent(this.element.nativeElement, "hidden.bs.dropdown")
.subscribe(() => this.appendDropdownMenu(this.dropdown));
}
/**
* Append the dropdown to the "parent" node.
*
* @param parent New dropdown parent node
*/
protected appendDropdownMenu(parent: HTMLElement): void {
this.renderer.appendChild(parent, this.dropdownMenu);
}
}
下拉body.directive.spec.ts
import {Component, DebugElement} from "@angular/core";
import {By} from "@angular/platform-browser";
import {from} from "rxjs";
import {TestBed, ComponentFixture, async} from "@angular/core/testing";
import {DropdownBodyDirective} from "./dropdown-body.directive";
@Component({
template: `<div class="btn-group dropdown" appDropdownBody>
<button id="openBtn" data-toggle="dropdown">open</button>
<div class="dropdown-menu">
<button class="dropdown-item">btn0</button>
<button class="dropdown-item">btn1</button>
</div>
</div>`
})
class DropdownContainerTestingComponent {
}
describe("DropdownBodyDirective", () => {
let component: DropdownContainerTestingComponent;
let fixture: ComponentFixture<DropdownContainerTestingComponent>;
let dropdown: DebugElement;
let dropdownMenu: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
DropdownContainerTestingComponent,
DropdownBodyDirective,
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(DropdownContainerTestingComponent);
component = fixture.componentInstance;
dropdown = fixture.debugElement.query(By.css(".dropdown"));
dropdownMenu = fixture.debugElement.query(By.css(".dropdown-menu"));
});
it("should create an instance", () => {
fixture.detectChanges();
expect(component).toBeTruthy();
expect(dropdownMenu.parent).toEqual(dropdown);
});
it("not shown", () => {
fixture.detectChanges();
expect(dropdownMenu.parent).toEqual(dropdown);
});
it("show then hide", () => {
fixture.detectChanges();
const nbChildrenBeforeShow = document.body.children.length;
expect(dropdownMenu.parent).toEqual(dropdown);
// Simulate the dropdown display event
dropdown.nativeElement.dispatchEvent(new Event("shown.bs.dropdown"));
fixture.detectChanges();
from(fixture.whenStable()).subscribe(() => {
// Check the dropdown is attached to the body
expect(document.body.children.length).toEqual(nbChildrenBeforeShow + 1);
expect(dropdownMenu.nativeElement.parentNode.outerHTML)
.toBe(document.body.outerHTML);
// Hide the dropdown
dropdown.nativeElement.dispatchEvent(new Event("hidden.bs.dropdown"));
fixture.detectChanges();
from(fixture.whenStable()).subscribe(() => {
// Check the dropdown is back to its original node
expect(document.body.children.length).toEqual(nbChildrenBeforeShow);
expect(dropdownMenu.nativeElement.parentNode.outerHTML)
.toBe(dropdown.nativeElement.outerHTML);
});
});
});
});
不确定引导程序 3,但如果您使用引导程序 4,则可以将“data-boundary="window”添加到下拉触发器中。它会将其附加到正文,然后您可以使用绝对定位来定位它。
使用 bootstrap 4,你可以像这样将下拉菜单放在外面:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script> <!-- Assuming a big table --> <table> <tr> <td> <!-- data-target is a selector --> <a href="#" role="button" data-toggle="dropdown" data-target="#dropdown-container" aria-haspopup="true" aria-expanded="false"> O </a> </td> </tr> </table> <div id="dropdown-container"> <div id="dropdown-menu" class="dropdown-menu"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <a class="dropdown-item" href="#">Something else here</a> </div> </div>
我必须有一个容器作为目标,才能使 Popper 指向正确的容器。 如果您对放置有疑问,请发表评论,我将添加一个更复杂的解决方案,我必须实现覆盖 Popper 放置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.