Form builder
The @fab4m/builder package provides the basic building blocks to create a visual
form builder UI capable of constructing fab4m forms.
Installing
Install @fab4m/builder package.
npm install --save @fab4m/builder
The form builder is built using tailwind css, so you will need to have that installed as well. Refer to their documentation page for information on how to do that.
Set up the form you want to edit
The form builder works with any serialized fab4m form, for example:
import { crateForm, serialize, textField } from "@fab4m/fab4m";
const formToEdit = serialize(
  createForm({
    component: textField({ label: "First component" }),
    second: textField({ label: "Second component" }),
  }),
);
Add the form builder provider context
Place any components that should edit the form within the <FormBuilderProvider> component. This will allow you to use the form builder hooks to manipulate the form.
The form builder context takes in plugins for the different components, widgets and validators you want to be able to work with inside of the form. Plugins for all fab4m core features are provided in the @fab4m/builder package.
an export containing all plugins is provided for convenience.
import { allPlugins, FromBuilderContext } form "@fab4m/builder"
<FormBuilderProvider form={formToEdit} plugins={allPlugins}>
/* You can use any part of the form builder in here. */
</FormBuilderProvider>
The FormComponents component
Start by adding the <FormComponents> component. This will give you a drag and drop interface
where you can drag your components around.
- Code
 - Example
 
import React, { useState } from "react";
import {
  FormComponents,
  FormBuilderProvider,
  allPlugins,
} from "@fab4m/builder";
import { createForm, serialize, textField } from "@fab4m/fab4m";
// The form builder works on the serialized version of the form.
const form = serialize(
  createForm({
    component: textField({ label: "First component" }),
    second: textField({ label: "Second component" }),
  }),
);
export default function FormComponentsExample() {
  const [draft, changeDraft] = useState(form);
  return (
    <FormBuilderProvider
      form={draft}
      formChanged={changeDraft}
      plugins={allPlugins}
    >
      <FormComponents />
    </FormBuilderProvider>
  );
}
Adding actions to each component
In the first step we're just able to drag the components around. The next step is to be able to perform actions on them.
We do this by adding actions to each component:
- Code
 - Example
 
import { FormComponents, FormBuilderProvider } from "@fab4m/builder";
import { createForm, serialize, textField } from "@fab4m/fab4m";
import React, { useState } from "react";
const form = serialize(
  createForm({
    component: textField({ label: "First component" }),
    second: textField({ label: "Second component" }),
  }),
);
export default function FormActionsExample() {
  const [draft, changeDraft] = useState(form);
  return (
    <FormBuilderProvider form={draft} formChanged={changeDraft}>
      <FormComponents
        actions={({ component, removeComponent, updateComponent }) => (
          <>
            <button type="button" onClick={removeComponent}>
              Remove
            </button>
            <button
              type="button"
              onClick={() =>
                updateComponent({ ...component, label: "Changed component" })
              }
            >
              Change component
            </button>
          </>
        )}
      />
    </FormBuilderProvider>
  );
}
Adding new components to the form
The NewComponent component can be used to easily provide a gallery of all availbale components that you can add, and offers the user an option to add them:
- Code
 - Example
 
import React, { useState } from "react";
import {
  FormComponents,
  FormBuilderProvider,
  allPlugins,
  NewComponent,
} from "@fab4m/builder";
import { createForm, serialize } from "@fab4m/fab4m";
const form = serialize(createForm({}));
export default function NewComponentsExample() {
  const [draft, changeDraft] = useState(form);
  return (
    <FormBuilderProvider
      form={draft}
      formChanged={changeDraft}
      plugins={allPlugins}
    >
      <FormComponents />
      <h2>Add new component</h2>
      <NewComponent
        attributes={{
          name: `component_${draft.components.length}`,
          label: `Component ${draft.components.length + 1}`,
        }}
      />
    </FormBuilderProvider>
  );
}
Editing components
You can use the EditFormComponent to be able to edit any component in the form:
- Code
 - Example
 
import React, { useState } from "react";
import {
  FormComponents,
  FormBuilderProvider,
  allPlugins,
  EditFormComponent,
} from "@fab4m/builder";
import { createForm, serialize, textField } from "@fab4m/fab4m";
const form = serialize(
  createForm({
    item: textField({ label: "Item" }),
  }),
);
export default function NewComponentsExample() {
  const [draft, changeDraft] = useState(form);
  const [editComponent, changeEditComponent] = useState<null | string>(null);
  return (
    <FormBuilderProvider
      form={draft}
      formChanged={changeDraft}
      plugins={allPlugins}
    >
      <FormComponents
        actions={(props) => (
          <button onClick={() => changeEditComponent(props.formKey)}>
            Edit
          </button>
        )}
      />
      {editComponent ? (
        <dialog open={true} className="backdrop:bg-gray-50">
          <EditFormComponent
            componentKey={editComponent}
            componentSaved={() => changeEditComponent(null)}
          />
        </dialog>
      ) : null}
    </FormBuilderProvider>
  );
}
Preview the form
The form builder package provides a FormPreview component that can be used to easily preview the form:
- Code
 - Example
 
import React, { useState } from "react";
import {
  FormComponents,
  FormBuilderProvider,
  allPlugins,
  FormPreview,
} from "@fab4m/builder";
import { basic, createForm, serialize, textField } from "@fab4m/fab4m";
const form = serialize(
  createForm({
    component: textField({ label: "First component" }),
    second: textField({ label: "Second component" }),
  }),
);
export default function FormPreviewExample() {
  const [draft, changeDraft] = useState(form);
  return (
    <FormBuilderProvider
      form={draft}
      formChanged={changeDraft}
      plugins={allPlugins}
    >
      <FormComponents />
      <h2>Example</h2>
      <FormPreview theme={basic} />
    </FormBuilderProvider>
  );
}
Full example
The example below combines all the bits below to make a complete form builder.
- Code
 - Example
 
import React, { useState } from "react";
import {
  FormComponents,
  FormBuilderProvider,
  allPlugins,
  NewComponent,
  EditFormComponent,
} from "@fab4m/builder";
import { createForm, serialize } from "@fab4m/fab4m";
const form = serialize(createForm());
export default function FullExample() {
  const [draft, changeDraft] = useState(form);
  const [currentKey, changeCurrentKey] = useState<null | string>(null);
  return (
    <FormBuilderProvider
      form={draft}
      formChanged={changeDraft}
      plugins={allPlugins}
    >
      <FormComponents
        actions={(props) => (
          <button type="button" onClick={() => changeCurrentKey(props.formKey)}>
            Edit
          </button>
        )}
      />
      {currentKey ? (
        <div className="border bg-slate-100 p-4">
          <EditFormComponent
            componentKey={currentKey}
            componentSaved={() => changeCurrentKey(null)}
          />
        </div>
      ) : (
        <div className="border bg-slate-100 p-4">
          <NewComponent
            attributes={{
              name: `component_${draft.components.length}`,
              label: `Component ${draft.components.length + 1}`,
            }}
          />
        </div>
      )}
    </FormBuilderProvider>
  );
}