web-dev-qa-db-fra.com

Utiliser le menu déroulant Bootstrap 3 en tant que menu contextuel

Avec Bootstrap 3, comment puis-je placer le menu déroulant au niveau du curseur et l’ouvrir à partir du code?

J'ai besoin de l'utiliser sur une table en tant que menu contextuel pour ses lignes.

69
MojoDK

C'est possible. Je t'ai fait une démo de travail pour bien commencer.

Démonstration de travail (Faites un clic droit sur une ligne du tableau pour la voir en action)

Créez d'abord votre menu déroulant, masquez-le et changez son position en absolute:

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

Ensuite, liez un événement contextmenu à vos lignes de table afin d'afficher un menu déroulant/contextuel et positionnez-le au niveau du curseur:

var $contextMenu = $("#contextMenu");

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

Puis lorsque l'utilisateur sélectionne une option masquer le menu déroulant/contextuel:

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

Je voulais juste améliorer letiagoalves excellente réponse avec quelques suggestions supplémentaires.
Voici une procédure pas à pas permettant d’ajouter un menu contextuel à n’importe quel élément HTML.

Commençons par une démo de travail dans jsFiddle

Balisage:

Tout d’abord, ajoutons un menu à partir du contrôle déroulant de démarrage . Ajoutez-le n'importe où à votre code HTML, de préférence au niveau racine du corps. La classe .dropdown-menu Définira display:none De sorte qu'elle est initialement invisible.
Ça devrait ressembler à ça:

<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>

Paramètres d'extension:

Pour que notre conception reste modulaire, nous allons ajouter notre code JavaScript sous la forme d’une extension jQuery appelée contextMenu.

Lorsque nous appelons $.contextMenu, Nous passons dans un objet settings avec 2 propriétés:

  1. menuSelector prend le sélecteur jQuery du menu que nous avons créé précédemment en HTML.
  2. menuSelected sera appelé lorsque l'utilisateur cliquera sur l'action du menu contextuel.
$("#myTable").contextMenu({
    menuSelector: "#contextMenu",
    menuSelected: function (invokedOn, selectedMenu) {
        // context menu clicked
    });
});

Modèle de plugin:

Sur la base du modèle du plugin jQuery boilerplate , nous utiliserons une expression expression de fonction immédiatement appelée afin de ne pas embrouiller l'espace de noms global. Puisque nous avons des dépendances sur jQuery et que nous avons besoin d'accéder à la fenêtre, nous les transmettons sous forme de variables afin de pouvoir survivre à la minification. Il ressemblera à ceci:

(function($, window){

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

})(jQuery, window);

Ok, plus de plomberie. Voici la viande de la fonction:

Gérer les événements liés à un clic droit:

Nous allons gérer l'événement de souris contextmenu sur l'objet qui a appelé l'extension. Lorsque l'événement se déclenche, nous allons saisir le menu déroulant que nous avons ajouté au début. Nous le localiserons à l'aide de la chaîne de sélection transmise par les paramètres lors de l'initialisation de la fonction. Nous allons modifier le menu en procédant comme suit:

  • Nous allons récupérer la propriété e.target Et la stocker sous la forme d'un attribut de données appelé invokedOn afin de pouvoir identifier ultérieurement l'élément qui a élevé le menu contextuel.
  • Nous allons basculer l'affichage du menu sur visible en utilisant .show()
  • Nous allons positionner l'élément en utilisant .css().
    • Nous devons nous assurer que position est défini sur absolute.
    • Nous définirons ensuite les emplacements gauche et supérieur à l'aide des propriétés pageX et pageY de l'événement.
  • Enfin, pour empêcher le clic droit d'ouvrir son propre menu, nous allons return false Empêcher le javascript de gérer quoi que ce soit d'autre.

Il ressemblera à ceci:

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

    return false;
});

Correction des cas de bord de menu:

