1. Menus
  2. How to Create Dynamic Tray Menus

Menus

How to Create Dynamic Tray Menus

Learn how to create dynamic tray menus for your ToDesktop Builder application

Introduction

Adding dynamic menus to your desktop app is simple through the use of the ToDesktop API. In this short walkthrough, we’ll re-create the following menu:

Context menu that displays dynamically-generated menu items

Before continuing, this guide assumes that you’re familiar with extending ToDesktop Builder applications with custom code. For a walkthrough on how to extend an existing application, please visit How to Build a TodoMVC Desktop App with ToDesktop Builder.

Code Walkthrough

We’ll walk step-by-step through creating a buildDynamicMenu function that does the following:

  • Creates the tray.
  • Creates the menu.
  • Attaches the menu to the tray at a set interval.
INFO

Dynamic menu functionality was added in version 1.0.0-alpha43 of @todesktop/client-core, so please make sure that you are on the latest version.

You can skip to the full implementation at the bottom of the page if you want to see the final code.

To start, we’ll sketch out the body of the function and get our imports out of the way:

        import { nativeImage, tray, menu } from '@todesktop/client-core';

async function buildDynamicMenu() {
  // create tray
  // create menu
  // attach menu to tray on interval
}

      

Next, we’ll create an electron NativeImage, passing a base64 data image as input. Importantly, interacting with electron from a web javascript environment involves managing electron references. trayIconRef and trayRef are two examples, but you can think of them as pointers to the underlying Electron objects that live in the node.js environment.

        async function buildDynamicMenu() {
	// create tray
	const trayIconImage =
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRElEQVR4AYXBXWjNcRjA8e/znP/ObItTiBHm9aQmYxfKtlY2aY2ipIVW42IjyrQrbpksF9Qidie3sgslZGuGi6k1ibG2s+2svM3LHGXOzs55fv7YdBrx+QgzmFmhc64OKAPy+GUYaFfVyyLyhDTCFOdctpk1q+pB/sHMWkSkXlW/4RN8zrlsM7ujqiUukSB+8zrxttukokO4lOHlLSezvIKsXVVIMIiZdYpIhap+8/CZWbOqltj7d8RO1JMcjpAuGeknGelnsvsxoaZmVLXUzM4Dh8TMCkWk2yUSjB2uJhUdQoJBsnbvI7ipCFRJdD1i4v49Qo3nCSxZRpqNnnOuTkRI9V7BfY5CIEDo3EUy8guYlpG/npyaOlAlnZnVekAZPklcY3bNc1JfDpCRX8AfVPmLMg/Iw+e+9qLZk2QUbSdd+ZmvzLR5TYDTe2bhW6FMc46fnON/Ekl+84BhYM145kqS8Vc8eP2SnfMrmdZ2Modpp1on6HiRJDckTBlUoB3fwNxq9n8q4mxfFz3ve5mp743xsC/JD5tWeUxp81T1MlC3LnycUPQtH2IjHO04RVW4kuJFhago3aPPaO0ZY9JVEc4NUhwO8IOItAi+VCp1RVVrR8c/cqyzkYHYCH+zOrOYC1vqWRgSzOxSIBA44uETkXozW7sge17p1W1N3Bi4y61oJ5HYCDjIm7OYrUuL2BveQZYnmFmHqjbgE6aYWZZz7oKq1vIPZnZJVRtEJI5PmME5t8HMaoEyYAW/DAJtItKiqk9J8x3Hf+nY61OcSgAAAABJRU5ErkJggg==";
  const trayIconRef = await nativeImage.createFromDataURL(trayIconImage);
  const trayRef = await tray.create(trayIconRef);

	// create menu

      
INFO

In this example, we used a data image that we created ahead of time. For your own projects, you can easily create a data image from an image element or URL source using vanilla JavaScript.

We’ll follow a similar process for creating the icon for our menu item. We’ll also resize it and set it as a Template Image so that it works in light or dark mode.

        // create menu
const menuItemIcon = `data:image/png;base64,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`;
const menuItemIconRef = await nativeImage.resize({
  ref: await nativeImage.createFromDataURL(menuItemIcon),
  height: 20,
  width: 6
});

await nativeImage.setTemplateImage({ ref: menuItemIconRef, value: true });

      

Directly after, we’ll create a utility function that allows us to set a tray menu. We’re opting to create a function because we’ll be using this in a setInterval invocation later.

        const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
const setTrayMenu = async () => {
  const diff = Math.abs(oneHourFromNow.getTime() - new Date().getTime());
  const minutes = Math.floor(diff / 1000 / 60);

  const menuRef = await menu.buildFromTemplate({
    template: [
      { label: `Ending in ${minutes} mins`, enabled: false },
      {
        label: 'Meeting with John',
        icon: menuItemIconRef,
        click: (...args) => {
          console.log('Redirecting to meeting page...', args);
        }
      },
      { type: 'separator' },
      { label: 'Settings...' }
    ]
  });

  await tray.setContextMenu({ ref: trayRef, menu: menuRef });
};

// attach menu to tray on interval

      

Inside the function's body, we calculate the remaining time for a fictitious meeting. We then create a menuRef and use that alongside the trayRef to set the tray’s context menu.

Finally, we’ll execute setTrayMenu and then re-execute it every 60 seconds.

          // attach menu to tray on interval
  await setTrayMenu();
  setInterval(setTrayMenu, 60 * 1000);
}

      
INFO

