Skip to content

Creating form definition for Campaign

Segun edited this page Oct 23, 2024 · 20 revisions

Campaign Form Definition

The Campaign module of SORMAS supports forms that can be defined using JSON and that are dynamically built based on these definitions. As there currently is no user interface to save these definitions, they need to be manually added to the campaignforms table in the database:

INSERT INTO campaignformmeta(id, uuid, changedate, creationdate, languagecode, formid, formname)
	VALUES (nextval('entity_seq'), overlay(overlay(overlay(
		substring(upper(REPLACE(CAST(CAST(md5(CAST(random() AS text) || CAST(clock_timestamp() AS text)) AS uuid) AS text), '-', '')), 0, 30) 
		placing '-' from 7) placing '-' from 14) placing '-' from 21), now(), now(), 'en', '???', '???');

A basic campaign form definition containing two fields would look like this and need to be inserted into the 'campaignformelements' column.

[
  {
    "type": "text",
    "id": "firstFieldId",
    "caption": "Caption of the first field"
  },
  {
    "type": "number",
    "id": "secondFieldId",
    "caption": "Caption of the second field"
  }
]

Fields can be customized with a number of attributes that, in turn, support a variety of potential values:

"type"

The type defines the basic functionalities of the field: whether it expects user input, which kind of input it supports and how it looks like. This attribute must be specified for all fields. The supported values are: NUMBERS YES_NO TEXT SECTION LABEL CHECKBOX (requires "options" field) CHECKBOXBASIC (requires "options" field) RADIO (requires "options" field) RADIOBASIC (requires "options" field) RANGE (requires max and min value) DATE DROPDOWN (requires options) DECIMAL

  • daywise : A tab sheet field that can be used for categorising forms into days for form entry on multiple days. This field also requires an id and a closing daywise tag to indicate the end of the daywise forms.
  • section : Marks the beginning of a new section. This will add a background color to all fields within the section. Starting a new section will end the previous one. Background colors alternate between a lighter and a darker grey.
  • label : A text label that can be used for descriptions or instructions. Does not support user input.
  • text : A text field that allows input without restrictions regarding the entered characters.
  • number : A number field that only allows entering numbers.
  • yes-no : A field that allows users to select between two options ("Yes" and "No").
  • checkbox : A field that takes input options and allows users select one or multiple values using a modern UI.
  • checkboxbasic : A field that takes options input and allows users select one or multiple values using a regular checkbox display.
  • radio : A field that allows users select ONLY ONE option value.
  • radiobasic : A field that allows users select ONLY ONE option value rendered in the regular radio display.
  • range : A field that allows only input greater than or equals to the MIN value provided and less than on equals to the MAX value provided.
  • date : Provides interface for the input of a date value in the standard dd/MM/YYYY format.
  • dropdown : A field that takes options inputs and allows users select a value from a dropdown list.
  • decimal : A field that allows only decimal numbers.

"options"

The options field is used to pass options for RADIO, DROPDOWN and CHECKBOX types. The options provide definite values to be selected by the user. This values must be set in K,V i.e Key and Caption format eg


"options": [{"key":"yes","caption":"Yes"}, {"key":"no","caption":"No"}]

"id"

The ID of the field that is used to reference it in other fields and to provide translations. This attribute must be specified for all fields and has to be unique within the form.

The * readonly * keyword can be used to define an attribute as a read-only, and hidden field for data analysis and data listing modules. Kindly concatenate the id field value with the keyword. This will trigger the system not to identify such fields as a data field on the JSON form.

{
    "type": "number",
    "id": "FieldId_readonly",
    "caption": "Caption of the readonly field"
  }

"Village Code"

This field automatically populates the village code.

{"type": "number",
"id": "Villagecode",
"caption": 
"This field automatically populates the village code"
}

"Using Population Data in form"

The code below must be in the top of any form that uses population as a numerator or denominator called within the expression calculatable fields.

{     "type": "number",    
 "id": "PopulationGroup_0_4",     
"caption":  
"This field automatically populates the district population data"
 }

"Permanently hide extra field"

To permanently hide a field, please include a dependingOn and a dependingOnValue relating to a valid field but invalid value(value that cannot be achievable) A dependOnValue field will look like this:

