0

Preamble: Searches are based on Diego answer about dynamic field JS creation and Anthony Awuley answer about MutationObserver of the created fields

I spent a lot of time (searching and) finding a solution that suits my needs but the result - even if it is functional - seems a bit heavy to me.

I want to create a dynamic form based on certain informations transmitted to it in order to create shipments that will be sent by a webservice to the carrier. This informations include among others a weight limit which must not be exceeded when creating the packages. The number of these packages is indicated by the user in a dedicated field and as many fields as necessary are dynamically created.

I previously tried to listen to the changes in the "weight" fields and to calculate the total of the weights as they are entered, but given that when the page loads, the fields are not yet created, there is nothing to listen to.

2 questions then:

  • Is it mandatory to use MutationObserver in my case
  • Is it "normal" to need almost 40 lines for this and therefore, is there an easier way to achieve my goal.

To lighten my demonstration, I reduced the number of fields created.

HTML

<div id="total-order-weight">20</div>
    <div id="over-weight-alert" style="display:none; color:red;">OVER WEIGHT</div>
    <form action="#" id="parcel-form" method="post">
        <div class="multiparcel">
            <input type="number" class="qty" min="0">
            <div class="parcels"></div>
        </div>
        <button id="submit" class="btn btn-success mt-3" type="submit">Go</button>

    </form>
</div>

JS FIELDS CREATION

<script>
        overWeightAlert = document.querySelector('#over-weight-alert');
        totalOrderWeight = parseInt(document.querySelector('#total-order-weight').innerHTML);
        parcelForm = document.querySelector('#parcel-form');
        //add input e listener to qty input
        qtyEl = document.querySelector('.qty')
        qtyEl.addEventListener('input', (e) => {
            const qtyEl = e.target;
            const qty = e.target.value;
            clearweights(qtyEl);
            addweights(qtyEl, qty);
        });

        //removes the weights in the given multiparcel  
        function clearweights(from) {
            const target = from.closest('.multiparcel').querySelector('.parcels');
            target.innerHTML = '';
        }

        //adds a number of weights in the given multiparcel
        function addweights(from, n) {
            const target = from.closest('.multiparcel').querySelector('.parcels');
            for (let i = 0; i < n; i++) {
                group = createGroup()
                const weight = createweights();
                const insurance = createInsurance();
                group.append(weight);
                group.append(insurance);
                target.append(group);
            }
        }

        function createGroup() {
            const group = document.createElement('div');
            group.classList.add('input-group', 'my-2');
            return group;
        }

        function createweights(i) {
        const label = document.createElement("Label");
        label.htmlFor = "weight" + i;
        label.innerHTML = "Poids";
        label.classList.add('form-label');
        const input = document.createElement('input');
        input.name = 'weight' + i;
        input.type = "number";
        input.classList.add('form-control', 'me-4');
        const weight = document.createElement('div');
        weight.classList.add('weight');
        weight.append(label);
        weight.append(input);
        return weight;
    }

    function createInsurance(i) {
        const label = document.createElement("Label");
        label.htmlFor = "insurance"+i;
        label.innerHTML = "Assurance";
        label.classList.add('form-label');
        const input = document.createElement('input');
        input.name = 'insurance' + i
        input.type = "number";
        input.classList.add('form-control', 'ms-4');
        input.value = 0;
        const insurance = document.createElement('div');
        insurance.classList.add('insurance');
        insurance.append(label);
        insurance.append(input);
        return insurance;
    }
    </script>

JS MUTATIONOBSERVER

<script>
    const targetNode = document.getElementById("parcel-form");

    // Options for the observer (which mutations to observe)
    const config = { childList: true, subtree: true };
    // Callback function to execute when mutations are observed
    const callback = (mutationList, observer) => {
        for (const mutation of mutationList) {
            if (mutation.type === "childList") {
                overWeightAlert.style.display = 'none';
                weights = parcelForm.querySelectorAll('input[name="parcel-weight"]');
                weights.forEach(weight => {
                    weight.addEventListener('keyup', (event) => {
                        var check = checkWeight();
                        if (check > totalOrderWeight) {
                            overWeightAlert.classList.add('d-block', 'alert', 'alert-danger');
                            overWeightAlert.classList.remove('d-none');
                            submitButton.classList.add('d-none');
                            // overWeightAlert.style.display = 'block';
                        } else {
                            overWeightAlert.classList.add('d-none');
                            submitButton.classList.remove('d-none');
                            submitButton.classList.add('d-block');
                        }
                    });
                })
            }
        }
    };
    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callback);
    // // Start observing the target node for configured mutations
    observer.observe(targetNode, config)

    // ---------------------------- END DOM CHANGES DETECTION 

    function checkWeight() {
        weights = parcelForm.querySelectorAll('input[name="parcel-weight"]');
        var totalWeight = 0;
        for (var i = 0; i < weights.length; i++) {
            qty = weights[i].value;
            if (qty) {
                totalWeight += parseInt(qty);
            }
        }
        return totalWeight;
    }
</script>
Abpostman1
  • 158
  • 1
  • 8

1 Answers1

1

You can use event delegation to handle the input. Here's a minimal reproducable example. The total will be now be calculated after creation of a new value only.

If using MutationObserver is obligatory, here is a Stackblitz project with the same snippet using that.

document.addEventListener(`click`, handle);
const maxWeight = 100;

function total() {
  const values = document.querySelectorAll("[data-value]");
  if (values.length) { 
    const total = [...values]
      .reduce( (acc, el) => acc + +el.dataset.value, 0);
    const totalElem = document.querySelector("#total")
    totalElem.innerHTML = `TOTAL: <b>${total}</b>`;
    
    if (total > maxWeight) {
      document.querySelector("#add").setAttribute("disabled", "disabled");
      totalElem.innerHTML += ` <b class="warn"><i>Overweight!</i></b>`;
    }
  }
}

function handle(evt) {
  if (evt.target.id === "add") {
    const inp = document.querySelector("#weight");
    const newElem = Object.assign(document.createElement("div"), 
      {textContent: +inp.value ?? "0"});
    document.querySelector("#weights").appendChild(newElem);
    newElem.dataset.value = inp.value ?? "0";
    inp.value = 0;
    total();
  }
  
  
}
#total {
  margin-top: 0.6rem;
  width: 125px;
}

.warn {
  color: red;
}
<div class="input">
<input type="number" id="weight" value="0">
<button id="add">add weight</button>
</div>

<div id="weights">
</div>

<div id="total">
</div>
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • Thanks for the time spent on my case. I didn't know about **event delegation** and I'm now sure I'll have to use that one of these days. But I want to stay on the **MutationObserver** option because I want minimal user interaction with this form and most importantly, real-time display updates. However, your syntax is much better than mine and I will take inspiration from it. And thank you also for the advice MRE. I keep that in mind. – Abpostman1 May 02 '23 at 13:42