Dalej drążymy temat, przy okazji poznając dlaczego delegacja eventów oraz mutation observery to narzędzia, z których warto korzystać.

Ok, nasz nowy markup:

<ul id="list">
        Languages to learn:
        <li>C# <button class="btn-del">X</button></li>
        <li>Java <button class="btn-del">X</button></li>
        <li>Scala <button class="btn-del">X</button></li>
        <li>Perl <button class="btn-del">X</button></li>
        <li>Ruby <button class="btn-del">X</button></li>
    </ul>

    <form id="addForm">
        <label for="language">Language: </label>
        <input type="text" name="language" id="ipt">
        <input type="submit" value="Add language">
    </form>

I teraz dodamy obsługę formularza:

let ul = document.querySelector("#list");
let form = document.querySelector("#addForm");
let ipt = form.querySelector("#ipt");

ul.addEventListener("click", function(e){
   
    if(!e.target.matches('button.btn-del'))
        return;

    e.target.closest("li").remove();

    
});

form.addEventListener("submit", function(e){

    e.preventDefault();

    ul.insertAdjacentHTML('beforeend', 
        `<li>${ipt.value}<button class="btn-del">X</button></li>`
    );

    ipt.value = "";
    
});

Już teraz widzimy, jak bardzo delegacja się przydaje. Nic nie musieliśmy podpinać, żadnych event listenerów do buttona, możemy dodawać i usuwać nowe rekordy.

Inne rzeczy wcześniej poznane też się przydają (jak insertAdjacentHTML).

Ok, teraz jak usuwamy mamy unknown mutation, bo tak sobie kod poprzednio napisaliśmy. Pora to zmienić:

const observer = new MutationObserver(callback);

function callback(mutationsList, observer) {

  for (let mutation of mutationsList) {

    switch(mutation.type) {

      case 'childList':

      let _target = mutation.target;

        if(mutation.removedNodes.length){
            console.log("you removed");
            break;
        } else if (mutation.addedNodes.length) {
            console.log("you added")
            break;
        }

      default:
        console.log("unknown mutation");
    } 
  }
}

observer.observe(ul, { childList: true});

Teraz możemy dopisać całą logikę:

const observer = new MutationObserver(callback);

function callback(mutationsList, observer) {

  for (let mutation of mutationsList) {

    switch(mutation.type) {

      case 'childList':

      let _target = mutation.target;

        if(mutation.removedNodes.length){

            if(
                _target.childElementCount === 0
                &&
                _target.childNodes.length > 0)
                {
                    _target.removeChild(_target.firstChild);
                }

            break;
            
        } else if (mutation.addedNodes.length) {

            if(_target.firstChild === _target.firstElementChild){
                _target.insertAdjacentText("afterbegin", "Languages to learn: ");
            }

            break;
        }

      default:
        console.log("unknown mutation");
    } 
  }
}

observer.observe(ul, { childList: true});

Pełna reaktywność, super. A jak ten kod uważamy za bałaganiarski (w sumie dużo ifów), to znaczy, że koniecznie potrzebujemy frameworka frontendowego. Tym niemniej to są rzeczy (jak delegacja, obserwery), które pozwalają wycisnąć maksimum z czystego JS.

Ponadto ogarniając te tematy łatwiej nam:

  • przyzwyczaić się do tego, co robi reaktywny framework frontendowy
  • ogarnąć konceptualnie co i jak robi ten framework i jakie ma ograniczenia
  • znając JS nie jesteśmy głąbami przy pierwszym lepszym problemie w frameworku JSowym do rozwiązania, którego „na tutorialu nie było”