Cela ouvrira le menu en bas à droite du curseur qui l’a ouvert. Cependant, si le curseur est sur le extrême droite de l'écran , le menu devrait s'ouvrir à gauche. De même, si le curseur est en bas, le menu devrait s’ouvrir vers le haut. Il est également important de faire la différence entre le bas du window , qui contient le cadre physique, et le bas du document qui représente le DOM entier HTML et peut faire défiler loin derrière la fenêtre.

Pour ce faire, nous allons définir l'emplacement à l'aide des fonctions suivantes:

Nous les appellerons comme ça:

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

Qui appellera cette fonction pour retourner la position appropriée:

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
}

Lier les événements de clic sur l'élément de menu:

Après l'affichage du menu contextuel, nous devons ajouter un gestionnaire d'événements pour écouter les événements de clic sur celui-ci. Nous allons supprimer toutes les autres liaisons déjà ajoutées afin de ne pas déclencher le même événement deux fois. Celles-ci peuvent se produire à tout moment lorsque le menu a été ouvert, mais rien n’a été sélectionné pour la raison d’un clic. Ensuite, nous pouvons ajouter une nouvelle liaison à l’événement click où nous allons gérer la logique dans la section suivante.

Comme notons valep , nous ne voulons pas enregistrer de clics sur autre chose que des éléments de menu. Nous avons donc configuré un gestionnaire délégué en passant un sélecteur dans le on fonction qui "filtrera les descendants des éléments sélectionnés qui déclenchent l'événement".

Jusqu'ici, la fonction devrait ressembler à ceci:

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

Gérer les clics du menu

Lorsque nous savons qu'un clic s'est produit dans le menu, nous procédons comme suit: Nous masquerons le menu à l'écran avec .hide(). Ensuite, nous voulons enregistrer l'élément sur lequel le menu a été appelé à l'origine, ainsi que la sélection dans le menu actuel. Enfin, nous activerons l'option de fonction qui a été transmise à l'extension en utilisant .call() sur la propriété et en transmettant les cibles de l'événement en tant qu'arguments.

$menu.hide();

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

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

Masquer lorsque vous cliquez dessus:

Enfin, comme pour la plupart des menus contextuels, nous souhaitons également fermer le menu lorsqu'un utilisateur clique dessus. Pour ce faire, nous écouterons tous les événements de clic sur le corps et fermerons le menu contextuel s'il est ouvert comme ceci:

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

Note : Merci au commentaire de Sadhir , Firefox linux déclenche l'événement click sur document lors d'un clic droit, vous devez donc configurer l'auditeur sur body.

Exemple de syntaxe:

L'extension renverra avec l'objet d'origine qui a soulevé le menu contextuel et l'élément de menu sur lequel l'utilisateur a cliqué. Vous devrez peut-être traverser le dom utiliser jQuery pour trouver quelque chose de significatif à partir des cibles d'événement, mais cela devrait fournir une bonne couche de fonctionnalités de base.

Voici un exemple pour renvoyer des informations sur l'élément et l'action sélectionnés:

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

Capture d'écran:

Context Menu Screenshot

Note de mise à jour:

Cette réponse a été sensiblement mise à jour en l'enveloppant dans une méthode d'extension jQuery. Si vous souhaitez voir mon original, vous pouvez consulter l'historique des publications, mais je pense que cette version finale utilise de bien meilleures pratiques de codage.

Bonus :

Si vous souhaitez ajouter des fonctionnalités Nice pour les utilisateurs ou pour vous-même lors du développement de fonctionnalités, vous pouvez ignorer le menu contextuel en fonction des combinaisons de touches détenues lorsque vous cliquez avec le bouton droit de la souris. Par exemple, si vous souhaitez autoriser le menu contextuel d'origine du navigateur à s'afficher lorsque vous maintenez Ctrl, vous pouvez ajouter ceci comme première ligne du gestionnaire contextMenu:

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

Ajout de quelques modifications au code de KyleMit :

  • déplacé "sur le document, cliquez sur" le gestionnaire de "foreach"
  • supprimé "foreach", il suffit d'ajouter l'événement au sélecteur
  • masquer le menu sur "menu contextuel du document"
  • événements de passage

    $("#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/

6
Far Dmitry