skip to Main Content

On my own compiler, the menu doesn’t even change a bit. When I tested here, it’s a bit better, for it does block the original default right-click menu. However, this hardcoded JS one does not appear either. I’d like to ask is there any problem with the code here, or it just doesn’t work like that?

const rMenu = document.getElementById('rClickMenu');

var timeout;
var lastTap = 0;

document.addEventListener("contextmenu", (e) => {
    e.preventDefault();
    let mouseX = e.clientX || e.touches[0].clientX;
    let mouseY = e.clientY || e.touches[0].clientY;
    let menuHeight = rMenu.getBoundingClientRect().height;
    let menuWidth = rMenu.getBoundingClientRect().width;

    let width = window.innerWidth;
    let height = window.innerHeight;
    if (width - mouseX <= 200) {
        rMenu.setAttribute("border-radius", "5px 0 5px 5px");
        rMenu.setAttribute("left", (width - menuWidth) + "px");
        rMenu.setAttribute("top", mouseY + "px");

    if (height - mouseY <= 200) {
        rMenu.setAttribute("top", (mouseY - menuHeight) + "px");
        rMenu.setAttribute("border-radius", "5px 5px 0 5px");
        }
    } else {
        rMenu.setAttribute("border-radius", "0 5px 5px 5px");
        rMenu.setAttribute("left", mouseX + "px");
        rMenu.setAttribute("top", mouseY + "px");

        if (height - mouseY <= 200) {
        rMenu.setAttribute("top", (mouseY - menuHeight) + "px");
        rMenu.setAttribute("border-radius", "5px 5px 5px 0");
        }
    }
    
    rMenu.setAttribute("visibility", "visible");
    }, 
    { passive: false }
);

document.addEventListener("click", function (e) {
    if (!rMenu.contains(e.target)) {
      rMenu.setAttribute("visibility", "hidden");
    }
  });
section #rClickMenu {
    background-color: #ffffff;
    box-shadow: 0 0 20px rgba(37, 40, 42, 0.22);
    color: #000000;
    width: 10em;
    padding: 0.8em 0.6em;
    font-size: 1.3rem;
    position: fixed;
    visibility: hidden;
}

section #rClickMenu div {
    padding: 0.3em 1.2em;
}

section #rClickMenu div:hover {
    cursor: pointer;
}
<section>  
  <div id="rClickMenu">
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
  </div>
</section>

2

