Capture model fields
An important extension point for projects is the ability to add new capture model field types. This allows you to change the forms and UI that is presented to a user when they are contributing to a project. These fields are building blocks that can be used together to create a full capture model.
Currently, the following fields are supported:
- Autocomplete Field - Shows a dropdown combo box with a list of options. The options can be fetched from a remote API.
- Checkbox Field - Shows a single checkbox.
- Checkbox List Field - Shows a list of checkboxes, with optional inline labels and descriptions for each.
- Color Field - Shows a color picker.
- Dropdown Field - Shows a dropdown combo box with a list of options, there is also a compact inline variation
- HTML Field - Shows a rich text editor.
- International Field - Shows a field where a user can select a language, and then enter a value for that language.
- Tagged Text Field - A rich text editor configured to work with Transcription terminology tags.
- Text Field - Shows a single line text input.
There are also some internal field types that are only used by Madoc for it's Admin interface:
- Media explorer - Allows the user to select an image from the media library.
- Canvas explorer - Allows the user to select a single canvas from content in Madoc
- Collection explorer - Allows the user to select a single collection from content in Madoc
- Border Field - Allows the user to draw a border on an image.
Creating a new field
There are a few parts that make up a custom field. These are captured in a "Field specification" object.
Here is the specification for the "Text Field":
import React from 'react';
import { registerField } from '../../../plugin-api/global-store';
import { FieldSpecification } from '../../../types/field-types';
import { TextField, TextFieldProps } from './TextField';
import { TextFieldPreview } from './TextField.preview';
const specification: FieldSpecification<TextFieldProps> = {
label: 'Text field',
type: 'text-field',
description: 'Simple text field for plain text',
Component: TextField,
defaultValue: '',
allowMultiple: true,
maxMultiple: 99,
required: false,
defaultProps: {},
Editor: React.lazy(() => import(/* webpackChunkName: "field-editors" */ './TextField.editor')),
TextPreview: TextFieldPreview,
};
registerField(specification);
export default specification;
A few things to note here:
- The
type
field is used to identify the field - The
Component
field is the React component that will be rendered when the field is used in a project - The
Editor
field is the React component that will be rendered when the field is used in the project editor - The
TextPreview
field is the React component that will be rendered when you view the project output
You can specify some default props, a default value and some other options specific to the field.
The registerField
function is used to register the field with the global store. Internal fields shouldn't be
registered. (registerField
may change in the future).
The Component
has a few props that are passed to it such as value
and setValue
, along with any other props that
you added in your Editor
component.
Here is a simplified version of the TextField
component
export interface TextFieldProps extends BaseField {
id: string;
type: 'text-field';
placeholder?: string;
required?: boolean;
multiline?: boolean;
previewInline?: boolean;
minLines?: number;
value: string;
disabled?: boolean;
}
export const TextField: FieldComponent<TextFieldProps> = ({
value,
id,
placeholder,
minLines,
multiline,
updateValue,
disabled,
required,
}) => {
const { t: tModel } = useModelTranslation();
const tPlaceholder = placeholder ? tModel(placeholder) : ' ';
return (
<StyledFormInputElement
name={id}
id={id}
placeholder={tPlaceholder}
value={value || ''}
disabled={disabled}
required={required}
onChange={e => updateValue(e.currentTarget.value)}
/>
);
};
You can see that the value
is passed in, along with an updateValue
function. This is used to update the value of
the field. The id
is also passed in, which is used to identify the field in the form. The type is also passed in, but
this is not used in the component. The other fields placeholder
, minLines
, multiline
, disabled
and required
are
all custom props that are passed in from the Editor
component.
Here is the Editor
component for the TextField
:
type Props = {
placeholder?: string;
required?: boolean;
multiline?: boolean;
previewInline?: boolean;
minLines?: number;
};
const TextFieldEditor: React.FC<Props> = ({ children, ...props }) => {
return (
<>
<StyledFormField>
<StyledFormLabel>
Placeholder
<Field
as={StyledFormInputElement}
type="text"
name="placeholder"
defaultValue={props.placeholder}
required={false}
/>
</StyledFormLabel>
</StyledFormField>
<StyledFormField>
<StyledFormLabel>
<Field as={StyledCheckbox} type="checkbox" name="multiline" defaultValue={props.multiline} required={false} />
Allow multiline input
</StyledFormLabel>
</StyledFormField>
<StyledFormField>
<StyledFormLabel>
Minimum lines
<Field
as={StyledFormInputElement}
type="number"
name="minLines"
defaultValue={props.minLines}
required={false}
/>
</StyledFormLabel>
</StyledFormField>
<StyledFormField>
<StyledFormLabel>
<Field
as={StyledCheckbox}
type="checkbox"
name="previewInline"
defaultValue={props.previewInline}
required={false}
/>
Preview text as inline (span)
</StyledFormLabel>
</StyledFormField>
</>
);
};
export default TextFieldEditor;
The editor should be a partial HTML Form with name
attributes on the fields. The defaultValue
is used to set the
initial value of the field. When this is displayed, it will be saved on form submission, and the name
and value
of
each field will be saved as a key-value pair in the capture model. They will then be made available to the Component
when it is rendered.