Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

jquery - Use Bootstrap 3 dropdown menu as context menu

Using Bootstrap 3, how can I place the dropdown menu at the cursor and open it from code?

I need to use it on a table as a context menu for its rows.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I just wanted to improve on letiagoalves great answer with a couple more suggestions.
Here's a walkthrough on how to add a context menu to any html element.

Let's start off with a working demo in jsFiddle

Markup:

First, let's add a menu from the bootstrap dropdown control. Add it anywhere to your HTML, preferably at the root level of the body. The .dropdown-menu class will set display:none so it's initially invisible.
It should look like this:

<ul id="contextMenu" class="dropdown-menu" role="menu">
    <li><a tabindex="-1" href="#">Action</a></li>
    <li><a tabindex="-1" href="#">Another action</a></li>
    <li><a tabindex="-1" href="#">Something else here</a></li>
    <li class="divider"></li>
    <li><a tabindex="-1" href="#">Separated link</a></li>
</ul>

Extension Settings:

To keep our design modular, we'll add our JavaScript code as a jQuery extension called contextMenu.

When we call $.contextMenu, we'll pass in a settings object with 2 properties:

  1. menuSelector takes the jQuery selector of the menu we created earlier in HTML.
  2. menuSelected will be called when the context menu action is clicked.
$("#myTable").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        // context menu clicked
    });
});

Plugin Template:

Based off the jQuery boilerplate plugin template, we'll use an Immediately-Invoked Function Expression so we don't muddle up the global namespace. Since we have dependencies on jQuery and need access to the window, we'll pass them in as variables so we can survive minification. It will look like this:

(function($, window){

    $.fn.contextMenu = function(settings) {  
        return this.each(function() {  
            // Code Goes Here
        }  
    };

})(jQuery, window);

Okay, no more plumbing. Here's the meat of the function:

Handle Right Click Events:

We'll handle the contextmenu mouse event on the object that called the extension. When the event fires, we'll grab the dropdown menu that we added in the beginning. We'll locate it by using the selector string passed in by the settings when we initialized the function. We'll modify the menu by doing the following:

  • We'll grab the e.target property and store it as a data attribute called invokedOn, so we can later identify the element that raised the context menu.
  • We'll toggle the menu's display to visible using .show()
  • We'll position the element using .css().
    • We need to make sure it's position is set to absolute.
    • Then we'll set the left and top location using the pageX and pageY properties of the event.
  • Finally, to prevent the right click action from opening up it's own menu, we'll return false to stop the javascript from handling anything else.

It will look like this:

$(this).on("contextmenu", function (e) {
    $(settings.menuSelector)
        .data("invokedOn", $(e.target))
        .show()
        .css({
            position: "absolute",
            left: e.pageX,
            top: e.pageY
        });

    return false;
});

Fix Menu Edge Cases:

This will open the menu to the bottom right of the cursor that opened it. However, if the cursor is to the far right of the screen, the menu should open to the left. Likewise, if the cursor is on the bottom, the menu should open to the top. It's also important to differentiate between the bottom of the window, which contains the physical frame, and the bottom of the document which represents the entire html DOM and can scroll far past the window.

To accomplish this, we'll set the location using the following functions:

We'll call them like this:

.css({
    left: getMenuPosition(e.clientX, 'width', 'scrollLeft'),
    top: getMenuPosition(e.clientY, 'height', 'scrollTop')
});

Which will call this function to return the appropriate position:

function getMenuPosition(mouse, direction, scrollDir) {
    var win = $(window)[direction](),
        scroll = $(window)[scrollDir](),
        menu = $(settings.menuSelector)[direction](),
        position = mouse + scroll;

    // opening menu would pass the side of the page
    if (mouse + menu > win && menu < mouse) 
        position -= menu;

    return position
}

Bind Click Events on the Menu Element:

After we display the context menu, we need to add an event handler to listen for click events on it. We'll remove any other bindings that might have already been added so that we won't fire the same event twice. These can occur anytime the menu was opened, but nothing was selected due to clicking off. Then we can add a new binding on the click event where we'll handle the logic in the next section.

As valepu noted, we don't want to register clicks on anything other than menu items, so we setup a delegated handler by passing a selector into the on function which will "filter the descendants of the selected elements that trigger the event".

So far, the function should look like this:

$(settings.menuSelector)
    .off('click')
    .on( 'click', "a", function (e) {
        //CODE IN NEXT SECTION GOES HERE
});

Handle Menu Clicks

Once we know a click has occurred on the menu, we'll do the following things: We'll hide the menu from the screen with .hide(). Next, we want to save the element on which the menu was originally invoked as well as the selection from the current menu. Finally, we'll fire the function option that was passed into the extension by using .call() on the property and passing in the event targets as arguments.

$menu.hide();

var $invokedOn = $menu.data("invokedOn");
var $selectedMenu = $(e.target);

settings.menuSelected.call($(this), $invokedOn, $selectedMenu);

Hide When Clicked Off:

Finally, as with most context menus, we want to close the menu when a user clicks off of it as well. To do so, we'll listen for any click events on the body and close the context menu if it's open like this:

$('body').click(function () {
    $(settings.menuSelector).hide();
});

Note: Thanks to Sadhir's comment, Firefox linux triggers the click event on document during a right click, so you have to setup the listener on body.

Example Syntax:

The extension will return with the original object that raised the context menu and the menu item that was clicked. You may have to traverse the dom using jQuery to find something meaningful from the event targets, but this should provide a good layer of base functionality.

Here's a example to return info for the item and action selected:

$("#myTable").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        var msg = "You selected the menu item '" + 
                  selectedMenu.text() +
                  "' on the value '" + 
                  invokedOn.text() + "'";
        alert(msg);
    }
});

Screenshot:

Context Menu Screenshot

Update Note:

This answer has been updated substantially by wrapping it in a jQuery extension method. If you'd like to see my original, you can view the post history, but I believe this final version utilizes much better coding practices.

Bonus Feature:

If you want to add some nice functionality for powerusers or yourself in developing features, you can bypass the context menu based on any key combinations being held when your right click. For example, if you wanted to allow the original browser context menu to display when holding Ctrl, you could add this as the first line of the contextMenu handler:

// return native menu if pressing control
if (e.ctrlKey) return;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...