How to prevent multiple dialog box in electron - javascript

I am opening some message boxes with electron's dialog.showMessageBox.
But it is currently opening multiple boxes. I would like to open only one message box at a time.
fetch(payload.url)
.then(res => res.json())
.then(data => {
download(BrowserWindow.getFocusedWindow(), data.presigned_url, {
saveAs: true,
// openFolderWhenDone: true,
showBadge: false,
filename: payload.filename
}).then(item => {
const filePath = item.getSavePath();
dialog.showMessageBox(
BrowserWindow.getFocusedWindow(),
{
type: 'info',
title: 'Download Status',
message: 'Download Complete',
buttons: ['Close', 'Open Folder', 'Open'],
},
dialogId => {
switch (dialogId) {
case 0:
break;
case 1:
shell.showItemInFolder(filePath);
break;
case 2:
shell.openItem(filePath);
default:
break;
}
},
);
});
});
For example, I would like to close last message box when a new one opens, OR just not allow opening next message box if one is already open.
Any form help would be appreciated.

In my project,
I give a state property, for e.g isOpenBrowseFileDialog and use it like a flag. If the dialog is launched, the flag is set to true and it will help to prevent open many dialogs.
browseFiles = async (e) => {
e.preventDefault();
const ref = this;
const { isOpenBrowseFileDialog } = this.state;
if(isOpenBrowseFileDialog === false) {
this.setState({
isOpenBrowseFileDialog: true
});
remote.dialog.showOpenDialog({
properties: ['openFile', 'multiSelections']
}, async (files) => {
if (files !== undefined) {
await ref.handleFilesAfterAdding(files);
}
ref.setState({
isOpenBrowseFileDialog: false
});
});
}
}

Related

Add or remove content script on click of a toggle button inside a chrome popup?

