簡體   English   中英

如何在 Bootstrap 中將單個下拉菜單附加到正文

[英]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>&nbsp;<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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM