カスタムフィールドコンポーネント

Dashboard > Actions > Forms > Custom field

カスタムコンポーネントを使用すると、フォームの外観や操作性を拡張したり、JavaScriptやHTML、CSSを用いて強力なロジックを追加したりできます。

カスタムフィールドには、フロントエンドやバックエンドの変数を追加し、フォーカスやぼかしなどをの共通イベントを処理するなど、フォームにデータを渡すための内部メソッドがあります。

カスタムフィールドは以下の作成に使用できます。

  • カスタムデータ構造のあるフィールド

    • :オブジェクト、文字列配列

  • サードパーティーのウィジェットを使用するフィールド

    • :Google Addressのオートコンプリート

  • 他のフィールドを表示または非表示するロジックのあるフィールド

  • 値の取得に外部のAPIが必要なフィールド

カスタムフィールドの設定

カスタムフィールドには以下の設定があります。

Params

カスタムフィールドのソースコードで参照するキーと値のペアを追加します。キーと値のペアはフィールド変数から含めることができます。

下の例では、キーと値のペアであるsymbol={{fields.symbol}}separator=,を使用して、カスタムフィールドの設定が入力されています。

function CustomComponent(context) {
  const input = document.createElement('input');
  let mask = null;

  function mountComponent() {
    /** getParams() method returns the params you've configured in your input */
    const config = context.custom.getParams();
    const { symbol, separator } = config;

    mask = IMask(input,
    {
      mask: `${symbol}num`,
      blocks: {
        num: {
          mask: Number,
          thousandsSeparator: separator,
        }
      }
    });
  }

  return {
    /** Invoked once when the field is created */
    init() {
      mountComponent();
      return input;
    },
    ...
  };
}

Was this helpful?

/

ソースコード

カスタムフィールドにJavaScriptコードを追加します。

function customInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    block() {
      input.disabled = true;
    },

    unblock() {
      input.disabled = false;
    },

    getValue() {
      return input.value;
    }
  };
}

Was this helpful?

/

JSONスキーマ

カスタムフィールドはデフォルトで任意の値形式を受け付けます。ただし、JSONスキーマを使用すると、サーバー側で値を検証することができます。

{
  "type": "array",
  "items": {
    "type": "string"
  },
  "minItems": 2
}

Was this helpful?

/

複雑な検証要件に対応させるには、フローを使用することができます。

CSS

カスタムフィールドにCSSコードを追加します。

カスタムフィールドハンドラー

以下のハンドラーを使用すると、フィールドにカスタムの動作を追加することができます。

init(params?)

フィールドの作成時に一度だけ呼び出され、Params設定で構成済みのprams値を渡します。

HTML要素、文字列または空の値を返します。

パラメーター 説明
params

任意のオブジェクトです。カスタムフィールド設定からのパラメーターです。

const input = document.createElement('input');
input.type = 'text';

init() {
  return input;
}

Was this helpful?

/

update(params?)

ユーザーがフォームにある同じ手順を再表示したときに呼び出されます。

このオプションは、UIロジックの再レンダリングや、変更されたかもしれないparams値の更新が必要な場合に便利です。

パラメーター 説明
params

任意のオブジェクトです。カスタムフィールド設定からのパラメーターです。

onFocus()

フォーカスがカスタムフィールドのHTML要素に移動すると呼び出されます。

onBlur()

フォーカスがカスタムフィールドのHTML要素から外れると呼び出されます。

getValue()

フォームがカスタムフィールド値の取得を1回以上必要とする場合に呼び出されます。これは通常、ユーザーがフォームの手順を送信したときに実行されます。クライアント側の検証が必要な場合には、エラーをスローして、ユーザーにカスタムエラーメッセージを表示することができます。

例:

function customTextInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    getValue() {
      if (input.value !== 'Auth0') {
        throw new Error('The value must be Auth0')
      }

      return input.value;
    }
  };
}

Was this helpful?

/

block()

カスタムフィールドをブロックしなければならない場合に呼び出されます。これは通常、ユーザーがフォームの手順を送信し、データが当方のバックエンドで処理されたときに実行されます。

unblock()

カスタムフィールドをブロック解除しなければならない場合に呼び出されます。これは通常、ユーザーがフォームの手順を送信した後か、検証エラーにより当方のバックエンドでデータの処理が停止したときに実行されます。

getScripts()

init()メソッドが呼び出される前に読み込みが終了することをフォームが保証するURLのリストを返します。

getScripts() {
  return ['https://example.com/script_a.js', 'https://example.com/script_b.js'];
}

Was this helpful?

/

コンテキストオブジェクト

コンテキストオブジェクトを渡す際には、以下のメソッドを使用して、フォームやコンポーネントのロジックを扱うことができます。

カスタムメソッド

context.custom.getValue()

現在のカスタムフィールドの値を受け取ります。

context.custom.setValue()

現在のカスタムフィールドに値を設定します。