You could also re-trigger setTrayMenu based on events that have occurred in your app - there’s no need for an explicit interval. Another important detail is that you must replace the entire menu, as opposed to updating specific menu items. This is because Electron’s rendering logic doesn’t support mutating menus in-place.

That’s it! You now have an example function for creating the following dynamic menu.

Context menu that displays dynamically-generated menu items

Full Implementation

        import { nativeImage, tray, menu } from '@todesktop/client-core';

async function buildDynamicMenu() {
  // create tray
  const trayIconImage =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRElEQVR4AYXBXWjNcRjA8e/znP/ObItTiBHm9aQmYxfKtlY2aY2ipIVW42IjyrQrbpksF9Qidie3sgslZGuGi6k1ibG2s+2svM3LHGXOzs55fv7YdBrx+QgzmFmhc64OKAPy+GUYaFfVyyLyhDTCFOdctpk1q+pB/sHMWkSkXlW/4RN8zrlsM7ujqiUukSB+8zrxttukokO4lOHlLSezvIKsXVVIMIiZdYpIhap+8/CZWbOqltj7d8RO1JMcjpAuGeknGelnsvsxoaZmVLXUzM4Dh8TMCkWk2yUSjB2uJhUdQoJBsnbvI7ipCFRJdD1i4v49Qo3nCSxZRpqNnnOuTkRI9V7BfY5CIEDo3EUy8guYlpG/npyaOlAlnZnVekAZPklcY3bNc1JfDpCRX8AfVPmLMg/Iw+e+9qLZk2QUbSdd+ZmvzLR5TYDTe2bhW6FMc46fnON/Ekl+84BhYM145kqS8Vc8eP2SnfMrmdZ2Modpp1on6HiRJDckTBlUoB3fwNxq9n8q4mxfFz3ve5mp743xsC/JD5tWeUxp81T1MlC3LnycUPQtH2IjHO04RVW4kuJFhago3aPPaO0ZY9JVEc4NUhwO8IOItAi+VCp1RVVrR8c/cqyzkYHYCH+zOrOYC1vqWRgSzOxSIBA44uETkXozW7sge17p1W1N3Bi4y61oJ5HYCDjIm7OYrUuL2BveQZYnmFmHqjbgE6aYWZZz7oKq1vIPZnZJVRtEJI5PmME5t8HMaoEyYAW/DAJtItKiqk9J8x3Hf+nY61OcSgAAAABJRU5ErkJggg==';
  const trayIconRef = await nativeImage.createFromDataURL(trayIconImage);
  const trayRef = await tray.create(trayIconRef);

  // create menu
  const menuItemIcon = `data:image/png;base64,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`;
  const menuItemIconRef = await nativeImage.resize({
    ref: await nativeImage.createFromDataURL(menuItemIcon),
    height: 20,
    width: 6
  });

  await nativeImage.setTemplateImage({ ref: menuItemIconRef, value: true });

  const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
  const setTrayMenu = async () => {
    const diff = Math.abs(oneHourFromNow.getTime() - new Date().getTime());
    const minutes = Math.floor(diff / 1000 / 60);

    const menuRef = await menu.buildFromTemplate({
      template: [
        { label: `Ending in ${minutes} mins`, enabled: false },
        {
          label: 'Meeting with John',
          icon: menuItemIconRef,
          click: (...args) => {
            console.log('Redirecting to meeting page...', args);
          }
        },
        { type: 'separator' },
        { label: 'Settings...' }
      ]
    });

    await tray.setContextMenu({ ref: trayRef, menu: menuRef });
  };

  // attach menu to tray on interval
  await setTrayMenu();
  setInterval(setTrayMenu, 60 * 1000);
}