Electron app: How to make dialog.showOpenDialog modal - javascript

In my small Electron app I have a couple of buttons to allow the user to browse for folders to use for the processing the app does. The call to open the dialog to do this passes the ID of the main browser window so that the dialog will be modal and this initially appeared to work. The buttons in the app main screen appear to be disabled. However, if the disabled buttons are clicked, when the dialog is closed those clicks are executed. This true of all the buttons in the main screen. If I click on the disabled "Cancel" button while the dialog is showing the app closes when the dialog is closed.
Seems to me that one should not be able to switch back to the parent of a modal dialog and "store" clicks.
The dialog.showOpenDialog call is made in the renderer process, is this possibly the issue?
Sid

In the renderer process you need to use the browser window reference, not the ID. You can get the reference in the renderer process by using: remote.getCurrentWindow(). You can make the call as follows. The example is specifically for opening multiple files, should be configured as needed of course:
const { remote } = window.require('electron')
const dialog = remote.dialog
dialog.showOpenDialog(
remote.getCurrentWindow(),
{ properties: ['openFile', 'multiSelections'] },
(filePaths?) => {
// do your thing
}
)

Not sure what was going on previously, I now cannot reproduce the problem, so I am going to mark this for closure.
Sorry for the noise,
Sid

You don't need electron/remote to achieve this, in fact, electron/remote is deprecated. The key is to provide the browser window's reference to the showOpenDialog API (in the main process). This is a general API pattern in window managers: in order to have a modal window, you have to specify a parent window. Code sample:
const ipcApi = {
'select-file': async () => {
// It's important to pass the window handler in showOpenDialog
// in order to make the dialog modal
const browserWindow = BrowserWindow.getFocusedWindow()
if (browserWindow) {
return dialog.showOpenDialog(browserWindow, { properties: ['openFile'] })
}
return
},

Related

Is there anyway to find whether the add to home screen - app icon has been added to home screen using Javascript?

Configured our app to support Add to home screen option, to ask for permission we added one button, onclick the prompt will ask to add the icon in home screen. If the user keep clicking close, then it wont ask further, so the button becomes non-functional.
If the user already added icon, i didn't get any method to find it.
There isn't any method best known to me to capture whether the app icon has been added to home screen or not. Simple reason for that could be absence of any valid existing use case. However, what you can capture is the action taken by the user. When the A2HS banner is shown, you can tap into the beforeinstallprompt event to determine the choice made by the user when presented with the banner.
The code below shows this in action:
window.addEventListener('beforeinstallprompt', function(event) {
event.userChoice.then(function(result) {
if(result.outcome == 'dismissed') {
// User dismissed
}
else {
// User accepted
}
});
});
UPDATE:
While going through the official doc for A2HS, found a way to determine if the app was successfully added to the users home screen after they accepted the prompt, you can listen for the appinstalled event. Code:
window.addEventListener('appinstalled', (evt) => {
app.logEvent('a2hs', 'installed');
});
appinstalled does not work
window.addEventListener('appinstalled', (evt) => {
app.logEvent('a2hs', 'installed');
});

How to use a Dialog throught a app, passing content to it

I am building my first Vue.js app and I want to open a Dialog (using Element.io) components. I want to call a Dialog and pass the content to it.
So my dialog would be "global" and I would pass content to it from different components.
How do I include this dialog? how do I pass arguments to it and so on?
Should I use something like:
import Dialog from '../GlobalComponents/Dialog.vue';
Vue.prototype.$dialog = Dialog;
or include it in each component, and how would that be?
I have no problem making a event handler for the toggle of the Dialog, just don't know how to call the dialog to open it (or change its dialogVisible state).
You can simply add the Dialog as vue component to make it available globally.
Vue.component('my-dialog', Dialog);
Then in your "main" file / index.html or whatever you use to start your Vue app you define your dialog
<my-dialog></my-dialog>
If you want it to display errors for example you can use emits and listenters
Vue.prototype.$bus = new Vue(); // event buts
in the created method of your my-dialog
created: function() {
this.$bus.$on('error', function(msg) {
// access message here
// make dialog visible
});
}
And wherever an error occurs
this.$bus.$emit('error', 'this is my error');
If your dialog is more complex you can ofcourse also pass objects instead of strings in the emit

Prevent user to navigate away with unsaved changes

I'm currently using Backbone.Marionette to create a SPA and in one of the views it is possible for the user to navigate away with unsaved changes. I can control some of these events, like buttons and menu options within the view that would take the user away, but some others would require to manipulate either Backbone.Router or work with the DOM events directly.
I already tried listening to beforeunload (doesn't work as the application is still loaded) and hashchange (doesn't work as you cannot stop the browser from navigating away). These solutions (1, 2, 3) don't work in this case, the Javascript is never unloaded.
Changing the Backbone.Router seems to be the best option, but because of how it is initialized I don't think it is possible to introduce this feature or at least I cannot find a way of doing it. This solution, for example, doesn't work because hashchange is not cancelable (you cannot call stopPropagation on it), and this other solution doesn't work because navigate is not defined on the Backbone.Router object.
Any suggestions?
I've managed to find a solution to this, although some more work is required. For this solution, I am assuming that you keep track when a view is dirty.
There are 4 main ways of moving out of a view;
Click on a link on the view
Click on link outside the view
Click on refresh or external link
Click on back/forward on the browser
1. Application link
This is the easiest case. When you click on your own link, you have to check if your view is dirty. For example, I have an in-app back button that is handled by a historyBack function. On the view:
historyBack: function() {
if (this.isDirty) {
answer = confirm("There are unsaved changes.\n\nDo you wish to continue?")
if (answer) {
this.isDirty = false
window.history.back()
}
}
else {
window.history.back()
}
}
2. Links outside your view
This type of interaction can be handled by extending the Router prototype's execute method, not the navigate method as proposed in other places.
There should be a variable somewhere accessible by the Router that stores the state of the view. In my case, I'm using the Router itself and I update this variable every time I change the dirty flag on the view.
The code should look something like this:
_.extend(Backbone.Router.prototype, {
execute: function (callback, args, name) {
if (Backbone.Router.isDirty) {
answer = confirm "There are unsaved changes.\n\nDo you wish to continue?";
if (!answer) {
return false;
}
}
Backbone.Router.isDirty = false
if (callback) callback.apply(this, args)
}
}
3. Refresh or external link
Refresh and external links actually unload your Javascript so here the solutions based on beforeunload (see question) actually work. Wherever you manage your view, I use a controller but let's assume it's on the same view, you add a listener on show and remove it on destroy:
onShow: function() {
$(window).bind("beforeunload", function (e) {
if (this.isDirty) {
return "There are unsaved changes.";
}
}
}
onDestroy: function() {
$(window).unbind("beforeunload");
}
4. Back/Forward on the browser
This is the trickiest case and the one I haven't figured out completely yet. When hitting back/forward, the user can navigate out of the app or within the app, both cases are covered by the code on 1 and 3, but there is an issue I can't figure out and I will create another question for it.
When hitting back/forward, the browser changes the address bar before calling the router so you end up with an inconsistent state: The address bar shows a different route to the application state. This is a big issue, if the user clicks again on the back button, after saving or discarding the changes, she will be taken to another route, not the previous one.
Everything else works fine, it shows a pop up asking the user if she wants to leave or continue and doesn't reload the view if the user chooses to stay.

javascript property race condition

I have a phonegap project on iPhone and Android. The issue appears to be a race condition on the surface, but I don't understand how it happens. Users are able to click on a button which has a closure callback that sets a property of an object, and then clears the screen and loads the main menu. In code:
button.onclick = function (employee) {
return function () {
employee.task = "some task";
returnToMenu();
}
}(employees[i]);
After the user is back on the main menu, they can click on a button that loads a screen which displays all the users. If an employee has that task property set, additionally formatting should be done to the button for that employee.
if (employee.task)
// style the button being created for this employee
Somehow, if one clicks fast enough, the formatting is not done. If you click back (to the main menu), and reload the screen, the formatting is now done. Given the code above, I do not see how employee.task could possibly return undefined after the menu has been loaded. What's going on here?

showSettings callback in Flex?

I am pretty new to flex, so forgive me if this is an obvious question.
Is there a way to open the Security.showSettings (flash.system.Security) with a callback? or at least to detect if it is currently open or not?
My flex application is used for streaming audio, and is normally controlled by javascript, so I keep it hidden for normal use (via absolute positioning it off the page).
When I need microphone access I need to make the flash settings dialog visible, which works fine, I move it into view and open the dialog.
When the user closes it, I need to move it back off the screen so they don't see an empty flex app sitting there after they change their settings.
thanks :)
If you do something like this, it will work in some situations:
var mic:Microphone = Microphone.getMicrophone();
mic.addEventListener(StatusEvent.STATUS, onMicStatus);
If you are just trying to use the microphone and relying on Flash to pop up the dialog to ask the user for permission, Flash will open a dialog with two buttons, Allow and Deny. When the user clicks one of the buttons the StatusEvent will fire, the dialog will close, and you can move the flex app out of the way.
If you are manually opening the settings panel (via Security.showSettings), you get the panel with Allow and Deny radio buttons, and the event will fire when the user clicks on the radio buttons, not when they close the panel, which is probably of less help to you.
Update: flex 4 solution
So when I moved to the flex 4 and started compiling my mxml with adobe's open source mxmlc compiler, the solution below no longer worked because the alert doesn't lose focus when you're in the settings anymore.
As far as I could tell I had to move to a less elegant solution where the user must click "OK" on the alert box every time they are done with the settings.
Here is the new code:
private function setup_smart_hide():void {
// Call this function whenever you make the flex app visible (or add it
// to a show_flex() function if you have such a thing set up)
alert = Alert.show('Click "OK" to continue.', 'Thanks!', Alert.OK, null, function(e:CloseEvent):void {
// calls the external javascript to hide the flex app
hide_self();
});
}
OLD: (flex 3) Got this working...
private function setup_smart_hide():void {
alert = Alert.show('Thanks');
alert.addEventListener(FocusEvent.FOCUS_IN, function(event:FocusEvent):void {
// javascript to hide the flex app
ExternalInterface.call("SB.flex.hide");
});
alert.addEventListener(FocusEvent.FOCUS_OUT, function(event:FocusEvent):void {
// javascript to show the flex app
ExternalInterface.call("SB.flex.show");
});
alert.setFocus();
}
Which is run first thing in the init() function... the only problem is (like Wesley said), the focusOut event occurs when the flex app itself loses focus as well.

Categories

Resources