JavaScript Percentage Calculation Not Working in DHIS2 Customized Form

Context:

I am working on a customized data entry form in DHIS2 and need to perform auto-calculations for percentages based on user-entered data. Specifically, I need to dynamically calculate and update several percentages as users input values into the form.

Problem:

The JavaScript code designed to calculate and update these percentages is not functioning as expected in the DHIS2 environment. While the script works in a standalone HTML environment (e.g., a local browser), it does not work when integrated into the DHIS2 form.

Details:

  • The JavaScript is intended to calculate percentages for fields such as “Contacts Tested,” “Tested Positive,” and “Linked to ART” based on input fields for “Contacts Reached,” “Contacts Tested,” and “Tested Positive.”
  • The script ensures logical constraints, such as “Contacts Tested” being less than or equal to “Contacts Reached.”
  • Despite the script working in isolated HTML tests, it fails to execute correctly within the DHIS2 customized form.

Steps Taken:

  1. I have verified that the JavaScript code is correctly linked and should run in the browser.
  2. I have ensured the IDs used in the script match those in the DHIS2 form.
  3. I have tested the script in a local environment, where it works as expected.
  4. However, when placed in the DHIS2 form, the calculations do not update as intended.

Request:

I need assistance with:

  • Understanding why the JavaScript code fails to execute properly in the DHIS2 environment.
  • Identifying potential conflicts or issues specific to DHIS2 that might cause this behavior.
  • Suggestions for debugging or modifying the script to ensure it works within the DHIS2 form.

Any guidance or insights from the community would be greatly appreciated. Thank you

Hi @Mofo

It would be great if you could share the code itself so we can debug this on play.dhis2.org. Thanks!

@Gassim Here is the code,

