Search code examples
javascriptmenuelectronmenuitem

Adding Submenu Item Only on Devices other than Mac: Electron JS


I would like to add a quit submenu item under the File menu item in my Electron JS app, but only on Windows and Linux. On Mac, I already have set up a Quit submenu item under the app name menu item. Currently, this is my code:

function createMenu(){
    const isMac = process.platform === 'darwin';
    const menu = [
        
        {
            label: "File",
            submenu: [
                {
                    label: 'Add expense',
                },
                !isMac && { 
                    label: 'Quit',
                    click(){
                        app.quit()
                    },
                    accelerator: 'Ctrl+Q'
                }
            ]
        },
        {
            label: "Menu2",
            submenu: [{label: 'Ok'}]
        }
    ];


    // Mac First Item in Template refers to app name (won't be able to override unless you package application)
    isMac && menu.unshift({
        label: app.name,
        submenu: [
            {
                label: 'About',
                click(){
                    open('https://www.google.com') // Using the open package
                }
            }, 
            {
                label: 'Quit',
                click(){
                    app.quit()
                },
                accelerator: 'Cmd+Q'
            }
        ]
    })


    return menu;
    
}

When I run this, however, I get an error saying

TypeError: Invalid template for MenuItem: must have at least one of label, role or type

How should I go about doing this?

Thank you!


Solution

  • You're getting the error because of the way you're trying to conditionally insert an object into the list literal.

    // The not macOS case:
    > [ {"foo": 15}, true && {"bar": 123} ]
    [ { foo: 15 }, { bar: 123 } ]
    
    // The macOS case:
    > [ {"foo": 15}, false && {"bar": 123} ]
    [ { foo: 15 }, false ]
    

    In the latter case, you end up inserting a false element into the array, which is why you get the error.

    If you want to use such a shorthand, you can use:

    > [ {"foo": 15}, ...(true?[{"bar": 123}]:[]) ]
    [ { foo: 15 }, { bar: 123 } ]
    
    > [ {"foo": 15}, ...(false?[{"bar": 123}]:[]) ]
    [ { foo: 15 } ]
    

    In your use case, this would get you:

    const menu = [
        {
            label: "File",
            submenu: [
                {
                    label: 'Add expense',
                },
                ...(!isMac)?[{ 
                    label: 'Quit',
                    click(){
                        app.quit()
                    },
                    accelerator: 'Ctrl+Q'
                }]:[]
            ]
        },
        {
            label: "Menu2",
            submenu: [{label: 'Ok'}]
        }
    ];
    

    Alternatively, you can simply adjust the menu template:

    const menu = [
        {
            label: "File",
            submenu: [
                {
                    label: 'Add expense',
                }
            ]
        },
        {
            label: "Menu2",
            submenu: [{label: 'Ok'}]
        }
    ];
    
    if (!isMac) {
        menu[0]["submenu"].push({ 
            label: 'Quit',
            click(){
                app.quit()
            },
            accelerator: 'Ctrl+Q'
        })
    }
    

    Which one to use is up to you; in the second case you may need to update the menu addressing if the structure changes. You should also weigh readability.