フロントエンド連携の例
ウィジェットは標準のカスタム要素なので、インストールするラッパーなしであらゆるフレームワークで動きます。形はいつも同じです。パッケージを一度読み込み(クライアント側の連携)、フォームの中に <caputchin-widget sitekey="cpt_pub_..."> をレンダリングし、訪問者がチャレンジをクリアしたときに注入される隠しの caputchin-token フィールドを読みます。ゲームの場合は <caputchin-game ... game="caputchin/games/leaf-memory"> に差し替えます。
以下の例は、各フレームワークの構文と、その 1 つのカスタム要素特有のクセだけが違います。
素の HTML
注入されるフィールドは通常のフォーム POST に乗っていくので、配線するものは何もありません:
<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>
<form action="/contact" method="POST">
<input name="email" type="email" required />
<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
<button type="submit">Send</button>
</form>React
起動時に一度インポートし、送信時に FormData からフィールドを読みます:
import "@caputchin/widget";
export function ContactForm() {
async function handleSubmit(e) {
e.preventDefault();
const data = new FormData(e.currentTarget);
await fetch("/api/contact", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
});
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<caputchin-widget sitekey="cpt_pub_..." />
<button type="submit">Send</button>
</form>
);
}React で TypeScript を使う場合、JSX が両方の要素を受け入れるよう、一度だけ宣言します:
// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
"caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
}
}
}この declare module "react" の形は React の JSX に固有です。素の TypeScript では要素は単なる HTMLElement であり宣言は不要で、以下のほかのフレームワークはカスタム要素をそれぞれ独自のやり方で型付けするので、このブロックが要るのは React プロジェクトだけです。
Vue
caputchin- タグがカスタム要素であると Vue のコンパイラに伝え、それらをコンポーネントとして解決しようとしないようにします:
// vite.config.js
export default {
plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};<script setup>
import "@caputchin/widget";
function onSubmit(e) {
const data = new FormData(e.target);
// send data.get("caputchin-token") to your backend
}
</script>
<template>
<form @submit.prevent="onSubmit">
<input name="email" type="email" required />
<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
<button type="submit">Send</button>
</form>
</template>Svelte
Svelte はカスタム要素をそのままレンダリングします。設定は要りません:
<script>
import "@caputchin/widget";
function onSubmit(e) {
const data = new FormData(e.currentTarget);
// send data.get("caputchin-token") to your backend
}
</script>
<form on:submit|preventDefault={onSubmit}>
<input name="email" type="email" required />
<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
<button type="submit">Send</button>
</form>Angular
Angular が未知のタグを許すよう、モジュールまたはスタンドアロンコンポーネントに CUSTOM_ELEMENTS_SCHEMA を追加し、パッケージを一度インポートします(main.ts で):
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";
@Component({
selector: "contact-form",
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<form (submit)="onSubmit($event)">
<input name="email" type="email" required />
<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
<button type="submit">Send</button>
</form>
`,
})
export class ContactFormComponent {
onSubmit(e: Event) {
const data = new FormData(e.target as HTMLFormElement);
// send data.get("caputchin-token") to your backend
}
}Next.js
同じ React のコードが動きます。カスタム要素はサーバーレンダリング中は空のタグとしてレンダリングされ、パッケージが読み込まれるとクライアント側でアップグレードされるので、クライアントコンポーネントで @caputchin/widget をインポートする(または next/script で CDN スクリプトを読み込む)以外に特別な扱いは要りません。
あわせて読む
- 読み込みの選択肢については クライアント側の連携。
- バックエンド側については サーバー側の連携。