Building a complex custom widget in UX Forms

UX Forms provides an exhaustive set of user interface widgets out of the box, which you can see in action in our pattern library form. We also realise that, no matter how many we provide, there will always be some forms that need something special. In this article we’re going to go through how to compose existing widgets and behaviour to create an entirely new user interface widget. Namely one that can conditionally reveal other widgets within itself.

Conditionally revealing content

Whilst UX Forms already provided checkbox and radio group widgets, as well as the ability to show and hide any widget based on answers to previous questions, we didn’t provide a widget that could combine the two in a single question.

Separate email and phone widgets conditionally visible upon selected checkboxes

A pattern that we’re seeing used more and more is one where the conditional question can be displayed within the group, just below the selected item.

Nested email and phone widgets conditionally visible upon selected checkboxes

So, let’s go through the process of building a new, custom, widget to do just this.

What does our widget need to do?

Well, we need to be able to include a form field between options in a multiple choice widget, i.e. radio groups and check boxes. We really ought to be able to leverage all the goodness of UX Forms’ built-in widget behaviours like widget compose-ability, validation, rendering, full internationalisation, etc., to build this up.

The first consideration is how the nested/conditional widgets should be related to the multiple choice widget. Given that the multiple choice’s options typically come from an internationalised properties bundle, and that different locales can have their choices in a different order, we’re going to have to associate our nested widget with the key of the relevant choice.

A properties file defining the choices for our checkboxes. These are passed through to widgets as instances of Messages.

So, it makes sense that we should be able to pass a map of String -> Widget to our multiple choice for its nested widgets.

Composing existing widgets and behaviours

Both theCheckbox and RadioGroup widgets are instances of the MultipleChoice trait which adds in behaviour over and above “regular” widgets that is unique to UI components that ask you to choose between multiple options. So we’ll definitely need to make use of this somewhere.

But our new widget also needs to know how to handle more widgets nested inside itself. It turns out we have a common trait for that, too, called WidgetGroup. Used by the WidgetGroup, er, widget, it handles how to save nested widgets back to the platform when a section is submitted, and how to perform validation on all of its nested widgets. So we’ll need one of these, too.

In fact, let’s start with a WidgetGroup. Given it already knows how to handle multiple widgets in one go, let’s try that as our base.

First cut of a trait to handle common behaviour between nested checkboxes and radio buttons

We can see our nested widgets is the map of String -> Widget, and there is a member for a MultipleChoice widget. Then widgets, inherited from WidgetGroup, is made up of all the nested widgets as well as our multiple choice widget. This should get all the multi-widget behaviours from WidgetGroup that we need and ensure that the form behaves as expected regardless of the type of widget that we want to nest.

MultipleChoice has a function called choicesForTemplate which iterates over the Messages passed to the widget to use as its choices and converts them to key-value pairs for use in the widget’s template. We’re going to need to extend this function somehow to also render the associated nested widget for each choice. Unfortunately the existing function doesn’t take enough arguments to be able to call render on our nested widgets, so we’re going to need something else. Let’s add that to our new trait.

Fleshing out NestingMultipleChoice with pseudo code to show how rendered nested widgets can be added to a widget’s template parameters

So far so good, but we haven’t told our new widget how it should be rendered. Given we’ve rendered the nested widgets already and added them to the template parameters for each choice, we can let the multiple choice widget take care of how to render everything together.

Delegating rendering to the multiple choice widget itself

Which is as simple as delegating our NestingMultipleChoice's render function to that on our member MultipleChoice widget.

Building the Nesting Checkboxes widget

So now we have a bunch of common behaviour in NestingMultipleChoice, let’s put that to use by actually building a nesting checkboxes widget.

First cut of a working checkboxes widget

Which is nearly but not quite enough. The provided Checkbox widget calls its own templateChoices function to add them to its template, whereas our new nested multiple widget needs to use the one from NestingMultipleChoice. So we can override Checkbox's render function to call the right one.

Our checkboxes widget now with the special behaviour it needs when rendering itself

Then, finally, we need to create a template for NestingCheckboxes which includes the new parameter for the rendered nested widget.

A modified checkboxes mustache template showing how to output the additional rendered nested widgets

Putting it all together

Whilst we have a working checkbox that can take nested widgets, to build the example from the top of this article we need to do one more thing — make the input text field appear inset with a grey border. Our GOV.UK theme already has the styles to do this, so it’s a matter of using a different layout template with the existing Input widget to use different css class names on the right elements.

A helper function to declare an Input widget with a different layout template

Then, finally, in our form definition we can use our shiny new nesting checkboxes widget. In fact, this is the very code used to build the form captured at the top of this article.

The definition of the working form built when writing this blog, demonstrating how to use our new NestingCheckboxes, as well as dynamically show/hide an input field depending on which option is selected

In conclusion, we’ve demonstrated how relatively complex UI behaviour can be built up quickly by re-using existing behaviours and widgets in UX Forms. The way that a form’s sections are composed of Widgets whose behaviour is, in turn, built up by mixing in traits provided by the platform, demonstrates the real flexibility and power of UX Forms. And then, for the rare cases where the built-in behaviour is not enough, form authors can even override specific widget behaviour to get exactly the functionality they need.

This is what we mean by Complete control. Instant insight.

The finished section showing a NestingCheckboxes widget containing two input widgets

Want to know more?

Come have a look around https://uxforms.com, follow UX Forms on twitter, or email us at hello@uxforms.com and see how we can make your forms better, together.