英文:
Make eventListener target multiple elements and determine that which parent triggered it
问题
我有一些根据内容变化的输入。但似乎脚本只能处理一个元素。我希望它能处理多个元素。
请注意,我将在将来使用自定义元素
onInput(document.getElementById('input'), "lol");
document.getElementById('input').addEventListener('input', onInput);
function onInput(i, ii) {
console.log('type');
if (ii) {
var spanElm = document.getElementById('measure');
spanElm.textContent = i.getAttribute("placeholder");
i.style.width = spanElm.offsetWidth + 'px';
} else {
var spanElm = document.getElementById('measure');
if (this.value == "") {
spanElm.textContent = this.getAttribute("placeholder");
this.style.width = spanElm.offsetWidth + 'px';
} else {
spanElm.textContent = this.value;
this.style.width = spanElm.offsetWidth + 'px';
}
}
};
<div class="input">
...
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
...
</div>
<div class="input">
...
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
...
</div>
英文:
I have some inputs that change according to content. But seems like the script only can handle one element only. I want it can handle multiple elements
Please note that I'm going to use custom elements in future
<!-- begin snippet: js hide: true console: true babel: false -->
<!-- language: lang-js -->
onInput(document.getElementById('input'), "lol");
document.getElementById('input').addEventListener('input', onInput)
function onInput(i, ii) {
console.log('type')
if (ii) {
var spanElm = document.getElementById('measure');
spanElm.textContent = i.getAttribute("placeholder");
i.style.width = spanElm.offsetWidth + 'px';
} else {
var spanElm = document.getElementById('measure');
if (this.value == "") {
spanElm.textContent = this.getAttribute("placeholder");
this.style.width = spanElm.offsetWidth + 'px';
} else {
spanElm.textContent = this.value;
this.style.width = spanElm.offsetWidth + 'px';
}
}
};
<!-- language: lang-html -->
<div class="input">
...
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
...
</div>
<div class="input">
...
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
...
</div>
<!-- end snippet -->
答案1
得分: 1
尝试简化事件委托并用一个示例回答问题。
像<input>
这样的元素触发的事件会"冒泡"到DOM。现在,您可以通过将监听器附加到触发事件的元素来捕获事件,也可以将监听器附加到父元素,当事件达到它时会捕获它。
这被称为"事件委托",因为您将事件的捕获委托给未触发事件的父元素。这很有用,因为您可以将子元素分组在一起,然后让它们的父元素监听这些事件,因为它们冒泡上来。例如,您可以附加一个监听器到包含的<form>
元素,以捕获其子输入和按钮元素的所有事件。
在这个示例中,我使用了一个<fieldset>
作为父容器,并附加了一个input
监听器。当任何子元素上触发input
事件时,它们会冒泡上来并被该监听器捕获。然后,您可以检查触发事件的元素,然后编写一些代码来处理它。在这里,我们将输入元素的值添加到其兄弟输出元素的文本内容中。
(因为我不确定在这个示例中您的代码意味着什么,所以我只是将输入值显示为<div class="output">
元素的文本内容。)
// 缓存fieldset元素,并附加监听器。这个监听器将捕获其子元素冒泡上来的事件
const fieldset = document.querySelector('fieldset');
fieldset.addEventListener('input', handleChange);
// 我们的事件处理程序 - 请注意,我们将事件(`e`)作为参数传递
function handleChange(e) {
// 首先,处理程序需要检查触发事件的子元素(事件目标)
// 是否是输入元素之一
if (e.target.closest('input')) {
// 然后,我们将输出元素(输入元素的`nextElementSibling`)分配给`output`变量,
// 并将事件目标的值分配给其textContent
const output = e.target.nextElementSibling;
output.textContent = e.target.value;
}
}
fieldset > .container ~ .container { margin-top: 0.5rem; }
.container { display: flex;}
label { width: 12vw; }
input { width: 40vw; }
.output{ margin-left: 1rem; }
<fieldset>
<legend>Measures</legend>
<div class="container">
<label for="measure1">One</label>
<input maxlength="10" maxsize="10" type="text" id="measure1">
<div class="output"></div>
</div>
<div class="container">
<label for="measure2">Two</label>
<input maxlength="10" maxsize="10" type="text" id="measure2">
<div class="output"></div>
</div>
</fieldset>
附加文档:
-
一些关于为什么
closest
优于matches
的信息:Why the vanilla JS matches method won't work with event listeners and nested links
英文:
To try and simplify event delegation and answer the question with an example using it.
Events fired by elements like <input>
"bubble up" the DOM. Now, you can either catch an event on the element that fired it by attaching a listener to it or you can attach a listener to a parent element which will catch it when the event reaches it.
This is known as "event delegation" because you're delegating the catching of an event to a parent element that didn't fire it. And this is useful because you can group child elements together and have their parent element listen for those events as they bubble up to meet it. For example you can attach a listener to a containing <form>
element to catch all of the events from its child input and button elements.
In this example I've used a <fieldset>
as a parent container, and attached one input
listener to it. When the input
event is fired on any of its children they bubble up and get caught in that listener. From there you can check to see what element fired the event, and then write some code to handle it. Here we're adding the value of the input element to the text content of its sibling output element.
(Because I wasn't sure what your code was meant to do in this example I've just made the input value appear as text content in the <div class="output">
elements.)
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
// Cache the fieldset element, and attach
// a listener to it. This listener will catch the
// events from its child elements as they "bubble up" the DOM
const fieldset = document.querySelector('fieldset');
fieldset.addEventListener('input', handleChange);
// Our event handler - note that we're passing in
// the event (`e`) as an argument
function handleChange(e) {
// First the handler needs to check that the
// child element that fired the event (the event target)
// is one of the input elements
if (e.target.closest('input')) {
// We then assign the output element (the input element's
// `nextElementSibling`) to an `output` variable,
// and assign the event target's value to its textContent
const output = e.target.nextElementSibling;
output.textContent = e.target.value;
}
}
<!-- language: lang-css -->
fieldset > .container ~ .container { margin-top: 0.5rem; }
.container { display: flex;}
label { width: 12vw; }
input { width: 40vw; }
.output{ margin-left: 1rem; }
<!-- language: lang-html -->
<fieldset>
<legend>Measures</legend>
<div class="container">
<label for="measure1">One</label>
<input maxlength="10" maxsize="10" type="text" id="measure1">
<div class="output"></div>
</div>
<div class="container">
<label for="measure2">Two</label>
<input maxlength="10" maxsize="10" type="text" id="measure2">
<div class="output"></div>
</div>
</fieldset>
<!-- end snippet -->
Additional documentation
答案2
得分: 0
我尝试过这个,但似乎自定义元素不起作用,但div
起作用。
更新:将自定义元素脚本放在这个脚本之上后可以工作。
function measure(element, init) {
var span = element.nextElementSibling;
if (init) {
const inputs = document.querySelectorAll('#input, input-test');
inputs.forEach(input => {
console.log(input);
if (input.tagName == "INPUT") {
console.log(input);
var span = input.nextElementSibling;
span.textContent = input.getAttribute("placeholder");
input.style.width = span.offsetWidth + 'px';
}
if (input.tagName == "INPUT-TEST") {
console.log(input.shadowRoot.querySelector('#input'));
var input = input.shadowRoot.querySelector('input');
var span = input.nextElementSibling;
span.textContent = input.getAttribute("placeholder");
input.style.width = span.offsetWidth + 'px';
}
})
}
if (element.value == "") {
span.textContent = element.getAttribute("placeholder");
element.style.width = span.offsetWidth + 'px';
} else {
span.textContent = element.value;
element.style.width = span.offsetWidth + 'px';
}
}
document.querySelectorAll('#input, input-test').forEach(input =>
input.addEventListener('input', function(i) {
if (i.target.tagName == "INPUT-TEST") {
measure(i.target.shadowRoot.querySelector('input'));
} else {
measure(i.target);
}
})
)
<div class="input">
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
</div>
似乎不能检测到shadowroot。
英文:
I have tried this but seemed like the custom element wont work but div works.
UPDATE: works after placing custom element script above this script.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function measure(element, init) {
var span = element.nextElementSibling;
if (init) {
const inputs = document.querySelectorAll('#input, input-test');
inputs.forEach(input => {
console.log(input);
if (input.tagName == "INPUT") {
console.log(input);
var span = input.nextElementSibling;
span.textContent = input.getAttribute("placeholder");
input.style.width = span.offsetWidth + 'px';
}
if (input.tagName == "INPUT-TEST") {
console.log(input.shadowRoot.querySelector('#input'));
var input = input.shadowRoot.querySelector('input');
var span = input.nextElementSibling;
span.textContent = input.getAttribute("placeholder");
input.style.width = span.offsetWidth + 'px';
}
})
}
if (element.value == "") {
span.textContent = element.getAttribute("placeholder");
element.style.width = span.offsetWidth + 'px';
} else {
span.textContent = element.value;
element.style.width = span.offsetWidth + 'px';
}
}
document.querySelectorAll('#input, input-test').forEach(input =>
input.addEventListener('input', function(i) {
if (i.target.tagName == "INPUT-TEST") {
measure(i.target.shadowRoot.querySelector('input'));
} else {
measure(i.target);
}
})
)
<!-- language: lang-html -->
<div class="input">
<div class="input_box">
<input type="text" placeholder="Value" id="input" />
<span id="measure"></span>
</div>
</div>
<!-- end snippet -->
Seems like it cant detect shadowroot
答案3
得分: -3
首先,使用 querySelectorAll
选择所有元素,它具有返回节点列表的优势,可以使用 forEach
进行迭代。
然后通过在 addEventListener
函数中添加一个元素参数(可以自己命名)来使用事件代理。
然后,您可以使用 element.target
在函数内引用被点击的元素:
const BUTTONS = document.querySelectorAll('button');
BUTTONS.forEach(button =>
button.addEventListener('click', function(element) {
let clickedButtonId = element.target.id;
console.log(clickedButtonId);
})
)
<button id="btn-1">按钮1</button>
<button id="btn-2">按钮2</button>
<button id="btn-3">按钮3</button>
<button id="btn-4">按钮4</button>
<button id="btn-5">按钮5</button>
英文:
First, select all the elements by using querySelectorAll
which has the advantage of returning a Node List that can be iterated with the usage of forEach
.
Then use an event delegation by adding an element parameter (you can name it as you like) to the function within the addEventListener
.
<br>
Then you can use element.target
to reference that clicked element within the function:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const BUTTONS = document.querySelectorAll('button');
BUTTONS.forEach(button =>
button.addEventListener('click', function(element) {
let clickedButtonId = element.target.id;
console.log(clickedButtonId);
})
)
<!-- language: lang-html -->
<button id="btn-1">Button 1</button>
<button id="btn-2">Button 2</button>
<button id="btn-3">Button 3</button>
<button id="btn-4">Button 4</button>
<button id="btn-5">Button 5</button>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论