Warning

This section has been left in because it is a useful exposition of the content loader, however as of 2018 developers should not be looking at adding new form field types to the Digital Marketplace frontend without

  1. looking at whether the GOV.UK design system has the same or equivalent field type

  2. reviewing whether the content loader is the best place to add that field type.

Adding a new form field type

Purpose

The purpose of this page is to aid in the addition of new form field types to our existing form infrastructure.

There are several parts to adding a new form field type and this page will aim to explain, through examples, how and why each step is necessary. As such it may also be useful as an introduction to frameworks, content loading, and form validation, amongst other things.

Intro

This page was started on the 4th of April 2017 having pushed to review the addition of a new 3-part date type field:

../_images/new-date-field.png

This was designed to replace the old, free-text date field:

../_images/old-date-field.png

This meant making changes in a load of places.

How this all works

How content pages are built

For a more in depth look at how content is loaded see the frameworks readme

Essentially both the frontend apps (ie digitalmarketplace-buyer-frontend, digitalmarketplace-supplier-frontend or digitalmarketplace-admin-frontend) and the api (digitalmarketplace-api) use the content in the frameworks repo. The frontends use it to display content and to understand form field types. The API uses it to validate incoming data.

The frontend apps use it through the content loader which is an interface which pipes the YAML into python objects representing content chunks (messages, questions, or sections). The content loader is instantiated when the apps start up, grabs the content from frameworks, and sits waiting to be accessed. It is instantiated in, and should be imported from, each of the respective apps’ app/__init__.py.

In the api we have a manual step of converting the YAML to a JSON Schema which is used to validate incoming question/ answer pairs.

How data flows through the application in forms

../_images/data-journey.png

How to

You’re going to (at least) need to:

  1. Add a new data handler/s for your new question type (In digitalmarketplace-content-loader)

  2. Add a new template and shortcut macros for your new field type (In digitalmarketplace-frontend-toolkit)

  3. Add a form macro with the same name as your new field in the given frontend app

  4. Update some YAML to make use of your new field (In digitalmarketplace-frameworks)

  5. Use a script to process the updated YAML and create a JSON schema (To be dumped into digitalmarketplace-api)

Adding a new data handler

This pull request digitalmarketplace-content-loader/pull/36 demonstrates the addition of a date handler. Predominantly the addition of the Date(Question) and DateSummary(QuestionSummary) classes are what are important here.

To add a new field type you’ll likely be expecting the information from the form to be in a different format from that of a regular text field. To that end you’ll want to define a Question class and override the get_data method to act as an interface between the information that is returned in the POST data and the information you pass to the JSON validator. In the case of the date field we needed to take data from the POST that was in 3 fields (a day textbox, month textbox and year textbox) and join them before passing to the validator. Our get_data looked like this:

class Date(Question):
    ...
    def get_data(self, form_data):
        """
        Incoming form data for this field looks like:
        {
            myDate-day: '25',
            myDate-month: '12',
            myDate-year: '2007',
            someOtherQuestion: 'foo'
        }
        the return should be:
        {myDate: '2007-12-25'}
        """
        field_names = self.id + suffix for suffix in ['-year', '-month', '-day']
        return {self.id: '-'.join([form_data[i] for i in field_names])}

Now you have an interface between the POST and the validator you’ll need one going the other way. This is achieved with the unformat_data method:

class Date(Question):
    ...
    ...
    def unformat_data(self, data):
        """
        Incoming data for this field looks like:
        {myDate: '2007-12-25'}
        the return should be:
        {
            myDate-day: '25',
            myDate-month: '12',
            myDate-year: '2007',
        }
        """
        field_names = self.id + suffix for suffix in ['-year', '-month', '-day']
        return zip(dict(field_names, data[self.id].split('-')))

As mentioned above the content loader will use these formatters to put the date into questions, sections and messages.

Adding templates and macros

This step adds the actual field HTML amongst other things.

The pull request for adding the actual HTML and Sass for the date field is here digitalmarketplace-frontend-toolkit/pull/325 and is actually fairly self explanatory. Following the existing naming conventions you’ll want to add the different macros for display in tables etc.

You’ll likely want to add an scss file, html file, some named macros for content and table display, and an entry in the ‘pages builder’ manual.

Adding a form macro

You’ll notice that the macro in the above PR uses a file called toolkit/forms/date.html. That is the file which sets out what a date field should look like in HTML. The macro is just a way of passing question information to that file.

To make templates more reusable we specify just that we want a question rendered then allow Jinja2 to work out exactly how from the question type on the question.

As long as there is a macro in a frontend repos app/templates/macros/toolkit_forms.html file which corresponds to the questions type attribute it will be rendered using said macro.

The pull request adding the date macro to the buyer frontend is here digitalmarketplace-buyer-frontend/pull/469

Updating the YAML and creating the JSON

In the frameworks repo firstly you’ll want to make sure the we can generate a valid json-schema for the api. The api json-schema is what the api uses to validate incoming question/ answers.

So the generator knows what to do for your field you’ll want to add a property for it which will be inserted as a validation rule against all instances of your new field type.

For the date field this was done in schema_generator/validation.py

This is also a good time to make changes to your existing questions or add your new question that will be using the new type. See the full PR for more digitalmarketplace-frameworks/pull/413

Once this has all been updated then run:

python scripts/generate-validation-schemas.py --output-path=../digitalmarketplace-api/json_schemas

to update the validation schemas in the api repo. See digitalmarketplace-api/pull/563

Finished

With any luck that should be all that’s required to create a new field.

Top Tips

  • Because this process touches so many branches I’d suggest keeping very detailed notes on version number bumps, and what’s being included where.

  • I’d also suggest not putting up PRs until you’re sure they’re as ready as possible.

  • Make a detailed deployment plan of what you want to merge, when. And what order everything needs to go in.

  • Please update here with anything you find, or add, and with any corrections you want to make.

Examples

References