[Keystone] カスタムフィールド
カスタムフィールド
Keystone は、システムを構築するために使用できるフィールドタイプのコレクションを提供しています。提供されていない必要があるフィールドタイプがある場合、または既存のフィールドタイプの専門バージョンが必要な場合は、カスタムフィールドタイプを定義することができます。
フィールドタイプには2つの部分があります。
- バックエンド部分:データベースに格納されるデータと、GraphQL API での表示方法を定義します。
- フロントエンド部分:Admin UI でのフィールドの外観や動作を定義します。
カスタムフィールドタイプを作成する一般的な方法は、既存のフィールドタイプを取り、必要な変更を加えることです。このガイドでは、整数フィールドタイプを再現する myInt フィールドタイプを作成します。
バックエンド
バックエンド部分はフィールドタイプのエントリーポイントです。私たちは、myInt フィールドタイプと、受け入れ可能な設定オプションを定義する対応する型 MyIntFieldConfig を定義します。
import {
BaseListTypeInfo,
FieldTypeFunc,
CommonFieldConfig,
fieldType,
orderDirectionEnum,
} from '@keystone-6/core/types';
import { graphql } from '@keystone-6/core';
export type MyIntFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
isIndexed?: boolean | 'unique';
};
export const myInt =
<ListTypeInfo extends BaseListTypeInfo>({
isIndexed,
...config
}: MyIntFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> =>
meta =>
fieldType({
kind: 'scalar',
mode: 'optional',
scalar: 'Int',
index: isIndexed === true ? 'index' : isIndexed || undefined,
})({
...config,
input: {
create: { arg: graphql.arg({ type: graphql.Int }) },
update: { arg: graphql.arg({ type: graphql.Int }) },
orderBy: { arg: graphql.arg({ type: orderDirectionEnum }) },
},
output: graphql.field({ type: graphql.Int }),
views: './view',
});
DB フィールド
fieldType は、フィールドがデータベースに保存する内容を定義するdbフィールドで呼び出されます。ここでは整数 (scalar: ‘Int’) ですが、他の種類も DBField の型定義で見つけることができます。
入力
入力オブジェクトは、フィールドタイプの GraphQL 入力を定義します。
input: {
create: { arg: graphql.arg({ type: graphql.Int }) },
update: { arg: graphql.arg({ type: graphql.Int }) },
orderBy: { arg: graphql.arg({ type: orderDirectionEnum }) },
},
GraphQL から渡される値を Prisma に渡す値に変換するためのリゾルバを提供することもできます。
input: {
create: { arg: graphql.arg({ type: graphql.Int }), resolve: (val, context) => val },
update: { arg: graphql.arg({ type: graphql.Int }), resolve: (val, context) => val },
orderBy: { arg: graphql.arg({ type: orderDirectionEnum }), resolve: (val, context) => val },
},
出力
出力フィールドは、フィールドから取得可能な内容を定義します。
output: graphql.field({ type: graphql.Int })
リゾルバも提供することができます。
output: graphql.field({
type: graphql.Int,
resolve({ value, item }, args, context, info) {
return value;
}
})
フロントエンド
フィールドのフロントエンド部分は、バックエンドの実装が views オプションで指定された別のファイルにある必要があります。
views オプションは、プロジェクトディレクトリ内のファイルからのインポートであるかのように解決されます。
views: './view',
コントローラ
コントローラのエクスポートは、フィールドのフロントエンドの機能的な部分を定義します。
// view.tsx
export const controller = (config: FieldControllerConfig): FieldController<string, string> => {
return {
path: config.path,
label: config.label,
graphqlSelection: config.path,
defaultValue: '',
deserialize: data => {
const value = data[config.path];
return typeof value === 'number' ? value + '' : '';
},
serialize: value => ({ [config.path]: value === '' ? null : parseInt(value, 10) }),
};
};
フィールド
Field のエクスポートは、React コンポーネントであり、アイテムビューと作成モーダルで使用され、ユーザーがフィールドの値を表示および編集できるようにします。
// view.tsx
import { FieldContainer, FieldLabel, TextInput } from '@keystone-ui/fields';
import { FieldProps } from '@keystone-6/core/types';
export const Field = ({ field, value, onChange, autoFocus }: FieldProps<typeof controller>) => (
<FieldContainer>
<FieldLabel htmlFor={field.path}>{field.label}</FieldLabel>
{onChange ? (
<TextInput
id={field.path}
autoFocus={autoFocus}
type="number"
onChange={event => {
onChange(event.target.value.replace(/[^\d-]/g, ''));
}}
value={value}
/>
) : (
value
)}
</FieldContainer>
);
セル
Cell のエクスポートは、リストビューのテーブルに表示される React コンポーネントであり、値を変更することはできないことに注意してください。
// view.tsx
import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components';
import { CellComponent } from '@keystone-6/core/types';
export const Cell: CellComponent = ({ item, field, linkTo }) => {
let value = item[field.path] + '';
return linkTo ? <CellLink {...linkTo}>{value}</CellLink> : <CellContainer>{value}</CellContainer>;
};
Cell.supportsLinkTo = true;
カードバリュー
CardValue のエクスポートは、関連するアイテムが編集されていないときに、displayMode: ‘cards’ を持つ関係フィールドのアイテムビューで表示される React コンポーネントであり、値を変更することはできないことに注意してください。
// view.tsx
import { FieldContainer, FieldLabel } from '@keystone-ui/fields';
import { CardValueComponent } from '@keystone-6/core/types';
export const CardValue: CardValueComponent = ({ item, field }) => {
return (
<FieldContainer>
<FieldLabel>{field.label}</FieldLabel>
{item[field.path]}
</FieldContainer>
);
};