I'm new to electron and I'm trying to create a Mac only menu bar app that has dynamic menu items in it. My idea is when clicking 'add...' it adds/appends/inserts a new menu item in it.
My code appends the new item into the menu array. However I can't make it render the 'updated menu'.
const { resolve } = require('path');
const { app, Tray, Menu } = require('electron');
const iconPath = resolve(__dirname, 'assets','iconTemplate.png');
let contextMenu;
let tray = null;
let menu;
app.on('ready', function() {
tray = new Tray(iconPath);
menu = [
{
label: 'Add...',
click: function() {
// add new item between 'Add...' and 'Quit'. New one always on top of the last.
menu.push({label: 'new item'});
}
},
{
label: 'Quit',
enabled: false
}
]
contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
});
Turns out I figured out myself.
let counter = 0; // Start counter globally
// add new item between 'Add...' and 'Quit'. New one always on top of the last.
menu.splice(1, 0, {label: String(counter)});
render(); // Call render for every alteration on the menu array
counter++;
function render() {
contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
}
Related
I try to build a simple Text Editor using Electron.
At the moment I want to add a custom title bar which doesn't really work as the buttons are not clickable...
I added an onclick tag to the buttons.
main.js:
//-- variables --\\
const { BrowserWindow, app, Menu, dialog, ipcMain } = require("electron")
let window
let filePath = null //currently opened file
let openFiles = [] //opened files
//-- startup --\\
app.whenReady().then(function()
{
createWindow()
createMenu()
})
//-- functions --\\
function createWindow()
{
window = new BrowserWindow
({
width: 1000,
height: 600,
frame: false,
icon: "./assets/icon.png",
darkTheme: true,
autoHideMenuBar: true,
webPreferences:
{
nodeIntegration: true,
contextIsolation: false
}
})
//window.maximize()
window.loadFile("./frontend/index.html")
window.webContents.openDevTools()
}
function createMenu()
{
Menu.setApplicationMenu(Menu.buildFromTemplate
([
{
label: "File",
submenu:
[
{
label: "Save",
click: saveFile,
accelerator: "CmdOrCtrl+S"
},
{
label: "Open",
click: openFile,
accelerator: "CmdOrCtrl+O"
},
{
label: "New",
click: newFile,
accelerator: "CmdOrCtrl+N"
}
]
}
]))
}
async function promptFilePathOpen()
{
await dialog.showOpenDialog
({ properties: ["openFile"] }).then(function(res)
{
if(res.canceled) return
filePath = res.filePaths[0]
})
}
async function promptFilePathSave()
{
await dialog.showSaveDialog().then(function(res)
{
if(res.canceled) return
filePath = res.filePath
})
}
async function openFile()
{
await promptFilePathOpen()
//openFiles.push(filePath)
window.webContents.send("crd-openFile", filePath)
}
async function saveFile()
{
if(filePath == null || undefined) await promptFilePathSave()
window.webContents.send("crd-saveFile", filePath)
}
async function newFile()
{
filePath = null
await promptFilePathSave()
window.webContents.send("crd-resetEditor")
window.webContents.send("crd-saveFile", filePath)
}
//-- listeners --\\
app.on("window-all-closed", function()
{
if(process.platform != "darwin") app.quit()
})
ipcMain.on("crd-minimizeWindow", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-toggleWindowSize", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-closeWindow", function() //does not get called by the renderer
{
console.log("quit")
})
renderer/script.js:
//-- imports --\\
const { ipcRenderer } = require("electron")
const fs = require("fs")
const editorElem = document.querySelector("textarea.editor")
//-- functions --\\
function minimizeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-minimizeWindow")
}
function toggleWindowSize() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-toggleWindowSize")
}
function closeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-closeWindow")
}
//-- listeners --\\
ipcRenderer.on("crd-openFile", function(e, path)
{
editorElem.value = fs.readFileSync(path, "utf-8")
})
ipcRenderer.on("crd-saveFile", function(e, path)
{
fs.writeFile(path, editorElem.value , function(res, err)
{
if(err) throw err
})
})
ipcRenderer.on("crd-resetEditor", function()
{
editorElem.value = ""
})
The entire code is avalable on my GitHub, because I do not want the whole question consisting of code :)
Two issues here:
You defined functions like closeWindow, but you didn't actually add an event listener for them. You mention onclick but I can't see that in your code. So the first step would be to add document.querySelector('.closeWindow').addEventListener('click', closeWindow) .
You made the whole title bar draggable, including the buttons. That means that the role of the buttons is also a draggable area, so when you click them, you start the drag operation instead of sending a click event. The solution is therefore to make sure the button area does not have the -webkit-app-region: drag style but only the area left to them has. This will probably require you to redesign the HTML layout for the title bar a bit, since this won't work well with the whole thing being a grid.
For more details, see this tutorial.
I have Electron app and I am trying to hide or show label base on user input. The label aways show. I am trying to hide or show Tutorials
{
id: "tut",
label: "Tutorials",
submenu: [
{
id: "subTut",
label: "Tutorials",
click: async () => {
const { shell } = require("electron");
await shell.openExternal("https://example.app/tutorials");
},
},
],
},
In my main.js process I call the menu
When I call:
Menu.getApplicationMenu().getMenuItemById("tut").visible = false;
it does not hide anything, however it I call
Menu.getApplicationMenu().getMenuItemById("subTut").visible = false;
it will hide the sub menu item
Here is a solution for any that runs across this issue this can be done by using the function
Menu.setApplicationMenu()
Just pass object to the function where you call you menu to handle if item is visible or not
const MenuItems = (options) => {
// MENU
const template = [
{
label: "Window",
submenu: [
{
visible: options.option,
label: "Support",
click: async () => {
await shell.openExternal("https://example.com");
},
},
]
]
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
};
When you run the function for you menu just pass object in function for what's to show.
In Mac OS, you cannot hide top-level menu items, just submenu items:
Nota Bene: The enabled and visibility properties are not available for top-level menu items in the tray on macOS.
Source: docs
Im want to create a tray app, before all Im trying to create a simple icon showing a menu with radios inputs to test Electron tray apps exact how the doc example shows up. But when i click on the icon nothing happening.
const { app, Menu, Tray } = require('electron')
const { resolve } = require('path')
app.on('ready', () => {
createTray()
})
const createTray = () => {
const tray = new Tray(resolve(__dirname, 'assets', 'tray-icon.png'))
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', checked: true },
{ label: 'Item4', type: 'radio' }
])
tray.setToolTip('This is my application.')
tray.setContextMenu(contextMenu)
// using this to check if the click event is working
tray.on('click', () => {
console.log('clicked')
})
}
On Windows, the menu normally opens with a right click on the tray icon.
You can also trigger it by using tray.popUpContextMenu() in the click event handler that you have.
tray.on("click", ()=>{
tray.popUpContextMenu();
});
I am working on an about this app window on Windows and I want to make a custom menu bar for my about window. Since I already have a custom menu is there a way I can create another one and apply it only to that specific window?
Side note:
Here is my code for the new window that is supposed to stop it from being adjusted and going into full screen, but for some reason the minimize and enlarge button still work.
app.on('ready', createWindow);
electron.app.on('ready', () => {
//Triger update check
if (!isDev) {
autoUpdater.checkForUpdates();
}
})
function createWindow(){
//create brower window
win = new BrowserWindow({
backgroundColor: '#2e2c29',
width: 800,
height: 600,
//transparent: true,
frame: false,
titleBarStyle: 'hidden',
backgroundColor: '#0000',
webPreferences: {
nodeIntegration: true
}
});
//Quit when all windows are closed
app.on('window-all-closed', () => {
app.quit()
})
app.once('ready', function() {
const template = [
{
label: 'File',
submenu: [
{
label: 'About Hubris',
click: () =>
openAboutWindow()
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'View',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
//here is the code for the about window
var newWindow = null
function openAboutWindow() {
if (newWindow) {
newWindow.focus()
return
}
newWindow = new BrowserWindow({
height: 439,
resizable: false,
width: 599,
title: 'About Hubris',
minimizable: false,
fullscreenable: false,
frame: false,
titleBarStyle: 'hidden',
})
newWindow.loadURL('file://' + __dirname + '/about-this-app.html')
newWindow.on('closed', function() {
newWindow = null
})
}
});
You can switch menus on the fly. I have an app with 'editor' and 'presentation' modes. I create and store a menu for each mode (they have different menu items):
let editorMenu = Menu.buildFromTemplate(menuTemplate);
and subscribe to the relevant window events (focus, blur, etc). Then when a window gets focus
Menu.setApplicationMenu(editorMenu);
To do this, you have to import menu from electron in at the top of our main.js file:
// From
const {app, BrowserWindow} = require('electron')
// To
const {app, BrowserWindow, Menu} = require('electron').
Then, near the bottom of our createWindow() function, we add:
function createWindow () {
// some code here removed for brevity
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label:'Adjust Notification Value'},
{label:'CoinMarketCap'},
{label:'Exit'}
]
}
])
Menu.setApplicationMenu(menu);
}
Next, we reference Menu.buildFromTemplate([{}]), which is where our menu is actually defined and built, within a series of arrays and objects.
The "label" represents the name you want your menu to display so put what you like.
The "submenu" property is an array of objects, with each object defining the actual menu items displayed when the label is clicked.
Finally, use .setApplicationMenu to set the menu. If you save the project, and run npm start in the console, you will see the menu with it's items (array) being displayed but if your click on them nothing happens.
You can change this by going back to our main.js, add the following code to make our Exit button close the application:
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label:'Adjust Notification Value'},
{label:'CoinMarketCap'},
{
label:'Exit',
click() {
app.quit()
}
}
]
}
])
So, to make a menu item clickable, we simply add a comma after the label value, and reference "click() { }"
In this case, we're calling app.quit()" when the Exit submenu item is clicked. Give it a try by running npm start in the console and click Exit.
That's it!
I'm new to Electron and JavaScript. I'm building an Electron app. I know how to open a URL in a browser via clicking an item in native menu(by studying the documentation), but I need to open a html file in another Electron window using an Electron's native menu click. If I have my menu structure like below, how can I achieve this? Please help.
const {Menu} = require('electron');
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click () {--- code to open about.html file in another electron window}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus);
Menu.setApplicationMenu(menu);
If it is all in the main.js just create a function to create a new window and then call that on menu item click.
const { Menu } = require('electron')
const ipc = require('electron').ipcRenderer
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click() {
openAboutWindow()
}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus)
Menu.setApplicationMenu(menu)
var newWindow = null
function openAboutWindow() {
if (newWindow) {
newWindow.focus()
return
}
newWindow = new BrowserWindow({
height: 185,
resizable: false,
width: 270,
title: '',
minimizable: false,
fullscreenable: false
})
newWindow.loadURL('file://' + __dirname + '/views/about.html')
newWindow.on('closed', function() {
newWindow = null
})
}
Let me know if this works for you.
You stored your BrowserWindow instance in a variable, for the sake of this answer I am gonna suppose it's win. user7252292 did provide you with a good answer. But if you need another window then you will have to create another function for the same purpose. I will make a function for creating modals. They are basically windows that need a parent window and you cannot respond to the parent window until the modal is closed.
const createModal = (htmlFile, parentWindow, width, height) => {
let modal = new BrowserWindow({
width: width,
height: height,
modal: true,
parent: parentWindow,
webPreferences: {
nodeIntegration: true
}
})
modal.loadFile(htmlFile)
return modal;
}
const {Menu} = require('electron');
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click () {
createModal("myfile.html",win,600,800); // Win is the browerwindow instance
}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus);
Menu.setApplicationMenu(menu);
Plus, you can store the createModal in a variable and modify the modal because it returns the modal itself.