I am developing my first chrome extension, I want to have a toggle button on my popup which allows users to enable or disable the extension for that site, though not completely disable the extension but technically control the execution of the content.js for that particular domain.
Something similar to
popup.js
const toggleBtn = document.querySelector(".toggle");
toggleBtn.addEventListener("click", function(){
if(toggleBtn.checked === true){
console.log('inject content.js');
}else{
console.log('remove content.js');
}
});
I can see there is a way to inject it but there is no way to remove it.
chrome.scripting.executeScript({
target: {tabId},
files: ['content.js'],
});
Note, I don't want to reload the page but want to disable the content.js. How can I achieve this functionality inside my chrome extension. I see there are many extensions doing this like Adblocker and Wordtune.
I went through few SO threads like this one but most of them are for Manifest V2 and few packages that are recommended are obsolate now. I also could not find a complete example anywhere.
Todo
I should be able to turn the extension on/off for the current domain from within the Popup.
It should store the state inside a chrome.storage.sync so that next time I installed the extension I should not again turn on/off for the selected websites.
As soon as I toggle the button, the changes should be visible if I have opened the same domain in another tab.
The extension's icon should be changed in real time as well e.g grey icon for disabled status
If I close and open a website, the icon and the toggle button should be restored their on/off status and update the icon accordingly.
My Attempt (Solves #toDo 1,2,3,4,5)
background.js
const icons = {
enabled: {
'16': 'icon-16.png',
'19': 'icon-19.png',
'38': 'icon-38.png',
'48': 'icon-48.png',
'128': 'icon-128.png'
},
disabled: {
'16': 'icon-16-off.png',
'19': 'icon-19-off.png',
'38': 'icon-38-off.png',
'48': 'icon-48-off.png',
'128': 'icon-128-off.png'
}
};
const getExtensionStatus = host => new Promise((resolve, reject) => {
chrome.storage.sync.get(host, result => {
if (result[host] !== undefined) {
resolve(result[host]);
} else {
setExtensionStatus(host, true);
}
});
});
const setExtensionStatus = (host, toggleBtnStatus) => {
const data = { [host]: toggleBtnStatus };
chrome.storage.sync.set(data, () => {});
};
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "install") {
chrome.action.setIcon({ path: icons.enabled });
}
});
const init = async tab => {
const url = new URL(tab.url);
if (url.protocol !== "chrome-extension:") {
const host = url.host;
const status = await getExtensionStatus(host);
const icon = status ? icons.enabled : icons.disabled;
chrome.action.setIcon({ path: icon });
}
};
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete") init(tab);
});
chrome.tabs.onActivated.addListener(info => {
chrome.tabs.get(info.tabId, init);
});
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.type === "getExtensionStatus") {
const status = await getExtensionStatus(request.host);
sendResponse({ status });
return true;
} else if (request.type === "setExtensionStatus") {
setExtensionStatus(request.host, request.status);
const icon = request.status ? icons.enabled : icons.disabled;
chrome.action.setIcon({ path: icon });
}
});
popup.js
document.addEventListener("DOMContentLoaded", function () {
const toggleBtn = document.querySelector(".toggle");
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const host = new URL(tabs[0].url).host;
chrome.runtime.sendMessage({ type: "getExtensionStatus", host }, (response) => {
toggleBtn.checked = response.status;
});
toggleBtn.addEventListener("click", () => {
chrome.runtime.sendMessage({ type: "setExtensionStatus", host, status: toggleBtn.checked });
});
});
});
What left?
The content.js is not available after some time of inactiveness of the tab. How can I inject the script again and avoid running event multiple times?
content.js
document.addEventListener("keydown", listenEnterkey);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "setExtensionStatus") {
console.log(request.status);
if (request.status) {
// Add event listener here
document.addEventListener("keydown", listenEnterkey);
} else {
// Remove event listener here
document.removeEventListener("keydown", listenEnterkey);
}
}
});
For example, if you add a button with a script, I don't think the added button will disappear even if you remove the script.
Therefore, the ON/OFF function is built into the script, and it is operated by the message.
manifest.json
{
"name": "content_scripts + popup",
"version": "1.0",
"manifest_version": 3,
"content_scripts": [
{
"js": [
"matches.js"
],
"matches": [
"<all_urls>"
]
}
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html"
}
}
matches.js
console.log("matches.js");
let isEnabled = true;
console.log(isEnabled);
chrome.runtime.onMessage.addListener((message) => {
switch (message) {
case "on":
isEnabled = true;
break;
default:
isEnabled = false;
break;
}
console.log(isEnabled);
});
popup.js
const send = (s) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, s);
});
}
document.getElementById("on").onclick = () => {
send("on");
}
document.getElementById("off").onclick = () => {
send("off");
}
popup.html
<html>
<body>
<button id="on">on</button>
<button id="off">off</button>
<script src="popup.js"></script>
</body>
</html>

Pop up message not await for user response and moving on on onclick function

