Search code examples
javascripthtmlalgorithminputrazor

Distribute Remaining Input Percent among n-inputs in JavaScript


I am trying to make a function for the following situation:

I have a number of user inputs that accept a number value from 0 to 100

If I entered 100 in input1 then input2 to input-n will not accept any number since the 100% is given to input1

If I entered 80 in input1 then input2 will be 20 and will not accept value above 20 If I entered 15 in input2 then the remaining 5 will go to input3

In my case I have 4 inputs but in the future those inputs may increase.

This is the function I have made so far:

function updatePayments() {
    var payment1 = parseInt(document.getElementById('in-line1').value) || 0;
    var payment2 = parseInt(document.getElementById('in-line2').value) || 0;
    var payment3 = parseInt(document.getElementById('in-line3').value) || 0;
    var payment4 = parseInt(document.getElementById('in-line4').value) || 0;

    if (payment1 >= 100) {
        payment2 = 0;
        payment3 = 0;
        payment4 = 0;
    }
    else {
        var remainingPercent = 100 - payment1;

        payment2 = remainingPercent;

        if (payment3 > 0) {
            payment2 = remainingPercent - payment3;
        }

        if (payment4 > 0) {
            payment3 = remainingPercent - payment2 - payment4;
        }

    }


    document.getElementById('in-line2').value = payment2;
    document.getElementById('in-line3').value = payment3;
    document.getElementById('in-line4').value = payment4;

    var poNetTotalValue = GetPoNetTotalValue();

    $('#out-line1').text(formatNumber(payment1 / 100 * poNetTotalValue));
    $('#out-line2').text(formatNumber(payment2 / 100 * poNetTotalValue));
    $('#out-line3').text(formatNumber(payment3 / 100 * poNetTotalValue));
    $('#out-line4').text(formatNumber(payment4 / 100 * poNetTotalValue));

    var totalPayments =
        payment1 / 100 * poNetTotalValue +
        payment2 / 100 * poNetTotalValue +
        payment3 / 100 * poNetTotalValue +
        payment4 / 100 * poNetTotalValue;

    var submitButton = document.getElementById('btn-submit');
    if (totalPayments != poNetTotalValue) {
        $('#payment-check-validation').removeClass('visually-hidden');
        submitButton.disabled = true;
    }
    else {
        $('#payment-check-validation').addClass('visually-hidden');
        submitButton.disabled = false;
    }
    $('#out-lines-total').text(formatNumber(totalPayments));

}

and this the HTML (its part of a cshtml)

<tbody>
    <!-- line1: down payment -->
    <tr>
        <td width="50%">
            Down Payment
            <input type="hidden" asp-for="PoPayments[0].Notes" value="Down Payment"/>
        </td>
        <td width="20%">
            <input id="in-line1"
                   asp-for="PoPayments[0].Percentage"
                   type="number" min="0" max="100"
                   class="w-100"
                   placeholder="%"
                   oninput="enforceMinMax(this); updatePayments()" />
        </td>
        <td>
            <span id="out-line1" data-down-payment-value="0">
                0
            </span>
        </td>
    </tr>

    <!-- line2: upon delivery -->
    <tr>
        <td width="50%">
            Upon Delivery
            <input type="hidden" asp-for="PoPayments[1].Notes" value="Upon Delivery" />
        </td>
        <td width="20%">
            <input id="in-line2"
                   asp-for="PoPayments[1].Percentage"
                   type="number" min="0" max="100"
                   class="w-100"
                   placeholder="%"
                   oninput="enforceMinMax(this); updatePayments()">
        </td>
        <td>
            <span id="out-line2" data-upon-delivery-value="0">
                0
            </span>
        </td>
    </tr>

    <!-- line3: criteria1 -->
    <tr>
        <td width="50%">
            <input type="text" asp-for="PoPayments[2].Notes"
                   placeholder="Enter Payment Criteria">
        </td>
        <td width="20%">
            <input id="in-line3"
                   asp-for="PoPayments[2].Percentage"
                   type="number" min="0" max="100"
                   class="w-100"
                   placeholder="%"
                   oninput="enforceMinMax(this); updatePayments()">
        </td>
        <td>
            <span id="out-line3" data-criteria1-value="0">
                0
            </span>
        </td>
    </tr>

    <!-- line4: criteria2 -->
    <tr>
        <td width="50%">
            <input type="text" asp-for="PoPayments[3].Notes"
                   placeholder="Enter Payment Criteria">
        </td>
        <td width="20%">
            <input id="in-line4"
                   asp-for="PoPayments[3].Percentage"
                   type="number" min="0" max="100"
                   class="w-100"
                   placeholder="%"
                   oninput="enforceMinMax(this); updatePayments()">
        </td>
        <td>
            <span id="out-line4" data-criteria2-value="0">
                0
            </span>
        </td>
    </tr>
</tbody>

EDIT

