How to process events from the application menu in the renderer - electron

The main process of an electron application receives the events when a user select a menu option
const menu = {
label: 'Foo',
submenu: [
{
label: 'Bar',
click: () => {
// execute function in render process
},
},
]
});
Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
and this should execute a function in the render process.
How would I intercept the menu events in the render process or alternatively execute a function in the render process from the main process?

You can send a message to the renderer when the menu item is clicked, then do what you want when the renderer receives the message.
This communication is called IPC, Inter Process Communication
Here's what the menu item would look like:
{
label: 'Bar',
click: () => {
win.webContents.send('menuItemClicked', 'Clicked!');
},
},
Then add this code to your renderer:
var ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.on('menuItemClicked', function (evt, message)
{
console.log(message); // Outputs: Clicked!
// Do your renderer stuff here.
});
And voila!

Related

Emitting Main to Main event from electron menu

I have a listener for 'select-current-project' in my Main process. It works fine when triggered from the renderer process, but how can I emit the same event from the Menu?
Menu is created in default createWindow()
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{
label:'Open',
click() {
// neither of these works
// app.emit('select-current-project')
// ipcRenderer.send('select-current-project')
},
accelerator: 'CmdOrCtrl+O'
},
{
label:'Exit',
click() {
app.quit()
}
},
]
}
])
My event handler
ipcMain.on('select-current-project', async (event, arg) => {
// code
})
I'm not sure I understand the issue you are describing – but here is what I do:
The function showProjectPicker exists in main.js and is called by a menu action as shown below. If I want to call it from the renderer process I send a message over IPC.
{
label: 'File',
submenu: [
{ label: 'Open Project...', click: () => { showProjectPicker(); }, accelerator: 'CmdOrCtrl+o' }
]
}

Why message listener does not work at first page load?

The problem that if I open an incognito window of chrome,
the updateLocalId() does not run. So I guess I dont receive events from sw.
But if I reload the page, everything works fine.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.info('[main] ServiceWorker registration successful: ', registration, ' ', '😍');
}, (err) => {
console.error('[main] ServiceWorker registration failed: 😠', err);
});
navigator.serviceWorker.addEventListener('message', async function (event) {
self.updateLocalId(event.data);
});
}
From SW I send message in the next way:
function send_message_to_all_clients(msg) {
self.clients.matchAll().then(clients => {
clients.map(client => client.postMessage(msg))
});
}
Found solution here
"A page is controlled by a service worker on navigation to an origin that the service worker is registered for. So the original page load that actually initializes the service worker is not itself controlled"
Updated function of sending message from SW:
function send_message_to_all_clients(msg) {
self.clients.matchAll({includeUncontrolled: true, type: 'window'}).then(clients => {
console.log('[sync] clients', clients);
clients.map(client => client.postMessage(msg))});
}

Electron require ipcRenderer not working

I am trying to do a simple ipc.send and ipc.on but for some reason I am getting undefined on this electron require.
libs/custom-menu.js:
'use-strict';
const BrowserWindow = require('electron').BrowserWindow;
const ipcRenderer = require('electron').ipcRenderer;
exports.getTemplate = function () {
const template = [
{
label: 'Roll20',
submenu: [
{
label: 'Player Handbook',
click() {
console.log('test');
},
},
],
},
{
label: 'View',
submenu: [
{
label: 'Toggle Fullscreen',
accelerator: 'F11',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
}
return 'Ctrl+Shift+I';
}()),
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.toggleDevTools();
}
},
},
{
label: 'Reload',
accelerator: 'F5',
click() {
BrowserWindow.getFocusedWindow().reloadIgnoringCache();
},
},
],
},
{
label: 'Random Generators',
submenu: [
{
label: 'World Generator',
click() {
ipcRenderer.send('show-world');
},
},
],
},
];
return template;
};
The error is
cannot read property 'send' of undefined.
The BrowserWindow module is only available in the main process, the ipcRenderer module is only available in the renderer process, so regardless of which process you run this code in it ain't gonna work. I'm guessing since ipcRenderer is not available you're attempting to run this code in the main process.
I know this answer might have been too late for you but for other
If you're trying access any of main process modules from renderer process you will need to go through remote module,
const {BrowserWindow} = require('electron').remote
see documentation remote
Just for those who can't get this to work in react app ipcRenderer or in any environment that requires preload file.
preload setup
These lines worked for me:
app.commandLine.appendSwitch('ignore-certificate-errors', 'true')
app.commandLine.appendSwitch('allow-insecure-localhost', 'true')
In the renderer process, the script tags that have the "require" statement needs to be:
<script type="javascript"></script>
Placing your call to require in a script tag without the type set doesn't work.

Backbone Paginator click event