"dependingOn": "numberOfVial"
"dependingOnValues": ["no"]

"caption"

The default caption for the field. Supports basic HTML tags, e.g. headline tags, line breaks, bold, italic, underline, etc.

"Required fields"

Form fields can be set as required to make sure the form cannot be submitted without inputting validated values at the point of data entry. This is done by setting:

"important":"true"

"Setting Default value"

Default values can be set to default to make sure the field has a value when the form is to be filled. This is done by setting the default value field in the JSON:

"defaultvalue":"32"

"Aggregation of form via API."

To obtain agggregated data from campaign forms via api, user is required to connect to the REST API server to access aggregated data in csv format.
url: /sormas-rest/apmisrestserver/aggregateCampaignCSV/

This above url will provide a resulting CSV format of the aggregate data based on the campaignUuid provided on the URL.

"Campaign Page"

Associate Campaign The associate campaign tab is populated by the population data. When a population for any district is imported under Campaign population. The region area under associate campaign is then populated. When a checkbox for a particular province is checked and then saved, the data for the selected province is immediately populated to dashboard.

"Open/Close/Publish Campaign Data"

  • Campaign can be closed for data entry but cannot be archived by clicking the close/open button.

  • Post campaign data can be publish by the admin by clicking publish and unpublish

"Expiry dates for forms"

Forms within campaigns now have expiry date. It is mandatory for an expiry date to be set when defining a form. The expiry date detects when the campaign ends. If a campaign starts on the 1st of april and ends on the 6th of april, datas will no longer be collected for that particular form daysexpired

campaignFormMeta

  {
    "type": "text",
    "id": "firstFieldId",
    "daysExpired": "02-06"
  }


"Adding Year to Dashboard and Campaign pages"

Adding Year dropdown to Dashboard and Campaign pages for limiting campaign selection based on year. If user doesn't select a year from the Year dropdown filter, Campaigns dropdown contains all campaigns. When user select a year from the Year dropdown filter, only campaigns from that year will be listed in Campaign dropdown.

"Modification of charts using population data."

Cards can be tweaked by colors on dashboard, pop data can be added to cards, the colors of charts can be changed.

  {
    "type": "text",
    "id": "firstFieldId",
    "colors": "red"
  }

"styles"

Optional attribute that can be used to change the visual appearance of the field. Multiple styles can be specified at once. The expected notation is ["style1", "style2"]. Campaign forms use a grid system with 12 columns. By default, text and number fields take 4 columns (so 1/3rd of the width) and yes-no fields take the full width of a row. The supported values are:

  • inline : Displays the field in line with others. This is the default for text and number fields.
  • row : Makes the field take the whole width of the row. This is the default for yes-no fields.
  • first : Pushes the field to the next row and makes it the first element in that row.
  • col-1, col-2, ..., col-12 : Defines how many columns the field should take. If the amount of columns would exceed the maximum number of columns for the current row, the field is pushed to the next row.

"dependingOn" and "dependingOnValues"

Allows to make the visibility of the field conditional on the value of another field in the same form. dependingOn expects the ID of the other field, dependingOnValues expects a list of values. If the field specified in dependingOn contains one of these values, the field for which this attribute is specified is made visible; otherwise it's hidden. The expected notation for dependingOnValues is ["dependingOnValues": ["yes"].

"min" and "max"

The min and max parameters are used to pass minimum and maximum input values for the RANGE type.

"expression"

Campaign data forms can also have fields (expression="... SpEL expression ...") that are calculated based on the number entered for e.g. "Missed children based on recall" (>=2) and "Number of houses team did not visit" (>=2). An expression can contain math operators (+, -, ..) and logic operators (e.g. ''missedChrildren > 2'').

[
  {
	"type": "number",
	"id": "missedChildren",
	"caption":"Total number of missed children (based on recall and FM)",
	"styles": ["row"],
	"important": true
  },
  {
	"type": "number",
	"id": "teamDidNotVisit",
	"caption": "Not visited by team",
	"styles": ["col-6"]
  },
  {
	"type": "yes-no",
	"id": "poorlyCoveredArea",
        "expression": "missedChildren > 2 and teamDidNotVisit >= 2",
	"caption": "Poorly covered area (2 or more children missed based on finger mark)?",
	"important": true
  }
]


