Simple way to add custom context menus to web pages.
Tags: javascript, webdev, ux, tutorial
Published: 2022-10-05 19:38:27

In this tutorial, I'll explain how to add context menus to elements of a web page using my newly developed component. This menu can appear when a user right-clicks on an element of a web page or just puts the mouse cursor on that element. This is an example of how the menus, created using this component work in practice.

![Demo](https://code.germanov.dev/context_menu/assets/menu.gif)


In the next sections I will show how to setup the menu in basic mode and then customize it.

# Contents

[Install](#install)
[Create basic menu](#basic_menu)
[React to user actions](#react_user_actions)
[Add images to menu items](#add_images)
[Styling the menu](#styling)
[Menu API](#api)
[Conclusion](#conclusion)

<a name="install"></a>

## Install

To include the component to your web page, download [context_menu.umd.cjs](https://code.germanov.dev/context_menu/context_menu.umd.cjs) script and include it to your web page:

```html
<script src="context_menu.umd.cjs"></script>
```

Also, if you use some JavaScript framework, you can install the component using NPM:

```
npm install simple_js_menu
```

and then import `Menus` factory object to your JavaScript code:

```javascript
import {Menus} from "simple_js_menu";
```

<a name="basic_menu"></a>

## Create basic menu

When you install the component using one of the methods described above, you get a `Menus` factory object that you can use to create context menus and connect them to objects on your web page. Let's assume that we have some `<div>` element on a web page and want to have a context menu that appears when the user right-clicks on it. Next example shows how to implement this:

```html
<html>
<head>
    <title>Context Menu Demo</title>
    <script src="https://code.germanov.dev/context_menu/context_menu.umd.cjs"></script>
</head>
<body>
<div id="myDiv">My element</div>

<script>
    // obtain a link to the element 
    const div = document.getElementById("myDiv");
    // define menu items
    const items = [
        {id:"item1", title: "Item 1"},
        {id:"item2", title: "Item 2"},
        {id:"item3", title: "Item 3"}
    ];
    // create menu with specified items
    // and bind it to specified DIV element
    const menu = Menus.create(items,div);
</script>
</body>
</html>
```

When you open this page, there will be "My element" text inside the DIV. If you right-click on it, you'll see the context menu with three items, as displayed below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sf04j6j6tjxbvyrdxa6n.png)

This is how it basically works. To initialize menu, we executed the following code:

```javascript
const menu = Menus.create(items,div)
```

`Menus.create` method receives `items` and `div` arguments, constructs a menu and returns the menu object.

* **items** argument defines an array of menu items. Each item is an object with 3 fields: `id` - unique ID of menu item, **title** - the text inside menu item, **image** - URL of image which appears on the left side of menu item.
* **div** argument defines an HTML element to which this menu connects. It can be any HTML element, not only a div.

Also, the `create` method has the third optional argument **eventName** which defines an event which should happen with `div` to display the menu. By default eventName equals to `contextmenu`, which shows a menu when the user right-clicks on the *div*, but you can change it to show a menu when the user hovers over the element, for example. The standard Javascript mouse events are supported (`mouseover`, `mousedown`, `mouseup`, `mousemove`, `click` and so on). For example, this code will show menu, if user moves cursor to the area of the element:

```javascript
const menu = Menus.create(items,div,'mouseover')
```

So, using `Menus.create` we created a basic context menu and received a 'menu' object. In the next sections I will show how you can extend features of the menu using that `menu` object.

<a name="react_user_actions"></a>

## React to user actions 

Obviously, the next step is to perform some actions when the user selects items from the menu. In most cases, it means that we have to reply to `click` events. You can, however, react to any events that the user can initiate using the mouse. To react to user actions with the menu use the `menu.on(eventName,handler)` method where `eventName` is an event, to which you want to react. For example you can add to the previous code:

```javascript
    menu.on('click', (event) => {
        switch (event.itemId) {
            case "item1":
                console.log("User clicked first item");
                console.log("Mouse cursor position X:",
                    event.cursorX,"Y:",event.cursorY);
                break;
            case "item2":
                console.log("User clicked second item");
                console.log("Mouse cursor position X:",
                    event.cursorX,"Y:",event.cursorY);
                break;
            case "item3":
                console.log("User clicked third item");
                console.log("Mouse cursor position X:",
                    event.cursorX,"Y:",event.cursorY);
                break;
        }
    })
```
Then, when you run and click any of the menu items, you'll see similar lines in the Javascript console:

```
User clicked first item Mouse cursor position X: 78 Y: 17
User clicked second item Mouse cursor position X: 68 Y: 16
User clicked third item Mouse cursor position X: 64 Y: 18
User clicked third item Mouse cursor position X: 13 Y: 16
```

Let's discuss the `on` method more. Basically, it works the same as the 'on' method in JQuery. It receives as arguments a JavaScript event name and a callback function that will be called when this event occurs on the menu object. The callback `function` receives an `event` argument that can be used to discover the properties of the event that triggered. This is an [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object extending by the following properties:

* **itemId** - ID of menu item that triggered this event
* **cursorX** - the X position of the mouse cursor when the user displays the menu
* **cursorY** - the Y position of the mouse cursor when the user displays the menu

So, this way using a single method, you can process clicks from all menu items. Furthermore you can react not only to clicks, but to other mouse events: `mouseover`,`mouseout`,`dblclick`,`mousedown`,`mouseup`,`mousemove`:

```javascript
menu.on('mouseover', (event) => {
...
}

menu.on('mouseout', (event) => {
...
}

menu.on('mousedown', (event) => {
...
}

```

<a name="add_images"></a>

## Add images to menu items

Menu items may have images. To add an image to each item, need to specify the 'image' field for these items when defining the `items` array. Let's redefine `items` array this way:

```javascript
    const items = [
        {
            id:"item1",
            title: "Batman",
            image:"https://code.germanov.dev/context_menu/assets/batman.svg"
        },
        {
            id:"item2",
            title: "Hacker",
            image:"https://code.germanov.dev/context_menu/assets/hacker.svg"
        },
        {
            id:"item3",
            title: "Santa",
            image:"https://code.germanov.dev/context_menu/assets/santa.svg"
        }
    ];
```

After run the previous menu example with menu items defined this way you will see this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntvpvfogoddr8gbmnswk.png)

You need not worry about the size of images, because they will be automatically resized to a height of text and aligned properly.

<a name="styling"></a>

## Styling the menu

By default the menu is a gray panel with a gray border. Menu items are displayed as transparent DIVs with black text in the default font and optional images. However, you can redefine the styling by providing your own CSS class to any or all of these parts using the following methods:

* **menu.setPanelClass(className)** - override CSS class for menu panel DIV
* **menu.setItemClass(className, id)** - override CSS class for menu items DIVs. If `id` parameter specified, then override CSS class only for item with specified ID.
* **menu.setTextClass(className, id)** - override CSS class for text of menu items DIVs. If `id` parameter specified, then override CSS class only for text of item with specified ID.
* **menu.setImageClass(className, id)** - override CSS class for image of menu items DIVs. If `id` parameter specified, then override CSS class only for image of item with specified ID.

Let's add some CSS classes to redesign the menu:

```css
.panel {
    background-color: #454d55;
    border-width:0;
    color: white;
    box-shadow: 5px 5px #88888855;
    border-radius: 10px;
}
.item {
    border-bottom-style: outset;
    padding:3px;
    border-bottom-width: 1px;
    font-weight:bold;
    font-size:15px;
    text-transform: uppercase;
}
.item:hover {
    background-color: yellow;
    color: black;
}

.last_item {
    padding:3px;
    border-bottom-width: 0;
    font-weight:bold;
    font-size:15px;
    text-transform: uppercase;
}

.last_item:hover {
    background-color: yellow;
    color: black;
}
```
Then, let's apply these styles to various parts of the menu:

```javascript
// Set class for menu panel
menu.setPanelClass("panel");
// Set class for all menu items
menu.setItemClass("item");
// Set specific class for last menu item (remove bottom border)
menu.setItemClass("last_item","item3");
// Reset default styles of text to use text color from panel
menu.setTextClass(" ");
```

If you apply these styles and add this Javascript code to the previous menu, you'll get the following result:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ty9h8woqd6cj8bxpmd4.png)

Notice that I also added `:hover` styles for items, because it's required to show selected item status when the mouse enters the menu item.

<a name="api"></a>

## Menu API

In addition to the basic features, described above, the `Menu` object has other helpful properties and methods. For example, the `panel` property contains a generated menu panel HTML node. Method `.setId(id)` can be used to set ID for this HTML node. The method `.addItem(id,title,image)` can be used to add new items to a menu. Method `.removeItem(id)` allows to remove menu item with specified ID. 

You can find full description of all properties and methods of Menu object in the [API documentation](https://github.com/AndreyGermanov/context_menu/blob/main/docs/API.md#Menu).

<a name="conclusion"></a>

## Conclusion

There are many wonderful UX libraries like MaterialUI, Ant Design or JQuery UI that contains a lot of widgets, including menus. However, if you integrate one of them to your project, it could increase the size of your site up to several megabytes. Also, you can face integration problems between CSS styles of your site and CSS styles of the library. So, if you have a simple project and if you only need a context menu, you can use my new component, and I would be happy if you found this tutorial useful.

The idea for creating this menu component came from Aneta Chwała. She is a frontend developer, the founder of Rock JavaScript Facebook group where I have the honor of being a group expert: https://www.facebook.com/groups/251411400345198

Aneta hosts a YouTube channel where you can learn JavaScript and other frontend technologies: 

https://www.youtube.com/c/RockJavaScript.

The full source code for this tutorial you can find here:

https://github.com/AndreyGermanov/context_menu/blob/main/tests/demo_prod.html

GitHub repository of this component: https://github.com/AndreyGermanov/context_menu

Context menu component on NPM: https://www.npmjs.com/package/simple_js_menu

Try these context menus in **SmartShape Studio**: https://shapes.germanov.dev . Here you can use context menus to work with vector shapes, as shown in the animation at the beginning of this article.

Feel free to connect and follow me on social networks where I publish announcements about my upcoming software, articles, similar to this one and other software development news:

LinkedIn: https://www.linkedin.com/in/andrey-germanov-dev/
Facebook: https://web.facebook.com/AndreyGermanovDev
Twitter: https://twitter.com/GermanovDev

My online services website: https://germanov.dev

Happy coding guys!