Form layout customization

This is the official documentation of the @forestadmin/agent Node.js agent.

Form layout

The form layout feature lets you organize your fields in pages or rows, and add separators or html blocks. This is especially useful if you have many fields to display, and you want to break down your action form into more manageable chunks !

Theses form elements are available since the version 1.49.0 of the agent.

Layout items

NameNested elementsDescription

None

Allow to add a horizontal separator between two form elements

None

Allow to show HTML content

Fields

Allow to put two (and only two) fields (and not layout elements) in a row

Fields & layouts elements (but no pages)

General properties

Some layouts items will have options, but here are the common properties to all the layout elements

NameUsageExpected valueDescription

type

required

Layout

component

required

Separator, Row, HtmlBlock, or Page

The layout component

if

optional

Separator element

NameUsageExpected valueDescription

component

required

"Separator"

To enable this component

This item doesn't have specific options.

Example:

agent.customizeCollection('customer', collection => {
  collection.addAction("What's your name", {
    scope: 'Single',
    form: [
      { type: 'String', label: 'firstName' },
      { type: 'Layout', component: 'Separator' },
      { type: 'String', label: 'lastName' },
    ],
    execute: async (context, resultBuilder) => {
      resultBuilder.success();
    },
  });
});

HTML Block element

NameUsageExpected valueDescription

component

required

"HtmlBlock"

To enable this component

content

required

This is the HTML content to show

Example:

agent.customizeCollection('customer', collection => {
  collection.addAction('Boring form', {
    scope: 'Global',
    form: [
      {
        type: 'String',
        label: 'firstName',
        defaultValue: ctx => ctx.caller.firstName,
      },
      {
        type: 'String',
        label: 'lastName',
        defaultValue: ctx => ctx.caller.lastName,
      },
      {
        type: 'Layout',
        component: 'HtmlBlock',
        content: ctx => `
<div style="text-align:center;">
    <p>
        <strong>Hi ${ctx.formValues.firstName} ${ctx.formValues.lastName}</strong>,
        <br/>here you can put
        <strong style="color: red;">all the html</strong> you want.
    </p>
</div>
<div style="display: flex; flex-flow: row wrap; justify-content: space-around;">
    <a href="https://www.w3schools.com" target="_blank">
        <img src="https://www.w3schools.com/html/w3schools.jpg">
    </a>
    <iframe src="https://www.youtube.com/embed/xHPKuu9-yyw?autoplay=1&mute=1"></iframe>
</div>`,
      },
    ],
    execute: async (context, resultBuilder) => {
      resultBuilder.success();
    },
  });
});

Row element

⚠️ This element is designed to work with only two inner fields. You can control the display of each field using the if property. If only one field is displayed, it will occupy the entire line. However, if the conditions defined by the if properties result in more than two fields being displayed, only the first two will be shown. If there is nothing inside, it will be removed.

NameUsageExpected valueDescription

component

required

"Row"

To enable this component

fields

required

Example:

agent.customizeCollection('customer', collection => {
  collection.addAction('Personal form', {
    scope: 'Global',
    form: [
      {
        type: 'Layout',
        component: 'Row',
        fields: [
          {
            label: 'gender',
            type: 'Enum',
            enumValues: ['M', 'F', 'other'],
          },
          {
            label: 'specify',
            description: 'you may specify here'
            type: 'String',
            if: async ctx => ctx.formValues?.gender === 'other',
          },
        ],
      },
    ],
    execute: async (context, resultBuilder) => {
      resultBuilder.success();
    },
  });
});

Multi pages form

Description

The pages feature is a way to break up your action form into more manageable chunks, by showing only a subset of fields at the same time, and letting the user navigate between the pages.

Limitations

Please note this list of limitations:

  • You cannot mix fields and pages at the root of your form, or put nest a page in a page

  • The next (or previous) button is not clickable on the last (or first) page

  • If you're using if conditions in a page; keep in mind that if all the elements in your page are hidden, the page will automatically be removed. You can avoid this by behavior by adding an unconditional htmlBlock inside your page explaining why is your page empty.

NameUsageExpected valueDescription

component

required

"Page"

To enable this component

elements

required

Array of fields and layout elements

nextButtonLabel

optional

String

The label on the next button

previousButtonLabel

optional

String

The label on the previous button

Example:

agent.customizeCollection('customer', collection => {
  collection.addAction('Create user with address', {
    scope: 'Global',
    form: [
      {
        type: 'Layout',
        component: 'Page',
        nextButtonLabel: 'Go to address',
        elements: [
          { type: 'String', id: 'Firstname', label: 'First name' },
          { type: 'String', id: 'Lastname', label: 'Last name' },
          { type: 'Layout', component: 'Separator' },
          { type: 'Date', id: 'Birthdate', label: 'Birth date' },
        ],
      },
      {
        type: 'Layout',
        component: 'Page',
        previousButtonLabel: 'Go back to identity',
        elements: [
          {
            type: 'Layout',
            component: 'Row',
            fields: [
              { type: 'Number', id: 'StreetNumber', label: 'Street number' },
              { type: 'String', id: 'StreetName', label: 'Street name' },
            ],
          },
          { type: 'Layout', component: 'Separator' },
          { type: 'String', id: 'PostalCode', label: 'Postal code' },
          { type: 'String', label: 'City' },
          { type: 'Layout', component: 'Separator' },
          { type: 'String', label: 'Country' },
        ],
      },
    ],
    execute: async (context, resultBuilder) => {
      // your business logic goes here
      return resultBuilder.success();
    },
  });
});

Last updated