0

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

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>
ducc
  • 82
  • 8
  • you might want to read into event delegation (element parameter + `element.target`) – tacoshy Jul 31 '23 at 12:54
  • 1
    Two `` and two `id="input"` ?? IDs **must** be unique. Use classes instead. Also, yes, use Event delegation https://stackoverflow.com/a/75007869/383904 and https://stackoverflow.com/a/14533243/383904 for more info. – Roko C. Buljan Jul 31 '23 at 12:55
  • ids are SINGULAR. getElementById is not going to return multiple elements. – epascarello Jul 31 '23 at 12:56
  • https://stackoverflow.com/questions/10693845/what-do-queryselectorall-and-getelementsby-methods-return – epascarello Jul 31 '23 at 12:57
  • Just out of curiosity... what is exactly that you're building? Never seen such a UI... – Roko C. Buljan Jul 31 '23 at 12:59
  • I will try out later, @RokoC.Buljan. im building my own portfolio website, a little bit complicated... – ducc Jul 31 '23 at 13:09
  • Update: I tried to understand but I found something really new to me because I just learnt and dig inside js just this year. Like you can hook a variable and use it for appendchild. And hooking el (I'm not really understand some el and elNew parameters :P ) – ducc Jul 31 '23 at 13:20
  • Update again, I tried using single script to handle one input nested inside custom element. But seems like it can't read the children property – ducc Jul 31 '23 at 13:43

3 Answers3

1

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.)

// 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;
  }
}
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>

Additional documentation

Andy
  • 61,948
  • 13
  • 68
  • 95
0

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.

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>

Seems like it cant detect shadowroot

ducc
  • 82
  • 8
-3

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.
Then you can use element.target to reference that clicked element within the function:

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">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>
tacoshy
  • 10,642
  • 5
  • 17
  • 34
  • Why not use a global event listener that loops through a list of event handlers, where one of them checks the `tagName` property and uses the switch statement for the `target.id` to process events. This way we don't have to define event listeners for each element, the approach you've given is memory intensive by nature. – oxou Jul 31 '23 at 13:04
  • that would be more resource intensive. Especially the usage of `switch` which also is poorly scalable. You do not want to create a case for every single element – tacoshy Jul 31 '23 at 13:06