I am trying to do something simple: I have a bunch of Images which are being load through JS.
I attach an event listener to the load event, and after the Image is being loaded, in the listener function I would like to get the calling Image and retrieve properties from it.
Here is my code, simplified:
function loadImages() {
for (var i = 0; i < arrDownloadQueueBasic.length; i++) {
var path = arrDownloadQueueBasic[i].path;
var img = new Image();
img.type = arrDownloadQueueBasic[i].type;
img.attachEvent(img, 'load', setBasicElement);
img.src = path;
}
}
function setBasicElement(e) {
var caller = e.target || e.srcElement;
alert(caller); // THIS DOESNT WORK - RETURN NULL
alert(caller.type) // OF COURSE THIS DOESNT WORK AS WELL...
}
There are a couple of things that you need to correct. First, the attachEvent method should not be used for browsers other than IE. You should structure your code to check if the method is implemented and then act accordingly like so:
if(img.addEventListener) {
img.addEventListener('load', setBasicElement, false);
}
else if(img.attachEvent) {
img.attachEvent('onload', setBasicElement);
}
else {
img.onload = setBasicElement;
}
The other issue is that you need to prefix the event name with "on" when using attachEvent.
EDIT
You can get the caller by using the following code in the setBasicElement function:
var caller = e.target || e.srcElement || window.event.target || window.event.srcElement;
Here is a working example - http://jsfiddle.net/BMsXR/3/
Try this:
var caller = window.event ? window.event.srcElement : e.target;
If I remember rightly IE doesn't pass the event object as a parameter when you've used attachEvent(), but it has a global event object.
Related
I've got a website that is using MooTools. I've been getting the following errror, but can't figure out what is causing it. I've had very little luck tracing it out. Does anyone know what this might be? I haven't been able to find anything on the web about it.
Uncaught TypeError: Cannot read property 'call' of undefined mootools-core.js:4497
condition mootools-core.js:4497
defn mootools-core.js:4511
Stackoverflow has this question, but it does not relate as far as I can tell. Ideas?
Update
Having looked at this a bit more, the cause of the error is still mysterious. The offending line in my code is an addEvent call:
window.addEvent('load', preloader(preload));
The variable preload is an array of image urls. And the callback preloader is a method that preloads the images specified in preload. Here's the preloader method:
/**
* Event callback that preloads images
*/
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}
}
}
The line of mootools code specified by the error is this:
addEvent: function(type, fn){
var events = this.retrieve('events', {});
if (!events[type]) events[type] = {keys: [], values: []};
if (events[type].keys.contains(fn)) return this;
events[type].keys.push(fn);
var realType = type,
custom = Element.Events[type],
condition = fn,
self = this;
if (custom){
if (custom.onAdd) custom.onAdd.call(this, fn, type);
if (custom.condition){
condition = function(event){
//error here--> if (custom.condition.call(this, event, type)) return fn.call(this, event);
return true;
};
}
if (custom.base) realType = Function.from(custom.base).call(this, type);
}
var defn = function(){
return fn.call(self);
};
var nativeEvent = Element.NativeEvents[realType];
if (nativeEvent){
if (nativeEvent == 2){
defn = function(event){
event = new DOMEvent(event, self.getWindow());
if (condition.call(self, event) === false) event.stop();
};
}
this.addListener(realType, defn, arguments[2]);
}
events[type].values.push(defn);
return this;
},
Er. you are not passing a function as callback.
this:
window.addEvent('load', preloader(preload));
/**
* Event callback that preloads images
*/
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}
}
}
it will essentially invoke the preloader function immediately, not onload - and it will try to bind the event to the result of the preloader function, which does not return anything at all.
when the interpreter sees preloader(preload), it just runs it straight away. you can return a function or better yet, rewrite to:
window.addEvent('load', function(){ preloader(preload); });
// or even
window.addEvent('load', preloader.bind(this, preload));
Running example:
var imagesArray = new Array(50).join(',').split(',');
imagesArray = imagesArray.map(function(el, i){
return 'http://dummyimage.com/600x400/000/' + (255 - i) + '?' + +new Date();
});
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
console.log(img.src);
}
}
}
window.addEvent('load', function(){
preloader(imagesArray);
});
<script src="//cdnjs.cloudflare.com/ajax/libs/mootools/1.5.0/mootools-core-full-nocompat.js"></script>
You can also have a look at my preloader class which gives you greater flexibility over how your images are pre-loaded, as well as progress etc. https://github.com/DimitarChristoff/pre-loader - it will actually wait for the images to download, allow you to choose how they are loaded etc.
mootools-more also has Asset.images you can use.
finally, not sure you want to bind to load event, which will trigger when all assets, including images, have been loaded, you should be able to start at domready instead.
It means, I believe, that mooTools is getting an undefined/malformed parameter in one of your call.
Find out which function is defined at line 4497 of mootools-core.js (can be an object method) and look for each call in your script. Log all parameters/object you're working with, and you'll find your error ;)
Edit
Seeing your code, I think your problem come from the fact that type is not declared inside condition. Try :
if (custom.condition){
condition = function(event,type){
if (custom.condition.call(this, event, type)) return fn.call(this, event);
return true;
};
I need to found other way how to use javascript function.
var b = ce("input"); // Here I create element.
b.setAttribute("name", "g4");
b.value = "Centimetrais(pvz:187.5)";
b.onfocus = function() { remv(this); };
b.onchange = function() { abs(this); };
b.onkeypress = function() { on(event); }; // I need to change this place becose then I pass "event" argument function doesn't work.
ac(1, "", b); // Here I appendChild element in form.
Here is the function:
function on(evt) {
var theEvent = evt|| window.event;
var key = theEvent.keyCode || theEvent.which;
key = String.fromCharCode( key );
var regex = /^[0-9.,]+$/;
if( !regex.test(key) ) {
theEvent.returnValue = false;
if(theEvent.preventDefault) theEvent.preventDefault();
}
}
In IE and chrome it work but in mozilla doesn't. Any alternative how to fix it for firefox?
Also at this path other function working in mozilla if pass other argument like "car","dog",this. For example:
firstFunction();
function firstFunction() {
var b = ce("input"); // Here I create element.
b.onkeypress = function() { on("hi!"); };
ac(1, "", b); // Here I appendChild element in form.
}
function on(evt) {
alert(evt);
}
If I understand your question correctly, the problem is that you're not accepting the event argument in the main handler. E.g., in your first example:
b.onkeypress = function() { on(event); };
it should be
b.onkeypress = function(e) { on(e || window.event); };
// Changes here --------^ and --^^^^^^^^^^^^
You're doing that in on, but in on, it's already too late, you've lost the argument provided to the onXYZ function.
The reason your code works in Chrome and IE is that IE uses a global event object instead of (or in modern versions, in addition to) the one it actually passes into the handler, and Chrome replicates that behavior for maximum compatibility with websites that expect that. Firefox does not.
I'm trying to make an extension that unbinds a click event added by the website itself.
The website uses jQuery, which would make this insanely simple:
jQuery('a[rel]').unbind('click');
The problem is that my extension (using "content_scripts") can't access the website's jQuery object and therefore doesn't have the event functions to unbind. I can include a jQuery in my extension, but that wouldn't help, because jQuery stores 'data' in the jQuery object (not in the DOM elements). My jQuery will not have those events stored.
Is there another way? It doesn't have to be pretty. Maybe without "content_scripts"?
var unbind_event_listeners = function (node) {
var parent = node.parentNode;
if (parent) {
parent.replaceChild(node.cloneNode(true), node);
} else {
var ex = new Error("Cannot remove event listeners from detached or document nodes");
ex.code = DOMException[ex.name = "HIERARCHY_REQUEST_ERR"];
throw ex;
}
};
Just call unbind_event_listeners(a_node) to unbind any listeners from a node. This will work on every node in the document except document itself. As for window, you're out of luck. unbind_event_listeners(document.documentElement) should remove most event listeners attached to nodes in the document.
In the case of a[rel], you'd want to do this:
var nodes = document.querySelectorAll("a[rel]"), i = nodes.length;
while (i--) {
unbind_event_listeners(nodes.item(i));
}
If it doesn't need to be pretty and you're okay with doing things slightly hack-like, this should forcefully unbind every click listener bound to that element:
var el = document.querySelector('a[rel]');
el.onclick = function() {};
el.addEventListener = function() {};
or for every element:
Array.prototype.slice.call(document.querySelectorAll('a[rel]')).forEach(function(el) {
el.onclick = function() {};
el.addEventListener = function() {};
});
EDIT: You could maybe do something even uglier and have a content script run at "document_start" and do:
Element.prototype.addEventListener = (function() {
var real = Element.prototype.addEventListener;
return function(ev) {
if (ev === 'click' && this.tagName === 'A' && this.hasAttribute('rel')) {
console.log('try again, jquery!');
} else {
return real.apply(this, arguments);
}
};
})();
I have this function check(e) that I'd like to be able to pass parameters from test() when I add it to the eventListener. Is this possible? Like say to get the mainlink variable to pass through the parameters. Is this even good to do?
I put the javascript below, I also have it on jsbin: http://jsbin.com/ujahe3/9/edit
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', check/*(WOULD LIKE TO PASS PARAMETERS HERE)*/, false);
};
};
};
function check(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === document.getElementById('mainlink')) {
var target = (e && e.target) || (event && event.srcElement);
var obj = document.getElementById('mainlink');
if (target!= obj) {
obj.removeChild(obj.lastChild);
};
};
};
};
Wrap your event listener into a function:
document.addEventListener(
'click',
function(e,[params]){
check(e,[params]);
}
);
One solution would be to move the "check" function up inside your test() function. As an inner function, it would automatically be able to refer to variables in its outer scope. Like this:
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', function(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === mainlink) {
var target = (e && e.target) || (event && event.srcElement);
if (target!= mainlink) {
mainlink.removeChild(mainlink.lastChild);
};
};
};
});
};
What I typically do in this situation is save arguments to the object (whenever it's convenient), and then retrieve them in the function, like this:
// Listener function receives e (the event object) by default.
function eventReceiver(e) {
var obj;
// Find object which triggered the event
e.srcElement ? obj = e.srcElement : obj = e.target;
// obj.someProperty has been set elsewhere, replacing a function parameter
alert(obj.someProperty);
}
This is cross browser, and allows you to pass objects and values through the properties of the event target.
I initially started with the this keyword, but that behaves differently cross-browser. In FF, it's the object that the event was triggered on. In IE, it's the event itself. Thus, the srcElement / target solution was born. I'm interested to see the other solutions though - have a +1.
I use event delegation in such way:
elWraper.onclick = (function(){
//how to get here "event" object
e = e || window.event;
var t = e.target || e.srcElement;
//event handler
return function(){
//manipulations with "t" variable
}
})();
how to get "event" object within the immediately executed function?
elWraper.onclick = (function(){
// misc stuff here
//event handler
return function(e){
e = e || window.event;
var t = e.target || e.srcElement;
//manipulations with "t" variable
}
})();
In standards compliant browsers the event object is the first parameter passed into the callback function. In IE it is a global variable (which is what e = e || window.event is trying to determine). Therefore, the function that you return by the immediately executed function should accept the event object declared as its first (and usually only) argument.
Clarification
Since people are wondering (and probably they are wondering why the OP accepted this answer) there are uses for this that is not clear from the OP's question. One is to create a closure to a variable to track something:
/* Count number of clicks,
* WITHOUT USING GLOBAL VARS!
*/
el.onclick = (function(){
var counter = 0;
return function(e){
e = e || window.event;
var t = e.target || e.srcElement;
counter ++;
alert('detected '+counter+' clicks!');
// do stuff with t or e ...
}
})();
also, this is the classic way of assigning event handlers in loops:
/* Use double closure to break closure in loop!
*/
for (var i=0; i<stuff.length; i++) {
var el = stuff[i];
el.onclick = (function(x){
return function(e){
e = e || window.event;
var t = e.target || e.srcElement;
alert(x);
// do stuff with t or e ...
}
})(i);
}
Or maybe the OP just thought that he could 'cache' the event object and mistakenly believed he could use this to do it. In which case, reading my explanation (instead of just the code) should enlighten the reader as to why that would be a bad idea.
elWraper.onclick = function (e) {
//how to get here "event" object
e = e || window.event;
var t = e.target || e.srcElement;
//manipulations with "t" variable
};
I think slebetman's answer is correct according to your question. However, I don't see the point of what you are doing. If you are trying to abstract the browser's event differences, you can use something like this.
function createHandler( context, handler ) {
return function (e) {
e = e || window.event;
var t = e.target || e.srcElement;
handler.call (context || window, e, t);
}
}
Then you can use it like
div.onclick = createHandler(div, function(e, t){
alert ("actual clicked target is " + t.id);
alert ("handler was set on node " + this.id);
});
Note that you can pass anything as the context (the 'this' keyword in the handler)
It's good to know this stuff, but jquery or many other libs already do this for you and it's a lot more tested than your code will ever be and it takes care of many more browser differences than this small function. But if this all you need, this does keep code bloat down.