Generating a step-by-step form from a set of configurations

Ivan Diaz
Ergeon

--

Context

At Ergeon, we built a web-based 3D modelling and quoting tool to help our customers plan, design and budget for their fence project. Customers go to our website, where they can personalize their desired fence configuration and preview how it would look in 3D. There’s over a million possible configurations to build a fence, so to facilitate use of the tool we created two editors: a “simple” editor and an “advanced” one.

Ergeon’s simple fence design tool

The simple editor allows quick navigation through the most popular set of configurations (which most customers want), and includes a minimal set of customization options so customers aren’t overwhelmed with choices.

The advanced editor allows for much more detailed customization options, hence it is geared towards power users who are willing to explore various fence builds and understand their corresponding parameters. The UI is more complex and can be difficult to navigate or more time consuming for someone who is just interested in a standard fence.

Since our tools address the needs of a broad audience, we decided to support both options. In this blog post, I describe the technical implementation of the simple editor, which poses the challenge of helping the user navigate a complex decision space via an intuitive interface.

Building a simplified editor

The idea for the simple editor was to create a step-by-step form where you select a fence part in each step.

Initially, we created a basic form with fewer parts and options; but this became difficult to scale as you expand the number of permutations available, while respecting real-life “as-built” fence configurations.

For example, maybe “6-feet picture-frame” and “3-feet nail-up” are in our list of parts and options, but “3-feet picture-frame” and “6-feet nail-up” are not.

If we have a static form, we would allow “3-feet”/”6-feet” options when selecting “height” and “nail-up”/”picture-frame” when selecting “style”.

So, we tried to create a dynamic form where options vary using conditionals, but, as I mentioned before, this method didn’t have full control of what configurations were valid / possible to build using the form.

Our solution for this problem was to create a script that will generate, based on a list of configs, a step-by-step form that would be ensured to only produce valid configs.

The problem

Our problem goes as follows:

Given a set of configurations (set of sets of key-value pairs) and a key order, produce a step-by-step form that will only produce configurations from that set.

A configuration is a set of key-value pairs, for example:

const configurations = [
["height:10", "color:red"],
["height:8", "color:blue"],
["height:10", "color:green],
];
const selectedUntilNow = ["height:10"];
getNextOptions(selectedUntilNow, configurations);
// next options should be 'color:red' and 'color:green'

This is practically an autocomplete suggestion problem. You can think of it as writing letters while a list of possible options from a set is shown. This is basically what Google Search does when you type on the search bar.

It can actually be reduced to a prefix matching problem, as we will see later: `Find all strings that match a given prefix`

The Algorithm

Let’s suppose we have a cars agency with a set of cars to be included in our step-by-step form.

The idea is to transform the configurations in an ordered string. You can do something similar to a query string in a GET request.

const availableCars = [
[{color: ‘red’}, {transmission: ‘automatic’}, {style: ‘coupe’}, {condition: ‘used’}],
[{color: ‘red’}, {transmission: ‘manual’}, {style: ‘coupe’}, {condition: ‘used’}],
[{color: ‘black’}, {transmission: ‘automatic’}, {style: ‘coupe’}, {condition: ‘new’}],
];
// transforms to:
const availableCars = [
‘color=red&transmission=automatic&style=coupe&condition=used’,
‘color=red&transmission=manual&style=coupe&condition=used’,
‘color=black&transmission=automatic&style=coupe&condition=new’,
];

This is assuming your desired key order is

[color,transmission,style,condition]

Now, given a prefix, we want to find all matching configs (cars in the example).

If already have a color selected (let’s suppose “red”), my prefix will be

‘color=red’

And the matching configs are

‘color=red&transmission=automatic&style=coupe&condition=used’,
‘color=red&transmission=manual&style=coupe&condition=used’,

There’s many ways we can do this check. The best way to do it is to use a Trie data structure.

Trie data structure

The above graphic shows the internal structure of a Trie when you add the words bear, bell, bid, bull, buy, sell, stock and stop to it.

It basically stores one node per word character. If you add a word that has a prefix that matches an existing word, it will use the same nodes for that prefix part, making it very memory efficient.

It’s not hard to image search in this case: as you type characters, you move through the graph and in each node you land, you have a list of possible options.

In our case, what we will do is that we store all the possible strings and then make queries to it to check if a given prefix exists and which are the possible next options.

Our nodes, instead of characters, will be strings of the form `attr=value`.

Since all the configs (cars) have the same number of attributes, each level of the Trie will represent a different area.

Practical Example

We have created a repository containing an example code of how it works for a fence building example.

Example step-by-step form

On each step, a pointer moves through the Trie nodes and checks the possible options, generating a different fence preview on each step.

You can check how it works on https://ergeon.github.io/trie-example/index.html

Also check the code on https://github.com/ergeon/trie-example

Final considerations

So, there’s two counter points to be made here:

  1. Performance: Use a Trie may be an overkill for this case. It may actually be enough to do some brute-force matching on config lists, and it will work with the same results. If configs are very long and there’s a lot of them, we may have a case for using Trie; but aging, this is probably meant to be used in a reduced number of configs.
  2. Attribute Order: The order of steps might be important. The user will be able to generate all the configs with any step order, but the order depends on the application purpose and it’s an UX decision.

Conclusion

Using a generated step-by-step form is very useful for when you have a reduced number of configurations and you want to present them in a nice, friendly way. It allows us to eliminate the need to consider special corner cases with invalid configurations and have full control of what results our users can get from the UI tool.

If you liked this post, please share it with your friends.
Be sure to follow us
here to be notified when a new post is published.

--

--

Ivan Diaz
Ergeon
Writer for

Computer scientist. Passionate about competitive programming, open source, React and blockchain technology.