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
typefield is used to identify the field - The
Componentfield is the React component that will be rendered when the field is used in a project - The
Editorfield is the React component that will be rendered when the field is used in the project editor - The
TextPreviewfield 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.