How to overcome event handlers being overridden?
I have a script say a.js
window.onload = function () {
//Handler in a.js
}
Another script say b.js
window.onload = function () {
//Handler in b.js
}
where,
a.js is a kind of 3rd party library built by me
b.js is a publisher who uses my script [I can't do any changes out here]
Will onload handler in b.js override a.js's handler?
If yes, How to prevent this from happening?
Will building a queue of all event handlers in a.js and deque them on event help?
But will a.js know all event handlers for an event upfront untill b.js is loaded?
Thoughts and references would help.
you should use addEventListener() to have various handlers for the same event
window.addEventListener("load", yourfunction, false);
Use element.addEventListener or window.attachEvent in down-level IE versions.
Sample addEvent method:
function addEvent(node, type, listener) {
if (node.addEventListener) {
node.addEventListener(type, listener, false);
return true;
} else if (node.attachEvent) {
node['e' + type + listener] = listener;
node[type + listener] = function() {
node['e' + type + listener](window.event);
}
node.attachEvent('on' + type, node[type + listener]);
return true;
}
return false;
};
Note - Most, if not all, modern JavaScript libraries like jQuery and MooTools have their own implementations. I recommend leveraging their API's - as they abstract out different browser implementations and have been thoroughly tested.
Yes, the second statement will override the first one. Because you are using the traditional registration model only one event handle is allowed for any given event and object. The function assignment are not cumulative.
But you can:
window.onload = function () {handlerinb(); handlerina()}
Related
I am thinking of a web application on browsers. I can dynamically add required js files as needed while traversing through different parts of application, but can I unload unnecessary js content from the current session's "memory" as the memory usage grows over time?
I know it is possible to remove the tag responsible for the content but it is not assured that it will eventually unload and unbind everything correspondent to that content.
Thanks
I know it is possible to remove the tag responsible for the content but it is not assured that it will eventually unload and unbind everything correspondent to that content.
In fact, it's assured that it will not. Once the JavaScript is loaded and executed, there is no link between it and the script element that loaded it at all.
You can dynamically load and unload modules, but you have to design it into your code by
Having a module have a single symbol that refers to the module as a whole
Having an "unload" function or similar that is responsible for removing all of a module's event listeners and such
Then unloading the module is a matter of calling its unload function and then setting the symbol that refers to it to undefined (or removing it entirely, if it's a property on an object).
Here's a very simple demonstration of concept (which you'd adapt if you were using some kind of Asynchronous Module Definition library):
// An app-wide object containing a property for each module.
// Each module can redefine it in this way without disturbing other modules.
var AppModules = AppModules || {};
// The module adds itself
AppModules.ThisModule = (function() {
var unloadCallbacks = [];
function doThis() {
// Perhaps it involves setting up an event handler
var element = document.querySelector(/*...*/);
element.addEventHandler("click", handler, false);
unloadCallbacks.push(function() {
element.removeEventHandler("click", handler, false);
element = handler = undefined;
});
function handler(e) {
// ...
}
}
function doThat() {
// ...
}
function unload() {
// Handle unloading any handlers, etc.
var callbacks = unloadCallbacks;
unloadCallbacks = undefined;
callbacks.forEach(function(callback) {
callback();
});
// Remove this module
delete AppModules.ThisModule;
}
return {
doThis: doThis,
unload: unload
};
})();
That callbacks mechanism is very crude, you'd want something better. But it demonstrates the concept.
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?
I'm wondering how to add another method call to the window.onload event once it has already
been assigned a method call.
Suppose somewhere in the script I have this assignment...
window.onload = function(){ some_methods_1() };
and then later on in the script I have this assignment
window.onload = function(){ some_methods_2() };
As it stands, only some_methods_2 will be called. Is there any way to add to the previous window.onload callback without cancelling some_methods_1 ? (and also without including both some_methods_1() and some_methods_2() in the same function block).
I guess this question is not really about window.onload but a question about javascript in general. I DON'T want to assign something to window.onload in such a way that that if another developer were to work on the script and add a piece of code that also uses window.onload (without looking at my previous code), he would disable my onload event.
I'm also wondering the same thing about
$(document).ready()
in jquery.
How can I add to it without destroying what came before, or what might come after?
If you are using jQuery, you don't have to do anything special. Handlers added via $(document).ready() don't overwrite each other, but rather execute in turn:
$(document).ready(func1)
...
$(document).ready(func2)
If you are not using jQuery, you could use addEventListener, as demonstrated by Karaxuna, plus attachEvent for IE<9.
Note that onload is not equivalent to $(document).ready() - the former waits for CSS, images... as well, while the latter waits for the DOM tree only. Modern browsers (and IE since IE9) support the DOMContentLoaded event on the document, which corresponds to the jQuery ready event, but IE<9 does not.
if(window.addEventListener){
window.addEventListener('load', func1)
}else{
window.attachEvent('onload', func1)
}
...
if(window.addEventListener){
window.addEventListener('load', func2)
}else{
window.attachEvent('onload', func2)
}
If neither option is available (for example, you are not dealing with DOM nodes), you can still do this (I am using onload as an example, but other options are available for onload):
var oldOnload1=window.onload;
window.onload=function(){
oldOnload1 && oldOnload1();
func1();
}
...
var oldOnload2=window.onload;
window.onload=function(){
oldOnload2 && oldOnload2();
func2();
}
or, to avoid polluting the global namespace (and likely encountering namespace collisions), using the import/export IIFE pattern:
window.onload=(function(oldLoad){
return function(){
oldLoad && oldLoad();
func1();
}
})(window.onload)
...
window.onload=(function(oldLoad){
return function(){
oldLoad && oldLoad();
func2();
}
})(window.onload)
You can use attachEvent(ie8) and addEventListener instead
addEvent(window, 'load', function(){ some_methods_1() });
addEvent(window, 'load', function(){ some_methods_2() });
function addEvent(element, eventName, fn) {
if (element.addEventListener)
element.addEventListener(eventName, fn, false);
else if (element.attachEvent)
element.attachEvent('on' + eventName, fn);
}
There are basically two ways
store the previous value of window.onload so your code can call a previous handler if present before or after your code executes
using the addEventListener approach (that of course Microsoft doesn't like and requires you to use another different name).
The second method will give you a bit more safety if another script wants to use window.onload and does it without thinking to cooperation but the main assumption for Javascript is that all the scripts will cooperate like you are trying to do.
Note that a bad script that is not designed to work with other unknown scripts will be always able to break a page for example by messing with prototypes, by contaminating the global namespace or by damaging the dom.
This might not be a popular option, but sometimes the scripts end up being distributed in various chunks, in that case I've found this to be a quick fix
if(window.onload != null){var f1 = window.onload;}
window.onload=function(){
//do something
if(f1!=null){f1();}
}
then somewhere else...
if(window.onload != null){var f2 = window.onload;}
window.onload=function(){
//do something else
if(f2!=null){f2();}
}
this will update the onload function and chain as needed
A pure JavaScript (no jQuery) method that would not override existing onload events but instead add to it, would be:
window.addEventListener('load', function() {
// do your things here
}
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';);
I am trying to intercept calls to document.write for all pages. Setting up the interception inside the page by injecting a script like
function overrideDocWrite() {
alert("Override called");
document.write = function(w) {
return function(s) {
alert("special dom");
w.call(this, wrapString(s));
};
}(document.write);
alert("Override finished");
}
Is easy and works, but I would like my extension to setup the interception for each document object from inside the extension. I couldn't find a way to do this. I tried to listen for the "load" event and set up the interception there but it also fails. How do I hook calls to doc.write from an extension?
I made some progress:
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if (appcontent)
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad,
true);
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered "onload" event
// do something with the loaded page.
// doc.location is a Location object (see below for a link).
// You can use it to make your code executed on certain pages only.
alert("Override called");
alert(doc);
alert(doc.write);
alert(doc.wrappedJSObject);
alert(doc.wrappedJSObject.write);
doc.wrappedJSObject.write = function(w) {
return function(s) {
alert("special dom");
w.call(this, "(" + s + ")");
};
}(doc.write);
alert("Override finished");
}
}
This seem to work, but DOMContentLoaded is the wrong event for the job, because it is fired too late! Is there an earlier event to listen to?
Ressurection of the question ! I got the answer. Here is a sample code :
const os = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
os.addObserver({
observe : function(aWindow, aTopic, aData) {
if (aWindow instanceof Ci.nsIDOMWindow && aTopic == 'content-document-global-created') {
aWindow.wrappedJSObject.myfunction = function(){
// Do some stuff . . .
}
}
}
}, 'content-document-global-created', false);
The same goes for document with the event document-element-inserted as of gecko 2.0 .
JavaScript uses a prototypical inheritance system, instead of having classes, objects have prototypes. Prototypes are real objects that are used as a reference to other objects for inheritance of methods and attributes.
The best strategy would be to override the method write in the prototype of "document" (which for the HTML document is HTMLDocument). This should effectively wrap the method for all instances of "document" inside the pages loaded in the browser since they all use the same prototype.
Instead of
document.write = function() { ... }
try something like this:
HTMLDocument.prototype.write= function() { ... }
UPDATE: It does not seem to be as easy as I initially thought, this does not seem to work at first try.