Javascript 如何在Bootstrap中向body附加一个下拉菜单
我已经看过了下拉菜单和菜单的文档 我想知道是否有可能在网站主体中添加一个下拉菜单(相对于可点击按钮元素绝对定位) 为什么?Javascript 如何在Bootstrap中向body附加一个下拉菜单,javascript,html,twitter-bootstrap,drop-down-menu,Javascript,Html,Twitter Bootstrap,Drop Down Menu,我已经看过了下拉菜单和菜单的文档 我想知道是否有可能在网站主体中添加一个下拉菜单(相对于可点击按钮元素绝对定位) 为什么? 因为如果我有一个500行的表,我不想在处理JS时将10个项目的列表增加500倍,从而使生成的HTML更大、更慢 因为父元素可以被隐藏,但我仍然希望下拉菜单是可见的,直到他们点击它的外部,解除它的焦点 我找到了更多的人,但在文档中找不到任何相关信息。正如所说,没有下拉菜单选项。。。这是可悲的,但这意味着目前没有一个“引导”解决方案来实现您想要的功能但是,如果您正在使用An
- 因为如果我有一个500行的表,我不想在处理JS时将10个项目的列表增加500倍,从而使生成的HTML更大、更慢
- 因为父元素可以被隐藏,但我仍然希望下拉菜单是可见的,直到他们点击它的外部,解除它的焦点
<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>
编辑
我终于找到了我最初找到这个解决方案的地方。必须承认,对于像我这样使用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);
});
}
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);
}
}
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);
});
});
});
});
下拉式正文.directive.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);
});
}
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);
}
}
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);
});
});
});
});
import{Directive,ElementRef,AfterViewInit,Renderer2}来自“@angular/core”;
从“rxjs”导入{fromEvent};
从“./shared/dom/events”导入{eventRelay};
/**
*指令,用于通过将下拉列表附加为主体子项而不是当前节点的子项来显示下拉列表。
*
*资料来源:
*
*- https://getbootstrap.com/docs/4.1/components/dropdowns/
*- https://stackoverflow.com/a/42498168/2611798
*- https://github.com/ng-bootstrap/ng-bootstrap/issues/1012
*
*/
@指示({
选择器:“[appDropdownBody]”
})
导出类DropDownBody指令在AfterViewInit后实现{
/**
*下拉列表
*/
私有下拉列表:HTMLElement;
/**
*下拉菜单
*/
私有下拉菜单:HTMLElement;
构造函数(私有只读元素:ElementRef,私有只读呈现器:Renderer2){
}
ngAfterViewInit(){
this.dropdown=this.element.nativeElement;
this.dropdownMenu=this.dropdown.querySelector(“.dropdownMenu”);
//使用可观测数据捕捉事件
eventRelay(“显示.bs.dropdown”,this.element.nativeElement);
eventRelay(“hidden.bs.dropdown”,this.element.nativeElement);
fromEvent(this.element.nativeElement,“显示.bs.下拉列表”)
.subscribe(()=>this.appendDropdownMenu(document.body));
fromEvent(this.element.nativeElement,“hidden.bs.dropdown”)
.subscribe(()=>this.appendDropdownMenu(this.dropdown));
}
/**
*将下拉列表附加到“父”节点。
*
*@param parent新建下拉列表父节点
*/
受保护的附件下拉菜单(父项:HtmleElement):无效{
this.renderer.appendChild(父级,this.dropdown菜单);
}
}
下拉式正文.指令.规范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);
});
}
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);
}
}
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);
});
});
});
});
从“@angular/core”导入{Component,DebugElement};
从“@angular/platform browser”导入{By}”;
从“rxjs”导入{from};
从“@angular/core/testing”导入{TestBed,ComponentFixture,async};
从“/dropdownbody.directive”导入{dropdownbody directive}”;
@组成部分({
模板:`
打开
btn0
btn1
`
})
类DropdownContainerTestingComponent{
}
描述(“DropdownBodyDirective”,()=>{
let组件:DropdownContainerTestingComponent;
let夹具:组件夹具;
下拉列表:DebugElement;
让下拉菜单:DebugElement;
beforeach(异步(()=>{
TestBed.configureTestingModule({
声明:[
DropdownContainerTestingComponent,
dropdownbody指令,
]
});
}));
在每个之前(()=>{
fixture=TestBed.createComponent(DropdownContainerTestingComponent);
组件=fixture.componentInstance;
dropdown=fixture.debugElement.query(By.css(“.dropdown”);
dropdownMenu=fixture.debugElement.query(By.css(“.dropdownMenu”);
});
它(“应该创建一个实例”,()=>{
fixture.detectChanges();
expect(component.toBeTruthy();
expect(下拉菜单.parent).toEqual(下拉菜单);
});
它(“未显示”,()=>{
fixture.detectChanges();
expect(下拉菜单.parent).toEqual(下拉菜单);
});
它(“先显示然后隐藏”,()=>{
fixture.detectChanges();
const nbChildrenBeforeShow=document.body.children.length;
expect(下拉菜单.parent).toEqual(下拉菜单);
//模拟下拉显示事件
dropdown.nativeElement.dispatchEvent(新事件(“show.bs.dropdown”);
fixture.detectChanges();
来自(fixture.whenStable()).subscribe(()=>{
//检查下拉列表是否连接到车身
expect(document.body.children.length).toEqual(nbChildrenBeforeShow+1);
expect(dropdownMenu.nativeElement.parentNode.outerHTML)
.toBe(document.body.outerHTML);
//隐藏下拉列表
dropdown.nativeElement.dispatchEvent(新事件(“hidden.bs.dropdown”);
fixture.detectChanges();
来自(fixture.whenStable()).subscribe(()=>{
//检查下拉列表是否返回到其原始节点
expect(document.body.children.length).toEqual(nbchildrenbefore show);
expect(dropdownMenu.na