ShowOpenDialog not working on recent versions of electron-js - javascript

I'm new on electronjs and developing a small application that reads a json file and build a small html form and return the values entered by the user.
So I've developed small scripts in javascript that link to html 'button' tags to call dialogs so that a user can enter directories, files and save the final form. Everything works nicely... on electronjs "^3.1.13". But if I'm updating to a recent version of the lib ("^8.2.5"), then all my cool ShowOpenDialog don't work at all. Any clue of what happens?
Here is the script to open a folder if it helps:
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions,
fileNames => {
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Thanks a lot for any help.

Apart from the fact that the call to dialog.showOpenDialog has indeed been updated in recent versions of Electron, and returns a promise instead of making use of a callback function, there is another flaw in your updated code: reading the above-mentioned documentation page shows that getCurrentWindow() is not a method of dialog; it can be obtained from remote instead, so you have to add it explicitely:
const { dialog, getCurrentWindow } = require('electron').remote;
then simply call it from inside dialog.showOpenDialog:
dialog.showOpenDialog( getCurrentWindow(), dialogOptions).then(result => {
but this is an error you could have caught yourself by looking at the DevTools's console, which would display:
TypeError: dialog.getCurrentWindow is not a function

Recent version of showOpenDialog receives two arguments: optional BrowserWindow, and options as second argument. It returns promise and not requires callback.
https://github.com/electron/electron/blob/8-x-y/docs/api/dialog.md#dialogshowopendialogbrowserwindow-options
So you need to change you callback logic to promises.
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions
).then((fileNames)=>{
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
}).catch(err=>console.log('Handle Error',err))
};
asyncBtn.addEventListener("click", onButtonClick);

thanks a lot Vladimir. So I've tried to update my code as explained, updating electron package to version 8.2.5 and modifying the script as you explained but it's not going any better. If I got it well, this code should be correct, but doesn't work on electron 8.2.5. Any error you still see on this?
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate']
};
dialog.showOpenDialog( dialog.getCurrentWindow(), dialogOptions).then(result => {
if(!result.canceled) {
replyField.value = result.filePaths[0];
}
}).catch(err => {
console.log(err)
})
};
asyncBtn.addEventListener("click", onButtonClick);
}

Ok, finally got it. Apart from the most appreciated help I had, I missed
"webPreferences": {
nodeIntegration: true
}
in the main.js to make it work.
The discovering of the Developer Tools were of great help as well :)
Now everything is fine again. Thanks a lot!

Related

Controling console.log client side

I'm working on a site that takes fairly long to build/deploy. I sometimes need information that is only available server-side for debugging. Using console.log is annoying since adding a console.log in the code and building takes too long.
But I also don't want the site to always log that information to the console.
My plan was to have a wrapper function for console.log, that checks if there is e.g dev_config set in localStorage.
For example to activate certain verbose levels that then show respective logs, or log only in certain sections.
Would this have a significant impact on performance of the site?
For instance something like this:
const devLog = (message) => {
devConfig = JSON.parse(localStorage.getItem('dev_config'))
if (devConfig != null) {
// ...
// other checks (set devMode to true)
// ...
if (devMode === true) {
const now = new Date()
const hours = now.getHours()
const min = (now.getMinutes() + '').padStart(2, '0')
const sec = (now.getSeconds() + '').padStart(2, '0')
console.log(`[${hours}:${min}:${sec}] `, message)
}
}
}
PS: I am aware of the built-in browser dev tools and I am using them in most cases. But since the information in my current problem is server side, I don't think I can get with the dev tools what I need.
You could overwrite console.log but that could annoy you later on. I prefer to have my own logger like your devLog function. It's more explicit.
As #Barmar suggested in the comments you could instead check the localStorage on load instead of on every call. I have something similar to the below in a few projects of mine:
{
let devConfig = JSON.parse(localStorage.getItem('dev_config'));
devLog = (...args) => {
if (devConfig) {
const time = new Date().toLocaleTimeString()
console.log(`[${time}] `, ...args)
}
};
devLog.activate = () => {
devConfig = true;
localStorage.setItem('dev_config', true)
}
devLog.deactivate = () => {
devConfig = false;
localStorage.setItem('dev_config', false)
}
devLog.toggle = () => {
if ( devConfig ) {
devLog.deactivate()
} else {
devLog.activate()
}
}
}
Since when you're reading dev_config from the localStorage for the first time you'll get null it will start off deactivated - which seems like a good default to me.