Answers


  1. You should be setting the style attribute.

    const rMenu = document.getElementById('rClickMenu');
    
    var timeout;
    var lastTap = 0;
    
    document.addEventListener("contextmenu", (e) => {
      e.preventDefault();
      let mouseX = e.clientX || e.touches[0].clientX;
      let mouseY = e.clientY || e.touches[0].clientY;
      let menuHeight = rMenu.getBoundingClientRect().height;
      let menuWidth = rMenu.getBoundingClientRect().width;
    
      let width = window.innerWidth;
      let height = window.innerHeight;
      if (width - mouseX <= 200) { 
        rMenu.style["borderRadius"] = "5px 0 5px 5px"
        rMenu.style["left"] = (width - menuWidth) + "px"
        rMenu.style["top"] = mouseY + "px"
    
        if (height - mouseY <= 200) {
          rMenu.style["top"] = (mouseY - menuHeight) + "px"
          rMenu.style["borderRadius"] = "5px 5px 0 5px"
        }
      } else {
        rMenu.style["borderRadius"] = "0 5px 5px 5px"
        rMenu.style["left"] = mouseX + "px"
        rMenu.style["top"] = mouseY + "px"
    
        if (height - mouseY <= 200) {
          rMenu.style["top"] = (mouseY - menuHeight) + "px"
          rMenu.style["borderRadius"] = "5px 5px 5px 0"
        }
      }
    
      rMenu.style["visibility"] = "visible"
    }, {
      passive: false
    });
    
    document.addEventListener("click", function(e) {
      if (!rMenu.contains(e.target)) {
        rMenu.style["visibility"] = "hidden"
      }
    });
    section #rClickMenu {
      background-color: #ffffff;
      box-shadow: 0 0 20px rgba(37, 40, 42, 0.22);
      color: #000000;
      width: 10em;
      padding: 0.8em 0.6em;
      font-size: 1.3rem;
      position: fixed;
      visibility: hidden;
    }
    
    section #rClickMenu div {
      padding: 0.3em 1.2em;
    }
    
    section #rClickMenu div:hover {
      cursor: pointer;
    }
    <section>
      <div id="rClickMenu">
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
      </div>
    </section>
    Login or Signup to reply.
  2. I put together this Javascript to be a generic solution for creating a context menu. You can create your menu from an array (see sample) and use applyClickFunction to add a click function to all items.

    After you’ve called createMenu you need to set css properties display (normally ‘block’) and position (normally "absolute" or "fixed") because this is set up to work with absolute and fixed scenarios and then add it to the parent component.

    const MyMenuWidget = {}
    
    MyMenuWidget.btnClick = (e) => {
      alert('Click: ' + this.innerText);
    }
    
    MyMenuWidget.sample = [
      { label:"A",
        title:"A",
        onClick:MyMenuWidget.btnClick
      },
      { label:"B",
        title:"B",
        subMenu: [
          {label:'B'},
          {label:'C'}
        ],
        class: 'selectedButton'
      },
      { label:"C",
        title:"C",
        subMenu: [
          { label:'A'},
          { label:'B',
            subMenu: [
              {label:'D'},
              {label:'E'}
            ]
          }
        ]
      }
    ]
    
    function closeMenu(m) {
    //  console.log("Close Menu");
            while (m) {
              if (m.parentNode) {
                m.parentNode.removeChild(m);
              }
              m.branch = null;
              let old = m;
              m = m.subMenu;
              old.subMenu = null;
            }
    }
    
    var listeners = [];
    
    MyMenuWidget.addMenuOpeningListener = function (listener) {
      listeners.push(listener);
    }
    
    MyMenuWidget.removeMenuOpeningListener = function (listener) {
      listeners.remove(listener);
    }
    
    function reportOpening(menu, parentMenu) {
      listeners.forEach(function(l) {
        l.reportOpening(menu, parentMenu);
      });
    }
    
    // adds onClick: func to all elements in the jsArray that do not have a submenu
    MyMenuWidget.applyClickFunction = function(jsArray, func) {
      if (typeof func != 'function') throw Error ('MyMenuWidget.applyClickFunction: 2nd parameter must be a function');
      jsArray.forEach(function(item) {
        if (item.subMenu) {
          MyMenuWidget.applyClickFunction(item.subMenu, func);
        } else {
          item.onClick = func;
        }
      });
    }
    
    function KeyboardNav(e) {
      var kc = e.keyCode;
      if ((kc >= 37 && kc <= 40) || kc == 13) {
        e.stopPropagation();
        e.preventDefault();
    //    console.log("Keycode: " + e.keyCode);
      }
      if ((kc == 10 || kc == 13)) {
        if (e.target.openSub) {
          e.target.openSub();
        } else {
          e.target.click();
        }
      }
      if (kc == 40) {
    //    console.log('down: '+ e.target.nextSibling); //.focus();
        if (e.target.parentNode.tagName == "DIV" && e.target.nextSibling) {
          let next = e.target;
          do {
            next = next.nextSibling.nextSibling;
          }
          while (next.disabled && next.nextSibling);
          next.focus();
        } else {
          if (e.target.parentNode.nextSibling) {
            e.target.parentNode.nextSibling.childNodes[0].focus();
          } else {
            if (e.target.parentNode.parentNode.nextSibling) {
              e.target.parentNode.parentNode.nextSibling.cells[0].childNodes[0].focus();
            }
          }
          //TODO: add support for multiple columns
        }  
      }
      else if (kc == 38) {
        if (e.target.parentNode.tagName == "DIV" && e.target.parentNode.previousSibling) {
          let prev = e.target;
          do {
            prev = prev.previousSibling.previousSibling;
          }
          while (prev.disabled && prev.previousSibling);
          prev.focus();
        } else {
          if (e.target.parentNode.previousSibling) {
            var ps = e.target.parentNode.previousSibling;
            ps.childNodes[0].focus();
          } else {
            if (e.target.parentNode.parentNode.previousSibling) {
              ps = e.target.parentNode.parentNode.previousSibling;
              ps.cells[ps.cells.length - 1].childNodes[0].focus();
            }
          }
          //TODO: add support for multiple columns
        }  
      
      }
      else if (kc == 39) {
        if (e.target.parentNode.subMenu) {
          e.target.parentNode.lastFocused = e.target;
          if (e.target.parentNode.subMenu.childNodes[0].tagName === "TABLE") {
            e.target.parentNode.subMenu.childNodes[0].rows[0].cells[0].childNodes[0].focus();
          } else {
            e.target.parentNode.subMenu.childNodes[0].focus();
          }
        }
      }
      else if (kc == 37) {
        //how to go back in the menu?
        if (e.target.parentNode.tagName == "DIV") {
          if (e.target.parentNode.previousSibling && e.target.parentNode.previousSibling.lastFocused) {
            e.target.parentNode.previousSibling.lastFocused.focus();
          }
        } else {
    
          if (e.target.closest("DIV").previousSibling && e.target.closest("DIV").previousSibling.lastFocused) {
            e.target.closest("DIV").previousSibling.lastFocused.focus();
          }
        }
      }
    
    }
    
    // subMenuOpenFunc expects a function (newMenu, parentMenu)
    
    MyMenuWidget.createMenu = (jsArray, x, y, subMenuOpenFunc, isSubmenu, top, dragHandle) => {
    //  console.log("jsArray.columns: " + jsArray.columns);
      var menu = document.createElement("DIV");
      if (top) {
        menu.topMenu = top;
      } else {
        menu.topMenu = menu;
    //    console.log("MyMenuWidget. x: " + x + " half-window: " + (window.innerWidth / 2)); 
    //    menu.setAttribute("data-openRight", x < (window.innerWidth / 2));
        menu.setAttribute("data-openRight", true);
      }
      if (y != undefined) {
        if (top && menu != menu.topMenu && top.getAttribute("data-openRight") == "false") {
          menu.style.right = (window.innerWidth - x) + "px";
        } else {
          menu.style.left = x + "px";
        }
        menu.style.top = y + "px";
      } else if (typeof x === 'object') {
        menu.style.left = x.pageX + "px";
        menu.style.top = x.pageY + "px";
      }
      menu.className = "MyMenuWidget-dontlist";
        //Not working
        if (dragHandle) {
        console.log("creating drag handle");
    //    if (true) {
          var div = document.createElement("div");
          menu.appendChild(div);
          div.style.cursor = "hand";
          div.classList.add("draggable");
          div.classList.add("dragHandle");
          div.style.minHeight="1em";
          div.style.width =  "100%";
          div.style.textAlign = "right";
          div.textContent="✥";
          var mouseMoveFunc = function (e) {
            console.log("mouseMove. Button: " + e.buttons);
          }
          div.addEventListener("mouseenter", function() {
            div.addEventListener("mousemove", mouseMoveFunc);
          });
          div.addEventListener("mousedown", function(e) {
            e.preventDefault();
            e.stopPropagation();
            console.log("e.button: " + e.buttons);
          });
          div.addEventListener("mouseup", function(e) {
            console.log("mouseup");
            e.preventDefault();
            e.stopPropagation();
          });
          div.addEventListener("click", function(e) {
            console.log("click");
            e.preventDefault();
            e.stopPropagation();
          });
          div.addEventListener("mouseexit", function() {
            div.removeEventListener("mousemove", mouseMoveFuc);
          });
        }    
      var tbl;
      if (jsArray.columns && jsArray.columns > 1) {
        tbl = document.createElement("TABLE");
        let endsOnLast = Math.floor(jsArray.length % jsArray.columns);
        let rows = (jsArray.length / jsArray.columns);
        if (endsOnLast != 0) rows++;
        let cols = rows < 2 ? jsArray.length : jsArray.columns;
        //sort items in jsArray that have submenus to be at the right
        for (var i = 0; i < jsArray.length; i++) {
          if (jsArray[i].subMenu && (i + 1) % jsArray.columns != 0) { 
            var row = 0;
            var spot;
            do {
              row++;
              spot = row * jsArray.columns - 1;
              if (spot >= jsArray.length) {
                break;
              }
            } while (jsArray[spot].submenu && jsArray.length > row * JSArray.columns - 1);
            if (spot >= jsArray.length) {
              throw new Error("Cannot sort the items with submenus to all be on the right");
            }
            //else
            var temp = jsArray[spot];
            jsArray[spot] = jsArray[i];
            jsArray[i] = temp;
          }
        }
        for (var i = 0; i < rows; i++) {
          let row = tbl.insertRow();
          for (var j = 0; j < cols; j++) {
             row.insertCell();
          }
        }
        menu.appendChild(tbl);
      }
      for (var i = 0; i < jsArray.length; i++) {
        var btn = document.createElement("BUTTON");
        btn.type = "button";
        btn.json = jsArray[i];
        btn.innerText = jsArray[i].label + (jsArray[i].subMenu ? " >" : "");
        btn.title = jsArray[i].title ? jsArray[i].title : jsArray[i].label;
        btn.className = jsArray[i].class ? jsArray[i].class : "";
        if (jsArray[i].bg) btn.style.background = jsArray[i].bg;
        if (jsArray[i].id) {
    //      console.log("Assigning id: " + jsArray[i].id);
          btn.id = jsArray[i].id;
        }
    
        //Disabling: The way to disable menu items is to set "disabled" : true in the schema
        //for top level menu options, you can do this using the button id as well
        //for dynamically setting submenu items as disabled, you must alter the *disabled* attribute
        //in the parent menu button "submenu" (JSON) object.
    
        if (jsArray[i].disabled == true) btn.disabled = true;  
        if (jsArray[i].onClick) {
          btn.addEventListener("click", jsArray[i].onClick);
        }
        if (jsArray[i].subMenu) {
          let submenu = jsArray[i].subMenu;
          btn.openSub = function() {
            if (menu.branch === submenu) return;
            if (menu.subMenu) closeMenu(menu.subMenu);       
            let rect = this.getBoundingClientRect();
            if (menu.topMenu.getAttribute("data-openRight") != "false") {
              menu.subMenu = MyMenuWidget.createMenu(submenu,rect.x + rect.width,rect.y + window.scrollY, subMenuOpenFunc, true, menu.topMenu);
            } else {
              menu.subMenu = MyMenuWidget.createMenu(submenu,rect.x,rect.y, subMenuOpenFunc, true, menu.topMenu);
            }
            menu.subMenu.style.zIndex = menu.style.zIndex;
            menu.branch = submenu;
            document.body.appendChild(menu.subMenu);
            if (subMenuOpenFunc) {
              subMenuOpenFunc(menu.subMenu, menu);
            }
          }
    
          btn.addEventListener("mouseenter", btn.openSub);
        } else {
          btn.addEventListener("mouseenter", function() {
            if (menu.subMenu) {
              closeMenu(menu.subMenu);
              menu.branch = null;
            }
          });
        }
    
        if (!tbl) {
          menu.appendChild(btn);
          if (i != jsArray.length - 1) {
            menu.appendChild(document.createElement("BR")); 
          }
        } else {
          tbl.rows[Math.floor(i / jsArray.columns)].cells[i % jsArray.columns].appendChild(btn);
        }
      }
    
      if (!isSubmenu) {
        //So that bubbling of the initial event doesn't trigger this
        menu.closeMenu = closeMenu;
        setTimeout(function() {
        $("*").on("click", function(e) {
            if (e.target.closest(".MyMenuWidget-dontlist")) {
              closeMenu(menu);
            } else {
              var sel = document.getSelection();
              if (sel && sel.anchorOffset == sel.focusOffset) {
                closeMenu(menu);
              }
            }
        });
        },20);
      }
      menu.addEventListener("keydown", KeyboardNav);
      return menu; 
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search