I am new to backbone and am using backbone in my rails application . This is what I am doing in my application
I am using Backbone Paginator for pagination support in my application as well using Gmaps for rendering locations on gmaps , for each time I am displaying 5 records from the server with pagination and displaying corresponding 5 location in map view , so now I need to show the remaining locations on map when I click on paginated links (prev page , next page) , I think I need to write some click events , but I am not sure where to write and how to write this events , Can any one please help me . please review the code below I have written evnets but those are not working
Thanks in advance
var Listings = Backbone.PageableCollection.extend({
model: Bdemo.Models.Listing,
mode: "server" ,
url: '/listings' ,
events: {
"click #paginationSelect" : "fetchSelectedData"
},
fetchSelectedData: function(){
console.log("CAMEEEEEEEEEEEEEEEEEEEEEEEEEEE")
},
// Initial pagination states
state: {
pageSize: 3,
/* sortKey: "updated",*/
order: 1
},
queryParams: {
totalPages: null,
totalRecords: null,
sortKey: "sort"
},
parseState: function (resp, queryParams, state, options) {
return {totalRecords: resp.total_pages};
},
parseRecords: function (resp, options) {
return resp.listings;
}
});
#ratnakar:
All you need is events function. Set an id for each of your paginated links. Then include the events function. I hope that you're developing SPA(single page application). With that note assume the following settings.
In the homeview.js("templates" folder) page include the paginated links enclosed by the footer tag.
<footer>
<button id="prevPage">previous</button>
<button id="nextPage">next</button>
</footer>
then the go to the corresponding homeview.js view file("views" folder)
backboneApp.Views.homeview = Backbone.View.extend({
//Default "events" function for handling delegate events.
events:{
//catching click events
"click #prevPage" : "goPrevious" //on click of DOM which has id as prevPage, calling a function goPrevious.
"click #nextPage" : "goNext" //same as above call goPrevious function.
},
//Defining the user created function goPrevious and goNext.
goPrevious: function(){
//Make your server call here.... for the previous 5 records.
},
goNext: function(){
// server call here for the next 5 records.
}
});
Thus the basic idea of using delegate events for paginated links is defined above.
From your question I understand that you are using backgrid-paginator in server mode.
Binding to the click event won't work, because you need to make sure that the models have been fetched from the server before you can access their data.
You can bind to your collections' request event and act on the xhr.done()
In your view:
initialize: function() {
this.listenTo(this.record_collection, "request", this.onCollectionRequested);
},
onCollectionRequested: function(collection, xhr, options) {
_this = this;
xhr.done(function(){
_this.showRecordLocationsOnMap(collection);
})
},
showRecordLocationsOnMap: function(records) {
/* Update your map here */
}
Hi finally solved this by calling my own function(callGmap) from Backbone.PageableCollection , here is my new code
var Listings = Backbone.PageableCollection.extend({
model: Bdemo.Models.Listing,
mode: "server" ,
url: '/listings' ,
events: {
"click #paginationSelect" : "fetchSelectedData"
},
fetchSelectedData: function(){
console.log("CAMEEEEEEEEEEEEEEEEEEEEEEEEEEE")
},
// Initial pagination states
state: {
pageSize: 3,
/* sortKey: "updated",*/
order: 1
},
queryParams: {
totalPages: null,
totalRecords: null,
sortKey: "sort"
},
parseState: function (resp, queryParams, state, options) {
return {totalRecords: resp.total_pages};
},
parseRecords: function (resp, options) {
callGmap(resp.hash);
return resp.listings;
}
});

leaflet geojson contextmenu

I want to add a context menu on right click for various elements on my geojson layer (I'm doing a road map so on a right click on the road at any part I want to show the context menu).
I've managed to get the left click working fine by using the onEachFeature and doing the following
function onEachFeature(feature, layer) {
layer.on({
click: showAssetInfo,
contextmenu: contextreg
});
}
function showAssetInfo(e) {
AssetMouseClick(e.target.feature.properties.objectid, e.latlng);
}
For the context menu I have followed the example here . The context menu library is found here
I have the following that gets called on the document ready (jquery)
$.contextMenu({
selector: 'path.leaflet-clickable',
zIndex: 99999,
callback: function (key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": { name: "Edit", icon: "edit" },
"cut": { name: "Cut", icon: "cut" },
"copy": { name: "Copy", icon: "copy" },
"paste": { name: "Paste", icon: "paste" },
"delete": { name: "Delete", icon: "delete" },
"sep1": "---------",
"quit": { name: "Quit", icon: "quit" }
}
});
I've tested it and the selector does return the GeoJson features, also if it attach the same menu to something else it works correctly.
Is there something I am missing here?
Also is there a good way to pass in the objectid to the menu when it starts up so I can use it when calling the different options of the menu
EDIT:
I have created this fiddle to demonstrate http://jsfiddle.net/Q3L4c/22/
There is a good leaflet plugin for context menu that was created in Aug 2013:
Leaflet.contextmenu
This context menu library has great documentation including step-by-step instructions for implementation in GeoJSON layers.
In the following code snippet, notice how we can easily pass through the full feature and layer objects to the function that is called when the edit menu item is selected. In this example the layer group is a GeoJSON layer group, the GeoJSON properties can be accessed via feature.properties
NOTE: In this solution content menu item definitions are generated during the onEachFeature processing, not dynamically when the context menu is invoked, just something to be aware of if your were planning on dynamic menu item generation which might be dependent on variables that could change at run time, you need to evaluate the visibility or enabled option for each item as a static value when you create the menu item.
function onEachFeature(feature, layer) {
layer.bindContextMenu({
contextmenu: true,
contextmenuInheritItems: false,
contextmenuItems: [
{ text: 'edit', icon: 'edit', callback: function () { editFeature(feature, layer); } },
{ text: 'cut', icon: cut', callback: function () { console.log('cut'); } },
{ text: 'copy', icon: 'copy', callback: function () { console.log('copy'); } },
{ text: 'paste', icon: 'paste', callback: function () { console.log('paste'); } },
{ text: 'delete', icon: 'delete', callback: function () { console.log('delete'); } },
{ separator: true },
{ text: 'quit', icon: 'quit', callback: function () { console.log('quit'); } },
]
});
layer.on({
click: showAssetInfo
});
}
/**
* Edit a feature on the map
* #param {GeoJSON} feature GeoJSON feature, access metadata through feature.properties
* #param {any} layer Leaflet layer represents this feature on the map (the shape)
*/
function editFeature(feature, layer) {
console.log(JSON.stringify(feature.properties));
}
function showAssetInfo(e) {
AssetMouseClick(e.target.feature.properties.objectid, e.latlng);
}

Resources