← Back to the hackathon home
Topic 10 of 18

Designing Custom Forms With HTML, CSS and JavaScript

Step by Step Guide: Building a Data Entry Form That Matches the Paper One

Purpose of this guide

When a section form cannot match the paper form, DHIS2 lets you write the form yourself. This guide covers the designer, the field identifier that makes a custom form work at all, layout in HTML, styling in CSS, running totals and warnings in JavaScript, and the maintenance you take on when you do it.

Step 1: When a Custom Form Is Worth It

Section forms cover most needs and cost nothing to maintain. A custom form is HTML you own, which means you can build any layout, but you also own it forever.

Form typeGood forCost
DefaultQuick testingNone, but unreadable for long data sets
SectionAlmost everything routineNone. Changes follow the data set
CustomPaper forms that must be copied exactly, running totals, colour rulesYou must edit the HTML every time the data set changes
Click: Maintenance app → DATA SET → your data set → row menu → Design data entry form
Step 2: The Designer
Figure 1. The custom form designer. This picture was drawn for the guide. Your screen will show your own names and dates.
  1. 1Section form. What you leave behind when you switch to custom.
  2. 2The HTML. Your own table, written or pasted here.
  3. 3Insert data element. Never type a field by hand. Use this, so the identifier is right.
  4. 4Insert total or indicator. Read only fields DHIS2 fills in.
  5. 5Style block. Where the CSS goes.
  6. 6Script block. Where the JavaScript goes.
  7. 7SAVE and PREVIEW. Preview after every change, not at the end.
Step 3: The One Rule That Makes It Work

Every input in a custom form is tied to a data element and a category option combination by its id. Get the id right and DHIS2 saves the value; get it wrong and the field does nothing at all.

<input id="<dataElementUid>-<categoryOptionComboUid>-val">

<!-- for example -->
<input id="Uvn6LCg7dVU-Prlt0C1RF0s-val">
Always use the Insert button: it writes that id for you. Typing it by hand is the single most common reason a custom form looks right and saves nothing.
Step 4: Layout With HTML

A table is usually the right structure, because the paper form is usually a table.

<table class="malaria">
  <tr>
    <th>Malaria testing</th><th>Male</th><th>Female</th><th>Total</th>
  </tr>
  <tr>
    <td>RDT tested</td>
    <td><input id="Uvn6LCg7dVU-Prlt0C1RF0s-val"></td>
    <td><input id="Uvn6LCg7dVU-bRTho9NV9dM-val"></td>
    <td><input id="rdt-total" readonly></td>
  </tr>
</table>
ElementUse it for
table, tr, th, tdThe grid of the paper form
inputOne field, with the id from Step 3
input readonlyA total the script fills in
h3 and fieldsetSection headings
colspan and rowspanMerged headings, as on paper
Step 5: Style With CSS

Put the style in a single block at the top of the form. Keep it to layout and readability, and leave the DHIS2 colours for the states DHIS2 controls, such as green when a value is saved.

<style>
  table.malaria { width: 100%; border-collapse: collapse; font-size: 14px; }
  table.malaria th { background: #1f6fb2; color: #fff; padding: 8px; text-align: left; }
  table.malaria td { border: 1px solid #d5dbe2; padding: 6px; }
  table.malaria tr:nth-child(even) td { background: #f7f9fc; }
  table.malaria input { width: 90%; padding: 4px; }
  table.malaria input[readonly] { background: #eef2f6; font-weight: bold; }
  .section-title { margin: 14px 0 6px; font-size: 16px; font-weight: 700; }
  @media (max-width: 700px) { table.malaria { font-size: 12px; } }
</style>
Mobile matters: a wide table is painful on a phone. A media query, or a table that scrolls inside its own box, keeps the form usable in a facility with no desktop.
Step 6: Add Behaviour With JavaScript

Scripts run inside the data entry page, so they can add up rows as they are typed and warn when a value cannot be right.

<script>
  function id(de, coc) { return document.getElementById(de + "-" + coc + "-val"); }

  function num(el) { return parseFloat(el && el.value) || 0; }

  function updateTotals() {
    var male   = id("Uvn6LCg7dVU", "Prlt0C1RF0s");
    var female = id("Uvn6LCg7dVU", "bRTho9NV9dM");
    document.getElementById("rdt-total").value = num(male) + num(female);

    var tested   = num(male) + num(female);
    var positive = num(id("hKZh1et5n7v", "Prlt0C1RF0s"));
    var row = document.getElementById("rdt-positive-row");
    row.style.background = positive > tested ? "#ffe0e0" : "";
  }

  document.addEventListener("keyup", updateTotals);
  document.addEventListener("change", updateTotals);
</script>
What scripts are good atWhat to leave to DHIS2
Running totals as the clerk typesSaving values
Colouring a row that cannot be rightValidation rules, which are checked on the server
Hiding a section that does not applySharing and access
Formatting and focus orderMinimum and maximum limits
A script is help, not a rule: anything that must never be saved belongs in a validation rule as well, because a browser script can be bypassed.
Step 7: Preview, Then Test With a Clerk
Figure 2. The finished custom form in data entry. This picture was drawn for the guide. Your screen will show your own names and dates.
  1. 1The heading. Your own layout, not the default one.
  2. 2The rows. In the same order as the paper form the clerk already knows.
  3. 3The fields. Ordinary DHIS2 inputs, so they still turn green when saved.
  4. 4The totals. Read only, filled by the script.
  5. 5What the script does. Adds up as you type.
  6. 6The warning colour. Positives above tested, which is impossible.
ProblemCauseFix
Field does not saveThe id is wrongDelete it and use Insert data element
Totals never changeThe script runs before the fields existRun it on keyup and change, as above
New data element missing from the formA custom form does not follow the data setEdit the HTML and add it
Form unusable on a phoneFixed widthsPercentages and a media query
Values save but look wrongTwo inputs share one idEach field has its own id

Final Checklist