Use Bootstrap 3 dropdown menu as context menu

JqueryTwitter BootstrapTwitter Bootstrap-3

Jquery Problem Overview


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.

Jquery Solutions


Solution 1 - Jquery

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:

$("#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.<b>contextMenu</b> = function(settings) {  
    return this.each(function() {  
        // Code Goes Here
    }  
};




})(jQuery, window);

})(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;

Solution 2 - Jquery

It is possible. I made you a working demo to give a good start.

Working demo (Right click on any table row to see it in action)

First create your dropdown menu, hide it and change its position to absolute:

#contextMenu {
  position: absolute;
  display:none;
}

Then bind a contextmenu event to your table rows so it shows dropdown/context menu and position it at the cursor:

var $contextMenu = $("#contextMenu");
  
$("body").on("contextmenu", "table tr", function(e) {
   $contextMenu.css({
      display: "block",
      left: e.pageX,
      top: e.pageY
   });
   return false;
});

Then when user select an option hide dropdown/context menu:

$contextMenu.on("click", "a", function() {
   $contextMenu.hide();
});

Solution 3 - Jquery

Added some modifications to KyleMit's code:

  • moved "on document click" handler from "foreach"

  • removed "foreach", just add event to selector

  • hide menu on "document context menu"

  • passing events

     $("#myTable tbody td").contextMenu({
     menuSelector: "#contextMenu",
     menuSelected: function (invokedOn, selectedMenu) {
         var msg = "You selected the menu item '" + selectedMenu.text() +
             "' on the value '" + invokedOn.text() + "'";
         alert(msg);
     },
     onMenuShow: function(invokedOn) {
         var tr = invokedOn.closest("tr");
         $(tr).addClass("warning");
     },
     onMenuHide: function(invokedOn) {
         var tr = invokedOn.closest("tr");
         $(tr).removeClass("warning");
     } });
    

http://jsfiddle.net/dmitry_far/cgqft4k3/

Solution 4 - Jquery

I found this simple and working context menu. I'm using this library http://swisnl.github.io/jQuery-contextMenu/index.html. Hope it helps

table:

<table id="ppmpsupplies" class="table table-bordered table-hover" cellspacing="0" width="100%">
          <thead>
            <tr>
              <th>Code</th>
              <th>General Description</th>
              <th>Unit</th>
              <th>Quantity</th>
              <th>Estimated Budget</th>
              <th>Mode of Procurement</th>                 

            </tr>
          </thead>
          <tbody>
            <?php foreach($items as $item){?>
            <tr>
             <td><?php echo $item->id;?></td>
             <td><?php echo $item->description;?></td>
             <td><?php echo $item->unit;?></td>
             <td><?php echo $item->quantity;?></td>
             <td><?php echo $item->budget;?></td>
             <td><?php echo $item->mode;?></td>                     
          </tr>
          <?php }?>

        </tbody>
        <tfoot>
          <td colspan="3"></td>
          <td>Total</td>
          <td></td>
        </tfoot>
      </table>

contextmenu:

    "edit": {
        name: "Edit",
        icon: "fa-pencil-square-o",
        callback: function(item, id) {
       
        return true;
        }
        },
"delete": {
        name: "Delete",
        icon: "fa-trash-o",
        callback: function(item, id) {
        
        return true;
        }
        },

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMojoDKView Question on Stackoverflow
Solution 1 - JqueryKyleMitView Answer on Stackoverflow
Solution 2 - JqueryletiagoalvesView Answer on Stackoverflow
Solution 3 - JqueryFar DmitryView Answer on Stackoverflow
Solution 4 - JqueryLarigynView Answer on Stackoverflow