I was tinkering around with some jQuery and created a little bit of code that I thought I would throw out into the Internet ether. I am sure it not the most efficiently optimized, but it does demonstrate some things can do with a little jQuery and the contextmenu event. I see this example as something you could easily build off of and with a little styling and a little more tinkering you could certainly create dozens of custom menus that could service a multitude of different projects. Menus that can have a state or possibly hold icons, linear gradients etc. At the end of this article I have provided a fiddle that demonstrates a little of what I am talking about here.
In our demonstration we will create a simple menu I called “boxMenu”. This menu is an object literal that essentially contains a name, a target (the element that triggered the menu) and a list of menu options. Each menu option could contain text, a command function (for when it is clicked) and whether or not it is an active option or not. But before we reach that part I want to start off creating a simple div target which we will bind a context menu to…
<div id="target"></div>
Here is some CSS to go long with our code that will add a bit of styling…
#target { border: #c0c0c0 solid 1px; background-color: #f2f2f2; width: 100px; height: 100px; } .menu { position: absolute; display: none; border: #c0c0c0 solid 1px; font-family: calibri, arial, helvetica, sans serif; } div .menuOption { padding: 4px 8px; background-color: #f0f0f0; } div .active:hover { cursor: pointer; background-color: #99cb33; color: #ffffff; } div .inactive { color: #c0c0c0; }
So far so good. Now we will create a contextmenu event binding that will do a few things. It will make a call to a buildMenu function which will be responsible for building the menu (or returning an existing menu if it was already built) and then determine where to display it on the screen.
$("#target").bind("contextmenu",function(e){ var newMenu = buildMenu(boxMenu,this); var winWidth = $(window).width(); var winHeight = $(window).height(); // Menu not off screen to right if ((e.pageX + newMenu.outerWidth()) > winWidth) newMenu.css("left", winWidth - newMenu.outerWidth()); else newMenu.css("left", e.pageX); // Menu not off screen at bottom if ((e.pageY + newMenu.outerHeight()) > winHeight) newMenu.css("top", winHeight - newMenu.outerHeight()); else newMenu.css("top", e.pageY); newMenu.show(); return false; });
We will place the menu at the coordinates of the mouse unless the menu’s borders would go outside the edge of the window object. In that case we would display it so that its entire menu region is visible. This is just like how context menus work today. We have one calculation for checking if the context menu is off the edge to the right and one if it is going off the edge towards the bottom.
Below is the code we use to build the menu. It takes two pieces of information. The first is the menu variable holding the type of menu we want to build (our object literal). Think of this as a blueprint. You can add to it for all sorts of functionality and more tricks. The second argument is our target triggering the menu… passed in from the context menu bind function above. Here we are passing our “target” div we created earlier.
// Takes a menu variable and the target element, builds the HTML and returns a reference to the menu. function buildMenu(menu, target) { if ($("#" + menu.name).length) { var m = $("#" + menu.name); m.hide(); return m; } // Build overall menu var m = document.createElement("div"); m.className = "menu"; m.target = target; m.id = menu.name; // Build options for menu based on menu variable for (var i = 0; i < menu.items.length; i++) { var item = document.createElement("div"); if (menu.items[i].active) item.className = "menuOption active"; else item.className = "menuOption inactive"; item.innerHTML = menu.items[i].text; item.onclick = menu.items[i].command; m.appendChild(item); } $("body").append(m); return $(m); }
If the menu had already been built before, we will simply select it with some jQuery selectors, hide it (in case it was showing from another element) and return it to be placed. Otherwise we will create it using the menu variable and append it to the body of the page. Our CSS will keep it hidden after being build and we will show it when we are ready. You may need to also adjust it’s z-index if you have items on the page that may go over the top of the menu. We want our menus to have the highest z-index.
So we are coming up on the part where we need to then create our menu object literal that will instruct the buildMenu function on how to build the menu. It is then used by the contextmenu event to place it on screen. Here we create a simple menu with two options. One that is active and another which is inactive. Active elements can be highlighted and clicked while the inactive are non clickable. As you will see, inactive menu items are stylized to be grayed out. When we click an item, the keyword “this” is going to be the actual div element that did the click. This means we have to check for the active/inactive class to determine its state before we can execute its command.
// Menus var boxMenu = { name: "boxmenu", target: null, items: [{ text: "Option 1", command: function() { // This is the menu option clicked if ($(this).hasClass("active")) { alert("Clicked option 1 and target is: " + target.id); } }, active: true }, { text: "Option 2", command: doSomeFunction, active: false }] }; // Example function of calling functions outside of a menu. // Here "this" is going to refer to the option clicked. function doSomeFunction() { if ($(this).hasClass("active")) { alert("Example of calling external function"); } }
Option 1 shows an example of calling the function inline while the second option would show how to call one externally. Again, not the cleanest but due to the fact that we are building actual HTML elements from the menu “map” it makes sense that we would want access to the actual element. This means that after we build the menu we have to then interact with the menu because it isn’t being rebuilt each time.
Last little code here is for when we determine a click up of the left mouse key. It hides all menus. This does double duty for after we have clicked an option or when we changed our minds and clicked elsewhere on the page to hide the context menu.
// Clears all menus when click the document (as an example) // Make your own custom trigger for when you want to dismiss them. $(document).bind("mouseup", function(e) { if (e.which == 1) { $(".menu").hide(); } });
So I believe that is all there is to the foundation of a possible context menu framework. Maybe clean up a few things and make it a little more optimized and you could be off to the races. Hope you guys enjoy it. I have included the fiddle below so you can play.
Thanks for reading! 🙂