I have this logic on changing radio-button selection, if the user made some changing I am showing a message. if he confirm it will enter Onconfirm, else - Onreject.
1 issue -> the change of the radio button happens before the message show.
2 issue -> one reject I want to cancel the choice he made and to undo to his last choise - whice not happenning.
please help me with this!!
radio button
<div class="right" *ngFor="let type of types">
<p-radioButton name="treesDetailsType" [(ngModel)]="oneselectedType" formControlName="selectedType" (onClick)="onChangeType(type,$event)" class="treeDetails" value="{{type.id}}" label="{{type.desc}}" [disabled]="isReadOnly && type.id != data.selectedType"></p-radioButton>
</div>
the function of onclick
onChangeType(type, $event) {
let isFormTouched = this.isFormTouched(type);
if (isFormTouched) {
this.messagingService.showConfirmById(44, () => {
this.onConfirm()
}, () => {
this.onReject($event);
});
}
else
this.onchangedTrue(type); //this set some validators for the choice
}
on reject
#HostListener('click', ['$event']) onReject($event) {
event.stopImmediatePropagation();
//whatever written here its not happens before the change !!!!!
console.log(event);
}
----edited after the perfect suggestion from #Eliseo
askConfirm(value: any) {
let isFormTouched = this.isFormTouched(value);
if (isFormTouched) {
this.messagingService.showConfirmById(44, () => {
this.oneselectedType = value;
this.fg.controls.selectedType.setValue(value);
}, () => {
this.radios.forEach(x => {
x.writeValue(this.oneselectedType);
})
},
);
}
else {
this.oneselectedType = value;
this.onchangedTrue(value);
}
}`
the code work perfectly without the condition
--edited - on get the value from the server and patch it - the radio button lost
There a problem in my code (the another answer). Really I'm not pretty sure the reason, so I create a function like
redraw()
{
const value = this.form.value.type;
this.radios.forEach((x) => {
x.writeValue(value)
});
}
So, my function "ask" becomes like
ask(value: any) {
this.confirmationService.confirm({
message: 'Do you want to choose ' + value + '?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
this.form.get('type').setValue(value);
},
reject: () => {
this.redraw()
},
});
}
This allow me, when change the form, call to the function redraw. If I has a function
getForm(data: any = null) {
data = data || { type: 1, prop: '' };
return new FormGroup({
type: new FormControl(data.type),
prop: new FormControl(data.prop),
});
}
I can do some like
loadData(id: number) {
this.dataService.getData(id).subscribe((res: any) => {
this.form = this.getForm(res);
//it's necesary call to the function this.redraw
this.redraw()
});
}
newData() {
this.form = this.getForm();
//it's necesary call to the function this.redraw
this.redraw()
}
See in the this stackblitz what happens if we don't call to this.redraw() (just comment the lines)
1.-Select "new York" and say that you don't want it
2.-Click the button to load user
As "user" has the type 3 -"new York", the radio buttons looks like that it's not selected.
Yes is an ugly work-around, but for now I can not imagine another solution
Well there're another approach, that is change the value as usually and if we say that we want not the value, return the old value
askAfterChange(value:any)
{
const oldValue=this.form2.value.type;
this.form2.get('type').setValue(value)
this.confirmationService.confirm({
message: 'Do you want to choose ' + value + '?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
},
reject: () => {
this.form2.get('type').setValue(oldValue);
},
});
}
The "key" is split the [(ngModel)] in [ngModel] and (ngModelChanged)
//NOT WORK yet
<p-radioButton ... [ngModel]="selectedType"
(ngModelChange)="askConfirm($event)">
askConfirm(value: any) {
this.confirmationService.confirm({
message: 'Are you sure do you want '+value+'?',
header: 'Delete Confirmation',
icon: 'pi pi-info-circle',
accept: () => {
this.selectedType=value
},
reject: () => {
},
key: "positionDialog"
});
}
Well the problem is that the element still show the value selected How resolved? The first is get our p-radio buttons using ViewChildren, so we are give a template reference variable (the same to all the buttons) see the #radio
<div *ngFor="let type of types" class="p-field-radiobutton">
<p-radioButton #radio ...
(ngModelChange)="ask($event)"
[ngModel]="oneselectedType" ></p-radioButton>
</div>
//get the "radio buttons"
#ViewChildren('radio', { read: RadioButton }) radios!: QueryList<RadioButton>
constructor(private confirmationService: ConfirmationService) { }
ask(value: any) {
this.confirmationService.confirm({
message: 'Do you want to choose this?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
//if accept
this.oneselectedType = value
},
reject: () => {
//else, we loop over all the "radios"
this.radios.forEach(x => {
//and force is checked
x.writeValue(this.oneselectedType);
})
}
});
}
If you're using reactive Forms, you can also use a [ngModel] (ngModelChange) in the way, see that the model is myForm.get('selectedType').value
<p-radioButton ... [ngModel]="myForm.get('selectedType').value"
(ngModelChanged)="askConfirm($event)"
[ngModelOptions]="{standalone:true}"
>
And change in askConfirm
askConfirm(value: any) {
this.confirmationService.confirm({
...
accept: () => {
this.form.get('oneselectedType').setValue(value)
},
reject: () => {
this.radios.forEach(x => {
//and force is checked
x.writeValue(this.form.value.oneselectedType);
})
},
key: "positionDialog"
});
}
a simple stackblitz
Well, In the stackblitz I hard-code the value of the formGroup. Generally we has a service so we can
1.-Define our Form
form=new FormGroup({
selectedCity:new FormControl(),
selectedColor:new FormControl(),
prop:new FormControl()
})
//And in ngOnInit
this.dataService.getData().subscribe(res=>{
this.form.patchValue(res)
})
Or 2.-simple declare our form
form:FormGroup
//and in ngOnInit
use in ngOnInit
this.dataService.getData().subscribe(res=>{
this.form=new FormGroup({
selectedCity:new FormControl(res.selectedCity),
selectedColor:new FormControl(res.selectedColor),
prop:new FormControl(res.prop)
})
})
If we need a default value, we can give the value at first
(the stackblitz has in code this options)

Why does chrome.storage.sync.set not load user setting instantly?

For my chrome extension's options page I have two radio buttons to switch between showing and not showing the buttons on a page. The function works but it takes me a few reloads of the web page before the changes take effect. Does it take some time to save to chrome.storage before the changes take effect/can be used?
here is the JS for my options page:
const saveBtn = document.getElementById('save-btn');
saveBtn.addEventListener('click', () => {
if(document.getElementById('show_btns').checked){
console.log('true');
chrome.storage.sync.set({ btn_disp: true });
} else {
console.log('false');
chrome.storage.sync.set({ btn_disp: false });
}
});
Background script waits for page to load then injects content scripts and css and sends user data with message.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
//inject the script into page
//only inject on complete load
//listening for the 'complete' message from tabs
if(changeInfo.status === 'complete' && /^http/.test(tab.url)){
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['./src/js/foreground.js']
})
.then(() => {
console.log('INJECTED');
})
.catch(err => console.log(err));
chrome.scripting.insertCSS({
target: {tabId: tabId},
files: ['./src/css/page_btn_styles.css']
})
.then(() => {
console.log('page_btn_styles.css injected');
sendToForeground();
})
.catch(err => console.log(err));
}
});
//page loaded -> load buttons
function sendToForeground() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { message: 'page_loaded', data: user_data }, (response) => {
console.log('page_loaded message sent');
});
});
}
My content script listens for a message from the background script that the page has loaded. The background script sends the user data object with the message.
//message from background.js that page has loaded
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if(request.message === 'page_loaded'){
const allElements = document.querySelectorAll('*');
for(let i = 0; i < allElements.length; i++) {
if(allElements[i].className.toString().match(/ingredient/g)){
console.log(request.data);
loadPageBtns(request.data);
break;
}
}
}
sendResponse({});
});
JS for loading page buttons in the content script
function loadPageBtns(user_data) {
if(user_data.btn_disp){
//container for buttons on page
let container = document.createElement('div');
container.id = 'btns-container';
//ingredients button
let ingredientsBtn = document.createElement('button');
ingredientsBtn.id = 'ingredients-btn';
ingredientsBtn.classList.add('page-btns');
ingredientsBtn.classList.add('animated');
ingredientsBtn.classList.add('bounceInLeft');
ingredientsBtn.innerText = 'Get Ingredients';
ingredientsBtn.addEventListener('click', () => {
scrollToIngredients();
});
ingredientsBtn.addEventListener('mouseover', () => {
ingredientsBtn.classList.remove('animated');
});
//load event handler for click on recipe
//recipe button
let recipesBtn = document.createElement('button');
recipesBtn.id = 'recipes-btn';
recipesBtn.classList.add('page-btns');
recipesBtn.classList.add('animated');
recipesBtn.classList.add('bounceInLeft');
recipesBtn.innerText = 'Get Recipe';
recipesBtn.addEventListener('click', () => {
scrollToDirections();
});
recipesBtn.addEventListener('mouseover', () => {
recipesBtn.classList.remove('animated');
});
container.appendChild(ingredientsBtn);
container.appendChild(recipesBtn);
document.body.appendChild(container);
}
}
Chrome storage does not seem to save anything when the data is false, i.e. chrome.storage.sync.set({ btn_disp: false });
does not store anything. If it is true then it does store the item.
This can be a problem depending on your default value. If you have a default of TRUE then you end up never being able to store a FALSE value.
I resolved this by storing text true or false rather then boolean.
I can't see your retrieve and default logic so I'm not sure if this is the root of your problem.

Stripe js: don't let empty form be sent

I'm trying to avoid letting users submit stripe form when inputs are empty, I`m using stripe.js elements integration to render my form and handle form submition inside my vue component.
this.cardNumberElement.on('change', this.enableForm);
this.cardExpiryElement.on('change', this.enableForm);
this.cardCvcElement.on('change', this.enableForm);
After checking the docs I tried to use the change event on inputs but this is not working sice the user can just not type anything and click submit button.
This is my component:
mounted()
{
console.log(this.$options.name + ' component succesfully mounted');
this.stripe = Stripe(this.stripePK);
this.elements = this.stripe.elements();
this.cardNumberElement = this.elements.create('cardNumber', {style: this.stripeStyles});
this.cardNumberElement.mount('#card-number-element');
this.cardExpiryElement = this.elements.create('cardExpiry', {style: this.stripeStyles});
this.cardExpiryElement.mount('#card-expiry-element');
this.cardCvcElement = this.elements.create('cardCvc', {style: this.stripeStyles});
this.cardCvcElement.mount('#card-cvc-element');
let stripeElements = document.querySelectorAll("#card-number-element, #card-expiry-element, #card-cvc-element");
stripeElements.forEach(el => el.addEventListener('change', this.printStripeFormErrors));
this.cardNumberElement.on('change', this.enableForm);
this.cardExpiryElement.on('change', this.enableForm);
this.cardCvcElement.on('change', this.enableForm);
},
methods:
{
...mapActions('Stripe', ['addSource', 'createSourceAndCustomer']),
...mapMutations('Stripe', ['TOGGLE_PAYMENT_FORM']),
...mapMutations('Loader', ['SET_LOADER', 'SET_LOADER_ID']),
enableForm:function(event){
if(event.complete){
this.disabled = false;
}
else if(event.empty){
this.disabled = true;
}
},
submitStripeForm: function()
{
this.SET_LOADER({ status:1, message: 'Procesando...' });
var self = this;
this.stripe.createSource(this.cardNumberElement).then(function(result) {
if (result.error) {
self.cardErrors = result.error.message;
}
else {
self.stripeSourceHandler(result.source.id);
}
});
},
stripeSourceHandler: function(sourceId)
{
console.log('stripeSourceHandler');
this.cardNumberElement.clear();
this.cardExpiryElement.clear();
this.cardCvcElement.clear();
if(this.customerSources.length == 0)
{
console.log('createSourceAndCustomer');
this.createSourceAndCustomer({ id: sourceId });
}
else
{
console.log('addSource');
this.addSource({ id: sourceId });
}
},
printStripeFormErrors: function(event)
{
if(event.error)
{
self.cardErrors = event.error.message
}
else
{
self.cardErrors = '';
}
}
}
Given the stripe docs, the use of the event seems correct (though it can be improved a bit with using this.disabled = !event.complete to cover error case and not only empty case).
You may try to console.log in the event callback enableForm to check if event is well fired.
Anyway, it's more likely coming from the disabling logic of the submit button and it misses in your post. I've created below a fake secure-component that triggers a change event when value change.
The interesting part in on the container component :
Submit is disabled by default through data disabled,
Submit is enabled if event received has a property complete set to true. If false, it is disabled.
Hope it will help you to focus your trouble.
/**
Mock component to emulate stripes card element behavior with change event
*/
const SecureInput = {
template: '<input type="text" v-model="cardnumber"/>',
data: () => ({
cardnumber: null
}),
watch: {
cardnumber: function(val) {
if(!val) {
this.$emit('change', {empty: true, error: false, complete: false});
return;
}
if(val.length < 5) {
this.$emit('change', {empty: false, error: true, complete: false});
return;
}
this.$emit('change', {empty: false, error: false, complete: true});
}
}
}
/* Logic is here */
const app = new Vue({
el: '#app',
components: {
SecureInput
},
data: {
disabled: true
},
methods: {
updateDisable: function(event) {
this.disabled = !event.complete;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form #submit.prevent="$emit('submitted')">
<p><secure-input #change="updateDisable"/></p>
<p><input type="submit" :disabled="disabled"/></p>
</form>
</div>

Error on React-Native Android hardaware BackButton click "Undefined is not a function(evaluating '_this2.close()') "

I have installed the react-native-orientation-loading-overlay package for applying the loader on the screen.
When user clicks on the hardware back button, the backAndroid's addEventListener is called. Depending on the condition, loader's animating property is set to true and the orientation loader starts loading.
While loading, the another function this.fetchData() is started running which fetches data by running the webservice and display in listview of same page i.e mainPage.
And while loading, if the user has again pressed the back button the red screen with the error "Undefined is not a function(evaluating _this2.close()) in onRequestClose method of react-native-orienation-loading-overlay\src\index.js file" displays .I have tried to BackAndroid.removeEventListener('hardwareBackPress', () => {}); after the loader's animating property is set to true but its not
working.
Please give me any solution.
import OrientationLoadingOverlay from 'react-native-orientation-loading-overlay';
<OrientationLoadingOverlay
visible={this.state.animating}
color="#6495ed"
indicatorSize="large"
messageFontSize={16}
message="Loading..."
/>
My hardware backbutton click event listener code is as follows:
BackAndroid.addEventListener('hardwareBackPress', () => {
if(this.state.drawerState===true) {
this.refs['draw'].closeDrawer();
} else {
if(this.props.navigator.getCurrentRoutes().length===1&&
this.state.tagPressed===false){
if (stack.length===0){
Alert.alert(
'Exit',
'Are you sure?',
[
{text: 'Cancel', onPress: () => {return true;}},
{text: 'Yes', onPress: () => BackAndroid.exitApp()},
],
)
} else {
this.backButtonEvent();
}
} else {
if(this.props.navigator.getCurrentRoutes().length===1&&
this.state.tagPressed===true) {
this.setState({ animating: true });
this.setState({ tagPressed: false });
this.setState({ title: 'Repository' });
this.fetchData();
} else {
this.props.navigator.pop();
}
}
}
return true;
});
Here the stack.length is the length of array of navigated routes. And my backButtonEvent() function is as follows:
backButtonEvent() {
if(stack.length===0) {
this.refs['draw'].openDrawer();
} else{
this.setState({animating:true});
dirPath = stack.pop();
title = this.titleStack.pop();
if(stack.length===0) {
this.setState({srcUrl:require('../image/drawer1.png')});
this.setState({drawerLock:'unlocked'});
}
this.fetchData();
}
}
<ListView
dataSource={this.state.dataSource}
renderRow={(this.renderItem.bind(this))}
enableEmptySections = {true}
/>
fetchData(data){
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data)
});
}
Refer to the line 82 of source code of the react-native-orientation-loading-overlay, it uses the function this.close() to close the Modal but when refer to the RN 0.41 Documentation, this.close() is not pre-defined function for Modal, therefore it triggered the mentioned error when typeof this.props.children === 'undefined'. It is a bug for the package. It might have new state to control the on/off of the Modal, and allow this.close() function to change the state to off the Modal.

Categories