<script>
    document.addEventListener("DOMContentLoaded", function () {
        const tables = document.querySelectorAll("table");
    
        tables.forEach((table) => {
            const rows = table.querySelectorAll(".tx-curr");
    
            rows.forEach((row) => {
                const txCurr = row.querySelector("[title$='# pediatric clients currently on treatment']");
                const treatmentInterruptions = row.querySelector("[title$='# pediatric clients with treatment interruption']");
                const attemptedContact = row.querySelector("[title$='# pediatric clients with attempted contact']");
                const successfullyTraced = row.querySelector("[title$='# successfully traced']");
                const broughtBackToCare = row.querySelector("[title$='# brought back to care']");
                const selfTransferredOut = row.querySelector("[title$='# Self-Transferred Out']");
                const died = row.querySelector("[title$='# Died']");
                const stoppedART = row.querySelector("[title$='# Stopped ART']");
                const ltfu = row.querySelector("[title$='# LTFU-- Total number not reachable (by phone, home visit, or both)']");
                const percentTraced = row.querySelector("[title$='% traced']");
                const percentBroughtBackToCare = row.querySelector("[title$='% brought back to care']");
                const percentLTFU = row.querySelector("[title$='% LTFU']");
    
                const validate = () => {
                    let valid = true;
                    const txCurrVal = parseFloat(txCurr.value) || 0;
                    const treatmentInterruptionsVal = parseFloat(treatmentInterruptions.value) || 0;
                    const attemptedContactVal = parseFloat(attemptedContact.value) || 0;
                    const successfullyTracedVal = parseFloat(successfullyTraced.value) || 0;
                    const broughtBackToCareVal = parseFloat(broughtBackToCare.value) || 0;
                    const selfTransferredOutVal = parseFloat(selfTransferredOut.value) || 0;
                    const diedVal = parseFloat(died.value) || 0;
                    const stoppedARTVal = parseFloat(stoppedART.value) || 0;
                    const ltfuVal = parseFloat(ltfu.value) || 0;
    
                    if (attemptedContactVal > treatmentInterruptionsVal) {
                        valid = false;
                        alert("Attempted contact cannot be greater than patients currently on treatment interruptions.");
                        attemptedContact.value = '';
                        attemptedContact.focus();
                    }
    
                    if (treatmentInterruptionsVal > txCurrVal) {
                        valid = false;
                        alert("Treatment interruptions cannot be greater than patients currently on treatment.");
                        treatmentInterruptions.value = '';
                        treatmentInterruptions.focus();
                    }
    
                    if (successfullyTracedVal > attemptedContactVal) {
                        valid = false;
                        alert("Successfully traced cannot be greater than attempted contact.");
                        successfullyTraced.value = '';
                        successfullyTraced.focus();
                    }
    
                    if (broughtBackToCareVal > successfullyTracedVal) {
                        valid = false;
                        alert("Brought back to care cannot be greater than successfully traced.");
                        broughtBackToCare.value = '';
                        broughtBackToCare.focus();
                    }
    
                    if (selfTransferredOutVal > attemptedContactVal) {
                        valid = false;
                        alert("Self-Transferred Out cannot be greater than Attempted contact.");
                        selfTransferredOut.value = '';
                        selfTransferredOut.focus();
                    }
    
                    if (diedVal > attemptedContactVal) {
                        valid = false;
                        alert("Died cannot be greater than Attempted contact.");
                        died.value = '';
                        died.focus();
                    }
    
                    if (stoppedARTVal > attemptedContactVal) {
                        valid = false;
                        alert("Stopped ART cannot be greater than Attempted contact.");
                        stoppedART.value = '';
                        stoppedART.focus();
                    }
    
                    // Auto-calculations
                    ltfu.value = (selfTransferredOutVal + diedVal + stoppedARTVal).toFixed(2);
    
                    if (attemptedContactVal > 0) {
                        percentTraced.value = ((successfullyTracedVal / attemptedContactVal) * 100).toFixed(2);
                    } else {
                        percentTraced.value = '';
                    }
    
                    if (successfullyTracedVal > 0) {
                        percentBroughtBackToCare.value = ((broughtBackToCareVal / successfullyTracedVal) * 100).toFixed(2);
                    } else {
                        percentBroughtBackToCare.value = '';
                    }
    
                    if (treatmentInterruptionsVal > 0) {
                        percentLTFU.value = ((ltfuVal / treatmentInterruptionsVal) * 100).toFixed(2);
                    } else {
                        percentLTFU.value = '';
                    }
    
                    return valid;
                };
    
                const fields = [txCurr, treatmentInterruptions, attemptedContact, successfullyTraced, broughtBackToCare, selfTransferredOut, died, stoppedART, ltfu];
                fields.forEach((field) => {
                    field.addEventListener("change", validate);
                });
            });
        });
    });
    
    
    document.addEventListener('DOMContentLoaded', function() {
        // Script for Disclosed adolescents
        var eligibleFieldDisclosed = document.getElementById('L0gIMsD3RZT-HllvX50cXC0-val');
        var enrolledFieldDisclosed = document.getElementById('zs9RUQM9rO2-HllvX50cXC0-val');

        function checkValuesDisclosed() {
            var eligibleValue = parseInt(eligibleFieldDisclosed.value, 10);
            var enrolledValue = parseInt(enrolledFieldDisclosed.value, 10);

            if (enrolledValue > eligibleValue) {
                alert('The value of "# enrolled in TC" cannot be greater than "# eligible for TC". Please enter a correct value.');
                enrolledFieldDisclosed.value = '';
                enrolledFieldDisclosed.focus();
            }
        }
        enrolledFieldDisclosed.addEventListener('change', checkValuesDisclosed);

        // Script for Undisclosed children
        var eligibleFieldUndisclosed = document.getElementById('iUijp4qVLoS-HllvX50cXC0-val');
        var enrolledFieldUndisclosed = document.getElementById('mWviwL3N79U-HllvX50cXC0-val');

        function checkValuesUndisclosed() {
            var eligibleValue = parseInt(eligibleFieldUndisclosed.value, 10);
            var enrolledValue = parseInt(enrolledFieldUndisclosed.value, 10);

            if (enrolledValue > eligibleValue) {
                alert('The value of "# enrolled in family model" cannot be greater than "# eligible for family model". Please enter a correct value.');
                enrolledFieldUndisclosed.value = '';
                enrolledFieldUndisclosed.focus();
            }
        }
        enrolledFieldUndisclosed.addEventListener('change', checkValuesUndisclosed);
    });
</script>
<meta charset="UTF-8">
<title></title>
<style type="text/css">table {
            width: 1500px;
            border-collapse: collapse;
        }
       .small-table{
        width: 400px;
        border-collapse: collapse;
       }
        th, td {
            border: 1px solid black;
            padding: 12px; /* Increased padding for better readability */
            text-align: center;
        }
        th {
            background-color: #4CAF50; /* Green */
            color: white;
        }
        td input[type="number"] {
            width: 100%;
            padding: 8px; /* Increased padding for larger input fields */
            box-sizing: border-box;
        }
        .tx-curr, .tx-curr, .tx-curr1 {
            background-color: #E6F4EA; /* Light green */
        }
        .tx-new, .tx-new, .tx-new1 {
            background-color: #FDECEA; /* Light red */
        }
