Or why full automatic forms generators are often a fake good idea, and what to use instead.
But first things first, why do we use form generators in the first place ?
Forms take more time to develop than expected
Let's say you want to build a login/password form. 2 input fields + a submit button. Seems easy and looks like it will take 5 min.
But experienced developers learned the hard way that forms hide some complexity :
- Field validation
- Error messages
- Layout problems
If you need a lot of forms in your application, they can be tedious to build. To tackle this problem, some developer's immediate reaction is to use a form generator. There are plenty of them, in every existing language.
Automatic form generators : the fake good idea
Form generators come with a handful of promises :
- Just configuration, no HTML/CSS/JS/Java to write
- The code base will become "cleaner"
- Compatible with any tool/framework/library
- Fully configurable
At the beginning, everything seems easy to use, and the demo works well.
Then you decide to use it in your project, on real use cases.
Then, you discover problems :
- A text field is nice, but I need a text area instead
- But I need a very special component for this field, like a color picker
- But here, I want to place components differently
- But here, I want to add a "special offer" tag
- But I need to react to a value change in this component
- But I need to show a different component if this field has that value.
And their usual solutions :
- You have an option for that
- You can use a design pattern provide your own component
- You can override the layout with CSS
- You can use a factory/builder/adapter in order to handle this very special use case
- You can provide a callback
- We developed a custom dynamic expression language to handle this use case
In the end, you realize that, for complex use cases, it would have been easier to just use the native API
A form generator that aims at handling every use case is bound to become more complex than the problem it was supposed to solve.
You did not want to write code, but your configuration became the code
Do you remember when you just wrote the form by hand, and were able to do exactly what you wanted without dealing with tons of options ?
With a full automatic form generator, you still end up coding the form manually. You just code with the generator options instead of the native API, but you actually finish the code manually.
Configuring form generators takes time, and if you have less than < 10 forms in your app... just write them by hand.
Or...
Code generators to the rescue
Use a code generator instead. What is the difference ? You generate some code then copy-paste this code in the project. The project does not contain the form generator anymore, only the generated code.
Then, you can finish the code manually, adapt the structure as you wish and use the native API for complex use cases.
Also, as the complexity comes from the intention to handle every case... what if you just generated some code that is only 90% right ?
A code that is only 90% accurate ? Really ?
As you don't need to handle every use case anymore, the complexity falls dramatically. With a rule like "1 field = 1 component", you can build yourself a code generator that will handle 90% of the use cases in only ~100 lines of code.
And as you code the generator yourself, you can decide what you want to automatize or not and fully customize the generated code to your good taste.
I show you how to do that, and I provide a boilerplate
Build your own code generator
This part shows one possible way to build a custom code generator. I provide a working example here.
It generates a basic Angular form with 2 templates :
- One template for the HTML code
- One template for the TypeScript code
It is not much, but enough to understand the concept.
The code generator consists in 3 steps :
- Describe the form fields
- Enrich the model with variables that will be useful in the templates
- Apply the enriched model on various templates
1) Describe the fields
The first thing you need is a place to describe the fields. As it will be your code generator and your templates, just think about the info that you will need when writing the templates.
export const input = {
fields:
[
{
name: "firstName",
component: "TEXTFIELD",
required: true,
placeholder: "John"
},
{
name: "lastName",
component: "TEXTFIELD",
required: true,
placeholder: "Doe"
},
[...]
]
}
2) Enrich the model
From this point, the variable name firstName
is useful, but you will probably need to have it in different cases : FIRST_NAME
, first-name
, First Name
etc. This can be automatized with a simple for loop in order to obtain that :
{
"fields": [
{
"name": "firstName",
"component": "TEXTFIELD",
"required": true,
"placeholder": "John",
"nameCamelCase": "firstName",
"nameUpperCase": "FIRST_NAME",
"nameSnakeCase": "first_name",
"nameCapitalized": "FirstName",
"nameKebabCase": "first-name",
"nameLabel": "First Name"
},
{
"name": "lastName",
"component": "TEXTFIELD",
"required": true,
"placeholder": "Doe",
"nameCamelCase": "lastName",
"nameUpperCase": "LAST_NAME",
"nameSnakeCase": "last_name",
"nameCapitalized": "LastName",
"nameKebabCase": "last-name",
"nameLabel": "Last Name"
},
3) Apply it to templates
For this part, you have to choose a template engine. I chose ejs because it has the required features : conditions and loops. Also, it is quite standard and well documented
The code is as simple as
console.log('Loading template ' + templatePath);
const template = readFileSync(templatePath, 'utf-8');
console.log('Compiling template');
const compiledTemplate = ejs.compile(template);
console.log('Applying template');
const result = compiledTemplate(enrichedInput);
And here is what the template looks like :
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
<% fields.forEach(function(field){ %>
<div>
<%_ if (field.component === 'TEXTFIELD') { -%>
<mat-form-field>
<mat-label><%= field.nameLabel %></mat-label>
<input matInput [formControl]="formControl<%= field.nameCapitalized%>"<% if (field.required === true) { %> placeholder="<%= field.placeholder %>"<% } %>>
</mat-form-field>
<%_ } -%>
<%_ if (field.component === 'TEXTAREA') { -%>
<mat-form-field>
<mat-label><%= field.nameLabel %></mat-label>
<textarea matInput [formControl]="formControl<%= field.nameCapitalized%>"<% if (field.required === true) { %> placeholder="<%= field.placeholder %>"<% } %>></textarea>
</mat-form-field>
<%_ } -%>
Put everything together
The complete program works like that :
- Read the list of form fields from the input file
- Enrich the model with various string cases
- For each template of the
templates
directory :- Apply the enriched model on the template
- Open the template in your favorite text editor
For me, it opens the generated files in gedit, ready to be copy-pasted to the project !
Conclusion
By taking back the control on the generated code, you can have 90% of the code automatically generated while still being able to fully customize the output.
Bonus : using the same strategy, it is possible, from a given class to generate most of the files for the frontend AND the backend part of the app that are more or less always the sames:
- DAO
- Service
- Controller
- Model
- View
- Presenter
- various pieces of configuration to add in those files that you systematically forget.
I applied this strategy with success in an app that contained hundreds of forms. The code generator (written in Java at the time) would generate 25 different files for every part of the application : frontend, backend, database, configuration.
Code generators are nothing new and they are incredibly efficient. Their power should not be forgotten
Top comments (0)