フォームエディター
フォームエディターは、フォームを直感的に編集するためのツールです。フォームのパーツをドラッグ&ドロップで配置し、設定をカスタマイズできます。編集画面上で実際のフォームをプレビューしながらフォームを作成できます。
フォーム管理機能を使うには、フォームエディターを導入する必要があります。
インストール
フォームエディターを導入するには @orizm-private/orizm-form-editor
パッケージをインストールします。
このパッケージは一般公開されておらず、アクセスするには一定の手順が必要です。インストールを実行する前に プライベートレジストリ を参照し、アクセスできるように設定してください。
スターターでプロジェクトを開始する場合は最初からインストールされています。既存プロジェクトに導入する場合は、以下の手順でインストールしてください。エディターを提供するCMSと、ページを表示するWebサイトの両方でインストールする必要があります。
以下のコマンドでパッケージをインストールします。
pnpm add @orizm-private/orizm-form-editor @orizm-private/orizm-editor
使い方
ここでは Next.js 15 App Router を使用したアプリケーションを例に、フォームエディターをアプリケーションに組み込む方法を説明します。
フォームエディターは、CMSとConsumerの両方に組み込む必要があります。CMSにはフォームを編集するためのエディターを実装し、Consumerにはフォームを表示するページを実装します。
CMS上のエディターでは、Consumer上のフォームをリアルタイムにプレビューしながら編集できます。プレビューは、CMS上にConsumerのプレビューページを iframeで埋め込むことで実現します。そのため、Consumerがiframeによる表示を許可している必要があります。
CMSの実装
CMSにフォームを編集するエディターを実装します。
FormEditorコンポーネントの実装例
<OrizmFormEditorComposer>
コンポーネントを使用して、エディターを実装します。previewSiteURL
プロパティに、ConsumerのプレビューページのURLを指定します。
"use client";
import "@orizm-private/orizm-editor/editor.css";
import { type Block, isBlocks } from "@orizm-private/orizm-editor/common";
import {
FormEditor as OrizmFormEditor,
OrizmFormEditorComposer,
} from "@orizm-private/orizm-form-editor";
import type { FormValidationRules } from "@orizm-private/orizm-form-editor/rules";
import { useCallback, useState } from "react";
import type { Form } from "@/orizm/cms";
import { cmsClient } from "@/lib/cms-client";
type Props = {
form: Form;
};
export function FormEditor(props: Props) {
const { id, key, displayName, body, formSchema } = props.form;
const [formState, setFormState] = useState<{
blocks: Block[];
rules: FormValidationRules;
}>(() => {
const blocksData = (body as { blocks: unknown } | null)?.blocks;
const blocks = isBlocks(blocksData) ? blocksData : [];
return {
blocks,
rules: formSchema,
};
});
// このハンドラ関数を OrizmFormEditorComposer の onChange プロパティに渡します
// フォームエディター上での変更を受け取り、stateを更新します
const onChangeEditor = useCallback(
(blocks: Block[], rules: FormValidationRules) => {
setFormState({ blocks, rules });
},
[],
);
const onClickSave = useCallback(async () => {
// CMS SDKを使用してフォームの構造をデータベースに保存します
await cmsClient.tables.form.update(id, {
formSchema: formState.rules,
body: { blocks: formState.blocks },
});
}, [id, formState]);
return (
<>
<button onClick={onClickSave}>フォームを保存</button>
<OrizmFormEditorComposer
initialBlocks={formState.blocks}
onChange={onChangeEditor}
pageSlug={key}
pageTitle={displayName}
previewSiteURL="https://consumer.example.com/form-preview"
>
<OrizmFormEditor />
</OrizmFormEditorComposer>
</>
);
}
フォーム編集ページの実装例
先に実装した <FormEditor>
コンポーネントを使用して、フォームを編集するページを実装します。
import { notFound } from "next/navigation";
import { FormEditor } from "@/components/form-editor";
import { cmsClient } from "@/lib/cms-client";
type Params = {
params: Promise<{
form_id: string;
}>;
};
export default async function FormEditPage(props: Params) {
const { form_id } = await props.params;
const form = await cmsClient.tables.form.get(form_id).catch(() => null);
if (!form) {
return notFound();
}
return <FormEditor form={form} />;
}
Consumerの実装
Consumerにフォームをレンダリングするページを実装します。
本番ページの実装例
<FormComposer>
コンポーネントを使用して、フォームを表示します。action
プロパティに、フォーム送信時のエンドポイントを指定します。
import "@orizm-private/orizm-form-editor/forms.css";
import { isBlocks } from "@orizm-private/orizm-editor/common";
import {
Form,
FormComponentRenderer,
FormComposer,
SubmitButton,
} from "@orizm-private/orizm-form-editor/renderer";
import { notFound } from "next/navigation";
import { cmsClient } from "@/lib/cms-client";
export default async function ContactPage() {
// Consumer SDKを使用してフォームの構造を取得します
const form = await consumerClient.tables
.form({ key: "contact" })
.catch(() => null);
if (!form) {
return notFound();
}
const blocksData = (form.body as { blocks: unknown } | null)?.blocks;
const blocks = isBlocks(blocksData) ? blocksData : [];
return (
<FormComposer
formId={form.key}
blocks={blocks}
validationRules={form.formSchema}
isPreview={false}
hasConfirmation={false}
action="/contact/submit"
>
<Form>
<FormComponentRenderer />
</Form>
<SubmitButton>送信する</SubmitButton>
</FormComposer>
);
}
プレビューページの実装例
プレビューページは、CMSのフォームエディター上でプレビューを表示するために使用します。
プレビューでは <FormPreviewRenderer>
コンポーネントで <FormComposer>
コンポーネントをラップします。editorOrigin
プロパティに、CMSのオリジンを指定します。
import "@orizm-private/orizm-form-editor/forms.css";
import { blocksToRules } from "@orizm-private/orizm-form-editor/common";
import { FormPreviewRenderer } from "@orizm-private/orizm-form-editor/preview";
import {
Form,
FormComposer,
FormComponentRenderer,
SubmitButton,
} from "@orizm-private/orizm-form-editor/renderer";
export default function PreviewPage() {
return (
<FormPreviewRenderer
editorOrigin="https://cms.example.com"
initialBlocks={[]}
renderForm={(blocks) => (
<FormComposer
formId="preview"
blocks={blocks}
validationRules={blocksToRules(blocks)}
isPreview
action="/contact/submit"
>
<Form>
<FormComponentRenderer />
</Form>
<SubmitButton>送信する</SubmitButton>
</FormComposer>
)}
/>
);
}
送信処理の実装例
フォームの送信処理を行うAPIを実装します。リクエストは POST
メソッドで受け取る必要があります。
import { cmsClient } from "@/lib/cms-client";
export async function POST(request: Request): Promise<Response> {
try {
const [formData, form] = await Promise.all([
request.formData(),
cmsClient.tables.form.getByUnique({ key: "contact" }),
]);
// CMS SDKを使用して submitForm() でフォームデータを送信します
await cmsClient.tables.form.submitForm(form.id, formData);
// ここで必要に応じて送信者に送信完了メールを送ったり、問い合わせ先に通知する処理を実装します
return new Response("Success", { status: 200 });
} catch (error) {
console.error(error);
return new Response("Failed to submit", { status: 500 });
}
}
ブロック一覧
フォームエディタでは、以下のブロックを使用してフォームを構築できます。
- テキスト
- 複数行テキスト
- メールアドレス
- 電話番号
- URL
- 数値
- チェックボックス(複数選択)
- チェックボックス(単一選択)
- ドロップダウンリスト
- 日付
- 日時
- 時刻
- ラジオボタン
- ファイル
ブロックのスタイル
フォームエディタのブロックにはデフォルトのスタイルが適用されているため、そのまま使い始めることができます。もしくは、Webサイトのデザインに合わせてスタイルをカスタマイズできます。
カスタマイズするには、 コンシューマーの実装 で使用する <FormComponentRenderer>
コンポーネントの components
プロパティにカスタムコンポーネントを指定します。
カスタムコンポーネントは必ず <ComponentMarker>
コンポーネントでラップしてください。ラップしない場合、フォームエディタのプレビュー上でブロックを操作できません。
import "@orizm-private/orizm-form-editor/forms.css";
import { isBlocks } from "@orizm-private/orizm-editor/common";
import {
Form,
FormComponentRenderer,
FormComposer,
FormErrorMessage,
FormHelperText,
FormLabel,
FormTextInput,
SubmitButton,
} from "@orizm-private/orizm-form-editor/renderer";
import { notFound } from "next/navigation";
import { cmsClient } from "@/lib/cms-client";
export default async function ContactPage() {
const form = await consumerClient.tables
.form({ key: "contact" })
.catch(() => null);
if (!form) {
return notFound();
}
const blocksData = (form.body as { blocks: unknown } | null)?.blocks;
const blocks = isBlocks(blocksData) ? blocksData : [];
return (
<FormComposer
formId={form.key}
blocks={blocks}
validationRules={form.formSchema}
isPreview={false}
hasConfirmation={false}
action="/contact/submit"
>
<Form>
<FormComponentRenderer
components={{
TextInput: (
<ComponentMarker>
<div>
<FormLabel />
<FormTextInput />
<FormHelperText />
<FormErrorMessage />
</div>
</ComponentMarker>
),
}}
/>
</Form>
<SubmitButton>送信する</SubmitButton>
</FormComposer>
);
}
確認ページを実装する
フォームでデータを送信する前に、確認ページを表示してユーザーに入力内容を確認させることができます。
<FormComposer>
コンポーネントを以下のように設定します。
hasConfirmation
プロパティにtrue
を設定afterSubmit
プロパティに、確認ページへの遷移処理を設定
// その他のpropsは省略しています
<FormComposer
hasConfirmation
afterSubmit={() => router.push("/contact/confirm")}
>
<Form>
<FormComponentRenderer />
</Form>
<SubmitButton>送信する</SubmitButton>
</FormComposer>
次に、確認ページを実装します。
useSavedFormData()
フックを使用して、フォームの入力内容を取得します。
<SubmitTemporaryDataButton>
コンポーネントを使用して、フォームの入力内容を送信します。action
プロパティに送信処理を行うエンドポイントを指定します。afterSubmit
プロパティを使用すると、サンクスページへの遷移を実装することも可能です。
"use client";
import {
SubmitTemporaryDataButton,
useSavedFormData,
} from "@orizm-private/orizm-form-editor/renderer";
import { redirect, useRouter } from "next/navigation";
export default function ConfirmPage() {
const formId = "contact";
const router = useRouter();
const savedData = useSavedFormData(formId);
if (savedData.isLoading) {
return <div>Loading...</div>;
}
if (savedData.data == null) {
redirect("/contact");
}
return (
<div>
<pre>{JSON.stringify(savedData, null, 2)}</pre>
<SubmitTemporaryDataButton
formId={formId}
action="/contact/submit"
afterSubmit={() => router.push("/contact/thanks")}
>
送信する
</SubmitTemporaryDataButton>
</div>
);
}