function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';

  input.addEventListener('change', () => {
    context.custom.setValue(input.value);
  });

  return {
    init() {
      return input;
    },
  };
}

Was this helpful?

/

context.custom.createUid()

現在のカスタムフィールドの一意の識別子を返します。

function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';
  input.id = context.custom.createUid();

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}

Was this helpful?

/

context.custom.getParams()

現在のカスタムフィールドの設定からパラメーターを受け取ります。

function customInput(context) {
  // Accessing parameters on the root of the function does NOT work
  // const { defaultValue } = context.custom.getParams();
  // console.log(defaultValue); // undefined

  function buildInput() {
    const { defaultValue } = context.custom.getParams();
    const input = document.createElement('input');
    input.type = 'text';
    input.value = defaultValue;

    return input;
  }

  return {
    init() {
      return buildInput();
    },

    getValue() {
      return input.value;
    }
  };
}

Was this helpful?

/

フォームメソッド

フォームとやり取りして他のフィールドから値を集めたり、他のフォームの手順に移動したりしなければならない場合には、以下のフォームメソッドを使用することができます。

context.form.getId()

現在のフォームの一意の識別子を返します。

context.form.getRoot()

現在のフォームのルートHTML要素を返します。

context.form.goForward()

1つ次のフォームの手順に移動します。

context.form.goPrevious()

1つ前のフォームの手順に移動します。

context.form.isValid()

フォームがクライアント側の検証にすべて合格した場合にブール値を返します。

context.form.validate()

続行する前にクライアント側の検証を用いて既存のフィールド値を評価します。フィールドが検証に合格しない場合には、エラーメッセージを表示します。

context.form.getAllHiddenFields()

すべての隠しフィールド値を含むオブジェクトを返します。

context.form.setHiddenField(id, value)

非表示フィールドの値を設定します。

パラメーター 説明
id

文字列。非表示フィールドのIDです。

value

文字列。非表示フィールドの値です。

context.form.getValues()

すべてのフィールドおよび隠しフィールド値を含むオブジェクトを返します。

context.form.getField(id)

指定したフィールドのインスタンスを返します。

  • getNode() | true フィールドのルートHTML要素を返します。
  • getValue() フィールド値を返します。
  • setRequired(boolean) フィールドを必須または任意に設定します。

パラメーター 説明
id

文字列。IDフィールド値です。

const fullName = context.form.getField('full_name');
const fullNameValue = fullName.getValue();
console.log(fullNameValue); // John Doe

Was this helpful?

/

カスタムフィールドの例

このセクションでは、フォームに追加できるカスタムフィールドの例を説明します。

範囲入力カスタムフィールド

あらかじめ設定された範囲からの値を返すカスタムフィールドです。

ソースコード::

