Multiple OnBeforeUnload - javascript

I am writing a JS which is used as a plugin. The JS has an onbeforeunload event.
I want suggestions so that my onbeforeunload event doesn't override the existing onbeforeunload event (if any). Can I append my onbeforeunload to the existing one?
Thanks.

I felt this has not been answered completely, because no examples were shown using addEventListener (but The MAZZTer pointed out the addEventListener solution though). My solution is the same as Julian D. but without using jQuery, only native javascript.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Before Unload</title>
</head>
<body>
<p>Test</p>
<script>
window.addEventListener('beforeunload', function (event) {
console.log('handler 1')
event.preventDefault()
event.returnValue = ''
});
window.addEventListener('beforeunload', function (event) {
console.log('handler 2')
});
</script>
</body>
</html>
In this example, both listeners will be executed. If any other beforeunload listeners were set, it would not override them. We would get the following output (order is not guaranteed):
handler 1
handler 2
And, importantly, if one or more of the event listener does event.preventDefault(); event.returnValue = '', a prompt asking the user if he really wants to reload will occur.
This can be useful if you are editing a form and at the same time you are downloading a file via ajax and do not want to lose data on any of these action. Each of these could have a listener to prevent page reload.
const editingForm = function (event) {
console.log('I am preventing losing form data')
event.preventDefault()
event.returnValue = ''
}
const preventDownload = function (event) {
console.log('I am preventing a download')
event.preventDefault()
event.returnValue = ''
}
// Add listener when the download starts
window.addEventListener('beforeunload', preventDownload);
// Add listener when the form is being edited
window.addEventListener('beforeunload', editingForm);
// Remove listener when the download ends
window.removeEventListener('beforeunload', preventDownload);
// Remove listener when the form editing ends
window.removeEventListener('beforeunload', editingForm);

You only need to take care of this if you are not using event observing but attach your onbeforeunload handler directly (which you should not). If so, use something like this to avoid overwriting of existing handlers.
(function() {
var existingHandler = window.onbeforeunload;
window.onbeforeunload = function(event) {
if (existingHandler) existingHandler(event);
// your own handler code here
}
})();
Unfortunately, you can't prevent other (later) scripts to overwrite your handler. But again, this can be solved by adding an event listener instead:
$(window).unload(function(event) {
// your handler code here
});

My idea:
var callbacks = [];
window.onbeforeunload = function() {
while (callbacks.length) {
var cb = callbacks.shift();
typeof(cb)==="function" && cb();
}
}
and
callbacks.push(function() {
console.log("callback");
});

Try this:
var f = window.onbeforeunload;
window.onbeforeunload = function () {
f();
/* New code or functions */
}
You can modify this function many times , without losing other functions.

If you bind using jQuery, it will append the binding to the existing list, so there is no need to worry.
From the jQuery Docs on() method:
As of jQuery 1.4, the same event handler can be bound to an element
multiple times.
function greet(event) { alert("Hello "+event.data.name); }
$("button").on("beforeunload", { name: "Karl" }, greet);
$("button").on("beforeunload", { name: "Addy" }, greet);

You can use different javascript frameworks like jquery or you could probably add a small event add handler to do this. Like you have an object thatcontains a number of functions that you have added and then in the onbefore unload you run the added functions. So when you want to add a new function to the event you add it to your object instead.
something like this:
var unloadMethods = [];
function addOnBeforeUnloadEvent(newEvent) { //new Event is a function
unloadMethods[unloadMethods.length] = newEvent;
}
window.onbeforeunload = function() {
for (var i=0; i<unloadMethods.length; i++) {
if(typeof unloadMethods[i] === "function") {unloadMethods[i]();}
}
}

Those frameworks mentioned use addEventListener internally. If you are not using a framework, use that.
https://developer.mozilla.org/en-US/docs/DOM/element.addEventListener
For older versions of IE you should have a fallback to use attachEvent instead:
http://msdn.microsoft.com/en-us/library/ie/ms536343(v=vs.85).aspx

