Wondering why when adding more than 1 dynamic button their toggle bg color listener won't work consistently.
If I add let's say 3 buttons, listener for button 1 & 3 will work while button 2 won't.
//document.ready
function ready(fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
//event listener on dynamic buttons not in DOM
ready(function() {
document.querySelector('.makeB').addEventListener('click', function() {
var btn = document.createElement("button");
var t = document.createTextNode("toggle bg");
btn.appendChild(t);
document.body.appendChild(btn);
btn.classList.add('elis');
var newBtns = document.querySelectorAll('.elis');
newBtns.forEach(function(i) {
i.addEventListener('click', function() {
document.body.classList.toggle('bg');
})
})
})
})
.bg {
background: red;
}
<button class="makeB">create button</button>
Researched a bit of bubbling & event delegation options to see if that would help (similar to jquery approach $(document).on( eventName, selector, function(){} ); but no luck)
How to make dynamic buttons work consistently and what's the issue with the current scenario?
JS only please (no jQuery).
//document.ready
function ready(fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
//event listener on dynamic buttons not in DOM
ready(function() {
document.querySelector('.makeB').addEventListener('click', function() {
var btn = document.createElement("button");
var t = document.createTextNode("toggle bg");
btn.appendChild(t);
document.body.appendChild(btn);
btn.classList.add('elis');
btn.addEventListener('click', function() {
document.body.classList.toggle('bg');
})
})
})
.bg {
background: red;
}
<button class="makeB">create button</button>
Another way to simplify your code would be to use event delegation. Just add click event listener to body or some other enclosing container (possibly the closest one) and execute its code only when event.target is one of your buttons.
That way you will skip that part where you are adding event listeners to the same buttons over and over again.
function ready(fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
document.body.addEventListener('click', event => {
if (event.target.className === 'elis') {
document.body.classList.toggle('bg');
}
});
ready(function() {
document.querySelector('.makeB').addEventListener('click', function() {
const btn = document.createElement("button");
btn.textContent = "toggle bg";
btn.classList.add('elis')
document.body.appendChild(btn);
})
})
.bg {
background: red;
}
<button class="makeB">create button</button>
They do work. The problem is you're just adding more listeners every time you add a button, so they only look like they work when there are an odd number of listeners on any particular button. Just add a listener to the button before you put it in the document.
Related
Originally I was doing this:
button.addEventListener('click', function(e) {
e.preventDefault();
var clickedButton = this;
}
However, I need to listen out for a button which is dynamically added to the DOM. So now I am doing this:
document.addEventListener('click', function(e) {
if (e.target.nodeName == "BUTTON") {
e.preventDefault();
var clickedButton = e.target;
}
}
This works in the case of:
<button>Click me</button>
However, it doesn't work with:
<button><i class="icon"></i></button>
The reason it doesn't work is because e.target ends up as I instead of BUTTON, however I need to specifically target the button. Some buttons might contain an icon, whilst others might be just text, and some could be a combination of the two.
You should check also .closest('button')
document.addEventListener('click', function(e) {
if (e.target.nodeName == "BUTTON" || e.target.closest('button')) {
e.preventDefault();
var clickedButton = e.target;
console.log(e.target)
}
})
<button><i class="icon">ss</i></button>
suggestion: better match className instead of element name
You can use CSS to disable click events on any elements nested inside a button:
Before:
document.addEventListener('click', function(e) {
console.log(e.target);
if (e.target.nodeName == "BUTTON") {
console.log("stop");
e.preventDefault();
var clickedButton = e.target;
}
})
.icon {
background-color: purple;
color: white;
}
<button>Click me</button>
<br/>
<button><i class="icon">image here</i></button>
After:
document.addEventListener('click', function(e) {
console.log(e.target);
if (e.target.nodeName == "BUTTON") {
console.log("stop");
e.preventDefault();
var clickedButton = e.target;
}
})
.icon {
background-color: purple;
color: white;
}
/* disable clicks */
button * {
pointer-events: none;
}
<button>Click me</button>
<br/>
<button><i class="icon">image here</i></button>
Vanilla JavaScript
In vanilla JavaScript, one can easily enable and disable a button using the following statement:
button.disabled = state;
This works both when humans try to click a button and when buttons are clicked programmatically:
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
alert('world');
});
button.disabled = true;
button.click(); // No output
button.disabled = false;
button.click(); // Output : "Hello" and "world
button.disabled = true;
button.click(); // No output
<input type="button" id="myButton" value="button" onClick="alert('Hello')"/>
This also works when using the MouseEvent interface:
var button = document.getElementById('myButton');
var click = new MouseEvent("click", {
"view": window
});
button.addEventListener('click', function() {
alert('world');
});
button.disabled = true;
button.dispatchEvent(click); // No output
button.disabled = false;
button.dispatchEvent(click); // Output : "Hello" and "world
button.disabled = true;
button.dispatchEvent(click); // No output
<input type="button" id="myButton" value="button" onClick="alert('Hello')"/>
jQuery
I can't seem to be able to do the same with jQuery, though :
var button = $("#myButton");
button.on("click", function() {
alert("world");
});
button.prop("disabled", true);
button.click(); // Output : "world" and "Hello"
button.prop("disabled", false);
button.click(); // Output : "world" and "Hello"
button.prop("disabled", true);
button.click(); // Output : "world" and "Hello"
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<input type="button" id="myButton" value="button" onClick="alert('Hello')"/>
Both button.prop("disabled", true); and button.attr("disabled", true); simply change the disabled property of the button element, but neither disables the actual click event. This means that events are triggered whenever button.click(); is called, even if the button is disabled!
Additionally, "world" and "Hello" are output in the wrong order.
The simplest code I could come up with to emulate the behavior of the vanilla JavaScript versions, is this :
var button = $("#myButton");
button.on("click", function() {
alert("world");
});
button.disable = (function() {
var onclick = null;
var click = [];
return function(state) {
if(state) {
this.prop('disabled', true);
if(this.prop('onclick') !== null) {
onclick = this.prop('onclick');
this.prop('onclick', null);
}
var listeners = $._data(this.get()[0], "events");
listeners = typeof listeners === 'undefined' ? [] : listeners['click'];
if(listeners && listeners.length > 0) {
for(var i = 0; i < listeners.length; i++) {
click.push(listeners[i].handler);
}
this.off('click');
}
} else {
this.removeProp('disabled');
if(onclick !== null) {
this.prop('onclick', onclick);
onclick = null;
}
if(click.length > 0) {
this.off('click');
for(var i = 0; i < click.length; i++) {
this.on("click", click[i]);
}
click = [];
}
}
}
})();
button.disable(true);
button.click(); // No output
button.disable(false);
button.click(); // Output : "Hello" and "world
button.disable(true);
button.click(); // No output
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<input type="button" id="myButton" value="button" onClick="alert('Hello')"/>
That is, of course, ridiculously convoluted and "hacky" code to achieve something as simple as disabling a button.
My questions
Why is it that jQuery - unlike vanilla JS - doesn't disable the events when disabling a button?
Is this to be considered a bug or a feature in jQuery?
Is there something I'm overlooking?
Is there a simpler way to get the expected behavior in jQuery?
To achieve expected result, you can utilize .isTrigger within jQuery triggered click handler to determine if event is triggered by javascript, and not user action.
Define attribute event listener as a named function, where this can be passed to check disabled property at if condition if alert() is called, or not called.
Use .attr("disabled", "disabled") to set disabled at element, .removeAttr("disabled") to remove attribute; .attr("onclick", null) to remove event attribute onclick handler; .attr("onclick", "handleClick(true)") to reset event attribute.
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<input type="button" id="myButton" value="button" onclick="handleClick(this)" />
<script>
function handleClick(el) {
if (el.disabled !== "disabled")
alert("Hello")
}
var button = $("#myButton");
button.on("click", function(e) {
console.log(e);
if (e.isTrigger !== 3 && !e.target.disabled)
alert("world");
});
button.attr("disabled", "disabled");
button.attr("onclick", null);
button.click(); // no output
setTimeout(function() {
button.removeAttr("disabled");
button.attr("onclick", "handleClick(button[0])");
button.click(); // Output : "world" and "Hello"
// click button during 9000 between `setTimeout` calls
// to call both jQuery event and event attribute
}, 1000);
setTimeout(function() {
button.attr("disabled", "disabled");
button.attr("onclick", null);
button.click(); // no output
}, 10000);
</script>
If you take a look to jquery-1.12.4.js at these lines:
handlers: function( event, handlers ) {
var i, matches, sel, handleObj,
handlerQueue = [],
delegateCount = handlers.delegateCount,
cur = event.target;
// Support (at least): Chrome, IE9
// Find delegate handlers
// Black-hole SVG <use> instance trees (#13180)
//
// Support: Firefox<=42+
// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
if ( delegateCount && cur.nodeType &&
( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
/* jshint eqeqeq: false */
for ( ; cur != this; cur = cur.parentNode || this ) {
/* jshint eqeqeq: true */
// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
You will you see a different handling of events according to the delegation type:
$(document).on("click", '#btn', function() {
console.log("world");
});
$(function () {
$('#btnToggle').on('click', function(e) {
$('#btn').prop('disabled', !$('#btn').prop('disabled'));
});
$('#btnTestClick').on('click', function(e) {
$('#btn').click();
});
});
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<button id="btn">Click Me</button>
<button id="btnToggle">Enable/Disable button</button>
<button id="btnTestClick">Test Click</button>
Of course, if you attach the event like in:
$('#btn').on("click", function() {
alert("world");
});
The behaviour is different and seems strange.
Using .prop() is the right way to do it. I think the issue is in the way that you are "testing" it. See this example where the buttons are disabled/enabled correctly using the toggle button regardless of whether the handler is attached via onclick or with jquery.
window.testFunc = function(event) {
if (!$('#myButton2').prop('disabled')) {
alert("hello");
console.log("hello");
}
}
$(document).ready(function() {
var button = $("#myButton2");
button.on("click", function(event) {
if (!$(this).prop('disabled')) {
alert("world");
console.log("world");
}
});
$('#toggleButton').click(function() {
$('#myButton1').prop('disabled', !$('#myButton1').prop('disabled'));
$('#myButton2').prop('disabled', !$('#myButton2').prop('disabled'));
});
$('#tester').click(function() {
$('#myButton1').click();
$('#myButton2').click();
});
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="myButton1" value="vanilla button (hello)" onclick="window.testFunc(event)"/>
<input type="button" id="myButton2" value="jquery button (world)"/>
<input type="button" id="toggleButton" value="toggle disabled"/>
<input type="button" id="tester" value="test the buttons"/>
The other obvious solution is to just use vanilla javascript. Just because you are using jQuery doesn't mean that everything "must" be done using it. There are some things that are fine to do without jQuery.
EDIT: I edited the snippet showing how you could prevent jquery's .click() from actually triggering the alerts.
You're calling the click function directly 3 times ( button.click() ) which fires regardless of disabled attribute.
The disabled property only responds to click events.
See the updated example:
var button = $("#myButton");
var button2 = $("#myButton2");
button.prop("disabled", false);
button.on("click", function() {
alert("world");
button2.prop("disabled", false);
});
button2.prop("disabled", true);
button2.on("click", function() {
alert("world");
button.prop("disabled", true);
});
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<input type="button" id="myButton" value="button" onClick="alert('Hello')"/>
<input type="button" id="myButton2" value="button2" />
This is really straight forward but I'm still fairly new to JavaScript and just found JSFiddle. I'm trying to find the element with the getElementById() to disable and enable a button. What am I missing?
<form name="frm" >
<div id="chkObj">
<input type="checkbox" name="setChkBx" onclick="basicList.modifyAndEnableButton(this)"></input>
</div>
<div id="Hello">
<input type="button" name="btn" value="Hello"></input>
</div>
</form>
This is a list that I am using to add checkboxes because there is going to be more than one:
var basicList = {
'items': {},
'modifyAndEnableButton': function(obj1) {
var element = document.getElementsByName("btn");
if (obj1.checked == true && element.getAttribute('disabled') == false) {
element.getAttribute('disabled') = true;
this.addRecord(obj2);
} else if (element.getAttribute('disabled') == true) {
if (hasItems == false) {
element.getAttribute('disabled') = false;
}
}
}
};
http://jsfiddle.net/Arandolph0/E9zvc/3/
All browsers support this (see example here):
mySelectedElement.onclick = function(e){
//your handler here
}
However, sometimes you want to add a handler (and not change the same one), and more generally when available you should use addEventListener (needs shim for IE8-)
mySelectedElement.addEventListener("click",function(e){
//your handler here
},false);
Here is a working example:
var button = document.getElementById("myButton");
button.addEventListener("click",function(e){
button.disabled = "true";
},false);
And html:
<button id='myButton'>Hello</button>
(fiddle)
Here are some useful resources:
addEventListener on mdn
The click event in the DOM specification
Click example in the MDN JavaScript tutorial
Benjamin's answer covers quite everything. However you need a delegation model to handle events on elements that were added dynamically then
document.addEventListener("click", function (e) {
if (e.target.id == "abc") {
alert("Clicked");
}
});
For IE7/IE8
document.attachEvent('onclick', function (e) {
if (window.event.srcElement == "abc") {
alert("Clicked");
}
});
You have a Error here
btnRush should be Rushbtn
This is a example of cross browser event's I just made (not tested) )
var addEvent = function( element, type, callback, bubble ) { // 1
if(document.addEventListener) { // 2
return element.addEventListener( type, callback, bubble || false ); // 3
}
return element.attachEvent('on' + type, callback ); // 4
};
var onEvent = function( element, type, callback, bubble) { // 1
if(document.addEventListener) { // 2
document.addEventListener( type, function( event ){ // 3
if(event.target === element || event.target.id === element) { // 5
callback.apply(event.target, [event]); // 6
}
}, bubble || false);
} else {
document.attachEvent( 'on' + type, function( event ){ // 4
if(event.srcElement === element || event.srcElement.id === element) { // 5
callback.apply(event.target, [event]); // 6
}
});
}
};
Steps
Create a function that accepts 4 values ( self explaining )
Check if the browser supports addEventListener
Add event on the element
else add event on the element for older IE
Check that the (clicked) element is = to the passed element
call the callback function pass the element as this and pass the event
The onEvent is used for event delegation.
The addEvent is for your standard event.
here's how you can use them
The first 2 are for dynamically added elements
onEvent('rushBtn', 'click', function(){
alert('click')
});
var rush = document.getElementById('rushBtn');
onEvent(rush, 'click', function(){
alert('click');
});
// Standard Event
addEvent(rush, 'click', function(){
alert('click');
});
Event Delegation is this basically.
Add a click event to the document so the event will fire whenever & wherever then you check the element that was clicked on to see if it matches the element you need. this way it will always work.
Demo
I want to detect in my firefox extension if a link has been clicked. So far, for this I add a click event listener to the window
window.addEventListener("click", function(event) { handleWindowClick(event); }, false);
...
handleWindowClick : function(event) {
if ("event.target is a link") {
// do something
}
};
For some links the event.target is simple the URL. However, for some links I get, e.g., a HTMLSpanElement as event.target. Am I on the right track to catch link clicks or are there other ways? If it works this way, how can I ensure the successfully test if the event.targer is a link?
You're adding an event listener to the main window which registers any click. The url's you're having problems with must be wrapped in a <span> tag. What you need is event delegation
Why don't you just put the click event listener to anchors (<a>)?
var hrefs = document.getElementsByTagName('a');
for (i = 0; i < hrefs.length; i++) {
hrefs[i].addEventListener(...)
...
}
or in jQuery:
$('a').click(function () {
...
});
check this out, i hope this is what you are looking for.
window.addEventListener("click", function(event) {
handleWindowClick(event);
}, false);
function handleWindowClick(event){
var origEl = event.target || event.srcElement;
if(origEl.tagName === 'A')
alert("anchor link is clicked");
else if(origEl.parentNode.tagName === 'A')
alert("clicked inside anchor");
else if(origEl.tagName === 'SPAN')
alert("span is clicked");
}
fiddle : http://jsfiddle.net/5zXkN/3/
I extended the answer of dku.rajkumar to support arbitrary constructs within "A"-tags. I'm simply go up the tree until I either find an "A" or I'm at the root (so no link clicked in this case). It seems to do the trick. Thanks to all for your help!
window.addEventListener("click", function(event) { handleWindowClick(event); }, false);
...
isLink : function(element) {
if(element.tagName === 'A')
return true;
else
if (element.parentNode)
return this.isLink(element.parentNode)
else
return false;
},
handleWindowClick : function(event) {
var element = event.target || event.srcElement;
var isLink = this.isLink(element);
if (isLink)
dump("A link has been clicked.\n");
},
$(document).click(function(evt) {
var target = evt.currentTarget;
var inside = $(".menuWraper");
if (target != inside) {
alert("bleep");
}
});
I am trying to figure out how to make it so that if a user clicks outside of a certain div (menuWraper), it triggers an event.. I realized I can just make every click fire an event, then check if the clicked currentTarget is same as the object selected from $(".menuWraper"). However, this doesn't work, currentTarget is HTML object(?) and $(".menuWraper") is Object object? I am very confused.
Just have your menuWraper element call event.stopPropagation() so that its click event doesn't bubble up to the document.
Try it out: http://jsfiddle.net/Py7Mu/
$(document).click(function() {
alert('clicked outside');
});
$(".menuWraper").click(function(event) {
alert('clicked inside');
event.stopPropagation();
});
http://api.jquery.com/event.stopPropagation/
Alternatively, you could return false; instead of using event.stopPropagation();
if you have child elements like dropdown menus
$('html').click(function(e) {
//if clicked element is not your element and parents aren't your div
if (e.target.id != 'your-div-id' && $(e.target).parents('#your-div-id').length == 0) {
//do stuff
}
});
The most common application here is closing on clicking the document but not when it came from within that element, for this you want to stop the bubbling, like this:
$(".menuWrapper").click(function(e) {
e.stopPropagation(); //stops click event from reaching document
});
$(document).click(function() {
$(".menuWrapper").hide(); //click came from somewhere else
});
All were doing here is preventing the click from bubbling up (via event.stopPrpagation()) when it came from within a .menuWrapper element. If this didn't happen, the click came from somewhere else, and will by default make it's way up to document, if it gets there, we hide those .menuWrapper elements.
try these..
$(document).click(function(evt) {
var target = evt.target.className;
var inside = $(".menuWraper");
//alert($(target).html());
if ($.trim(target) != '') {
if ($("." + target) != inside) {
alert("bleep");
}
}
});
$(document).click((e) => {
if ($.contains($(".the-one-you-can-click-and-should-still-open").get(0), e.target)) {
} else {
this.onClose();
}
});
I know that the question has been answered, but I hope my solution helps other people.
stopPropagation caused problems in my case, because I needed the click event for something else. Moreover, not every element should cause the div to be closed when clicked.
My solution:
$(document).click(function(e) {
if (($(e.target).closest("#mydiv").attr("id") != "mydiv") &&
$(e.target).closest("#div-exception").attr("id") != "div-exception") {
alert("Clicked outside!");
}
});
http://jsfiddle.net/NLDu3/
I do not think document fires the click event. Try using the body element to capture the click event. Might need to check on that...
This code will open the menu in question, and will setup a click listener event. When triggered it will loop through the target id's parents until it finds the menu id. If it doesn't, it will hide the menu because the user has clicked outside the menu. I've tested it and it works.
function tog_alerts(){
if($('#Element').css('display') == 'none'){
$('#Element').show();
setTimeout(function () {
document.body.addEventListener('click', Close_Alerts, false);
}, 500);
}
}
function Close_Alerts(e){
var current = e.target;
var check = 0;
while (current.parentNode){
current = current.parentNode
if(current.id == 'Element'){
check = 1;
}
}
if(check == 0){
document.body.removeEventListener('click', Close_Alerts, false);
$('#Element').hide();
}
}
function handler(event) {
var target = $(event.target);
if (!target.is("div.menuWraper")) {
alert("outside");
}
}
$("#myPage").click(handler);
try this one
$(document).click(function(event) {
if(event.target.id === 'xxx' )
return false;
else {
// do some this here
}
});
var visibleNotification = false;
function open_notification() {
if (visibleNotification == false) {
$('.notification-panel').css('visibility', 'visible');
visibleNotification = true;
} else {
$('.notification-panel').css('visibility', 'hidden');
visibleNotification = false;
}
}
$(document).click(function (evt) {
var target = evt.target.className;
if(target!="fa fa-bell-o bell-notification")
{
var inside = $(".fa fa-bell-o bell-notification");
if ($.trim(target) != '') {
if ($("." + target) != inside) {
if (visibleNotification == true) {
$('.notification-panel').css('visibility', 'hidden');
visibleNotification = false;
}
}
}
}
});