</style>
<h2 style="text-align: center;">TREATMENT</h2>

<table border="1" cellpadding="1" cellspacing="1">
	<tbody>
		<tr>
			<th rowspan="2" style="color: white">Pediatric age group</th>
			<th colspan="12" style="text-align: center; color: white">Tracing Outcomes, Patients who Missed Appointment &ge; 7 Days and Successfully Traced</th>
		</tr>
		<tr>
			<th style="color: white"># pediatric clients currently on treatment</th>
			<th style="color: white"># pediatric clients with treatment interruption</th>
			<th style="color: white"># pediatric clients with attempted contact</th>
			<th style="color: white"># successfully traced</th>
			<th style="color: white">% traced</th>
			<th style="color: white"># brought back to care</th>
			<th style="color: white">% brought back to care</th>
			<th style="color: white"># Self-Transferred Out</th>
			<th style="color: white"># Died</th>
			<th style="color: white"># Stopped ART</th>
			<th style="color: white"># LTFU-- Total number not reachable (by phone, home visit, or both)</th>
			<th style="color: white">% LTFU</th>
		</tr>
	</tbody>
	<tbody>
		<tr class="tx-curr">
			<td>0-4</td>
			<td><input id="gY3gWuGqydt-f71XHxMSENL-val" name="entryfield" title="TR_0-4 TR_# pediatric clients currently on treatment" value="[ TR_0-4 TR_# pediatric clients currently on treatment ]" /></td>
			<td><input id="gY3gWuGqydt-kJwQDUPdSV1-val" name="entryfield" title="TR_0-4 TR_# pediatric clients with treatment interruption" value="[ TR_0-4 TR_# pediatric clients with treatment interruption ]" /></td>
			<td><input id="gY3gWuGqydt-xUYWgtOXgYa-val" name="entryfield" title="TR_0-4 TR_# pediatric clients with attempted contact" value="[ TR_0-4 TR_# pediatric clients with attempted contact ]" /></td>
			<td><input id="gY3gWuGqydt-H5A2yLBq4ry-val" name="entryfield" title="TR_0-4 TR_# successfully traced" value="[ TR_0-4 TR_# successfully traced ]" /></td>
			<td><input id="gY3gWuGqydt-ZaNk3byy6V8-val" name="entryfield" title="TR_0-4 TR_% traced" value="[ TR_0-4 TR_% traced ]" /></td>
			<td><input id="gY3gWuGqydt-cc4c4hJSO1H-val" name="entryfield" title="TR_0-4 TR_# brought back to care" value="[ TR_0-4 TR_# brought back to care ]" /></td>
			<td><input id="gY3gWuGqydt-lxfW0YfAmbo-val" name="entryfield" title="TR_0-4 TR_% brought back to care" value="[ TR_0-4 TR_% brought back to care ]" /></td>
			<td><input id="gY3gWuGqydt-Uv4we1Y4OlM-val" name="entryfield" title="TR_0-4 TR_# Self-Transferred Out" value="[ TR_0-4 TR_# Self-Transferred Out ]" /></td>
			<td><input id="gY3gWuGqydt-Tz3T501GscU-val" name="entryfield" title="TR_0-4 TR_# Died" value="[ TR_0-4 TR_# Died ]" /></td>
			<td><input id="gY3gWuGqydt-yHQSwwkLixM-val" name="entryfield" title="TR_0-4 TR_# Stopped ART" value="[ TR_0-4 TR_# Stopped ART ]" /></td>
			<td><input id="gY3gWuGqydt-maKlgcg3tUk-val" name="entryfield" title="TR_0-4 TR_# LTFU-- Total number not reachable (by phone, home visit, or both)" value="[ TR_0-4 TR_# LTFU-- Total number not reachable (by phone, home visit, or both) ]" /></td>
			<td><input id="gY3gWuGqydt-zMKTNqkeL2k-val" name="entryfield" title="TR_0-4 TR_% LTFU" value="[ TR_0-4 TR_% LTFU ]" /></td>
		</tr>
		<tr class="tx-curr">
			<td>5-14</td>
			<td><input id="TmLZ7GmolPD-f71XHxMSENL-val" name="entryfield" title="TR_5-14 TR_# pediatric clients currently on treatment" value="[ TR_5-14 TR_# pediatric clients currently on treatment ]" /></td>
			<td><input id="TmLZ7GmolPD-kJwQDUPdSV1-val" name="entryfield" title="TR_5-14 TR_# pediatric clients with treatment interruption" value="[ TR_5-14 TR_# pediatric clients with treatment interruption ]" /></td>
			<td><input id="TmLZ7GmolPD-xUYWgtOXgYa-val" name="entryfield" title="TR_5-14 TR_# pediatric clients with attempted contact" value="[ TR_5-14 TR_# pediatric clients with attempted contact ]" /></td>
			<td><input id="TmLZ7GmolPD-H5A2yLBq4ry-val" name="entryfield" title="TR_5-14 TR_# successfully traced" value="[ TR_5-14 TR_# successfully traced ]" /></td>
			<td><input id="TmLZ7GmolPD-ZaNk3byy6V8-val" name="entryfield" title="TR_5-14 TR_% traced" value="[ TR_5-14 TR_% traced ]" /></td>
			<td><input id="TmLZ7GmolPD-cc4c4hJSO1H-val" name="entryfield" title="TR_5-14 TR_# brought back to care" value="[ TR_5-14 TR_# brought back to care ]" /></td>
			<td><input id="TmLZ7GmolPD-lxfW0YfAmbo-val" name="entryfield" title="TR_5-14 TR_% brought back to care" value="[ TR_5-14 TR_% brought back to care ]" /></td>
			<td><input id="TmLZ7GmolPD-Uv4we1Y4OlM-val" name="entryfield" title="TR_5-14 TR_# Self-Transferred Out" value="[ TR_5-14 TR_# Self-Transferred Out ]" /></td>
			<td><input id="TmLZ7GmolPD-Tz3T501GscU-val" name="entryfield" title="TR_5-14 TR_# Died" value="[ TR_5-14 TR_# Died ]" /></td>
			<td><input id="TmLZ7GmolPD-yHQSwwkLixM-val" name="entryfield" title="TR_5-14 TR_# Stopped ART" value="[ TR_5-14 TR_# Stopped ART ]" /></td>
			<td><input id="TmLZ7GmolPD-maKlgcg3tUk-val" name="entryfield" title="TR_5-14 TR_# LTFU-- Total number not reachable (by phone, home visit, or both)" value="[ TR_5-14 TR_# LTFU-- Total number not reachable (by phone, home visit, or both) ]" /></td>
			<td><input id="TmLZ7GmolPD-zMKTNqkeL2k-val" name="entryfield" title="TR_5-14 TR_% LTFU" value="[ TR_5-14 TR_% LTFU ]" /></td>
		</tr>
		<tr class="tx-curr">
			<td>15-19</td>
			<td><input id="mt0I1jMN6ju-f71XHxMSENL-val" name="entryfield" title="TR_15-19 TR_# pediatric clients currently on treatment" value="[ TR_15-19 TR_# pediatric clients currently on treatment ]" /></td>
			<td><input id="mt0I1jMN6ju-kJwQDUPdSV1-val" name="entryfield" title="TR_15-19 TR_# pediatric clients with treatment interruption" value="[ TR_15-19 TR_# pediatric clients with treatment interruption ]" /></td>
			<td><input id="mt0I1jMN6ju-xUYWgtOXgYa-val" name="entryfield" title="TR_15-19 TR_# pediatric clients with attempted contact" value="[ TR_15-19 TR_# pediatric clients with attempted contact ]" /></td>
			<td><input id="mt0I1jMN6ju-H5A2yLBq4ry-val" name="entryfield" title="TR_15-19 TR_# successfully traced" value="[ TR_15-19 TR_# successfully traced ]" /></td>
			<td><input id="mt0I1jMN6ju-ZaNk3byy6V8-val" name="entryfield" title="TR_15-19 TR_% traced" value="[ TR_15-19 TR_% traced ]" /></td>
			<td><input id="mt0I1jMN6ju-cc4c4hJSO1H-val" name="entryfield" title="TR_15-19 TR_# brought back to care" value="[ TR_15-19 TR_# brought back to care ]" /></td>
			<td><input id="mt0I1jMN6ju-lxfW0YfAmbo-val" name="entryfield" title="TR_15-19 TR_% brought back to care" value="[ TR_15-19 TR_% brought back to care ]" /></td>
			<td><input id="mt0I1jMN6ju-Uv4we1Y4OlM-val" name="entryfield" title="TR_15-19 TR_# Self-Transferred Out" value="[ TR_15-19 TR_# Self-Transferred Out ]" /></td>
			<td><input id="mt0I1jMN6ju-Tz3T501GscU-val" name="entryfield" title="TR_15-19 TR_# Died" value="[ TR_15-19 TR_# Died ]" /></td>
			<td><input id="mt0I1jMN6ju-yHQSwwkLixM-val" name="entryfield" title="TR_15-19 TR_# Stopped ART" value="[ TR_15-19 TR_# Stopped ART ]" /></td>
			<td><input id="mt0I1jMN6ju-maKlgcg3tUk-val" name="entryfield" title="TR_15-19 TR_# LTFU-- Total number not reachable (by phone, home visit, or both)" value="[ TR_15-19 TR_# LTFU-- Total number not reachable (by phone, home visit, or both) ]" /></td>
			<td><input id="mt0I1jMN6ju-zMKTNqkeL2k-val" name="entryfield" title="TR_15-19 TR_% LTFU" value="[ TR_15-19 TR_% LTFU ]" /></td>
		</tr>
	</tbody>
