Verifying dynamic number inputs generated using JavaScript values and calculating the total with a MutationObserver

Preamble: I've referenced Diego's answer on dynamic field JS creation and Anthony Awuley's answer on MutationObserver for the created fields.

After extensive searching, I found a solution that meets my needs, but it feels somewhat bulky despite its functionality.

I aim to design a flexible form that can generate shipments based on specific input data received. These details include criteria such as a weight limit that must not be exceeded when packaging items. Users specify the number of packages in a dedicated field, and dynamically create as many fields as required.

In previous attempts, I tried monitoring changes in the 'weight' fields to calculate the total weight entered. However, since the fields are not yet created when the page loads, there was nothing to monitor.

My queries are:

  • Is using MutationObserver necessary in this scenario?
  • Is it normal to require nearly 40 lines of code for this operation, or is there a simpler method to achieve the same result?

To streamline my demonstration, I have reduced the number of generated fields.

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 event 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);
        });

        function clearweights(from) {
            const target = from.closest('.multiparcel').querySelector('.parcels');
            target.innerHTML = '';
        }

        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");

    const config = { childList: true, subtree: true };
    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');
                        } else {
                            overWeightAlert.classList.add('d-none');
                            submitButton.classList.remove('d-none');
                            submitButton.classList.add('d-block');
                        }
                    });
                })
            }
        }
    };
    const observer = new MutationObserver(callback);
    observer.observe(targetNode, config)

    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>

Answer №1

To efficiently handle the input, consider utilizing event delegation. Here is a concise demonstration showcasing this technique: minimal reproducible example. The total value will now be computed only upon the addition of a new input.

If the use of MutationObserver is indispensable, you can explore a working example on Stackblitz project.

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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Storing values from a content script into textboxes using a button press: a simple guide

I am new to creating chrome extensions and currently utilizing a content script to fetch values. However, I am facing difficulty in loading these values into the popup.html. Here is the code snippet: popup.html <head> <script src ...

Tips for updating iframe source without refreshing the iframe?

I am facing an issue with two frames where I need to copy the content from the first frame (myframe) to the second frame (myframe2) using JavaScript. The challenge is that when I set the "src" attribute for myframe2, it causes a reload of FrameSecond. Is ...

Select dropdown options in Material UI are overlapping

Exploring React for the first time and utilizing material-ui's select button feature. It displays highlighted text based on user input, but ideally this highlight should disappear upon selection of an element. https://i.stack.imgur.com/2ccaS.png How ...

Show the React component once the typewriter effect animation is complete

Hello there, I am looking to showcase my social links once the Typewriter effect finishes typing out a sentence in TypeScript. As someone new to React, I'm not quite sure how to make it happen though. Take a look at the code snippet below: ` import ...

Integrating tilt and zoom functionality on a web page based on mouse movements

While browsing through some messages on chat, I came across something interesting: I noticed that as I moved my mouse left or right, the content would tilt in that direction, and when moving up and down, the content would zoom in or out. It was a really c ...

At what point is it appropriate for me to delete the token?

Seeking Answers: Token Dilemmas Is it best to create the token upon user login and registration, or just on login? Should the token be saved in local storage? Do I need to send the token after every user request? Should the token o ...

JavaScript is not being loaded onto the client by Express

I am looking to incorporate requireJS into my client code. Here is my file structure: ProjectRoot |-server.js |-public/ |-index.html |-js/ |-app.js |-lib/ |-require.min.js |-underscore.js |-backbone.js ...

The issue encountered during a POST request in Postman is a SyntaxError where a number is missing after the minus sign in a JSON object at position 1 (line 1

Running my API in a website application works flawlessly, but encountering SyntaxError when testing it in Postman - specifically "No number after minus sign in JSON at position 1" (line 1 column 2). The data is correctly inputted into the body of Postman a ...

Rendering React Router server-side with client-side session information

Currently, I am working with mozilla client-sessions in conjunction with express/node. My goal is to pass my session.user to the react-router within a standard * request. Despite my efforts and attempts, I keep encountering an issue where it becomes unde ...

The issue arises when using an Angular directive with an input type set as "number"

When the input type is text, the code below works perfectly fine. However, it fails to function when the input type is changed to number. <div ng-app="myApp" ng-controller="myCtrl as model"> <input type="text" ng-model="cero" ng-decimal > ...

Trigger a warning pop-up if a selection has not been made in a dropdown menu using jQuery

I am attempting to display an alert popup when the user fails to select a value from the dropdown menu. Below is my HTML code: <div id="reminder" class="popup-layout"> ... ... </form> </div> In my JavaScript function page, I have tried ...

Having trouble with routing nesting in react-router v4?

I find myself in the following situation: <Wrapper> <Container> <Route exact path="/" component={UserListing} /> <Route path="/user/:id" component={UserDetails} /> <Route exact path="(/|/user/\d+)" comp ...

Instruction to pay attention to the event

After noticing that I've added similar event listening code to many of my controllers, I began to think about how to streamline it. The code looks something like this: document.addEventListener("resume", function(e){ $scope.doSomething(); }, ...

Utilizing Shopify API to seamlessly add items to cart without any redirection for a smoother shopping experience

Looking for a solution to submit an add to cart POST request without any redirection at all? I have tried changing return_to to "back" but that still reloads the page, which is not ideal. My goal is to smoothly add the item to the cart and notify the cli ...

Triggering a click event on various instances of a similar element type using the onclick function

Hey there, I'm a newcomer to Javascript. I've been practicing my Javascript skills and trying to replicate something similar to what was achieved in this example: Change Button color onClick My goal is to have this functionality work for multip ...

"Vue.js integrates seamlessly with Tracking.js for advanced tracking

Has anyone successfully integrated the tracking.js library into a vueJS application? I followed these steps to install the package: npm install --save tracking After that, I defined the library in my main.js file like this: import tracking from 't ...

Selecting the most popular post from a Facebook group

Here is the JSON file that I am currently using JSON.parse(); in Google Apps Script. Currently, I have found a temporary solution with this code by selecting posts that have more than 15 likes. However, my goal is to be able to select the post with the ...

Retrieve the Content-Type header response from the XHR request

My intention is to check the header content type and see if it is text/html or text/xml. If it is text/html, then it indicates an error that I need to address before moving forward. ...

The functionality of res.send is not working correctly

Attempting to send a response from my express application to the front-end in order to display an alert. Here is what I have attempted in my app: if (info.messageId) { res.redirect('back'); res.send({ success: true }); } ...

What is the best way to pass JavaScript object literals from a Node.js server to the frontend browser?

In Node, I am working with JavaScript object literals containing methods in the backend. For example: const report = { id: 1, title: 'Quarterly Report for Department 12345', abstract: 'This report shows the results of the sales ...