Is it possible to attach a new event to every links of a page recursively? Currently I'm using the following code:
var linkHandler = function (e) {
e = e || window.event;
var element = e.target || e.srcElement;
console.log("Element clicked, type: "+element.tagName);
if (element.tagName == 'A') {
console.log("Catched "+element.href);
element.href = url(element.href);
}
};
document.addEventListener("click", linkHandler, true);
But it only works if I directly click on a link. For example here, only the click on "Work" of the second link works.
<strong>Doesn't work</strong>
Work <strong>Doesn't</strong>`
Is there another possibility ?
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 get href attribute when a use clicks on a URL.
I've tried with:
var e = clickedElement || window.event;
var t = e.target || e.srcElement;
alert(t.href);
This is working fine in Chrome and Firefox, but there's a problem with IE; its give an error null.
What is possible solution to get href from event object?
Depends what clickedElement is, given that its either the event object argument placeholder or a this argument;
function event_obj(event) {
if (!event) var event = window.event;
var element = event.target || event.srcElement;
alert(element.href);
return false;
}
function element_obj(element) {
alert(element.href)
return false;
}
.
.
foo
bar
Use this function to get the event target
function getEventTarget(event) {
var targetElement = null;
try {
if (typeof event.target != "undefined") {
targetElement = event.target;
}
else {
targetElement = event.srcElement;
}
} catch (ex) { alert("getEventTarget failed: " + ex); }
return targetElement;
};
Then call it when you trigger the event
function evtrigger(ev) {
alert(getEventTarget(ev).href);
}
Note the getEventTarget() function works for all events so the DOM object returned can be manipulated any way you like, not just to get the href.
I've got a weird problem in Internet Explorer. Attaching a click event to the children of an element when the parent already has a click event doesn't seem to work.
I've got the following code so far:
<span>Some text
<strong class="opts">
Opt name
</strong>
</span>
I'm attaching the click event listener with the following code(where el is the span):
el.onclick = function()
{
..
}
var optWrapper = el.firstChild.nextSibling,
opts = optWrapper.getElementsByTagName('a'),
numOpts = opts.length;
for (var i = 0; i < numOpts; i++)
{
var opt = opts[i],
f = null;
switch (opt.getAttribute('href').split('#', 2)[1])
{
case '#opt-action':
f = someFunction;
break;
}
if (f !== null)
opt.onclick = f;
}
And someFunction makes a call to stopEvent, which is defined as:
var stopEvent = function(e)
{
e = e || event;
// Stop bubbling
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
// Prevent the default action
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
and someFunction returns false at the end.
Any suggestions what could be causing the event to not fire on the children?
EDIT:
As noted by #Kevin Babcock the firstChild was indeed one of my problems, the second problem was that the href attribute in IE is different from retrieving the href in Firefox or Google Chrome.
If I call getAttribute('href') in IE I get an absolute url and in Firefox and Google Chrome I get a relative url. I've updated the code to split the href on the #.
The code shown above is dynamically loaded. After the load has completed any javascript found in the data is executed, I think my problem is that the script isn't loaded by IE.
This line is your problem:
var optWrapper = el.firstChild
Because your <span> element is immediately followed by "Some text" the result of calling firstChild() on it will be a TextNode element. Subsequently, getElementsByTagName() is undefined on a TextNode element.
Instead, do this:
var opts = el.getElementsByTagName("a"),
numOpts = opts.length;
Here's the full source that you can drop in your page to get the results you are after:
(function(){
var stopEvent = function(e) {
console.log("stopEvent called");
e = e || event;
// Stop bubbling
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
// Prevent the default action
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
var someFunction = function(e) {
console.log("someFunction called");
stopEvent(e);
};
var span = document.getElementsByTagName("span")[0];
span.onclick = function() {
console.log("span clicked");
};
var opts = span.getElementsByTagName("a"),
numOpts = opts.length;
for (var i = 0; i < numOpts; i++) {
var opt = opts[i],
f = null;
switch (opt.getAttribute('href')) {
case '#opt-action':
f = someFunction;
break;
}
if (f !== null)
opt.onclick = f;
}
})();
I have code like:
document.onmousedown = function(){
alert('test');
}
Now, except the element with ID "box", clicking should call this function, i.e. the equivalent of jQuery's .not() selector.
The jQuery code would be:
$(document).not('#box').mousedown(function(){
alert('test');
});
How can I achieve the same thing without using jQuery?
Edit: I don't want jQuery code, but i want an action similar to the .not() selector of jQuery in Javascript.
Edit: I am making an addthis-like widget. It is a 10kb file which will show a popup when a text is selected. It will not use jQuery.
In my case, when a text is selected, a popup is shown. When the document is clicked somewhere other than the widget, the widget should disappear.
To do this properly, you need to check whether e.target || e.srcElement or any of its parents has id === 'box'.
For example: (with jQuery)
$(document).mousedown(function(e) {
if ($(e.target).closest('#box').length)
return;
//Do things
});
Without jQuery:
function isBox(elem) {
return elem != null && (elem.id === 'box' || isBox(elem.parentNode));
}
document.onmousedown = function(e) {
e = e || window.event;
if (isBox(e.target || e.srcElement))
return;
//Do things
};
Alternatively, you could handle the mousedown event for the box element and cancel bubbling.
Here's one way that should work:
document.onmousedown = function(e){
var event = e || window.event;
var element = event.target || event.srcElement;
if (target.id !== "box") { alert("hi"); }
}
or if you would like it to be reusable with different ids:
function myNot(id, callback) {
return function (e) {
var event = e || window.event;
var element = event.target || event.srcElement;
if (target.id !== id) { callback(); }
}
}
and to use it:
document.onmousedown = myNot("box", function () {
alert("hi");
});
The cleanest way I can come up with for what you're trying to do is to set a document.onmousedown event and then halt event propagation on the box.onmousedown event. This avoids creating a large number of onmousedown events all over the document, and avoids having to recurse through the entire parent hierarchy of a node every time an event is triggered.
document.onmousedown = function() {
alert("Foo!");
};
document.getElementById("box").onmousedown = function(e) {
alert("Bar!");
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
};