function rangeInput() {
  const input = document.createElement('input');
  input.type = 'range';
  input.min= '0';
  input.max= '100';
  input.value = '0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}

Was this helpful?

/

色入力カスタムフィールド

色の16進数値を返すカスタムフィールドです。

ソースコード::

function colorInput() {
  const input = document.createElement('input');
  input.type = 'color';
  input.value = '#20c5a0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}

Was this helpful?

/

APIからの値を用いたオートコンプリート入力カスタムフィールド

サードパーティーのAPIを使用したオートコンプリート値を返すカスタムフィールドです。

ソースコード::

function textInputWithAutocomplete(context) {
  const input = document.createElement('input');
  input.type = 'text';

  function populateInputValue(json) {
    const { city } = json;

    input.value = city;
  }

  function fetchIpInfo() {
    const url = 'https://ipinfo.io/json';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateInputValue(json));
  }

  return {
    init() {
      fetchIpInfo();
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}

Was this helpful?

/

APIからの値を用いた動的ドロップダウンカスタムフィールド

サードパーティーのAPIを使用した動的ドロップダウンリストからの値を返すカスタムフィールドです。

ソースコード::

function dynamicDropdown() {
  const select = document.createElement('select');
  select.classList.add('af-stringField-input');

  function buildOption(data) {
    const { name: { first } } = data;
    const option = document.createElement('option');
    option.value = first;
    option.innerText = first;

    return option;
  }

  function populateNames(json) {
    const { results } = json;

    results.forEach((o) => {
      const option = buildOption(o);

      select.appendChild(option);
    });
  }

  function fetchNames() {
    const url = 'https://randomuser.me/api/?results=10&inc=name';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateNames(json));
  }

  return {
    init() {
      fetchNames();
      return select;
    },

    getValue() {
      return select.value;
    },
  };
}

Was this helpful?

/

+ボタンでフィールドを追加できる動的入力カスタムフィールド

ユーザーがフィールドを追加できるカスタムフィールドです。

ソースコード::

function DynamicInputs(context) {
  const DEFAULT_INITIAL_INPUTS = 2;
  const DEFAULT_PLACEHOLDER = 'jane.doe@example.com';
  const DEFAULT_ADD_BUTTON_TEXT = 'Add new item';
  const DEFAULT_INPUT_TYPE = 'email';
  const STATE_VALUE = {};
  const FIELD_ID = context.custom.createUid();
  let UUID_COUNTER = 0;
  let INPUTS_COUNTER = 0;

  const container = document.createElement('div');

  const inputsContainer = document.createElement('div');
  container.appendChild(inputsContainer);

  function buildAddNewItem() {
    const config = context.custom.getParams();
    const { add_button_text } = config;

    const ADD_BUTTON_TEXT = add_button_text || DEFAULT_ADD_BUTTON_TEXT;

    const addInputButton = document.createElement('button');
    addInputButton.type = 'button';
    addInputButton.classList.add('af-dynamic-input-add-button');
    addInputButton.id = `${FIELD_ID}_add-input-button`;
    addInputButton.onclick = buildInputContainer.bind(this);

    const addInputButtonIcon = document.createElement('span');
    addInputButtonIcon.classList.add('af-button', 'af-dynamic-input-add-button-icon');
    addInputButtonIcon.innerText = '+';

    const addInputButtonText = document.createElement('span');
    addInputButtonText.classList.add('af-dynamic-input-add-button-text');
    addInputButtonText.innerText = ADD_BUTTON_TEXT;

    addInputButton.appendChild(addInputButtonIcon);
    addInputButton.appendChild(addInputButtonText);
    container.appendChild(addInputButton);
  }

  function removeInput(container, input) {
    delete STATE_VALUE[input.name];
    container.remove();
  }

  function buildRemoveInputButton(container, input) {
    const button = document.createElement('button');
    button.type = 'button';
    button.classList.add('af-button', 'af-dynamic-input-remove-button');
    button.innerText = '-';
    button.onclick = removeInput.bind(this, container, input);

    INPUTS_COUNTER--;

    return button;
  }

  function buildInput() {
    const config = context.custom.getParams();
    const { placeholder, input_type } = config;

    const PLACEHOLDER = placeholder || DEFAULT_PLACEHOLDER;
    const INPUT_TYPE = input_type || DEFAULT_INPUT_TYPE;

    const input = document.createElement('input');
    input.type = INPUT_TYPE;
    input.placeholder = PLACEHOLDER;
    input.classList.add('af-stringField-input');
    input.name = `${FIELD_ID}_${UUID_COUNTER}`;
    input.id = input.name;
    input.addEventListener('change', () => {
      STATE_VALUE[input.name] = input.value;
    });

    UUID_COUNTER++;

    return input;
  }

  function buildInputContainer() {
    const container = document.createElement('div');
    container.classList.add('af-dynamic-input-container');

    const input = buildInput();
    container.appendChild(input);

    const removeButton = buildRemoveInputButton(container, input);
    container.appendChild(removeButton);

    inputsContainer.appendChild(container);

    INPUTS_COUNTER++;
  }

  function initComponent() {
    const config = context.custom.getParams();
    const { initial_inputs } = config;

    const INITIAL_INPUTS = initial_inputs || DEFAULT_INITIAL_INPUTS;
    INPUTS_COUNTER = INITIAL_INPUTS

    for (let i = 0; i < INITIAL_INPUTS; i++) {
      buildInputContainer();
    }
  }

  function blockFields(value) {
    const inputKeys = Object.keys(STATE_VALUE);

    inputKeys.forEach((o) => {
      const selector = document.getElementById(o);
      selector.disabled = value;
    });
  }

  return {
    init() {
      buildAddNewItem();
      initComponent();
      return container;
    },

    block() {
      blockFields(true);
    },

    unblock() {
      blockFields(false);
    },

    getValue() {
      return Object.values(STATE_VALUE);
    },
  };
}

Was this helpful?

/

CSSコード

.af-button.af-dynamic-input-remove-button {
  width: 48px;
  color: var(--button-font-color);
  background: var(--button-background-color);
}

.af-dynamic-input-container {
  display: flex;
  margin-bottom: var(--spacing-1);
}

.af-dynamic-input-container input {
  margin-right: var(--spacing-1);
}

button.af-dynamic-input-add-button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  margin-top: var(--spacing-1);
  display: inline-flex;
  cursor: pointer;
  align-items: center;
}

.af-button.af-dynamic-input-add-button-icon {
  background: var(--primary-color);
  width: 24px;
  padding: 0;
  height: 24px;
  border-radius: .3em;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  margin-right: var(--spacing-1);
}

.af-dynamic-input-add-button-text {
  color: var(--label-font-color);
  font-size: var(--label-font-size);
}

.af-dynamic-input-add-button:focus {
  outline: none;
}

.af-dynamic-input-add-button:hover .af-button {
  transition: filter var(--transition-normal), box-shadow var(--transition-normal);
  filter: brightness(1.1);
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}

.af-dynamic-input-add-button:focus .af-button {
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}

Was this helpful?

/