There is an iFrame with buttons inside that I cannot change directly. The requirement is to use this specific iFrame. Inside this iFrame, there have many buttons with the same classname that the user can click to navigate to another page. Outside the iFrame, I have a form of user data and tracking to know when the user has made any changes.
Note: I'm not actually using an iFrame, but the basic idea is I have content that I cannot directly change.
I have a message when the user has unsaved changes and they click on any item inside the iFrame ~ "You have unsaved changes, are you sure you want to discard them?".
I would like to override the original function's definition to have my blocking confirmation logic in front of whatever logic is already there. When the user confirms, I need to continue the original navigation.
The best way I can think to do this is to query the button element and override the button.click() function with an my popup message and then the original function call. Maybe something like this:
useEffect(()=>{
const elements = document.getElementsByClassname("classname");
for (let element of elements){
const originalFunction = element.onclick // <--
element.onclick = () => {
if (blocked) {
confirmation({
content: "You have unsaved changes, are you sure you want to discard them?",
proceed: "Discard changes",
cancel: "Cancel",
handler: function () {
discardChanges();
originalFunction(); // <--
}
});
} else {
originalFunction(); // <--
}
}
}
return ()=>{
restoreOriginalFunctionDefinitions(); // <--
}
},[]);
What is the correct way to inside a function in-front of another function definition?
Is it possible to do this and then restore the original definition when the element unmounts?
Is there just a better way to do this?
I've tried the following useEffect
useEffect(()=>{
const elements = Array.from(document.getElementsByClassName(watchingClassName));
console.info("elements", elements);
elements.forEach((element)=>{
const htmlElement = element as HTMLElement;
AdviceFunction(htmlElement.click, blocking, "Are you sure", "Discard",onDiscard,"Cancel" );
});
}, [])
Advice Function has the following definition:
const AdviceFunction = (
originalFunction,
blocked,
message,
discardCaption,
discardAction,
cancelCaption
) =>
function (...args) {
console.info("advice", { this: this, originalFunction, blocked });
if (blocked) {
confirmation({
content: message,
proceed: discardCaption,
cancel: cancelCaption,
handler: function () {
discardChanges(discardAction);
originalFunction.apply(this, args);
}
});
} else {
originalFunction.apply(this, args);
}
};
Actual result: useEffect successfully queries the dom for the buttons. When a button is clicked, only the original function is ran and the console.info in AdviceFunction is never logged.
I came up with another approach that works.
Use a class name with document.getElementsByClassname to get a list
of content to supersede
Iterate that list to create components that have the same position and dimensions as the elements retrieved (position fixed, top, left, width, height, opacity: 0)
When the user clicks on the component, record the coordinates of the click event then show the confirmation message
If the user confirms the navigation, use document.elementsFromPoint(x,y)1 to get the first content underneath the generated components then click that content
See code here:
Main component https://github.com/bsgriggs/mendix-unsaved-changes-message/blob/master/src/UnsavedChangesMessage.tsx
Blocker component https://github.com/bsgriggs/mendix-unsaved-changes-message/blob/master/src/components/Blocker.tsx
Demo https://widgettesting105-sandbox.mxapps.io/p/unsaved-changes-message
Back to the original example, I can simply add a class to the iFrame itself and any other buttons I want to block to cover it with a transparent div.
In the screenshot, the red content is what was retrieved by the class name
When the user clicks on any red container, it shows the confirmation.
Related
I am showing user a modal that takes over the entire screen if user is accessing the website on phone. A user tends to instinctively click the back button on their phone to go back to the previous screen instead of using the close button provided on the modal.
Is there a way to intercept the function that is triggered when a user clicks the back button? I want to close the modal when user clicks the back button on their phone, instead of having them redirect to the previous page.
Use the History API. An example on how to acomplish this is:
//listen for state changes
window.onpopstate = (event) =>
{
if (!event.state.modalOpened)
{
closeModal()
}
}
//change the actual page state so it contains the modalOpened property
window.history.replaceState({modalOpened: false})
function openModal(content)
{
//push new state, put the modal information in the state object, this will push a new state, when the user presses the back button, the browser will just trigger the onpopstate event, instead of going to the previous page
window.history.replaceState({modalOpened: true})
//this is any code you use to open your modal, for example
ReactDOM.render(<Modal>{content}</Modal>, document.getElementById("modal-container")
}
class based component
There are many ways to aproach this, this is one of them, everything you need to make something that fits with your app is in the History API DOCS.
//listen for state changes
window.onpopstate = (event) =>
{
if (!event.state.modalOpened)
{
updateModal(false, null)
}
}
function openModal()
{
//push new state, put the modal information in the state object, this will push a new state, when the user presses the back button, the browser will just trigger the onpopstate event, instead of going to the previous page
window.history.replaceState({modalOpened: true})
updateModal(false, <div>Modal content!</div>)
}
function updateModal(open, content)
{
ReactDOM.render(<Modal open={open} content={content} />, document.getElementById("modal-container")
}
//change the actual page state so it contains the modalOpened property
window.history.replaceState({modalOpened: false})
class Modal extends React.Component {
constructor(props) {
super(props);
}
render() {
//check the history to determine if we have to open or close the modal
return <div className={"modal " + (props.open)? "show" : ""}><div className="modal-content">{props.content}</div><button onClick={() => window.history.back()}>OK</button></div>;
}
}
I want to wait user's click to make interact contents.
And code below is example with vanilla js.
But with Vue.js, I can't add eventlistener to specific dom element dynamically.
Then, how can I make this with Vue.js?
async mainTask(){
//something...
showText.innerText += "please input value and then click ok button.\n";
const input = await waitInput();
//...
}
async function waitInput() {
return new Promise(resolve => {
okButton.addEventListener("click",() => {
const text = someinput.value;
resolve();
}, { once:true });
});
}
if you want to get value from input after user clicking a button.
in your Vue
<template>
<input v-modle="valueYouWant" />
<button #click="getValueAndDoSth">
</template>
<script>
// ... common vue script header
getValueandDoSth(){
// return this.valueYouWant
}
</script>
// Edit Based on your comment
If you want to run some function as soon as page load, you can
run a function in mounted function of Vue component
once user click the button, get value, and run left code you want to run
I'm not sure 'you want to run a function and wait for user click button and then run left', if user does not click on button, your function stops anyway.
I am building a chrome extension whereby I want to be able to right click on a certain part of a page and then scrape some info from it. So using chrome.contextMenus I'd like to be able to only scrape from the element(one of it's attributes) where I've right clicked. Sort of like the behaviour in chrome when you right click somewhere on a page and select inspect it will open the element view on the page element you right clicked. The reason I want to do this is because there will be a number of similar type elements with different ids(attribute) so I want to be able get only the id of the particular element I'm interested in.
Is this even possible?
I was looking though the chrome.contextMenus documentation and I'm wondering if I know the element type(article)could I set the context menu on that and get the id that is stored in it that way?
I'd say your extension has to remember what was the last element under the right click. And content-script suits well for it.
background.js
chrome.contextMenus.create({
title: "Get ID",
id: "menu1",
onclick: function (info, tab) {
// send message about context manu action
chrome.tabs.sendMessage(tab.id, {
msg: 'get_id'
}, {
frameId: info.frameId
});
}
});
content-script.js
let lastClickedEl = null;
// remember last clicked element
document.body.addEventListener('mousedown', function (e) {
if (e.button === 2) { // right click to an element
lastClickedEl = e.target;
}
});
// receive message about context menu action
chrome.runtime.onMessage.addListener(function (request) {
if(request.msg === 'get_id') {
console.log(lastClickedEl.id); // your code here
}
});
I want to use two javascript events: onbeforeunload and inside it I need to use onhashchange. The point is on leaving the page if user has filled some fields then the confirmation message appear warning user whether they want to stay on page or leave.
My code is following
window.onbeforeunload = function (e) {
window.onhashchange = function (r) { return ''; }
};
But the above code does not seem to be working? what am I doing wrong?
[Updated]
Again, what I am trying to achieve is I want to alert a user if the leave the page and if they have filled some fields on the page
You can try
window.onbeforeunload = function (e) {
if (content on page changed) { confirm("Do you want to leave page?") }
};
window.onhashchange = function (r) { if (content on page changed) { confirm("Do you want to leave page?") } }
I have just given pseudo code, you can replace with actual code.
Basic mistake is you are trying to bind event onhashchange in onbeforeunload.
Firstly, I am quite new to programming, so please be gentle. Stack Overflow has been a wonderful resource for me, so thankyou to all of you contributors.
This one I cannot crack though, and it's a difficult search item, as you can probably tell by the cryptic title...
Anyway, I have a jQuery popup function which gets called from a events.register control. Essentially whenever a user clicks in the map it sends a request to a web service, gets the data back, populates a form which the user can then interact with and save data back to SQL.
It works really well I am happy with its progress.
One of the functions is to change the value in a table, I want the user to get a prompt to ensure they don't accidentally save something they don't intend. It works perfectly the first time after a refresh, but then each time after that it adds one confirm, then again, until eventually after the fifth time there are 5 confirms that appear one after the other. Once the user clicks through them the code runs fine and I get expected results.
I only call the confirm once (I think?) but, yeah, I am lost, here is my function, sorry if it is hard to read or poorly formatted:
function popup() {
j = jQuery.noConflict();
j(document).ready(function() {
//open popup
j("#detailsform").height(250);
j("#detailsform").fadeIn(1000);
positionPopup();
document.getElementById("condition").value = getCondition;
j("#selCatHead").hide();
j("#cats").hide();
//dispose tree
j("#dispose").click(function() {
if (confirm("Are you sure?")) {
disposetree();
}
else { return false; };
});
//save defect
j("#savedef").click(function() {
saveDef();
});
//fade in defects
j("#adddefect").click(function() {
j("#detailsform").height(400);
j("#selCatHead").fadeIn(1000);
j("#cats").fadeIn(1000);
});
//fade out popup
j("#close").click(function() {
j("#detailsform").fadeOut(500);
});
}); // close document ready function
//position the popup at the center of the page
function positionPopup() {
if (!j("#detailsform").is(':visible')) {
return;
}
j("#detailsform").css({
left: (j(window).width() - j('#detailsform').width()) / 2,
top: (j(window).width() - j('#detailsform').width()) / 7,
position: 'absolute'
});
} // close positionPopup function
//maintain the popup at center of the page when browser resized
j(window).bind('resize', positionPopup);
}; // close popup function
EDIT: Thanks to #jfriend00, removed function from popup call and turned off handler each time, eg:
function popup() {
j = jQuery.noConflict();
j(document).ready(function() {
//open popup
j("#detailsform").height(250);
j("#detailsform").fadeIn(1000);
//dispose
j("#dispose").off();
j("#dispose").click(function() {
disposeClickHandle();
});
//save
j("#savedef").off();
j("#savedef").click(function() {
saveDef();
});
});
and then:
function disposeClickHandle() {
if (confirm("Are you sure?")) {
disposetree();
}
else { return false; };
};
You should only install a click handler ONCE. If you install it multiple times, it will trigger multiple times.
So, everytime you call your popup() function it installs yet another click handler. And, then when you click, the click handler will trigger multiple times. You should either move your click handler installation outside the function that you call multiple times and put it in an initialization function that is only called once at the beginning OR you can remove the click handlers after your operation so when you install it again, it will only be installed one time.