Generate an HTML form from an object in TypeScript
In the previous post about TypeScript decorators, I used decorators to quickly add validation rules. In this post, we'll use other features of the decorators. TypeScript can automatically add the type of the property to the metadata. Let's see how we can use this information and other custom attributes to automatically generate a form from a class.
The idea is to be able to use the following code:
class Person {
@editable()
@displayName("First Name")
public firstName: string;
@editable()
@displayName("Last Name")
public lastName: string;
@editable()
public dateOfBirth: Date;
@editable()
public size: number;
}
var author = new Person();
author.firstName = 'Gérald';
generateForm(document.body, author);
Generated form from the Person class
Let's configure TypeScript to enable decorators and metadata.
experimentalDecorators
allows using the decorators in your code.emitDecoratorMetadata
instructs the compiler to add a metadatadesign:type
for each property with a decorator.
The project.json
should contain the 2 attributes:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
}
}
The compiler will translate the TypeScript class to JavaScript, and decorates the properties with the required
, displayName
and design types. Here's an extract of the generated code:
Person = /** @class */ (function () {
function Person() {
}
__decorate([
editable(),
displayName("First Name"),
__metadata("design:type", String) // Added by emitDecoratorMetadata: true
], Person.prototype, "firstName", void 0);
// ...
return Person;
}());
Let's declare the editable
and displayName
decorators. You can look at the previous post to get a better understanding of TypeScript decorators.
function editable(target: any, propertyKey: string) {
let properties: string[] = Reflect.getMetadata("editableProperties", target) || [];
if (properties.indexOf(propertyKey) < 0) {
properties.push(propertyKey);
}
Reflect.defineMetadata("editableProperties", properties, target);
}
function displayName(name: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata("displayName", name, target);
}
}
Now, you can use the metadata to generate the form. You have to find the editable properties, get their display name to create the label, and their type to create the right input type. For instance, you have to use <input type="number"/>
for a property of type number, and <input type="checkbox"/>
for a property of type boolean. Then, you have to bind the inputs to the model, so changes in the UI are propagated to the model. The input
event should be ok with that.
function generateForm(parentElement: HTMLElement, obj: any) {
const form = document.createElement("form");
let properties: string[] = Reflect.getMetadata("editableProperties", obj) || [];
for (let property of properties) {
const dataType = Reflect.getMetadata("design:type", obj, property) || property;
const displayName = Reflect.getMetadata("displayName", obj, property) || property;
// create the label
const label = document.createElement("label");
label.textContent = displayName;
label.htmlFor = property;
form.appendChild(label);
// Create the input
const input = document.createElement("input");
input.id = property;
if (dataType === String) {
input.type = "text";
input.addEventListener("input", e => obj[property] = input.value);
} else if (dataType === Date) {
input.type = "date";
input.addEventListener("input", e => obj[property] = input.valueAsDate);
} else if (dataType === Number) {
input.type = "number";
input.addEventListener("input", e => obj[property] = input.valueAsNumber);
} else if (dataType === Boolean) {
input.type = "checkbox";
input.addEventListener("input", e => obj[property] = input.checked);
}
form.appendChild(input);
}
parentElement.appendChild(form);
}
You can now use the code at the beginning of the post, and it should work:
var author = new Person();
author.firstName = 'Gérald';
generateForm(document.body, author);
#Conclusion
Decorators and metadata are very similar to what C# provides out of the box with attributes. It allows you to enrich the code with additional information, and get this information at runtime. In the previous post, I created a generic validation system. Today, I write a generic form generator in a few lines of code. They are lots of possibilities! If you are familiar with reflection in C#, I think you already have hundreds of ideas 😃
Do you have a question or a suggestion about this post? Contact me!