Automated form builder

Overview

Forms on canonical.com and ubuntu.com now use a shared template structure for consistency and simplicity. The form generator consists of three key files:

  • form-data.json
  • form-template.html
  • form-fields.html

If you need any help please reach out to someone in the Sites team.

Key Notes

  • Do not modify the form-template.html, form-fields.html or modals.js files. These are standardized across all forms.
  • To create a new form, you only need to define a form-data.json file specific to your form and include the form-template.html in the desired page. There are some examples below for all your copy&pasting needs.

Creating a New Form

  1. Add form-data.json: Place this file in the local directory of the page that will use the form. For multi-page forms, store the form-data.json at the highest logical level in the directory tree (eg. if it serves all the pages in /aws, store it there).

    • Previously, all form data was stored in a single forms-data.json file, but this approach has been deprecated due to size and maintainability concerns.
  2. Include form-template.html: In the target page, place this somewhere at the bottom:
    {% include "/shared/forms/form-template.html" %}

    2.a. Note: If it is a ‘modal’ (pop-up form) and it is on canonical.com, you will need to include the forms script in the page:
    <script defer src="{{ versioned_static('js/modals.js') }}"></script>

  3. Add the appropriate trigger: To set up the ‘contact-us’ button add the following:
    3.a. On ubuntu.com add class="js-invoke-modal"
    3.b. On canonical.com add aria-controls="whatever-the-modal-id-is"

Using the template

In order to add new forms or update existing one, we can update forms-data.json with the new form data.

General forms-data.json structure

This is where form data lives. The form generator parses data from this file and builds it with the template. For this example, we are using an example use case for the form on https://canonical.com/data/postgresql#get-in-touch.

"forms": {
    "/data/postgresql": {                            // Page url
         "templatePath": "/data/postgresql/index.html", 
         "childrenPaths": ["/data/postgres/child1", "/data/postgres/child2"],
         "isModal": true,                            // False by default for static forms
         "modalId": "data-relational-dbs-modal",                 // Only for modals
         "formData": {
            "title": "Talk to our relational databases' experts",
            "introText": "Intro text",               // optional
            "formId": "1266",
            "returnUrl": "/data/postgresql#contact-form-success",    // For c.com, use full path e.g https://canonical.com/data/postgresql#contact-form-success
            "product": ""
         },
         "fieldsets": [{
            "title": "Tell us more about your use case",
            "id": "comments",
            "isRequired": false,
            "noCommentsFromLead": false,
            "fields": [
              {
                  "type": "long-text",
                  "id": "comments",
                  "placeholder": "Anything you'd like to communicate about your needs or interests?"
                  "isRequired": false
              }
            ],                         
         }],   
        ...           
      }
   }
}
  • templatePath (required): Path of page where the form lives
  • childrenPaths (optional): Paths of children pages (within the same bubble) that are using the shared form. The original templatePath must be applied on the index page
  • isModal (optional): Whether the form is static or dynamic/modal, false by default
  • modalId (optional): Only required for modals. Set it to contact-modal for u.com and an appropriate ID (e.g data-relational-dbs-modal for c.com). This is because modals are invoked differently on both sides. For c.com, make sure that the aria-controls for CTA is set to the same ID as the modalID.
  • formData (required): General form data
    • title (required): Title of form
    • introText (optional): Form description
    • formId (required): Marketo ID for the form
    • returnUrl (required): Return URL on form submission, attach #contact-form-success to use the standardized form success banner. For c.com, use full path e.g https://canonical.com/data/postgresql#contact-form-success
    • product: Which product this form is for, can be left as an empty string if unknown.
  • fieldsets (required): Can be a single fieldset with one field, or a group fieldset with a list of fields. If it is a group, we can initiate an array for the group of fields. More example use cases are shown below.
    • title (required): Title of fieldset
    • id (required): Fieldset id
    • isRequired (optional): Whether the fieldset is required or not, False by default
    • noCommentsFromLead (optional): Whether to append this fieldset to Comments_from_lead__c parameter in the payload. This is usually set as True for contact fields, False by default
    • inputType (optional): radio/checkbox/checkbox-visibility. Only needed for fieldsets with JS functionality

Fieldsets

Fieldset is a group/section of data in the form. They can have a single field or a group of fields. If there is a list of fields, we have to initiate an array to store the different fields. We have JS code for fieldsets with special behaviours such as radio and toggling checkbox visibility, this can be applied using inputType parameter.

inputType values and explanation

  • radio: Must include for radio fieldset to remove name attribute before submitting to payload. Attaches js-remove-radio-names class to fieldset
  • checkbox: Include for required checkbox fields. Attaches js-required-checkbox class to fieldset
  • checkbox-visibility: Include for checkbox fields where “Other” options are greyed out when non-other options are selected and vice versa. Attaches js-toggle-checkbox-visibility class to fieldset

Fieldsets can have different fields

