Featuresフォーム管理フォームエディター

フォームエディター

フォームエディターは、フォームを直感的に編集するためのツールです。フォームのパーツをドラッグ&ドロップで配置し、設定をカスタマイズできます。編集画面上で実際のフォームをプレビューしながらフォームを作成できます。

フォームエディター

フォーム管理機能を使うには、フォームエディターを導入する必要があります。

インストール

フォームエディターを導入するには @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を指定します。

components/form-editor.tsx
"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> コンポーネントを使用して、フォームを編集するページを実装します。

page.tsx
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 プロパティに、フォーム送信時のエンドポイントを指定します。

app/contact/page.tsx
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のオリジンを指定します。

app/form-preview/page.tsx
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 メソッドで受け取る必要があります。

app/contact/submit/route.ts
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> コンポーネントでラップしてください。ラップしない場合、フォームエディタのプレビュー上でブロックを操作できません。

app/contact/page.tsx
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 プロパティを使用すると、サンクスページへの遷移を実装することも可能です。

app/contact/confirm/page.tsx
"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>
  );
}