I liked Marius's solution, but embellished on it to cater for situations where the var f is null, and to return the first string returned by any function in the chain:
function eventBeforeUnload(nextFN){
//some browsers do not support methods in eventAdd above to handle window.onbeforeunload
//so this is a way of attaching more than one event listener by chaining the functions together
//The onbeforeunload expects a string as a return, and will pop its own dialog - this is browser behavior that can't
//be overridden to prevent sites stopping you from leaving. Some browsers ignore this text and show their own message.
var firstFN = window.onbeforeunload;
window.onbeforeunload = function () {
var x;
if (firstFN) {
//see if the first function returns something
x = firstFN();
//if it does, return that
if (x) return x;
}
//return whatever is returned from the next function in the chain
return nextFN();
}
}
In your code where required use it as such
eventBeforeUnload(myFunction);
//or
eventBeforeUnload(function(){if(whatever) return 'unsaved data';);

Related

Unbind event in Javascript

I'm just getting started with A/B test using Dynamic Yield. I'm facing a few issues overwriting events that are fired by Javascript.
Let's for the sake of this example take this function written in the frontend:
$('body')
.on('mouseenter focusin', 'class1', () => {
$('body').trigger('menu:close');
});
Now my question is, how can I overwrite this event on after the whole file is already initialized? As you know this kind of A/B test has to overwrite the code loaded on the page. So for example I'd like to remove this trigger event. Does anyone has any idea on how to proceed? I should write only pure Javascript because at this stage I do not have access to Jquery.
Thanks
Use the .off() method:
$('body').off('mouseenter focusin', 'class1');
Note that this will remove all handlers for these events delegated to this class, not just the ones added by the original code. If you want be more selective you need to use a named function rather than an anonymous function, so you can give the same function later.
function handler() {
$('body').trigger('menu:close');
}
$('body').on('mouseenter focusin', 'class1', handler);
// later
$('body').off('mouseenter focusin', 'class1', handler);
Another solution is to use namespaced events.
$('body').on('mouseenter.test focusin.test', 'class1', () => $('body').trigger('menu:close'););
// later
$('body').off('mouseenter.test focusin.test', 'class1');
You can remove the event by deleting the property Jquery appends to the object:
In my case it's called jQuery360075100310324570631. In code below the event of the button is removed
$("body").on("click", ".class1", () => {
console.log("click!!");
});
function deleteEvent() {
const a = document.getElementsByTagName("body")[0];
const regex = new RegExp(/jQuery\d*/);
for (const key in a) {
if (regex.test(key)) {
delete a[key];
return;
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="class1">button</button>
<button class="class2" onclick="deleteEvent()">remove event from button</button>
$('body').off('mouseenter focusin', 'class1');

In IE8: Create a custom event in vanilla JS and pick it up in Jquery

All of this is happening for IE8.
Due to script import orders, I'm having a bit of code being executed before JQuery is loaded where I need to fire a custom event.
This event will be picked up later in another bit of code when I'm sure JQuery will have been loaded. So I'd like to use JQuery to pick up this event.
I saw this previously asked question: How to trigger a custom javascript event in IE8? and applied the answer, which worked, but when I'm trying to pick up the Event via JQuery, then nothing happens.
Here's what I've tried:
function Event() {}
Event.listen = function(eventName, callback) {
if (document.addEventListener) {
document.addEventListener(eventName, callback, false);
} else {
document.documentElement.attachEvent('onpropertychange', function(e) {
if (e.propertyName == eventName) {
callback();
}
});
}
};
Event.trigger = function(eventName) {
if (document.createEvent) {
var event = document.createEvent('Event');
event.initEvent(eventName, true, true);
document.dispatchEvent(event);
} else {
document.documentElement[eventName] ++;
}
};
Event.listen('myevent', function() {
document.getElementById('mydiv-jquery').innerText = "myevent jquery";
});
$(document).on('myevent', function() {
document.getElementById('mydiv-vanilla').innerText = "myevent vanilla";
});
Event.trigger('myevent');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mydiv-jquery">Nothing</div>
<div id="mydiv-vanilla">Nothing</div>
PS: The snippet doesn't seem to work properly in IE. Here's a jsfiddle that should be working.
There are a few problems with this code.
You shadow the built-in window.Event without checking if it exists; this could cause problems for other scripts.
You don't preserve the this binding when calling the callback from your onpropertychange listener. You should apply the callback to the document rather than calling it directly so the behavior will be as close as possible to addEventListener.
You attempt to increment document.documentElement[eventName] while it is undefined. The first call will change the value to NaN, so onpropertychange should pick it up, but on subsequent calls it will remain NaN.
You make no attempt to have .on() recognize your Event.listen function, so naturally the code in Event.listen will never be executed from a listener attached with .on().
Have you tried using Andrea Giammarchi's CustomEvent shim?

How can jQuery's click() function be so much faster than addEventListener()?

I'm sure we've all seen the site for vanilla-js (the fastest framework for JavaScript) ;D and I was just curious, exactly how much faster plain JavaScript was than jQuery at adding an event handler for a click. So I headed on over to jsPerf to test it out and I was quite surprised by the results.
jQuery outperformed plain JavaScript by over 2500%.
My test code:
//jQuery
$('#test').click(function(){
console.log('hi');
});
//Plain JavaScript
document.getElementById('test').addEventListener('click', function(){
console.log('hi');
});
I just can't understand how this would happen because it seems that eventually jQuery would end up having to use the exact same function that plain JavaScript uses. Can someone please explain why this happens to me?
As you can see in this snippet from jQuery.event.add it does only create the eventHandle once.
See more: http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.event.add
// Init the element's event structure and main handler, if this is the first
events = elemData.events;
if (!events) {
elemData.events = events = {};
}
eventHandle = elemData.handle;
if (!eventHandle) {
elemData.handle = eventHandle = function (e) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}
And here we have the addEventListener:
// Init the event handler queue if we're the first
handlers = events[type];
if (!handlers) {
handlers = events[type] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special events handler returns false
if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
// Bind the global event handler to the element
if (elem.addEventListener) {
elem.addEventListener(type, eventHandle, false);
} else if (elem.attachEvent) {
elem.attachEvent("on" + type, eventHandle);
}
}
}
I think it's because internally jQuery really only has to call addEventListener() once, for its own internal handler. Once that's set up, it just has to add your callback to a simple list. Thus most of the calls to .click() just do some bookkeeping and a .push() (or something like that).

Remove EventListener in JavaScript after event occurred?

There are 24 div-objects waiting/listening for a mouse-click. After click on one div-object, I want to remove the EventListener from all 24 div-objects.
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener()//Problem lies here
}
//Some other code to be run after mouseclick
},false);
}
The problem is that the removeEventListener is nested in addEventListener and I need to define type, listener, caption as attributes to the removeEventListener method. And I think it is impossible to define the listener because of nesting.
I also tried to define a function name, but it didn't worked:
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function helpme(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener('click',helpme,false);
}
//Some other code to be run after mouseclick
},false);
}
You can tell the event listener to simply fire just once:
document.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
The documentation says:
once:
A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
It should work with a named function. If your second approach does not work properly, try storing the initial listener into a variable, like this:
var handler = function(event) {
for(...) {
removeEventListener('click', handler, false);
}
};
addEventListener('click', handler, false);
Ps. if you care about speed, you may wish to consider using just one event handler. You can put the handler into the parent element of the divs, and then delegate the event from there. With 24 handlers your current approach probably doesn't have a very big performance hit, but this is something you should keep in mind if it ever feels slow.
For those who needs to remove after a certain condition (or even inside a loop too), one alternative is using AbortController and AbortSignal:
const abortController = new AbortController();
let handler = function(event) {
if(...) {
abortController.abort();
}
};
addEventListener('click', handler, {signal: abortController.signal});
The same answer:
element.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
You can read more here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Javascript: simulate a click on a link