"fieldsets": [
    ...
    "fields": [
        {
            ...
        },
        {
            ...
        }
    ]
]

Setting required fields

To set a field as required, we can add isRequired to the field and set to True. If there are multiple field types such as for radio or checkbox, then we also have to change the parent fieldset, add inputType and set to the appropriate type (i.e radio, checkbox). This adds JS functionality to the fieldset and fields to ensure that it is selected before submission.

Fields

The general list of field types used for the forms are:

  • Text field
  • Country
  • Email
  • Mobile
  • Long text
  • Dropdown select
  • Radio
  • Checkbox

Text

"fieldsets": [
    ...
    "fields": [
        {
            "type": "text",
            "id" : "text-id",
            "label": "Example text label",
            "isRequired": true
        }
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • label (optional): Text label
  • isRequired (optional): Whether the field is required, false by default.

Country

"fieldsets": [
    ...
    "fields": [
        {
            "type": "country",
            "id" : "",
            "label": "",
            "isRequired": true
        }
    ]
]
  • type (required): Type of field
  • id (required): Input field ID, set as empty
  • label (optional): Text label
  • isRequired (optional): Whether the field is required, false by default.

Email

"fieldsets": [
    ...
    "fields": [
        {
            "type": "email",
            "id" : "email",
            "label": "Email address",
            "isRequired": true
        }
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • label (optional): Field label
  • isRequired (optional): Whether the field is required, false by default.

Mobile

"fieldsets": [
    ...
    "fields": [
        {
            "type": "tel",
            "id" : "phone",
            "label": "Mobile/cell phone number"
        },
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • label (optional): Field label

Long text

"fieldsets": [
    ...
    "fields": [
        {
            "type": "long-text",
            "id" : "long-text-id",
            "placeholder": "Example placeholder text on textbox"
        },
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • placeholder (optional): Placeholder text shown on textbox

Dropdown select

Instantiate an array of fields for the different dropdown selection

"fieldsets": [
    ...
    "fields": [
        {
            "type": "select",
            "id" : "select-id",
            "label": "Example dropdown",
            "options": [
            {
              "value": "dropdown-value-1",
              "label": "Example value 1"
            },
            {
              "value": "dropdown-value-2",
              "label": "Example value 2"
            },
          ]
        }, 
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • label (required): Input field label
  • options (required): Dropdown options
    • value (required): Value of dropdown selection
    • label (required): Label of dropdown selection

Radio

Instantiate an array of fields for the different radio selection.
Add "inputType": "radio" to fieldsets to concatenate value to payload string.

"fieldsets": [
    ...
    "inputType": "radio",
    "inputName": "name-example",
    "fields": [
        {
            "type": "radio",
            "id": "radio-id-1",
            "value": "value-1",
            "label": "Example first selection"
        },
        {
            "type": "radio",
            "id": "radio-id-2",
            "value": "value-2",
            "label": "Example second selection"
        },
       {
            "type": "radio",
            "id": "radio-id-3",
            "value": "value-3",
            "label": "Example third selection"
        },
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • value (required): Input field value
  • label (required): Input field label

Checkbox

There are 2 types of checkboxes, normal required/non-required checkboxes and visibility checkboxes.

Normal checkbox fields

"fieldsets": [
    ...
    "fields": [
        {
            "type": "checkbox",
            "id": "checkbox-id-1",
            "value": "value-1",
            "label": "Example first selection"
        },
        {
            "type": "checkbox",
            "id": "checkbox-id-2",
            "value": "checkbox-2",
            "label": "Example second selection"
        }
    ]
]
  • type (required): Type of field
  • id (required): Input field ID
  • value (required): Input field value
  • label (required): Input field label

Visibility checkbox fields

This means that there are a few divided fields, and one Other group. If a checkbox within Other is selected, then the fields in other field groups will be greyed out and vice versa. This is achieved by attaching inputType as checkbox-visibility to fieldset.

"fieldsets": [
    ...
    "inputType": "checkbox-visibility",
    "fields": [
      {
        “fieldTitle”: “Example section 1”,
        "options": [
          {
            "type": "checkbox",
            "id": "1-1",
            "value": "1-1",
            "label": "1-1"
          },
          {
            "type": "checkbox",
            "id": "1-2",
            "value": "1-2",
            "label": "1-2"
          },
       ],
    },
    {
       “fieldTitle”: “Example section 2”,
       "options": [
         {
           "type": "checkbox",
           "id": "2-1",
           "value": "2-1",
           "label": "2-1"
         },
         {
            "type": "checkbox",
             "id": "2-2",
             "value": "2-2",
             "label": "2-2"
           },
        ],
    },
  ]
]
  • fieldTitle (required): Title of the checkbox field section
  • options (required): List of checkbox selection in an array
    • type (required): Input type
    • id (required): Input field ID
    • value (required): Input field value
    • label (required): Input field label

Last updated a day ago.