How can I get the id attribute of a dynamically generated element when you don't know it's id? I am wanting to setup some eventListeners for about 2000 Checkbox's on a page.
I know I could call a function from within the element:
<input type="checkbox" id="{databaseResult.Value}" onclick="someFunction(this)" />
But I'm not allowed to use JavaScript, or references to it in my HTML. Pure JavaScript (or a language that compiles to it) is my only option.
The code I already have for some elements where I do know the id, is:
var tb = <HTMLInputElement>document.getElementById("tbox");
if(tb.addEventListener("", function (e) {
sayHello(tb.value);
}, false));
Put a single listener on an ancestor element, say the body, and listen for events on that, e.g.:
<body onclick="handleClick(event);" ...>
and the function is:
function handleClick(evt) {
var el = evt.target || evt.srcElement;
if (el && el.type == 'checkbox') {
alert(el.id);
}
}
Of course you can add the listener dynamically, I've used inline for convenience.
Edit
To attach the listener dynamically, use an addEvent function to cope with IE and W3C event models:
function addEvent(el, evt, fn) {
// W3C event model
if (el.addEventListener) {
el.addEventListener(evt, fn, false);
// IE event model
// Set this and pass event as first parameter
} else if (el.attachEvent) {
el.attachEvent('on' + evt, (function(el, fn) {
return function() {fn.call(el, window.event);};
})(el, fn)
);
}
// Prevent circular reference
el = null;
}
And call it as:
window.onload = function() {
addEvent(document.body, 'click', handleClick);
}
Related
I need to delegate a 'tap' event to a close button in a custom element, and in turn call a close() method on the root element. Here is an example:
xtag.register('settings-pane', {
lifecycle: {
created: function () {
var tpl = document.getElementById('settings-pane'),
clone = document.importNode(tpl.content, true);
this.appendChild(clone);
}
},
events: {
'tap:delegate(button.close)': function (e) {
rootElement.close(); // <- I don't know the best way to get rootElement
}
},
methods: {
close: function () {
this.classList.add('hidden');
}
}
});
<template id="settings-pane">
<button class="close">✖</button>
</template>
Hey there, this is the library author, let me clear this up:
For any listener you set in the DOM, X-Tag or vanilla JS, you can always use the standard property e.currentTarget to access the node the listener was attached to. For X-Tag, whether you're using the delegate pseudo or not, e.currentTarget will always refer to your custom element:
xtag.register('x-foo', {
content: '<input /><span></span>',
events: {
focus: function(e){
// e.currentTarget === your x-foo element
},
'tap:delegate(span)': function(e){
// e.currentTarget still === your x-foo element
// 'this' reference set to matched span element
}
}
});
Remember, this is a standard API for accessing the element an event listener was attached to, more here: https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
The best approach to this problem, after dealing with it for several months, is to create another "pseudo", similar to the :delegate() pseudo, but that calls the callback differently. This adds the :descendant() pseudo to xtag:
xtag.pseudos.descendant = {
action: function descendantAction(pseudo, event) {
var match,
target = event.target,
origin = target,
root = event.currentTarget;
while (!match && target && target != root) {
if (target.tagName && xtag.matchSelector(target, pseudo.value)) match = target;
target = target.parentNode;
}
if (!match && root.tagName && xtag.matchSelector(root, pseudo.value)) match = root;
return match ? pseudo.listener = pseudo.listener.bind({component: this, target: match, origin: origin}) : null;
}
};
You would use this exactly as you would :delegate(), but this will reference an object which will in turn reference the target element (the element matching the CSS selector e.g. button.close), the origin (the element that received the event), and the component (the custom element itself). Usage example:
xtag.register('settings-pane', {
methods: {
close: function () {
this.classList.add('hidden');
}
},
events: {
'tap:descendant(button.close)': function (e) {
this.origin; // <button> or some descendant thereof that was tapped
this.target; // <button> element
this.component; // <settings-pane> element
this.component.close();
}
}
});
I'm building a decision tree in JavaScript. I do not have jQuery available to me for this project.
I would like to be able to have buttons, placed anywhere in the decision tree (Hidden or displayed anywhere on the page), with the same class name. The listener on the JS side would then run a function.
Here is what I am using for and ID based listener. It works well but I need to be able to have multiple buttons with the same class or name available. Although I have seen examples of this, I cannot get it to function properly.
function q1a1() {
var q1a1button = document.getElementById("q1answer1");
if(q1a1button.addEventListener){
q1a1button.addEventListener("click", function() { q1answer1();}, false);
} else if(q1a1button.attachEvent){
q1a1button.attachEvent("onclick", function() { q1answer1();});
}
};
if(window.addEventListener){
window.addEventListener("load", q1a1, false);
} else if(window.attachEvent){
window.attachEvent("onload", q1a1);
} else{
document.addEventListener("load", q1a1, false);
}
function q1answer1() {
//DO SOME STUFF
}
This also needs to work in as many versions of IE as possible. For single class handling I'm using querySelectorAll.
What you are really looking for is JavaScript Event Delegation. In your case, you have BUTTON elements, which I'm going to assume are <button> tags. Now you want to know when one of those buttons was clicked and then run a function:
if (document.addEventListener) {
document.addEventListener("click", handleClick, false);
}
else if (document.attachEvent) {
document.attachEvent("onclick", handleClick);
}
function handleClick(event) {
event = event || window.event;
event.target = event.target || event.srcElement;
var element = event.target;
// Climb up the document tree from the target of the event
while (element) {
if (element.nodeName === "BUTTON" && /foo/.test(element.className)) {
// The user clicked on a <button> or clicked on an element inside a <button>
// with a class name called "foo"
doSomething(element);
break;
}
element = element.parentNode;
}
}
function doSomething(button) {
// do something with button
}
Anywhere on the page that a <button class="foo">...</button> element appears, clicking it, or any HTML tag inside of it, will run the doSomething function.
Update: Since Event Delegation is used, only a single click handler is registered on the document object. If more <button>s are created as a result of an AJAX call, you don't have to register click handlers on those new <button>s since we take advantage of the click event bubbling up from the element the user clicked on to the document object itself.
If you don't have jquery:
if (document.body.addEventListener){
document.body.addEventListener('click',yourHandler,false);
}
else{
document.body.attachEvent('onclick',yourHandler);//for IE
}
function yourHandler(e){
e = e || window.event;
var target = e.target || e.srcElement;
if (target.className.match(/keyword/))
{
//an element with the keyword Class was clicked
}
}
If you use a cross browser library like jquery:
HTML:
<div class="myClass">sample</div>
<div class="myClass">sample 2</div>
JS:
function theFuncToCall(event){
//func code
}
$(document).on('click', '.myClass', theFuncToCall);
var buttons = document.querySelectorAll(".MyClassName");
var i = 0, length = buttons.length;
for (i; i < length; i++) {
if (document.addEventListener) {
buttons[i].addEventListener("click", function() {
// use keyword this to target clicked button
});
} else {
buttons[i].attachEvent("onclick", function() {
// use buttons[i] to target clicked button
});
};
};
This answer is a bit overkill, but it should show you ways you could structure your code in a "modern" way even if you're still targeting old browsers
Write code to add event listeners so there is minimal difference between new and old browsers
var listen = (function () { // will return the handler for use in unlisten
if (window.addEventHandler) {
return function (node, type, handler) {
node.addEventListener(type, handler);
return handler;
};
} else if (window.attachEvent) {
return function (node, type, handler) {
var fn = function (e) {
if (!e) {
e = window.event;
}
if (!e.target && e.srcElement) {
e.target = e.srcElement;
}
return handler.call(this, e);
};
node.attachEvent('on' + type, fn);
return fn;
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = function () { ... };
}
}());
and if you'd like the reverse, too
var unlisten = (function () { // use handler given by listen
if (window.removeEventListener) {
return function (node, type, handler) {
node.removeEventListener(type, handler);
};
} else if (window.detachEvent) {
return function (node, type, handler) {
node.detachEvent('on' + type, handler);
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = null;
}
}());
Write your click handler
function clickHandler(e) {
// do stuff
}
Wrap your click handler in a function to choose only clicks on buttons with the right class
function wrappedClickHandler(e) {
var tokens, i;
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') {
return;
}
tokens = (e.target.className || '').split(' ');
for (i = 0; i < tokens.length; ++i) {
if (tokens[i] === 'theClassTokenWeWant') {
return clickHandler.call(this, e);
// or
// return clickHandler.call(e.target, e);
}
}
}
Add this as a listener to a common ancestor node
var h = listen(document, 'click', wrappedClickHandler);
// .. later, if desired
unlisten(document, 'click', h);
Would the simpler way of writing the event delegation function be to add it to the container of the buttons? For example,
// Select Container Element
const questionContainer = document.querySelector(".container");
// Listen For Clicks Within Container
questionContainer.onclick = function (event) {
// Prevent default behavior of button
event.preventDefault();
// Store Target Element In Variable
const element = event.target;
// If Target Element Is a Button
if (element.nodeName === 'BUTTON') {
// Event Code
}
}
I want to remove only the mouseup event listners from a selected HTML element.
I used the below code but it will remove all listners.
var old_element = divs[d];
var new_element = old_element.cloneNode(true);
old_element.parentNode.replaceChild(new_element, old_element);
this is how i attach event listners.
var divs = document.getElementsByTagName('body');// to enhance the preformance
for(var d in divs) {
try{
if (divs[d].addEventListener) {
divs[d].addEventListener('mouseup',callHighlight);
} else {
divs[d].attachEvent('mouseup', callHighlight);
}
}catch(err){
//alert(err.message);
}
}
You should use removeEventListener instead of replacechild which will obviously remove all events.
old_element.removeEventListener('mouseup', handler);
When cloning an element, listeners added using addEventListener or by direct property assignment (element.onclick = fn;) are removed, but in–line listeners and those added using IE's attachEvent are not.
In your scenario where listeners are added by reference and also possibly using attachEvent, you are best to remove them using removeEventListener and detachEvent. So you might like to create add and remove functions like:
function addEvent(element, event, fn) {
if (element.addEventListener) {
element.addEventListener(event, fn, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, fn);
}
}
function removeEvent(element, event, fn) {
if (element.removeEventListener) {
element.removeEventListener(event, fn);
} else if (element.detachEvent) {
element.detachEvent('on' + event, fn);
}
}
Note that there are some significant differences between addEventListener and attachEvent, the most important are that in the latter, this is not set to the element whose handler is calling the function and a reference to the event isn't passed as the first argument to the listener. So the listener function ends up looking like:
function foo(evt) {
evt = evt || window.event;
var target = evt.target || evt.srcElement;
...
}
There are ways around this, but they introduce more issues. Keep it simple if you can.
By default all event listeners are null, so simply just reset it. Problem is that all your mouseup events are registered to the body, so therefore you won't be able to drop the event without first stopping the event from bubbling to the body. You can solve that problem with, stopPropagation()
old_element.onmouseup = function (e) {
// event won't go up to the body tag
e.stopPropagation();
return null;
};
or
function kill (e) {
e.stopPropagation();
return null;
}
old_element.onmouseup = kill;
second_element.onmouseup = kill;
JSFIDDLE
I add an event listener to an element:
/* sitepoint.com/javascript-this-event-handlers */
function AttachEvent(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
}else{
element.attachEvent("on"+type, handler);
}
}
window.addEventListener("load", function() {
var els = getElementsByClassName('name', 'img');
var elsnum = els.length;
if(elsnum) //found
{
var i = 0;
for(i=0; i < elsnum; i++)
{
var the_els = els[i];
AttachEvent(the_els, "click", myfunction);
}
}
}, false);
Later in myfunction, I want to remove the handler again, to prevent duplicate clicks:
function myfunction(e) {
e = e || window.event;
var target = e.target || e.srcElement;
//more code
//...
//remove click handler
target.removeEventListener('click', e, false);
//more code
//...
}
The event listener is not being removed, though. When I click on one of the elements, the code of myfunction is executed again. How can I remove the event listener to prevent the clicked element from being clicked again?
PS: I do not use jQuery.
I believe you're almost there, but you have to pass the listener to removeEventListener, not the event itself. So try:
target.removeEventListener('click', myFunction, false);
Use element.removeEventListener(type, listener, useCapture)
Remember to use that on the same element, and give it the exact same parameters as you did for adding.
One excellent way to code this would be to make a function that stores the listener details in a variable, mimicking how setTimeout() works for instance, and adding that to the element prototype. Here's an example function.
HTMLElement.prototype.eventListener=
function(type, func, capture){
if(typeof arguments[0]=="object"&&(!arguments[0].nodeType)){
return this.removeEventListener.apply(this,arguments[0]);
}
this.addEventListener(type, func, capture);
return arguments;
}
That will add a method to all HTML nodes that already can accept event listners, and allow you to do this.
var a=element.eventListener('click', myFunction, false); //to add
element.eventListener(a); //to remove
http://www.quirksmode.org/js/events_advanced.html
To remove an event handler, use the removeEventListener() method.
Also see http://help.dottoro.com/ljahxbsx.php
object.removeEventListener (eventName, listener, useCapture);
listener - Required. Reference to the event handler function to remove. You need to pass what listener you want to remove.
I want to listen for events on <p> elements at window or document level as there are too many such elements to attach an onclick event hander for each.
This is what I've got:
window.onload=function()
{
window.addEventListener('click',onClick,false);
}
function onClick(event)
{
alert(event.target.nodeName.toString());
}
I need advice on the code above, is it good?
And also, how can I check if the clicked element is a <p> element other than checking nodeName?
For example, if the <p> element contains a <b> element and that is clicked, the nodeType will be b not p.
Thank you.
I think it is good, and you can perform checking this way
var e = event.target
while (e.tagName != 'P')
if (!(e = e.parentNode))
return
alert(e)
If I was you I'd register for the "load" event in the same way that you are for "click". It's a more modern way of event handling, but might not be supported by some older browsers.
Checking the nodeName is the best way to interrogate the node:
function onClick(event) {
var el = event.target;
while (el && "P" != el.nodeName) {
el = el.parentNode;
}
if (el) {
console.log("found a P!");
}
}
Consider this:
(function () {
// returns true if the given node or any of its ancestor nodes
// is of the given type
function isAncestor( node, type ) {
while ( node ) {
if ( node.nodeName.toLowerCase() === type ) {
return true;
}
node = node.parentNode;
}
return false;
}
// page initialization; runs on page load
function init() {
// global click handler
window.addEventListener( 'click', function (e) {
if ( isAncestor( e.target, 'p' ) ) {
// a P element was clicked; do your thing
}
}, false );
}
window.onload = init;
})();
Live demo: http://jsfiddle.net/xWybT/