I have a link that has a listener attached to it (I'm using YUI):
YAHOO.util.Event.on(Element, 'click', function(){ /* some functionality */});
I would like to the same functionality to happen in another scenario that doesn't involve a user-click. Ideally I could just simulate "clicking" on the Element and have the functionality automatically fire. How could I do this?
Too bad this doesn't work:
$('Element').click()
Thanks.
MDC has a good example of using dispatchEvent to simulate click events.
Here is some code to simulate a click on an element that also checks if something canceled the event:
function simulateClick(elm) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
var canceled = !elm.dispatchEvent(evt);
if(canceled) {
// A handler called preventDefault
// uh-oh, did some XSS hack your site?
} else {
// None of the handlers called preventDefault
// do stuff
}
}
You're looking for fireEvent (IE) and dispatchEvent (others).
For YUI 3 this is all wrapped up nicely in Y.Event.simulate():
YUI().use("node", function(Y) {
Y.Event.simulate(document.body, "click", { shiftKey: true })
})
You can declare your function separately.
function DoThisOnClick () {
}
Then assign it to onclick event as you do right now, e.g.:
YAHOO.util.Event.on(Element, 'click', DoThisOnClick)
And you can call it whenever you want :-)
DoThisOnClick ()
In case anyone bumps into this looking for a framework agnostic way to fire any HTML and Mouse event, have a look here: How to simulate a mouse click using JavaScript?
1) FIRST SOLUTION
The article http://mattsnider.com/simulating-events-using-yui/ describes how to simulate a click using YAHOO:
var simulateClickEvent = function(elem) {
var node = YAHOO.util.Dom.get(elem);
while (node && window !== node) {
var listeners = YAHOO.util.Event.getListeners(node, 'click');
if (listeners && listeners.length) {
listeners.batch(function(o) {
o.fn.call(o.adjust ? o.scope : this, {target: node}, o.obj);
});
}
node = node.parentNode;
}
};
As you can see, the function loops over the node and its parents and for each of them gets the list of listeners and calls them.
2) SECOND SOLUTION
There is also another way to do it.
For example:
var elem = YAHOO.util.Dom.get("id-of-the-element");
elem.fireEvent("click", {});
where function is used as
3) THIRD SOLUTION
Version 2.9 of YUI2 has a better solution: http://yui.github.io/yui2/docs/yui_2.9.0_full/docs/YAHOO.util.UserAction.html
4) FORTH SOLUTION
YUI3 has also a better and clean solution: http://yuilibrary.com/yui/docs/event/simulate.html
Of course $('Element').click() won't work, but it can if you include jquery, it works well alongside yui.
As I untestand you need to do the following:
function myfunc(){
//functionality
}
YAHOO.util.Event.on(Element, 'click', myfunc);
Then call myfunc when something else needs to happen.
The inability to simulate a user-click on an arbitrary element is intentional, and for obvious reasons.

Categories

Resources