</table>

<p>&nbsp;</p>

<h2>PEDIATRIC TREATMENT OTHER</h2>

<table class="small-table">
	<tbody>
		<tr>
			<th colspan="12" style="text-align: center; color: white">Undisclosed children</th>
		</tr>
		<tr>
			<th style="color: white"># eligible for family model</th>
			<th style="color: white"># enrolled in family model</th>
		</tr>
		<tr class="tx-curr">
			<td><input id="iUijp4qVLoS-HllvX50cXC0-val" name="entryfield" title="PED_TREATMENT_Undisclosed_# eligible for family model" value="[ PED_TREATMENT_Undisclosed_# eligible for family model ]" /></td>
			<td><input id="mWviwL3N79U-HllvX50cXC0-val" name="entryfield" title="PED_TREATMENT_Undisclosed_# enrolled in family model" value="[ PED_TREATMENT_Undisclosed_# enrolled in family model ]" /></td>
		</tr>
	</tbody>
</table>

<p>&nbsp;</p>

<table class="small-table">
	<tbody>
		<tr>
			<th colspan="12" style="text-align: center; color: white">Disclosed adolescents</th>
		</tr>
		<tr>
			<th style="color: white"># eligible for TC</th>
			<th style="color: white"># enrolled in TC</th>
		</tr>
		<tr class="tx-curr">
			<td><input id="L0gIMsD3RZT-HllvX50cXC0-val" name="entryfield" title="PED_TREATMENT_Disclosed_# eligible for TC" value="[ PED_TREATMENT_Disclosed_# eligible for TC ]" /></td>
			<td><input id="zs9RUQM9rO2-HllvX50cXC0-val" name="entryfield" title="PED_TREATMENT_Disclosed_# enrolled in TC" value="[ PED_TREATMENT_Disclosed_# enrolled in TC ]" /></td>
		</tr>
	</tbody>
</table>

Thanks for the code.

I tested the DOMContentLoaded listener and it seems to be the one causing functions not to work, you might want to use event listeners directly on the input fields without the DOMContentLoaded.

Another thing I noticed, when we’re using ‘title’ to select input fields, if the title of the input field in the HTML code is modified and not exactly the same as it when we click on it, it will be renamed later when the data set is loaded. If the script isn’t working, check that the input fields still have the same titles as they are in the HTML code.

Note that I tested in the Data Entry (legacy) app but the script is not running at all in the Data Entry (Beta)… You might want to consider the default and common way of using validation to continue using these forms in the future since it seems that the custom scripts might not be supported in the newer app.

Thanks so much, it has now worked. But when I fill the form and try to generate report, the report of a customized form are coming out without any data in it yet i have completed the form alreay, is there anything i am missing out that is causing this?

Okay, glad it worked.

Well, another disadvantage to the custom HTML, is that reports generated for custom forms work at best-effort which requires the use of basic HTML with only certain elements for the report generation to work.

@Gassim But is there a way I can configure the customized form to be able to get report, just share anything that may help.