[
  {
	"type": "number",
	"id": "childrenSeenByMonitor",
	"caption": "Number of < 5 years children seen by the monitor",
	"styles": ["row", "col-3"]
  },
  {
	"type": "number",
	"id": "childrenFingerMarking",
        "expression": "childrenSeenByMonitor + 5",
	"caption": "Number of < 5 years children seen with finger marking by monitor",
	"styles": ["row", "col-3"]
  }
]

See Spring Expression Language (SpEL) for more information.

Enable the Expression field for User input

Expression fields are disabled by default to disallow user from interfering with the pre-calculations. However, if the admin wishes to allow the user to override such calculations. The ignoredisabled command could be used. An enabled expression field will look like this:

"ignoredisabled": true

Setting Range field dynamically

To allow admin to set the system to check minimum and maximum range programatically based on the data entered by user i.e validating data to check minimum or maximum range. A basic dynamic range field would look like this modification while resolving issue #70 :


"constraints":[
         "expression"
      ],
      "expression":"((Field 1 + Field 2 + Field 3) <= maxRangeField) ? (thisField * 1) : 0",
      "styles":[
         "col-6"
      ]
   }


Logical Checks

To allow users to continue submitting a form i.e overriding data validation. Administrators are required to add the keyword: warnOnError. . A basic logical check would look like this:

{
	"type": "number",
	"id": "missedChildren",
	"caption":"Total number of missed children (based on recall and FM)",
	"styles": ["row"],
	"important": true,
        "warnOnError": true
  }


Logical check dependency on two Yes-No fields

To enable expression on fields whereby you still want the user to be able to make some decision based on conditions. Admin can add ignoredisable key words to enable a pre calculated field. A basic logical check condition would look like this:

  {
	"ignoredisable":"true",
        "expression":"(poorlyCoveredArea != false) ? null : false"
  }


In the above example, if poorlyCoveredArea is not NO, this field will be editable by the user. For more information refer to #240

Translations

Campaign forms can be translated to every language that is supported by SORMAS. To do this, JSON needs to be specified in the campaignformtranslations column. A basic translation definition would look like this:

 {
	"type": "number",
	"id": "missedChildren",
	"caption":"Total number of missed children (based on recall and FM)",
	"styles": ["row"],
	"important": true
  }

The * languageCode * attribute defines the locale the translation is for and needs to match one of the locales supported by SORMAS. The * translations * attribute expects a list of translations, each of which need to specify the * *elementId, which is the id of the translated field in the form, and a translated * ** caption * that supports basic HTML elements.

Examples

An example JSON definition to collect data on missed children for vaccination campaigns:

