Skip to main content
Version: Current

Foundation Forms

lerna TypeScript

API Docs

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.

Forms

Basic install

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.
{
...
"dependencies": {
"@genesislcap/foundation-forms": "latest"
},
...
}

1. Register components

import { Form } from '@genesislcap/foundation-forms';
...
Form
...

2. Add component to the template

<foundation-form resourceName="EVENT_TRADE_INSERT"></foundation-form>

This should generate a 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.

3. Configure form using UI schema

const sampleUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/QUANTITY",
label: "Quantity",
},
{
type: "Control",
scope: "#/properties/SIDE",
label: "Side",
},
],
};
<foundation-form resourceName="EVENT_TRADE_INSERT" :uischema=${() => sampleUISchema}></foundation-form>

4. Configure form using JSON schema (optional)

Instead of providing resourceName, you can hard-code the JSON schema on the client.

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'],
};
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,
},
},
],
};
<foundation-form :jsonSchema=${() => sampleJsonSchema} :uischema=${() => sampleUISchema}></foundation-form>
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.

5. Pre-fill forms with data (optional)

Use the data attribute, which allows you to pre-fill the form with ready-made information.

const sampleData = {
ISSUER_NAME: 'Some Issuer',
INVIS: 'Invisible value!',
USER: 'JohnDoe',
};
<foundation-form resourceName="EVENT_TRADE_INSERT" :uischema=${() => sampleUISchema} :data=${() => sampleData}></foundation-form>

Filters

Basic install

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.
{
...
"dependencies": {
"@genesislcap/foundation-forms": "latest"
},
...
}

1. Register components

import { Filters } from '@genesislcap/foundation-forms';
...
Filters
...

2. Add component to the template

<foundation-filters resourceName="ALL_TRADES"></foundation-filters>

This should generate a working form based on the JSON schema for that endpoint. The DevTools console will output an autogenerated UI schema that you can use to configure the filters

3. Configure form using UI schema

const sampleUISchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/QUANTITY",
label: "Quantity",
},
{
type: "Control",
scope: "#/properties/SIDE",
label: "Side",
},
],
};
<foundation-filters resourceName="ALL_TRADES" :uischema=${() => sampleUISchema}></foundation-filters>

4. Configure form using JSON schema (optional)

Instead of providing resourceName, you can hard-code the JSON schema on the client.

const sampleJsonSchema = {
type: 'object',
properties: {
INSTRUMENT_ID: {
type: 'string',
minLength: 3,
description: 'kotlin.String',
},
QUANTITY: {
type: 'number',
description: 'kotlin.Double',
},
},
};
const sampleUiSchema = {
type: 'VerticalLayout',
elements: [
{
type: 'Control',
label: 'Instrument ID',
scope: '#/properties/INSTRUMENT_ID',
},
{
type: 'Control',
label: 'Quantity',
scope: '#/properties/QUANTITY',
},
],
};
<foundation-filters :jsonSchema=${() => sampleJsonSchema} :uischema=${() => sampleUISchema}></foundation-filters>
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.

5. An example of synchronizing values with datasource criteria

  <zero-card>
<foundation-filters
resourceName="ALL_USERS"
:value=${sync((x) => x.allUsersfilters)}>
</foundation-filters>
</zero-card>
<zero-grid-pro>
<grid-pro-genesis-datasource
resource-name="ALL_USERS"
criteria=${(x) => x.allUsersfilters}
></grid-pro-genesis-datasource>
</zero-grid-pro>

Advanced customisation

Default layout renderers and examples

This is the default layout for VerticalLayout, which is defined if no uiSchema is specified. This arranges the control elements vertically.

const VerticalUISchema = {
type: 'VerticalLayout',
elements: [
...
],
};

This example arranges the control elements in two columns vertically.

const VerticalColumnsUISchema = {
type: 'LayoutVertical2Columns',
elements: [
...
],
};

This arranges our control elements horizontally.

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.

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",
},
],
},
},
},
],
};
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.

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.

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.

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',
},
],
},
],
},
},
],
};
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'],
},
},
};

Default control renderers and examples

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.

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.

const numberJsonSchema = {
type: "object",
properties: {
PRICE: {
type: "number",
description: "kotlin.Double",
},
},
};

The boolean renderer creates a checkbox-field.

const booleanJsonSchema = {
type: "object",
properties: {
vegetarian: {
type: "boolean",
},
},
};

The connected multiselect renderer creates a multiselect component with options from datasource.

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.

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.

const dateJsonSchema = {
type: "object",
properties: {
tradeDate: {
type: "string",
description: "org.joda.time.DateTime",
},
},
};

Specific renderers for filters and examples

The filter date renderer creates two date-fields with minimum and maximum value.

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.

const numberJsonSchema = {
type: "object",
properties: {
PRICE: {
type: "number",
description: "kotlin.Double",
},
},
};

License

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.

Licensed components

Genesis low-code platform