Using vanilla JS I would like to know if would be possible to see the property onclick on a HTML object (div)
for (var name in element) {
if(name == "onclick"){
// do smt
}
}
Instead of enumerating properties of element, you can immediately retrieve the onclick property of element with:
var clickHandler = element.onclick;
Events nowadays are bound with addEventListener (and attachEvent in old IE), which allow for multiple handlers per event type. Setting the onevent property only allows for one handler, can be overwritten, and normally isn't the way to bind handlers in a web page.
Unfortunately, you are not able to retrieve any listeners bound with addEventListener (and attachEvent), without writing a wrapper function that tracks them...for example:
var events = [];
function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
var found = false;
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
found = true;
events[i].list.push(callback);
break;
}
}
if (!found) {
events.push({
el: element,
list: [callback]
});
}
}
function viewEvents(element) {
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
return events[i].list;
}
}
return null;
}
And you'd use it like:
var div = document.getElementById("some_id");
addEvent(div, "click", function () {
console.log("whatever");
});
console.log(viewEvents(div));
(of course, you'd need a wrapper for removeEventListener that removes handlers from events too)
Related
I'm using this structure to create one-time click events:
function structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
}
}
The handler of those events is implemented as follows:
structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', arguments.callee);
}
};
The idea is to fire the handler once if any of the registered elements gets clicked, and then unregister the event from each of those elements
Unfortunately the handler still gets fired everytime I click on one of the registered items
I'm aware anonymous functions can't be used to reference the same object, but specifying arguments.callee or the entire name of the referenced function still didn't help the cause
An alternative is to make your objects implement the EventListener interface. You can do this by adding a handleEvent method to the .prototype of the constructor, and then passing the object itself in place of the event handler.
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].addEventListener('click', this);
}
}
// Implement the interface; gets invoked when an event occurs
Structure.prototype.handleEvent = function(e) {
// Used a switch statement in anticipation of other event types
switch (e.type) {
case "click":
this.elementClicked(e);
break;
}
};
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].removeEventListener('click', this);
}
};
Now there's no longer any need to use .bind(). Instead the value of this in handleEvent will be the bound object. You can still get the element to which the handler was bound via e.currentTarget.
Each time you call...
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
... bind creates another instance of a method. It uses this.elementClicked, true, but otherwise is a completely different function. That's why you won't drop it with remoteEventListener called on this.elementClicked.
What's the workarounds? One possible option - passing { once: true } as addEventListener param - has been given in the comments, but it's not supported by IE and Edge (and most likely won't be supported by the Safari you encounter in the nearest future). Here's another approach:
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
// reassign a bound method onto instance:
this.elementClicked = this.elementClicked.bind(this);
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked);
}
}
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', this.elementClicked);
}
};
Now you create a bound elementClicked method for each instance of structure object, having its context set permanently.
var fn = (function() {
return {
'init': function(className) {
// Access <a> tag here and apply className
}
};
}());
Link
In the above code snippet, would it be possible to implicitly pass this to fn.init?
I realize that I can change the declaration of fn.init to function(className, el) and use onmouseover="fn.init('myClass', this)" to access the element, but I am just curious if it would be possible without passing this in the inline event listener.
You can use call as well (I realize that you said you didn't want to pass this in the inline event listener, but this is probably the only way). The only other way is to use addEventListener and not use an inline event.
var fn = (function() {
return {
'init': function(className) {
// Notice that I can use "this" in this function
// Which refers to the <a> element
this.className = className;
}
};
}());
.myClass {
color: red;
}
Link
If you wanted to add an event to every <a> element, you would do this:
var elems = document.getElementsByTagName("a");
for (var i = 0; i < elems.length; ++i) {
elems[i].addEventListener("click", function() {
fn.init.call(this, 'myClass');
}, false);
}
Or if you are using only modern browsers (no IE)
var elems = document.getElementsByTagName("a");
for (var i = 0; i < elems.length; ++i) {
elems[i].addEventListener("click", fn.init.bind(elems[i], 'myClass'), false);
}
No. You get a new value of this each time you call a function.
fn.init('myClass') is a different function call to theElement.onmouseover(event).
You could use addEventListener to bind the event handler instead of using intrinsic event attributes.
I am collecting all images elements from the document var listen = document.getElementsByTagName('img'); which results in an array. Now I have to specify listen[0], listen[1], listen[2], listen[3] etc to listen for events on a element from this array. The question is is there any way to just do something like listen[any item from this array], or a function. Just to don't have to specify every element to listen on manually.
Example
Now I have to do something like this, for every array item:
listen[3].click = function() {};
Depending on what it is exactly what you want and what the DOM looks like you may want to use an event listener higher up the DOM tree somewhere instead of create a lot of listeners:
document.addEventListener('click', function(e) {
if (e.target.nodeName !== 'IMG') {
return;
}
alert('img clicked');
});
Demo: http://jsfiddle.net/APSMT/1/
Note that I have attached the event listener to document, but if you can make it more specific you should
With a reasonable new browser you can use Array.forEach:
[].forEach.call(document.getElementsByTagName('img'), function (img) {
el.addEventListener('click', callback);
});
Or "old skool" (and maybe even better readable):
var imgs = document.getElementsByTagName('img');
for (var i = 0, len = imgs.length; i < len; ++i) {
imgs[i].addEventListener('click', callback);
}
Reading the HTMLCollection.length only once can give a speed-up. It will never be slower.
You should not create a Function object for every iteration:
// Don't
for (...) {
img[i].onclick = function () { ... }
}
// Do
function onclick () {
// `this` will be img[i]
}
for (...) {
img[i].onclick = onclick;
}
I am writing a vanilla JavaScript tool, that when enabled adds event listeners to each of the elements passed into it.
I would like to do something like this:
var do_something = function (obj) {
// do something
};
for (var i = 0; i < arr.length; i++) {
arr[i].el.addEventListener('click', do_something(arr[i]));
}
Unfortunately this doesn't work, because as far as I know, when adding an event listener, parameters can only be passed into anonymous functions:
for (var i = 0; i < arr.length; i++) {
arr[i].el.addEventListener('click', function (arr[i]) {
// do something
});
}
The problem is that I need to be able to remove the event listener when the tool is disabled, but I don't think it is possible to remove event listeners with anonymous functions.
for (var i = 0; i < arr.length; i++) {
arr[i].el.removeEventListener('click', do_something);
}
I know I could easily use jQuery to solve my problem, but I am trying to minimise dependencies. jQuery must get round this somehow, but the code is a bit of a jungle!
This is invalid:
arr[i].el.addEventListener('click', do_something(arr[i]));
The listener must be a function reference. When you invoke a function as an argument to addEventListener, the function's return value will be considered the event handler. You cannot specify arguments at the time of listener assignment. A handler function will always be called with the event being passed as the first argument. To pass other arguments, you can wrap the handler into an anonymous event listener function like so:
elem.addEventListener('click', function(event) {
do_something( ... )
}
To be able to remove via removeEventListener you just name the handler function:
function myListener(event) {
do_something( ... );
}
elem.addEventListener('click', myListener);
// ...
elem.removeEventListener('click', myListener);
To have access to other variables in the handler function, you can use closures. E.g.:
function someFunc() {
var a = 1,
b = 2;
function myListener(event) {
do_something(a, b);
}
elem.addEventListener('click', myListener);
}
// Define a wrapping function
function wrappingFunction(e) {
// Call the real function, using parameters
functionWithParameters(e.target, ' Nice!')
}
// Add the listener for a wrapping function, with no parameters
element.addEventListener('click', wrappingFunction);
// Save a reference to the listener as an attribute for later use
element.cleanUpMyListener = ()=>{element.removeEventListener('click', wrappingFunction);}
// ...
element.cleanUpMyListener ()
Step 1) Name your function.
Step 2) Save a reference to your function (in this case, save the reference as an attribute on the element itself)
Step 3) Use the function reference to remove the listener
// Because this function requires parameters, we need this solution
function addText(element, text) {
element.innerHTML += text
}
// Add the listener
function addListener() {
let element = document.querySelector('div')
if (element.removeHoverEventListener){
// If there is already a listener, remove it so we don't have 2
element.removeHoverEventListener()
}
// Name the wrapping function
function hoverDiv(e) {
// Call the real function, using parameters
addText(e.target, ' Nice!')
}
// When the event is fired, call the wrapping function
element.addEventListener('click', hoverDiv);
// Save a reference to the wrapping function as an attribute for later use
element.removeHoverEventListener = ()=>{element.removeEventListener('click', hoverDiv);}
}
// Remove the listener
function removeListener() {
let element = document.querySelector('div')
if (element.removeHoverEventListener){
// Use the reference saved before to remove the wrapping function
element.removeHoverEventListener()
}
}
<button onclick="addListener()">Turn Listener on</button>
<button onclick="removeListener()">Turn Listener off</button>
<div>Click me to test the event listener.</div>
To pass arguments to event handlers bind can be used or handler returning a function can be used
// using bind
var do_something = function (obj) {
// do something
}
for (var i = 0; i < arr.length; i++) {
arr[i].el.addEventListener('click', do_something.bind(this, arr[i]))
}
// using returning function
var do_something = obj => e {
// do something
}
for (var i = 0; i < arr.length; i++) {
arr[i].el.addEventListener('click', do_something(arr[i]))
}
But in both the cases to remove the event handlers it is not possible as bind will give a new referenced function and returning function also does return a new function every time for loop is executed.
To handle this problem we need to store the references of the functions in an Array and remove from that.
// using bind
var do_something = function (obj) {
// do something
}
var handlers = []
for (var i = 0; i < arr.length; i++) {
const wrappedFunc = do_something.bind(this, arr[i])
handlers.push(wrappedFunc)
arr[i].el.addEventListener('click', wrappedFunc);
}
//removing handlers
function removeHandlers() {
for (var i = 0; i < arr.length; i++) {
arr[i].el.removeEventListener('click', handlers[i]);
}
handlers = []
}
This can be done quite easily, just not as you have it right now.
Instead of trying to add and remove random anonymouse functions, you need to add or remove a function that handles the execution of your other functions.
var
// Here we are going to save references to our events to execute
cache = {},
// Create a unique string to mark our elements with
expando = String( Math.random() ).split( '.' )[ 1 ],
// Global unique ID; we use this to keep track of what events to fire on what elements
guid = 1,
// The function to add or remove. We use this to handler all of other
handler = function ( event ) {
// Grab the list of functions to fire
var handlers = ( cache[ this[ expando ] ] && cache[ this[ expando ] ][ event.type ] ) || false;
// Make sure the list of functions we have is valid
if ( !handlers || !handlers.length ) {
return;
}
// Iterate over our individual handlers and call them as we go. Make sure we remeber to pass in the event Object
handlers.forEach( function ( handler ) {
handler.call( this, event );
});
},
// If we want to add an event to an element, we use this function
add = function ( element, type, fn ) {
// We test if an element already has a guid assigned
if ( !element[ expando ] ) {
element[ expando ] = guid++;
}
// Grab the guid number
var id = element[ expando ];
// Make sure the element exists in our global cache
cache[ id ] = cache[ id ] || {};
// Grab the Array that we are going to store our handles in
var handlers = cache[id ][ type ] = cache[ id ][ type ] || [];
// Make sure the handle that was passed in is actually a function
if ( typeof fn === 'function' ) {
handlers.push( fn );
}
// Bind our master handler function to the element
element.addEventListener( type, handler, false );
};
// Add a click event to the body element
add( document.body, 'click', function ( event ) {
console.log( 1 );
});
This is just a cut down version of what I've written before, but you can get the gist of it I hope.
Maybe its not perfect solution, but near to ideal, in addition I dont see other ways
Thanks to Kostas Bariotis
Solution key here is:
So what do we do when we need to remove our attached event handlers at some point at runtime? Meet handleEvent, the default function that JavaScript looks for when tries to find a handler that has been attached to an event.
In cas link is broken (I placed first way)
let Button = function () {
this.el = document.createElement('button');
this.addEvents();
}
Button.prototype.addEvents = function () {
this.el.addEventListener('click', this);
}
Button.prototype.removeEvents = function () {
this.el.removeEventListener('click', this);
}
Button.prototype.handleEvent = function (e) {
switch(e.type) {
case 'click': {
this.clickHandler(e);
}
}
}
Button.prototype.clickHandler = function () {
/* do something with this */
}
P.S:
Same tehnics in JS class implementation.
If you develop in typescript you have to implement handleEvent method from EventListenerObject interface
To 'addEventListener' with some parameters, you can use the following code:
{
myButton.addEventListener("click",myFunction.bind(null,event,myParameter1,myParameter2));
}
And the function 'myFunction' should be something like this:
{
function myFunction(event, para1, para2){...}
}
I have a list which contains links . I am using this code to access them:
function initAll() {
var allLinks = document.getElementById("nav").getElementsByTagName("a");
for (var i=0; i< allLinks.length; i++) {
allLinks[i].onmouseover = showPreview;
allLinks[i].onmouseout = function() {
document.getElementById("previewWin").style.visibility = "hidden";
allLinks[i].onclick=mainProcess;
}
}
}
function mainProcess(evt){
alert(this.value);
false;
}
This is not the exact code, what I am trying to do is that I need to identify link is clicked and perform some function on the basis of link clicked. I don't know where code needs to be modified... Page is giving error on the allLinks[i].onclick=mainProcess(this); line.
Now the problem is that I don't know how I should handle all the three events?
1) You're setting the onclick property of each of the links to be the value returned by mainProcess() - which always returns false. So, in effect, you're writing allLinks[i].onclick = false;
2) When you define an event handler directly, the argument that gets passed to it when the event fires, is the event object - not the element it was fired on.
To figure out the element, you can either look in the event object, or (since the handler has been added to the element itself) simply use this, as that will refer to the link element
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}
You do need to pass this to mainProcess(link). As stated in http://www.quirksmode.org/js/events_tradmod.html "No parentheses!" and "this" chapters. Check it out, there's an example there too. Should be everything you need.
Try changing to this:
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}