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?
I would like to be able to focus on input field when certain keys are entered. The input I would like to focus on exists inside autocomplete-vue.
This is where I call it:
<autocomplete v-shortkey="['alt', 's']"
#shortkey.native="theAction()"
ref="autocompleteInput"
></autocomplete>
theAction method which I would like to allow me to focus on the input, looks like this:
theAction () {
this.$refs.autocompleteInput.$el.focus()
}
this focus on the whole section which is not what I want. the input exists 2 divs inside the what theAction focuses on. For bettere perspective, this is what this.$refs.autocompleteInput.$el returns :
<div>
<div data-position="below" class="autocomplete">
<input role="combobox" class="autocomplete-input">
</div>
</div>
Any ideas on how I can focus on the input with class autocomplete-input? any suggestion is helpful!
Add a ref in the autocomplete component for the <input> and add a method to focus it
<input role="combobox" class="autocomplete-input" ref="input">
methods: {
focus() {
this.$refs.input.focus()
}
}
You can then call it from the parent component like this
this.$refs.autocompleteInput.focus()
I have a form with state handled in a context. within the form I have a checkbox component which I am trying to have its state handled within the context. So when I submit the form the checkbox returns a Boolean whether it is checked or not. I'm having trouble with the event handler triggering the checkbox.
I have a number of textfields in the form which are behaving as expected and their values can be seen in the Db once the form is submitted.
Within the checkbox component the actual input is hidden for styling purposes. So the user will be clicking on the label(which is styled to look like a checkbox) or a secondary label (acting as a label)
I'd like it so that when a user clicks on either of the labels the checkbox is triggered. I had the onClick on the checkbox input but seeing as it's display is hidden the user cannot trigger the event.
If I put the onClick on either of the labels or a parent element my onChange functions e.target is pointing to the wrong location.
Is there a way to pass in the the input as the e.target? if the onClick event is on the outer div?
onChange (within RecordForm.js)
const onChange = (e) => {
setRecord({ ...record, [e.target.name]: e.target.value });
};
Checkbox.js
const CheckBox2 = ({ value, label, name, onChange }) => {
return (
<CheckBoxContainer onClick={onChange}>
<LabelContainer>
<CheckboxInput
type='checkbox'
value={value}
name={name}
/>
<CheckBoxLabel>
<Tick className='Tick' />
</CheckBoxLabel>
</LabelContainer>
<VisibleLabel>{label}</VisibleLabel>
</CheckBoxContainer>
);
};
export default CheckBox2;
Here is my select form:
<form ref={skuSelectRef}>
<label htmlFor="size" />
<select
id="size"
value={selectedSKU}
onChange={() => changeSKU(size.options[size.selectedIndex].value)}
>
<option value="default">
{outOfStock ? 'OUT OF STOCK' : 'SELECT SIZE'}
</option>
{availableSizes.map((size, i) => (
<option key={i} value={size}>
{size}
</option>
))}
</select>
</form>
The button that when I click I would like for the select form above to open:
<div className="a2cbtn">
<button onClick={onAddToCartClick} id="a2cbtn">
<span>ADD TO CART</span>
</button>
</div>
Both of the above components are in my AddToCart subcomponent. Here is a snippet of the button click handler in the parent component where I am trying to make the select form open:
if (this.state.selectedSKU === 'default') {
this.skuSelectRef.current.click();
console.log(this.skuSelectRef);
console.log('clicked');
}
It seems that the ref is properly created because I am getting {current: form} logged to the console and the click is working as I'm getting "clicked" logged to the console as well. However the select form is not opening!
At first I tried having the ref in my select element instead of form but then when I clicked the button it gave me an error saying that the function click doesn't exist so I moved the ref to the form element enclosing it and now I am not getting that error but it is still not opening.
I have tried this.skuSelectRef.current.focus() and this.skuSelectRef.current.select() as well to no avail.
Any ideas?
If you want to focus the select when clicking the button, that can be done with refs. It's not working for you because you attached the ref to the form element and you need to be attaching it to the select element directly.
const skuSelectRef = createRef();
const onAddToCartClick = () => {
if (size === "default") {
skuSelectRef.current?.focus();
}
};
<select ref={skuSelectRef} ....
CodeSandbox Link
If you want to open the select element, that's tough. There is no open() method like there is for focus(). There's a whole discussion about how to do it on this question. But I would recommend that you not reinvent the wheel. There are lots of libraries for form and UI components that can do this via a boolean open prop. Material UI is one of the biggest. Here is their demo for a controlled open.
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)