Genesis Foundation Forms
npm install @genesislcap/foundation-forms

foundation-forms is a library for efficiently building complex forms and filters at scale.
Foundation forms is defined by using two schemata:
- resourceName/jsonSchema defines the underlying data to be shown in the UI (objects, properties, and their types).
- uiSchema defines how this data is rendered as a form, e.g. the order of controls, their visibility, and the layout.
``ts`
import { Form } from '@genesislcap/foundation-forms';
...
Form
...
`html`
This should generate working form based on the JSON schema for that endpoint. The DevTools console will output autogenerated UI schema that you can use to configure the form.
`ts`
const sampleUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/QUANTITY",
label: "Quantity",
},
{
type: "Control",
scope: "#/properties/SIDE",
label: "Side",
},
],
};
`html`
Instead of providing resourceName, you can hard-code the JSON schema on the client.
`ts`
const sampleJsonSchema = {
type: 'object',
properties: {
ISSUER_NAME: {
type: 'string',
minLength: 3,
description: 'kotlin.String',
},
PRICE: {
type: 'number',
description: 'kotlin.Double',
},
MAIN_CONTACT: {
type: 'string',
pattern: '^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$',
description: 'kotlin.String',
},
PASSWORD: {
type: 'string',
description: 'kotlin.String',
},
},
additionalProperties: false,
required: ['ISSUER_NAME', 'MAIN_CONTACT'],
};
`ts`
const sampleUiSchema = {
type: 'VerticalLayout',
elements: [
{
type: 'Control',
label: 'Issuer Name',
scope: '#/properties/ISSUER_NAME',
},
{
type: 'Control',
label: 'Phone',
scope: '#/properties/MAIN_CONTACT',
},
{
type: 'Control',
label: 'Price',
scope: '#/properties/PRICE',
},
{
type: 'Control',
scope: '#/properties/COUNTERPARTY',
options: {
allOptionsResourceName: 'COUNTERPARTY',
valueField: 'COUNTERPARTY_ID',
labelField: 'COUNTERPARTY_ID',
datasourceConfig: {
request: {
COUNTERPARTY_ID: 'ACME',
},
},
},
},
{
type: 'Control',
label: 'Password',
scope: '#/properties/PASSWORD',
options: {
isPassword: true,
},
},
],
};
`html`
:::info
Use this when you want to avoid fetching metadata from the server, but be aware that it could get out of sync if metadata changes on the server.
:::
Use the data attribute, which allows you to pre-fill the form with ready-made information.
`ts`
const sampleData = {
ISSUER_NAME: 'Some Issuer',
INVIS: 'Invisible value!',
USER: 'JohnDoe',
};
`html`
`ts`
import { Filters } from '@genesislcap/foundation-forms';
...
Filters
...
`html`
This should generate a working form based on the JSON schema for that endpoint.UI schema
The DevTools console will output an autogenerated that you can use to configure the filters
`ts`
const sampleUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/QUANTITY",
label: "Quantity",
},
{
type: "Control",
scope: "#/properties/SIDE",
label: "Side",
},
],
};
`html`
Instead of providing resourceName, you can hard-code the JSON schema on the client.
`ts`
const sampleJsonSchema = {
type: 'object',
properties: {
INSTRUMENT_ID: {
type: 'string',
minLength: 3,
description: 'kotlin.String',
},
QUANTITY: {
type: 'number',
description: 'kotlin.Double',
},
},
};
`ts`
const sampleUiSchema = {
type: 'VerticalLayout',
elements: [
{
type: 'Control',
label: 'Instrument ID',
scope: '#/properties/INSTRUMENT_ID',
},
{
type: 'Control',
label: 'Quantity',
scope: '#/properties/QUANTITY',
},
],
};
`html`
:::info
Use this when you want to avoid fetching metadata from the server, but be aware that it could get out of sync if metadata changes on the server.
:::
`html`
:value=${sync((x) => x.allUsersfilters)}>
criteria=${(x) => x.allUsersfilters}
>
This is the default layout for VerticalLayout, which is defined if no uiSchema is specified. This arranges the control elements vertically.
`ts`
const VerticalUISchema = {
type: 'VerticalLayout',
elements: [
...
],
};
This example arranges the control elements in two columns vertically.
`ts`
const VerticalColumnsUISchema = {
type: 'LayoutVertical2Columns',
elements: [
...
],
};
This arranges our control elements horizontally.
`ts`
const horizontalUISchema = {
type: 'HorizontalLayout',
elements: [
...
],
};
An array Layout enables you to create a dynamic form with the ability to add, for example, multiple users.
It is more complicated when it comes to customisation, because it needs proper jsonSchema and uiSchema.
`ts`
const arrayUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/users",
options: {
childUiSchema: {
type: "HorizontalLayout",
elements: [
{
type: "Control",
scope: "#/properties/firstname",
label: "First Name",
},
{
type: "Control",
scope: "#/properties/lastname",
label: "Last Name",
},
{
type: "Control",
scope: "#/properties/email",
label: "Email",
},
],
},
},
},
],
};
`ts`
const arrayJsonSchema = {
type: "object",
properties: {
users: {
type: "array",
items: {
type: "object",
title: "Users",
properties: {
firstname: {
type: "string",
},
lastname: {
type: "string",
},
email: {
type: "string",
format: "email",
},
},
},
},
},
};
Categorization layout enables you to create more complex forms that can be divided into appropriate categories (for example, personal information and address), which will be in separate tabs.
`ts`
const categoryUISchema = {
type: "Categorization",
elements: [
{
type: "Control",
scope: "#/properties/basic",
label: "Personal information",
options: {
childElements: [
{
type: "HorizontalLayout",
elements: [
{
type: "Control",
scope: "#/properties/firstName",
},
{
type: "Control",
scope: "#/properties/secondName",
},
],
},
],
},
},
{
type: "Control",
label: "Address",
scope: "#/properties/address",
options: {
childElements: [
{
type: "HorizontalLayout",
elements: [
{
type: "Control",
scope: "#/properties/address/properties/street",
},
{
type: "Control",
scope: "#/properties/address/properties/streetNumber",
},
],
},
],
},
},
],
};
Group layout is similar to Categorization layout; it divides forms into groups. These are visible on the same tab, but they are separated from each other by their own labels.
`ts`
const groupUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Group",
label: "Person",
scope: "#/properties/person",
options: {
childElements: [
{
type: "LayoutVertical2Columns",
elements: [
{
type: "Control",
label: "First Name",
scope: "#/properties/person/properties/firstName",
},
{
type: "Control",
scope: "#/properties/person/properties/lastName",
},
],
},
],
},
},
{
type: "Group",
label: "Address",
scope: "#/properties/address/",
options: {
childElements: [
{
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/person/properties/shippingAddress",
},
{
type: "Control",
scope: "#/properties/address/properties/street",
},
],
},
],
},
},
],
};
Stepper layout enables you to create more complex forms that can be divided into appropriate groups (for example, personal information and address), which will be in separate steps.
It is more complicated when it comes to customisation, because it needs proper jsonSchema and uiSchema so that validation and data saving work properly.
:::info
Remember to add a hide-submit-button attribute to foundation-forms, because in this case, submit is built directly into stepper-layout.
:::
`ts`
const uiSchemaStepper = {
type: 'Stepper',
elements: [
{
type: 'Control',
scope: '#/properties/person',
label: 'Entity',
options: {
childElements: [
{
type: 'HorizontalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/person/properties/firstName',
},
{
type: 'Control',
scope: '#/properties/person/properties/secondName',
},
],
},
{
type: 'HorizontalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/person/properties/birthDate',
},
{
type: 'Control',
scope: '#/properties/person/properties/nationality',
},
],
},
],
},
},
{
type: 'Control',
label: 'Doc',
scope: '#/properties/address',
options: {
childElements: [
{
type: 'HorizontalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/address/properties/street',
},
{
type: 'Control',
scope: '#/properties/address/properties/streetNumber',
},
],
},
{
type: 'HorizontalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/address/properties/city',
},
{
type: 'Control',
scope: '#/properties/address/properties/postalCode',
},
],
},
],
},
},
{
type: 'Control',
label: 'Primary doc',
scope: '#/properties/vegetarianOptions',
options: {
childElements: [
{
type: 'VerticalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/vegetarianOptions/properties/favoriteVegetable',
},
{
type: 'Control',
scope: '#/properties/vegetarianOptions/properties/otherFavoriteVegetable',
},
],
},
],
},
},
],
};
`ts`
const jsonSchemaStepper = {
type: 'object',
properties: {
person: {
type: 'object',
properties: {
firstName: {
type: 'string',
minLength: 3,
description: 'Please enter your first name',
},
secondName: {
type: 'string',
minLength: 3,
description: 'Please enter your second name',
},
birthDate: {
type: 'string',
format: 'date',
description: 'Please enter your birth date.',
},
nationality: {
type: 'string',
description: 'Please enter your nationality.',
},
},
required: ['firstName', 'secondName'],
},
address: {
type: 'object',
properties: {
street: {
type: 'string',
},
streetNumber: {
type: 'string',
},
city: {
type: 'string',
},
postalCode: {
type: 'string',
maxLength: 5,
},
},
required: ['postalCode'],
},
vegetarianOptions: {
type: 'object',
properties: {
favoriteVegetable: {
type: 'string',
enum: ['Tomato', 'Potato', 'Salad', 'Aubergine', 'Cucumber', 'Other'],
},
otherFavoriteVegetable: {
type: 'string',
},
},
required: ['otherFavoriteVegetable'],
},
},
};
Most renderers are defined directly in the jsonSchema that comes from the server, but there are also those that you can add via uiSchema.
String renderer is the default renderer, which creates a text-field.
`ts`
const stringJsonSchema = {
type: "object",
properties: {
ISSUER_NAME: {
type: "string",
minLength: 3,
description: "kotlin.String",
},
USER: {
type: "string",
description: "kotlin.String",
},
MAIN_CONTACT: {
type: "string",
pattern: "^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$",
description: "kotlin.String",
},
},
};
The number renderer creates a number-field.
`ts`
const numberJsonSchema = {
type: "object",
properties: {
PRICE: {
type: "number",
description: "kotlin.Double",
},
},
};
The boolean renderer creates a checkbox-field.
`ts`
const booleanJsonSchema = {
type: "object",
properties: {
vegetarian: {
type: "boolean",
},
},
};
The connected multiselect renderer creates a multiselect component with options from datasource.
`ts`
const connectedMultiselectUISchema = {
type: "HorizontalLayout",
elements: [
{
type: 'Control',
label: 'Rights',
scope: '#/properties/RIGHT_CODES',
options: {
allOptionsResourceName: 'RIGHT',
valueField: 'CODE',
labelField: 'CODE',
},
},
{
type: 'Control',
label: 'Users',
scope: '#/properties/USER_NAMES',
options: {
allOptionsResourceName: 'USER',
valueField: 'USER_NAME',
labelField: 'USER_NAME',
},
},
],
};
Connected Select renderer is a select renderer that creates a select component with options.
`ts`
const connectedSelectUISchema = {
type: "HorizontalLayout",
elements: [
{
type: "Control",
scope: "#/properties/COUNTERPARTY_ID",
options: {
data: CounterpartyOptions,
valueField: "value",
labelField: "name",
},
label: "Counterparty",
},
{
type: "Control",
scope: "#/properties/INSTRUMENT_ID",
options: {
data: InstrumentOptions,
valueField: "value",
labelField: "name",
},
label: "Instrument",
},
],
};
The date renderer creates a date-field.
`ts`
const dateJsonSchema = {
type: "object",
properties: {
tradeDate: {
type: "string",
description: "org.joda.time.DateTime",
},
},
};
The filter date renderer creates two date-fields with minimum and maximum value.
`ts`
const dateJsonSchema = {
type: "object",
properties: {
tradeDate: {
type: "string",
description: "org.joda.time.DateTime",
},
},
};
The filter number renderer creates two number-fields with minimum and maximum value.
`ts`
const numberJsonSchema = {
type: "object",
properties: {
PRICE: {
type: "number",
description: "kotlin.Double",
},
},
};
To enable this module in your application, follow the steps below.
1. Add @genesislcap/foundation-forms as a dependency in your package.json file. Whenever you change the dependencies of your project, ensure you run the $ npm run bootstrap command again. You can find more information in the package.json basics page.
`json``
{
...
"dependencies": {
...
"@genesislcap/foundation-forms": "latest"
...
},
...
}
Note: this project provides front-end dependencies and uses licensed components listed in the next section; thus, licenses for those components are required during development. Contact Genesis Global for more details.