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
ormodals.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 theform-template.html
in the desired page. There are some examples below for all your copy&pasting needs.
Creating a New Form
-
Add
form-data.json
: Place this file in the local directory of the page that will use the form. For multi-page forms, store theform-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.
- Previously, all form data was stored in a single
-
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>
-
Add the appropriate trigger: To set up the ‘contact-us’ button add the following:
3.a. On ubuntu.com addclass="js-invoke-modal"
3.b. On canonical.com addaria-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 liveschildrenPaths
(optional): Paths of children pages (within the same bubble) that are using the shared form. The originaltemplatePath
must be applied on the index pageisModal
(optional): Whether the form is static or dynamic/modal, false by defaultmodalId
(optional): Only required for modals. Set it tocontact-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 thearia-controls
for CTA is set to the same ID as the modalID.formData
(required): General form datatitle
(required): Title of formintroText
(optional): Form descriptionformId
(required): Marketo ID for the formreturnUrl
(required): Return URL on form submission, attach#contact-form-success
to use the standardized form success banner. For c.com, use full path e.ghttps://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 fieldsetid
(required): Fieldset idisRequired
(optional): Whether the fieldset is required or not, False by defaultnoCommentsFromLead
(optional): Whether to append this fieldset toComments_from_lead__c
parameter in the payload. This is usually set as True for contact fields, False by defaultinputType
(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 removename
attribute before submitting to payload. Attachesjs-remove-radio-names
class to fieldsetcheckbox
: Include for required checkbox fields. Attachesjs-required-checkbox
class to fieldsetcheckbox-visibility
: Include for checkbox fields where “Other” options are greyed out when non-other options are selected and vice versa. Attachesjs-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
- 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 fieldid
(required): Input field IDlabel
(optional): Text labelisRequired
(optional): Whether the field is required, false by default.
Country
"fieldsets": [
...
"fields": [
{
"type": "country",
"id" : "",
"label": "",
"isRequired": true
}
]
]
type
(required): Type of fieldid
(required): Input field ID, set as emptylabel
(optional): Text labelisRequired
(optional): Whether the field is required, false by default.
"fieldsets": [
...
"fields": [
{
"type": "email",
"id" : "email",
"label": "Email address",
"isRequired": true
}
]
]
type
(required): Type of fieldid
(required): Input field IDlabel
(optional): Field labelisRequired
(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 fieldid
(required): Input field IDlabel
(optional): Field label
Long text
"fieldsets": [
...
"fields": [
{
"type": "long-text",
"id" : "long-text-id",
"placeholder": "Example placeholder text on textbox"
},
]
]
type
(required): Type of fieldid
(required): Input field IDplaceholder
(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 fieldid
(required): Input field IDlabel
(required): Input field labeloptions
(required): Dropdown optionsvalue
(required): Value of dropdown selectionlabel
(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 fieldid
(required): Input field IDvalue
(required): Input field valuelabel
(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 fieldid
(required): Input field IDvalue
(required): Input field valuelabel
(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 sectionoptions
(required): List of checkbox selection in an arraytype
(required): Input typeid
(required): Input field IDvalue
(required): Input field valuelabel
(required): Input field label
Last updated a day ago.