[{
	"type": "section",
	"id": "totalNumbersSection"
}, {
	"type": "label",
	"id": "totalNumbersLabel",
	"caption": "<h3>Total Numbers</h3>"
}, {
	"type": "checkbox",
	"id": "amenities",
	"caption": "Select all that applies",
	"styles": ["row", "col-3"],
	"important": true,
	"options":["water","electricity","road","toilet"]
	},{
	"type": "radio",
	"id": "typeoffamily",
	"caption": "Select the type of family",
	"styles": ["row", "col-3"],
	"important": true,
	"options":["nuclear","polygamous","extended"]
	}, {
	"type": "number",
	"id": "childrenLivingInHouses",
	"caption": "Number of < 5 years children living in houses",
	"styles": ["row", "col-3"],
	"important": true
}, {
	"type": "number",
	"id": "childrenVaccinatedRecall",
	"caption": "Number of < 5 years children vaccinated based on recall",
	"styles": ["row", "col-3"]
}, {
	"type": "number",
	"id": "childrenSeenByMonitor",
	"caption": "Number of < 5 years children seen by the monitor",
	"styles": ["row", "col-3"]
}, {
	"type": "number",
	"id": "childrenFingerMarking",
	"caption": "Number of < 5 years children seen with finger marking by monitor",
	"styles": ["row", "col-3"]
}, {
	"type": "section",
	"id": "missedChildrenSection"
}, {
	"type": "label",
	"id": "missedChildrenLabel",
	"caption": "<h3>Missed Children</h3>"
}, {
	"type": "number",
	"id": "missedChildren",
	"caption": "Total number of missed children (based on recall and FM)",
	"styles": ["row"],
	"important": true
}, {
	"type": "label",
	"id": "missedChildrenNumbers",
	"caption": "<h4>Number of missed children for the following reasons:</h4>",
	"styles": ["col-6"]
}, {
	"type": "number",
	"id": "houseNotIncluded",
	"caption": "House not included in micro plan",
	"styles": ["col-6"]
}, {
	"type": "number",
	"id": "teamDidNotVisit",
	"caption": "Not visited by team",
	"styles": ["col-6"]
}, {
	"type": "number",
	"id": "poorScreening",
	"caption": "Poor screening by team",
	"styles": ["col-6"]
}, {
	"type": "range",
	"id": "childAbsent",
	"caption": "Children absent",
	"styles": ["col-6"],
        "constraints": ["max=10", "min=2"]
}, {
	"type": "decimal",
	"id": "weight",
	"caption": "Average Newborn weight",
	"styles": ["col-6"]
}, {
	"type": "date",
	"id": "reportDate",
	"caption": "Date of Reporting",
	"styles": ["col-6"]
}, {
	"type": "number",
	"id": "guest",
	"caption": "Guest",
	"styles": ["col-6"],
	"dependingOn": "refusal",
	"dependingOnValues": [1, 5]
}, {
	"type": "section",
	"id": "doorMarkingsSection"
}, {
	"type": "label",
	"id": "doorMarkingsLabel",
	"caption": "<h3>Door Markings</h3>"
}, {
	"type": "number",
	"id": "doorMarkingCorrect",
	"caption": "Correct"
}, {
	"type": "number",
	"id": "doorMarkingIncorrect",
	"caption": "Incorrect"
}, {
	"type": "number",
	"id": "doorMarkingNotMarked",
	"caption": "Not marked"
}, {
	"type": "section",
	"id": "areaSection"
}, {
	"type": "yes-no",
	"id": "poorlyCoveredArea",
	"caption": "Poorly covered area (2 or more children missed based on finger mark)?",
	"important": true
}, {
	"type": "yes-no",
	"id": "missedArea",
	"caption": "Missed area (2 or more houses missed due to no team visit)?",
	"dependingOn": "poorlyCoveredArea",
	"dependingOnValues": ["YES"]
}, {
	"type": "text",
	"id": "comment",
	"caption": "Comment",
	"styles": ["col-12"],
	"dependingOn": "missedArea",
	"dependingOnValues": ["yes"]
}]

Handling Null Values in Numerical Calculations

When working with numerical fields that might be null, it's crucial to implement proper null-checking to ensure accurate calculations. The primary approach uses conditional logic to substitute null values with zeros while preserving non-null values.

Basic Syntax

The fundamental pattern for handling a potentially null field is:

"(field1 != null ? field1 : 0)"

This ternary expression:

  • Returns the field's value if it's not null
  • Returns 0 if the field is null

Multiple Field Calculations

When calculating across multiple fields, apply the null check to each field:

"(field1 != null ? field1 : 0) + (field2 != null ? field2 : 0) + .... + (fieldN... != null ? fieldN... : 0)"

Practical Example

[
  {
   "type": "number",
    "id": "TotalVials_day1",
    "caption": "Total Vials Used Day 1"
  },
  {
   "type": "number",
    "id": "TotalVials_day2",
    "caption": "Total Vials Used Day 2"
  },
  {
   "type": "number",
    "id": "TotalVials_day4",
    "caption": "Total Vials Used Day 4"
  },
  {
   "type": "number",
    "id": "TotalNumberOfVials_day1_4",
    "caption": "Total Vials Used Day 1 - 4",
    "expression": "(TotalVials_day1 != null ? TotalVials_day1 : 0) + (TotalVials_day2 != null ? TotalVials_day2 : 0) + (TotalVials_day3 != null ? 
     TotalVials_day3 : 0) + (TotalVials_day4 != null ? TotalVials_day4 : 0)"
  }
]

In this example:

  • Each day's vials are defined as separate number fields
  • The total calculation handles null values for any day
  • The result will be the sum of all non-null values, with null values counted as 0