Rich text editor control for form engine

Hey :slight_smile: We are working a rich text editor which will replace a text input in some forms when the proper annotation is added.

export function FormEngineEditorElement(
    props: FormModelMap.FormModelComponentProps<FormModel.Control>
): React.ReactElement {
    const activity = (props.config.renderOptions as any).activity;
    const dataHolder = Activity.findDefaultDataHolder(activity);
    const data = (dataHolder?.data as any)?.cddState?.cachedCdd?.cdd;
    const locale = useSelector(localeSelector);

    const elementPath = props.modelElement.elementPath;
    const initialValue =
        elementPath.reduce((prev, curr) => {
            return prev?.[curr.elementName];
        }, data ?? {}) ?? "";
    const parentPath = props.config.parentPath;

    const [label] = useState(
        findObjectById(
            props.config.renderOptions.state.models.documentModel.content.modelRoot.elements,
            props.modelElement.elementRef
        ).label.find((l: any) => l.locale === locale.language).text
    );

    const contentDataState = data
        ? ContentState.createFromBlockArray(convertFromHTML(initialValue).contentBlocks)
        : undefined;
    const editorDataState = contentDataState ? EditorState.createWithContent(contentDataState) : undefined;
    const [editorState, setEditorState] = useState<EditorState | undefined>(undefined);
    const [activityId, setActivityId] = useState(activity.id);
    const dispatch = useDispatch();

    if (!editorState && editorDataState) {
        setEditorState(editorDataState);
    }

    if (activityId !== activity.id) {
        setActivityId(activity.id);
        setEditorState(editorDataState);
    }

    const onEditorStateChange = (editorStateData: EditorState) => {
        if (editorState) {
            setEditorState(editorStateData);
        }
    };

    const onBlur = () => {
        const action = FormEngineActions.event({
            activityId: activity.id,
            engineEvent: {
                type: "form-engine/event/VALUE_CHANGE",
                payload: {
                    value: stateToHTML(editorState!.getCurrentContent()),
                    path: elementPath.map(p => ({ ...p, index: 1 })),
                    formModelElementPath: parentPath
                }
            }
        });
        dispatch(action);
    };

    return (
        <>
            <div style={{ marginBottom: "3px", fontWeight: "600" }}>
                <label>{label}</label>
            </div>
            <DefaultEditor
                editorState={editorState}
                onChange={onEditorStateChange}
                onBlur={onBlur}
                toolbarPluginConfig={toolbarPluginConfig}
            />
        </>
    );
}
function CustomControl(props: FormModelMap.FormModelComponentProps<FormModel.Control>): React.ReactElement {
    const rteAnnotation = props?.modelElement?.annotation?.find(
        a => a.name === "useRichTextEditor" && a.value === "true"
    );

    if (rteAnnotation) {
        return <FormEngineEditorElement {...props} />;
    }

    return <RelationshipFormModelMap.Control.component {...props} />;
}
const CustomFormModelMap: FormModelMap = {
    ...DefaultFormModelMap,
    ...RelationshipFormModelMap,
    CustomScreenElement: { component: CustomScreenElement },
    DetachedRepeat: { component: CustomDetachedRepeat },
    Control: { component: CustomControl },
    InlineRepeat: { component: CustomInlineRepeat },
    ButtonPanel: { component: CustomButtonPanel }
};

We use props.config.parentPath to retrieve the initial value from the dataHolder. The problem appears when we want to have a detached repeat with the separate screen to edit the entries:

The elementPath does not include the array index of the entry. How is it handled for out-of-the-box controls? Maybe there is another way to work with the data holder

Hi @k.szwej,

please have a look into useDocumentPathForInput like mentioned in that topic(Customize Fields in Repeat)

Thank you!