Do disabled button's children bubble? - javascript

EDIT
I think this has to do with custom elements. If I replace the button's child with a simple <p> we get the expected behavior (i.e. clicking on the child does not result in the parent's event handler being executed)
<div id="container">
<button disabled>
<p>X</p>
<button>
</div>
Say I have the following:
<div id="container">
<button disabled>
<some-custom-element>
< /* some svg stuff /* ></>
</some-custom-element>
<button>
</div>
say I have
$container = document.getElementById('container')
$container.addEventListener('click', () => {
// stuff
})
If I click the BUTTON (like the very edge of it, so not the child), I do not end up in the button's parent's click listener.
But if I click the button's child, the event makes its way up to the button's parent, despite the button being disabled. I had expected the propagation to stop at the disabled button but I don't really know why I expected that.
Is this expected?

Related

Trigger parent onClick when clicking children

In ReactJS, how can I trigger the onClick listener of the parent div, regardless of whether or not the click is fired over a child element? With the following code, onItemClick is only triggered if I click somewhere in the parent div where the children are not present.
<div class={className} onClick={this.onItemClick}>
<Row key={"row_"+id}>
<Col><FormGroup controlId={"name"}><Form.Control type="text" disabled value={name}/></FormGroup></Col>
<Col><FormGroup controlId={"status"}><Form.Control type="text" disabled value={status}/></FormGroup></Col>
<Col><FormGroup controlId={"warmth"}><Form.Control type="text" disabled value={warmth}/></FormGroup></Col>
</Row>
</div>
You can give parent's onclick function to the child with props and call this.props.onClick in the div onClick of the child.

Javascript event listener fires twice when clicking on text inside label element

How do i make a function not fire twice when clicking on text inside a label.
If I use event.preventDefault() then basic browser functionality for making the checkbox checked will stop working too.
const label = document.querySelector('.parent');
label.addEventListener('click', handleLabelClick);
function handleLabelClick(event) {
console.log('Clicked')
}
<div class="parent">
<label for="option1">
<span>Select me</span>
<input id="option1" type="checkbox">
</label>
</div>
As I understand it, you want clicks on .parent elements to fire a click handler, but you don't want that handler fired for clicks related to a checkbox or its label within .parent.
Two ways to do that:
Add a handler for the label that calls stopPropagation, or
Check within the event handler whether the event passed through the label
Here's approach #1:
const parent = document.querySelector('.parent');
parent.addEventListener('click', handleLabelClick);
// Stop clicks in the label or checkbox from propagating to parent
parent.querySelector("label").addEventListener("click", function(event) {
event.stopPropagation();
});
function handleLabelClick(event) {
console.log('Clicked');
}
.parent {
border: 1px solid #ddd;
}
<div class="parent">
<label for="option1">
<span>Select me</span>
<input id="option1" type="checkbox">
</label>
</div>
Here's approach #2:
const parent = document.querySelector('.parent');
parent.addEventListener('click', handleLabelClick);
function handleLabelClick(event) {
const label = event.target.closest("label");
if (label && this.contains(label)) {
// Ignore this click
return;
}
console.log('Clicked');
}
.parent {
border: 1px solid #ddd;
}
<div class="parent">
<label for="option1">
<span>Select me</span>
<input id="option1" type="checkbox">
</label>
</div>
This is a standard (unfortunate) browser behavior.
Attribute for assigns <label> to <input>, so when <label> element is clicked, browser will emulate a click on <input> element right after your real click.
On a plus side, this allows focus of <input type="text", switching of <input type="radio", or toggling of <input type="checkbox".
But for the unfortunate side, this also causes that both elements send the click event. In case you are listening on clicks on a parent element, this means that you'll receive one "human interaction" twice. Once from <input> and once from "the clicked element".
For those who wonder, <input> could be inside <label> element, you'll get less styling possibility, but you can then click in between check-box and text.
Your putting <span> and <input> inside <label> actually creates a nice test case.
Try clicking from left to right;
On the text, you'll receive SPAN + INPUT events,
between text and check-box you'll get LABEL + INPUT events,
on the check-box directly only INPUT event,
then further right, only DIV event.
(because DIV is a block element and spans all the way to the right)
One solution would be to listen only on <input> element events, but then you will not capture <div> clicks and you also can't put <div> inside <label>.
The simplest thing to do is to ignore clicks on <label> and all clickable elements inside <label> except <input>. In this case <span>.
const elWrapper = document.querySelector('.wrapper');
elWrapper.addEventListener('click', handleLabelClick);
function handleLabelClick(event) {
console.log('Event received from tagName: '+event.target.tagName);
if (event.target.tagName === 'LABEL' || event.target.tagName === 'SPAN') { return; }
console.log('Performing some action only once. Triggered by click on: '+event.target.tagName);
}
<div class="wrapper">
<label for="option1">
<span>Select me</span>
<input id="option1" type="checkbox">
</label>
</div>
(from OP's example I changed parent to wrapper and label to elWrapper,
because parent and label are keywords.)
The solution from #T.J. causes events from <label> and everything inside it to be ignored down-the-road.
To get the event fired, you'd need to click somewhere on the <div>, but not directly on the text or check-box.
I added my answer because I didn't think this was the OP's intention.
But even for this other case, you might use similar approach as I offered above. Checking if clicked element name is DIV then allowing further actions. I think it's more straightforward, more localized (doesn't affect event propagation), more universal (if you select capturing: addEventListener(...,...,true) this will still work)

Buttons with dynamic content

I have buttons with labels that change depending on the topic that gets loaded. When clicked, the button loads new data to a chart. This works well on span elements but it doesn't work when I wrap the span in a button.
Example:
<button type = "button" class = "btn btn-default"><span id="cat1"></span></button>
Labels are set with :
click: function (e) {
document.getElementById('cat1').innerHTML = 'something'
};
Data is bound on click with:
$("#cat1").click(function() {
// do stuff
});
something gets lost when I add the button for the span. I have tried to replace span with an anchor but it doesn't work either.
Did you try?
<button type="button" class="btn btn-default" id="cat1"></button>
How you are using jQuery, you can try a more simple form.
Try it:
$('#cat1').click(function () {
$(this).html('something');
});
Or if the set value is out of the event, you cant try it:
$('#cat1').html('something');
You can update the text in span when button is clicked using following code
$('#btnClick #cat1').text("something");
In the above I gave a id for button as btnClick.
You can achieve the functionality without using span as well. For that you need to specify button as
<button id="btnClick">Button</button>
To change text of button, the code which need to be in click event will be
$('#btnClick').text("change")
When a span is nested inside a button, every time you click on it, the button click is fired. but the span click is not fired
based on your html, it looks like you are using bootstrap, you can do something like the following
<div class="btn btn-default">
<span id="cat1"></span>
</div>
I have a working example in the fiddle.
http://jsfiddle.net/pparas/dv3amy7k/1/

ng-mouseover on disabled button in Angularjs

I want to use mouseover when the button is disabled. In the below code mouseover will work if ng-disabled="false" but it won't work if ng-disabled="true".
<body ng-app="ngAnimate">
<button ng-disabled="true" ng-mouseover="show=true" ng-mouseleave="show = false">
Mouseover
</button>
<div>
Show:
<span class="test" ng-show="show">
I show up when your mouse enter on button
</span>
</div>
</body>
It's not possbile. Actually it has nothing to do with Angular. It's expected behaviour when browsers are not supposed to fire onmouseover, onclick, etc. events on disabled form controls. So you can't do it directly.
Can't do it directly - meaning, that you can bind mouseover even to wrapping container which would not have this limitation. Then you would need to control action and proceed only if disabled flag is true or false if you need.
That being said, you should probably not try to workaround this behaviour. Form UX perspective disabled control should not be interaction-able, after all that's what disabled means.
I recently faced a similar problem where i disable a submit button on a form unless the form is valid. When the user hover over the disabled button, I wanted all required fields to get a different color.
I solved this using a html structure like this:
<div ng-class="{error: showError}">
<div disabled-wrapper ng-mouseenter="checkValid()" ng-mouseleave="showError = false">
<div><button ng-disabled="!valid">Next</button></div>
</div>
</div>
And css like this:
[disabled-wrapper] {
position: relative;
z-index: 0;
}
[disabled-wrapper] [disabled] {
position: relative;
z-index: -1;
}
And controller function:
$scope.checkValid = function() {
$scope.showError = !$scope.valid;
}
// I have more logic regarding validity of form.
// I am not sure why the div within the wrapper is needed (but it is).
// The positioning and z-index of the wrapper prevents any parent element with back-ground color from overshadowing the disabled button.

Prevent checkbox from ticking/checking COMPLETELY

I have been asked to disable the "ticking" of a checkbox. I am not being asked to disable the checkbox, but to simply disable the "ticking".
In other words, a user will think that a checkbox is tickable, but it is not. Instead, clicking on the checkbox will cause a modal dialog to appear, giving the user more options to turn on or off the feature that the checkbox represents. If the options chosen in the dialog cause the feature to be turned on, then the checkbox will be ticked.
Now, the real problem is that for a split second, you can still see that the checkbox is being ticked.
I have tried an approach like this:
<input type='checkbox' onclick='return false' onkeydown='return false' />
$('input[type="checkbox"]').click(function(event) {
event.preventDefault();
alert('Break');
});
If you run this, the alert will appear, showing that the tick is visible (the alert is just there to demonstrate that it still does get ticked, in production, the alert is not there). On some users with slower machines and/or in browsers with slow renderers/javascript, users can see a very faint flicker (the flicker sometimes lasts for half a second, which is noticeable).
A tester in my team has flagged this as a defect and I am supposed to fix it. I'm not sure what else I can try to prevent the tick in the checkbox from flickering!
From my point of view it is as simple as:
$(this).prop('checked', !$(this).prop('checked'));
Works both for checked and unchecked boxes
Try
event.stopPropagation();
http://jsfiddle.net/DrKfE/3/
Best solution I've come up with:
$('input[type="checkbox"]').click(function(event) {
var $checkbox = $(this);
// Ensures this code runs AFTER the browser handles click however it wants.
setTimeout(function() {
$checkbox.removeAttr('checked');
}, 0);
event.preventDefault();
event.stopPropagation();
});
This effect can't be suppressed I fear. As soon as you click on the checkbox, the state (and rendering) is changed. Then the event handlers will be called. If you do a event.preventDefault(), the checkbox will be reset after all the handlers are executed. If your handler has a long execution time (easily testable with a modal alert()) and/or the rendering engine repaints before reseting, the box will flicker.
$('input[type="checkbox"]').click(function(event) {
this.checked = false; // reset first
event.preventDefault();
// event.stopPropagation() like in Zoltan's answer would also spare some
// handler execution time, but is no more needed here
// then do the heavy processing:
alert('Break');
});
This solution will reduce the flickering to a minimum, but can't hinder it really. See Thr4wn's and RobG's answer for how to simulate a checkbox. I would prefer the following:
<button id="settings" title="open extended settings">
<img src="default_checkbox.png" />
</button>
document.getElementById("settings").addEventListener("click", function(e) {
var img = this.getElementsByTagName("img")[0]);
openExtendedSettingsDialog(function callbackTick() {
img.src = "checked_checkbox.png";
}, function callbackUntick() {
img.src = "unchecked_checkbox.png";
});
}, false);
It is very important to use return false at the end.
Something like this:
$("#checkbox").click((e) => {
e.stopPropagation();
return false;
});
Isn't is simpler ? :
<input type="checkbox" onchange="this.checked = !this.checked">
TL:DR;
HTML api's execute before JavaScript. So you must use JavaScript to undo HTML's changes.
event.target.checked = false
WHAT is the problem?
Strictly speaking: we cannot "stop" the checkbox from being ticked. Why not? Because "being ticked" exactly means that the DOM's, HTML <input> element has a checked property value of true or false, which is immediately assigned by the HTML api
console.log(event.target.checked) // will be opposite of the previous value
So it's worth explicitly mentioning this HTML api is called before scripts. Which is intuitive and should make sense, because all JavaScript files are themselves the assignment of a <script> element's attribute src, and the ancestral relationship in the DOM tree, between your <input> in question, and the <script> element running your JavaScript, is extremely important to consider.
HOW to get our solution
The HTML assigned value has not yet been painted before we have a chance to intercept the control flow (via JS file like jQuery), so we simply re-assign the checked property to a boolean value we want: false (in your case).
So in conclusion, we CAN, in-effect, "stop" the checkbox from being checked, by simply ensuring that the checked property is false on the next render and thus, won't see any changes.
Why not simply add a class in your CSS that sets pointer-events: none;?
Something like:
<style>
input.lockedCbx { pointer-events: none; }
</style>
...
<input type="checkbox" class="lockedCbx" tabindex=-1 />
...
You need the tabindex=-1 to prevent users from tabbing into the checkbox and pressing a space bar to toggle.
Now in theory you could avoid the class and use the tabindex=-1 to control the disabling as in:
<script>
input[type="checkbox"][tabindex="-1"] { pointer-events: none; }
</script>
With CSS, you can change the image of the checkbox. See http://ryanfait.com/resources/custom-checkboxes-and-radio-buttons/ and also CSS Styling Checkboxes .
I would disable the checkbox, but replace it with an image of a working checkbox. That way the checkbox doesn't look disabled, but won't be clickable.
Wrap the checkbox with another element that somehow blocks pointer events (probably via CSS). Then, handle the wrapper's click event instead of the checkbox directly. This can be done a number of ways but here's a relatively simple example implementation:
$('input[type="checkbox"').parent('.disabled').click( function() {
// Add in whatever functionality you need here
alert('Break');
});
/* Insert an invisible element that covers the checkbox */
.disabled {
position: relative;
}
.disabled::after {
content: "";
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Only wrapped checkboxes are "disabled" -->
<input type="checkbox" />
<span class="disabled"><input type="checkbox" /></span>
<input type="checkbox" />
<span class="disabled"><input type="checkbox" /></span>
<span class="disabled"><input type="checkbox" /></span>
<input type="checkbox" />
Note: You could also add the wrapper elements programmatically, if you would like.
Sounds to me like you are using the wrong interface element, a more suitable one would be a button that is disabled by default, but enabled when that option is available. The image displayed can be whatever you want.
<button disabled onclick="doSomething();">Some option</button>
When users have selected that feature, enable the button. The image on the button can be modified by CSS depending on whether it's enabled or not, or by the enable/disable function.
e.g.
<script type="text/javascript">
function setOption(el) {
var idMap = {option1:'b0', option2: 'b1'};
document.getElementById(idMap[el.value]).disabled = !el.checked;
}
</script>
<div><p>Select options</p>
<input type="checkbox" onclick="setOption(this);" value="option1"> Option 1
<br>
<input type="checkbox" onclick="setOption(this);" value="option2"> Option 2
<br>
</div>
<div>
<button id="b0" onclick="alert('Select…');" disabled>Option 1 settings</button>
<button id="b1" onclick="alert('Select…');" disabled>Option 2 settings</button>
</div>
The Event.preventDefault method should work for change, keydown, and mousedown events, but doesn't in my testing.
My solution to this problem in a Mozilla Firefox 53.0 extension was to toggle an HTML class that enabled/disabled the CSS declaration pointer-events: none being applied to the checkbox. This addresses the cursor-based case, but not the key-based case. See https://www.w3.org/TR/SVG2/interact.html#PointerEventsProp.
I addressed the key-based case by adding/removing an HTML tabindex="-1" attribute. See https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex.
Note that disabling pointer-events will disable your ability to trigger CSS cursors on hover (e.g., cursor: not-allowed). My checkbox was already wrapped in a span element, so I added an HTML class to that span element which I then retargeted my CSS cursor declaration onto.
Also note that adding a tabindex="-1" attribute will not remove focus from the checkbox, so one will need to explicitly defocus it by using the HTMLElement.blur() method or by focusing another element to prevent key-based input if the checkbox is the active element at the time the attribute is added. Whether or not the checkbox is the focused element can be tested with my_checkbox.isEqualNode(document.activeElement).
Simply revert the value back
$('input[type="checkbox"]').on('change', function(e) {
if (new Date().getDate() === 13) {
$(this).prop('checked', !$(this).prop('checked'));
e.preventDefault();
return false;
}
// some code here
});
Add this to click event in js file
event.stopPropagation();
$('#term-input').on('change click',function (e){
e.preventDefault();
})
works for me

Categories