function GetPoNetTotalValue() { return document.getElementById('poNetTotal').dataset.poNetTotalValue; }

this is just a function so that I don't have to repeat the same variable again and again and also if the element id changed I don't have to change it in many places because another part in the JS is using this value

as for the formatNumber and enforeMinMax

function formatNumber(num) {
return num.toLocaleString('en-US', { maximumFractionDigits: 2 });
}
 function enforceMinMax(el) {
    if (el.value != "") {
        if (parseInt(el.value) < parseInt(el.min)) {
            el.value = el.min;
            el.dispatchEvent(new Event("input"));
        }
        if (parseInt(el.value) > parseInt(el.max)) {
            el.value = el.max;
            el.dispatchEvent(new Event("input"));
        }
    }
    }

Solution

  • As you apparently use jQuery, I would use it to the full and not mix the standard DOM methods with it.

    For the 4 inputs, I would suggest looping over them and keeping track of the value that is still available for the remaining inputs:

    function updatePayments() {
        const poNetTotalValue = GetPoNetTotalValue();
        let totalPayments = 0;
        let available = 100;
        const $outs = $('#out-line1,#out-line2,#out-line3,#out-line4');
        $('#in-line1,#in-line2,#in-line3,#in-line4').each(function (i) {
            const value = +$(this).val();
            const payment = Math.max(0, Math.min(available, value));
            available -= payment;
            if (value != payment) $(this).val(payment);
            const out = payment / 100 * poNetTotalValue;
            $outs.eq(i).text(formatNumber(out));
            totalPayments += out;
        });
    
        const submitButton = $('#btn-submit');
        $('#payment-check-validation').toggleClass('visually-hidden', totalPayments == poNetTotalValue);
        submitButton.disabled = totalPayments !== poNetTotalValue;
        $('#out-lines-total').text(formatNumber(totalPayments));
    }
    
    function enforceMinMax(el) {
        if (el.value != "") {
            if (+el.value < +el.min) {
                el.value = el.min;
                el.dispatchEvent(new Event("input"));
            } else if (+el.value > +el.max) {
                el.value = el.max;
                el.dispatchEvent(new Event("input"));
            }
        }
    }
    
    function GetPoNetTotalValue() {
        return $('#poNetTotal').data("poNetTotalValue") ?? 1;
    }
    
    function formatNumber(num) {
        return num.toLocaleString('en-US', { maximumFractionDigits: 2 });
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <table>
    <tbody>
        <!-- line1: down payment -->
        <tr>
            <td width="50%">
                Down Payment
                <input type="hidden" asp-for="PoPayments[0].Notes" value="Down Payment"/>
            </td>
            <td width="20%">
                <input id="in-line1"
                       asp-for="PoPayments[0].Percentage"
                       type="number" min="0" max="100"
                       class="w-100"
                       placeholder="%"
                       oninput="enforceMinMax(this); updatePayments()" />
            </td>
            <td>
                <span id="out-line1" data-down-payment-value="0">
                    0
                </span>
            </td>
        </tr>
    
        <!-- line2: upon delivery -->
        <tr>
            <td width="50%">
                Upon Delivery
                <input type="hidden" asp-for="PoPayments[1].Notes" value="Upon Delivery" />
            </td>
            <td width="20%">
                <input id="in-line2"
                       asp-for="PoPayments[1].Percentage"
                       type="number" min="0" max="100"
                       class="w-100"
                       placeholder="%"
                       oninput="enforceMinMax(this); updatePayments()">
            </td>
            <td>
                <span id="out-line2" data-upon-delivery-value="0">
                    0
                </span>
            </td>
        </tr>
    
        <!-- line3: criteria1 -->
        <tr>
            <td width="50%">
                <input type="text" asp-for="PoPayments[2].Notes"
                       placeholder="Enter Payment Criteria">
            </td>
            <td width="20%">
                <input id="in-line3"
                       asp-for="PoPayments[2].Percentage"
                       type="number" min="0" max="100"
                       class="w-100"
                       placeholder="%"
                       oninput="enforceMinMax(this); updatePayments()">
            </td>
            <td>
                <span id="out-line3" data-criteria1-value="0">
                    0
                </span>
            </td>
        </tr>
    
        <!-- line4: criteria2 -->
        <tr>
            <td width="50%">
                <input type="text" asp-for="PoPayments[3].Notes"
                       placeholder="Enter Payment Criteria">
            </td>
            <td width="20%">
                <input id="in-line4"
                       asp-for="PoPayments[3].Percentage"
                       type="number" min="0" max="100"
                       class="w-100"
                       placeholder="%"
                       oninput="enforceMinMax(this); updatePayments()">
            </td>
            <td>
                <span id="out-line4" data-criteria2-value="0">
                    0
                </span>
            </td>
        </tr>
    </tbody>
    </table>
    
    <button id="btn-submit">Submit</button>