Ionic/Angular Square Web Payments

I'm currently developing an application in Ionic 5. One of the only things left to do is integrate Square Payments into the app. Currently I load the script using a script service and then run the code to generate the payment form etc:
/// within ngOnInit
this.scriptService.load('square-sandbox').then(async data => {
const payments = await Square.payments(this.applicationId, this.locationId);
//Card payments
let card;
const cardButton = document.getElementById(
'card-button'
);
const backBtn = document.getElementById('toggle-drawer');
try {
card = await this.initializeCard(payments);
} catch (e) {
console.error('Initializing Card failed', e);
return;
}
//googlepayments
let googlePay;
try {
googlePay = await this.initializeGooglePay(payments);
} catch (e) {
console.error('Initializing Google Pay failed', e);
}
async function handlePaymentMethodSubmission(event, paymentMethod) {
event.preventDefault();
try {
const token = await this.tokenize(paymentMethod);
const paymentResults = await this.createPayment(token);
console.log("SUCCESS")
setTimeout(()=>{
window.location.href = "/payment-success";
},250);
console.debug('Payment Success', paymentResults);
} catch (e) {
cardButton.setAttribute("disabled ", "false");
backBtn.setAttribute("disabled ", "false");
console.log('FAILURE');
console.error(e.message);
}
}
if (googlePay !== undefined) {
const googlePayButton = document.getElementById('google-pay-button');
googlePayButton.addEventListener('click', async function (event) {
await handlePaymentMethodSubmission(event, googlePay);
});
}
}).catch(error => console.log(error));
this part all works fine. However after the loading of square.js it seems to take over the application? All console.logs will come from square.js and all routing seems to also go through square.js with angular throwing an error.
As you can see the first console log is thrown out by page.ts, then the rest are by square despite them actually being called from a function within page.ts. The navigation is also now apparently triggered outside the Angular zone. I'm not sure what is going on and hoping someone can point me in the right direction.
EDIT: I forgot to mention window.location.href makes everything work fine again if this helps. But it isn't ideal as it fully refreshes the page.
Thanks
I ran into something similar in NG12. I added zone.js rxjs patching to the polyfills.ts file in angular to resolve
/**************************************************************************> *************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js/dist/zone-patch-rxjs';
Reference: https://betterprogramming.pub/zone-js-for-angular-devs-573d89bbb890

Bootstrap V5 Modal not hiding via Javascript Unless Window Method

I'm currently developing a react app and using normal bootstrap.The command to show a modal and toggle to one works fine; however the hide command doesn't hide the modal unless I make it a property on window.
For example:
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = new bootstrap.Modal(currElement);
const secondModal = new bootstrap.Modal(element);
setLoading(false);
// #ts-expect-error
window.modal.hide(); // works fine
// second.modal.hide() doesn't work
new bootstrap.Modal(element).show();
resetForm();
}
}
However, I notice that on Chrome dev tools the _isShown is changing correctly to false
I was able to figure out a fix. The solution for anyone encountering this in the future is to not use the 'new bootstrap.Modal()' constructor syntax but to use the getInstance method on the modal.
Changing my code to the below caused it to work completely fine and without the use for creating a function on the window.
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = bootstrap.Modal.getInstance(currElement);
setLoading(false);
currentModal?.hide()
bootstrap.Modal.getInstance(element)?.show();
resetForm();
}
}

Nightwatch: Using of custom commands inside page objects

For the new version of a product I decided to try a page objects approach instead of using views and probably I started to use it wrong.
We have a custom command that simply waits for an element and clicks (waitAndClick.js):
exports.command = function(selector, callback) {
return this
.waitForElementPresent(selector, 30000)
.click(selector, callback);
};
It works perfectly inside the test:
const {client} = require('nightwatch-cucumber');
const {defineSupportCode} = require('cucumber');
defineSupportCode(({Given, Then, When}) => {
Given(/^I enable Dashboard management$/, () => {
return client.waitAndClick('[id=enableManagement]');
});
});
But when I am trying to use it inside the page object it throws an error:
module.exports = {
url() {
return this.api.launchUrl;
},
elements: {
username: '[name="_Nitro_Login_username"]',
password: '[name="_Nitro_Login_password"]',
enter_button: '[title="Enter"]'
},
commands: [
{
loginAs(username, password) {
return this.waitForElementVisible('#username', 50000)
.setValue('#username', username)
.setValue('#password', password)
.waitAndClick('#enter_button')
.waitForElementNotPresent('#enter_button', 50000);
}
}
]
};
I also tried with .api.waitAndClick('#enter_button'), the same result.
And the error message:
Error while running click command: Provided locating strategy is not
supported: [title="enter"]. It must be one of the following:
class name, css selector, id, name, link text, partial link text, tag
name, xpath
at Object.exports.command (/Users/eaflor/dev/jive-next/test/ui/commands/waitAndClick.js:9:63)
at Object.F.command (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/api.js:274:31)
at Object.commandFn (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/api.js:287:24)
at AsyncTree.runCommand (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:154:30)
at AsyncTree.runChildNode (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:114:8)
at AsyncTree.walkDown (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:80:10)
at AsyncTree.walkUp (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:97:8)
at AsyncTree.walkDown (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:90:12)
at AsyncTree.traverse (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:73:8)
at F.onCommandComplete (/Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/core/queue.js:131:12)
at F.g (events.js:291:16)
at emitNone (events.js:86:13)
at F.emit (events.js:185:7)
at /Users/eaflor/dev/jive-next/node_modules/nightwatch/lib/api/client-commands/_locateStrategy.js:18:10
at _combinedTickCallback (internal/process/next_tick.js:67:7)
at process._tickCallback (internal/process/next_tick.js:98:9)
Is it even possible to use custom commands inside the page object?
I found the way to fix it. In order to use custom commands in page objects you have to write them in class-style: http://nightwatchjs.org/guide#writing-custom-commands
Here how it should look like:
var util = require('util');
var events = require('events');
function waitAndClick() {
events.EventEmitter.call(this);
}
util.inherits(waitAndClick, events.EventEmitter);
waitAndClick.prototype.command = function(selector) {
const api = this.client.api;
api
.waitForElementPresent(selector)
.click(selector, () => {
this.emit('complete');
})
;
return this;
};
module.exports = waitAndClick;
Hope it will help someone.
I'm still new with Nightwatch, but I'm using commands in Page Objects like this:
commands: [
{
login: function() {
this.api
.waitForElementVisible('body', 2000)
.setValue(this.elements.username.selector, user.loginUser) //here I'm inputting username from json file into element that I've defined in this page object
.setValue(this.elements.password.selector, pass.loginPass) //here I did same thing with password
.pause(500)
.click(this.elements.submitButton.selector) //here I'm clicking on predefined button element
}
}
]
Works perfectly, and it's very readable. This is simple login command. Hope this helps.
Cheers
Too late for an answer here but might help others facing similar issue. Returning this could fix the chaining issue from the page object.
exports.command = function(selector, callback) {
this
.waitForElementPresent(selector, 30000)
.click(selector, callback);
return this;
};

firefox detect tab id in "sdk/system/events" api

Good day.
i have problem with porting chromium extension to firefox.
i need to detect all outgoing request and id's of tabs to which it belongs.
to detect requests i using system/events api, but i can't find a way how to detect id of tab from incomming events. As i understand this events is xpcom objects and i should use QueryInterface to get some interface to get some other interface to get some other interface to get some other interface ..... to get some other interface to get id of tab from it (just like in COM implementation in windows), but i can't find which interface i need, can't find documentation about this events at all...
code which i using in chromium:
chrome.webRequest.onBeforeRequest.addListener(
function(info) {
if(info.tabId)
//do stuff here
}
so it's what i want to achieve from firefox...
code which i currently write for firefox:
exports.main = function(options)
{
//stuf here ....
........
function listener(event)
{
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
console.log(channel);
//TODO: get tab here somehow
}
events.on("http-on-opening-request", listener);
}
i have looked on xpcom docs few days, but still have not enough info to implement this simple thing... so if someone have success with this, please help.
I just found a code snippet for getting the browser that fires the http-on-modify-request notification. The code there seems to be broken but I used some of it to create this function to get a tab from the channel.
const getTabFromChannel = (aChannel) => {
try {
let notificationCallbacks = aChannel.notificationCallbacks || aChannel.loadGroup.notificationCallbacks;
if (!notificationCallbacks)
return null;
let domWin = notificationCallbacks.getInterface(Ci.nsIDOMWindow);
let chromeTab = tabsUtils.getTabForContentWindow(domWin);
return getSdkTabFromChromeTab(chromeTab);
}
catch (e) {
// some type errors happen here, not sure how to handle them
console.log(e);
return null;
}
}
This function converts the low-level tab to a high-level tab. Depending on which one you need you could skip this function of course. Again, in the latest SDK you probably can replace it with tabs.viewFor(chromeTab).
const tabs = require("sdk/tabs");
const tabsUtils = require("sdk/tabs/utils");
const getSdkTabFromChromeTab = (chromeTab) => {
const tabId = tabsUtils.getTabId(chromeTab);
for each (let sdkTab in tabs){
if (sdkTab.id === tabId) {
return sdkTab;
}
}
return null;
};
There seems to be a problem that the listener fails when switching between windows when using system/events. Use Services.obs.addObserver instead:
const httpRequestObserver = {
observe: function (subject, topic, data) {
var channel = subject.QueryInterface(Ci.nsIHttpChannel);
console.log("channel");
var tab = getTabFromChannel(channel);
if(tab) {
console.log("request by tab", tab.id);
}
}
}
exports.main = function() {
Cu.import('resource://gre/modules/Services.jsm');
Services.obs.addObserver(httpRequestObserver, 'http-on-opening-request', false);
}
I can only hope that it works for all the requests you need to detect. The documentation already mentions some cases where it won't work:
Note that some HTTP requests aren't associated with a tab; for example, RSS feed updates, extension manager requests, XHR requests from XPCOM components, etc.
The article Listening to events on all tabs describes how to set up web progress listeners for tabs. With this listener you can get requests and redirects.
const tabsUtils = require("sdk/tabs/utils");
const listener = {
QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
onLocationChange: (browser, progress, request, uri) => {
let tab = tabsUtils.getTabForContentWindow(progress.DOMWindow);
// ...
},
onStateChange: (browser, progress, request, state) => {
let tab = tabsUtils.getTabForContentWindow(progress.DOMWindow);
// ...
}
// ...
};
getChromeWindow(sdkWindow).getBrowser().addTabsProgressListener(listener);
At some point you may need to convert between low- and high-level tabs or chrome/dom/sdk windows which is implemented really bad and confusing. An sdk window in this case is one you get with windows.browserWindows, the chrome window has a reference to the gBrowser. If you are using the latest sdk maybe this helps: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_API/tabs#Converting_to_XUL_tabs and https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/windows#Converting_to_DOM_windows. I used this function to get the chrome window from a sdk window: https://bugzilla.mozilla.org/show_bug.cgi?id=695143#c15
const { BrowserWindow } = require('sdk/windows');
const { windows } = require('sdk/window/utils');
function getChromeWindow(sdkWindow) {
// to include private window use the as second argument
// { includePrivate: true }
for (let window of windows('navigator:browser'))
if (BrowserWindow({window: window}) === sdkWindow)
return window;
return null;
}

Categories