[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>
  );
};