カスタムフォーム項目タイプの書き込み
Forms アプリケーションには、高度に構成可能な フィールド タイプが多数用意されており、すぐに使用できます。 既存のフィールドタイプのいずれかを使えば、ほとんどのユースケースに対応できます。 デフォルトのフィールドタイプでユースケースに対応できない場合、独自のフィールドタイプを作成することができます。

ドキュメントとメディア (メタデータ セット)、Web コンテンツ (構造)、およびフォーム アプリケーションで作成されたフォームはすべて、同じフォーム フィールドを使用できます。 デフォルトでは、カスタムフォームフィールドは、フォームアプリケーションでのみ使用されます。 どのアプリケーションでフォーム フィールド タイプを有効にするかを明示的に指定するには、コンポーネント プロパティを追加します。
"ddm.form.field.type.scope=document-library,forms,journal"
サンプル プロジェクトは Liferay 7.4 で実行されます。 Liferay 7.3 を実行している場合、ソース コードは互換性がありますが、 Workspace プロジェクト を Liferay 7.3 用に再構成する必要があります。 そのための手順が、以下のインストラクションに記載されています。
Liferay 7.2 を実行している場合、サポートされているフロントエンド フレームワークの違いにより、このソース コードは実行されません。 C2P9 スライダーのコードサンプルを 7.2 に適合させる方法については、 Liferay 7.2 用のカスタムフォームフィールドの開発 を参照してください。
Liferayのカスタムフォームフィールドを調べる
カスタム フォーム フィールドがどのように機能するかを確認するには、サンプルを展開し、新しいフィールドを使用してフォーム データを追加します。
サンプルをデプロイする
新しいLiferay インスタンスを起動し、以下を実行します。
docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.132-ga132
http://localhost:8080でLiferayにサインインします。 メールアドレス test@liferay.com とパスワード testを使用してください。 プロンプトが表示されたら、パスワードを learnに変更します。
次に、以下の手順に従います。
-
カスタムフォームフィールドタイプのプロジェクトをダウンロードし、解凍します。
curl https://resources.learn.liferay.com/examples/liferay-c2p9.zip -Ounzip liferay-c2p9.zip -
モジュールのルートから、ビルドおよびデプロイします。
./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)ヒントこのコマンドは、デプロイされたjarをDockerコンテナの/opt/liferay/osgi/modulesにコピーするのと同じです。
注Liferay 7.3 の場合、プロジェクトをデプロイする前に次の調整を行います。
-
c2p9-impl/package.jsonで、devDependencies参照を@liferay/portal-7.4から@liferay/portal-7.3に変更します。 -
gradle.propertiesで、liferay.workspace.productの値をportal-7.3-ga8に変更します (GA8 よりも新しい Liferay 7.3 バージョンが使用可能な場合は、代わりにここでそれを参照してみてください)。
-
-
Liferay Dockerコンテナコンソールでデプロイを確認します。
STARTED com.acme.c2p9.impl_1.0.0 [1009]
デプロイされたスライダーフィールドを使用する
-
ブラウザを開いて http://localhost:8080にアクセスします。
-
サイト メニュー → コンテンツ & データ → フォームでフォーム アプリケーションに移動します。
-
追加 ボタン (
) をクリックして、フォーム ビルダーを開きます。 -
フォームにC2P9 Sliderフィールドを追加します。
-
ラベル、初期値、ヘルプテキストを記入し、フィールドを必須とすることもできます。 これらの設定は、多くの すぐに使用できるフィールド が基本設定として提供する設定と一致します。
-
フォームを公開し、スライダーフィールドを使用してレコードを送信します。

フォームフィールドのコードを理解する
基本的なフォームフィールドは、JavaクラスとJavaScriptファイルから構成されています。 C2P9スライダーフィールドでは、抽象クラス BaseDDMFormFieldTypeを拡張してOSGiコンポーネントでそのメタデータを定義することで、C2P9DDMFormFieldType.javaがDDMFormFieldTypeの実装を提供します。
@Component(
property = {
"ddm.form.field.type.description=c2p9-description",
"ddm.form.field.type.display.order:Integer=10",
"ddm.form.field.type.group=customized", "ddm.form.field.type.icon=text",
"ddm.form.field.type.label=c2p9-label",
"ddm.form.field.type.name=c2p9-slider"
},
service = DDMFormFieldType.class
)
public class C2P9DDMFormFieldType extends BaseDDMFormFieldType {
ddm.form.field.type.description:説明テキストに言語キーを指定します。 翻訳された値がLanguage.propertiesファイルに定義されていることを確認してください。
ddm.form.field.type.display.order: フォーム ビルダー サイドバーでフィールドが表示される場所を決定する整数または浮動小数点値を設定します。 同じ値を持つフィールドはランダムに並べられます。
ddm.form.field.type.icon: フィールドに使用するアイコンタイプを決定します。 任意の 粘土アイコンを選択します。
ddm.form.field.type.label: ラベルテキストに言語キーを指定します。 翻訳された値がLanguage.propertiesファイルに定義されていることを確認してください。
ddm.form.field.type.name:フィールドタイプ識別子を指定します。 これは内部および他のコンポーネントでフィールドを識別するために使用されます。
getModuleNameメソッドは、Slider.es.jsファイルパスをNPMResolverサービスに渡します。
@Override
public String getModuleName() {
return _npmResolver.resolveModuleName(
"dynamic-data-mapping-form-field-type-c2p9-slider/C2P9/Slider.es");
}
@Reference
private NPMResolver _npmResolver;
パス定義の一部はpackage.jsonファイルで実現されています(name宣言とscriptsセクションで定義されたsource-mapsをご覧ください)。
getNameメソッドは、フォームフィールド識別子を返します。 これは、コンポーネントプロパティ ddm.form.field.type.nameの値と一致しなければなりません。
@Override
public String getName() {
return "c2p9-slider";
}
isCustomDDMFormFieldTypeは内部で使用されます。 getModuleNameメソッドでNPMResolver.resolveModuleName()の結果を返している場合は、trueを返します。
@Override
public boolean isCustomDDMFormFieldType() {
return true;
}
Slider.es.jsは、フィールドのJavaScriptロジックを指定します。 ファイル内に 1 つのコンポーネント Sliderが定義されています。
インポートステートメントは、Liferayの基本フォームフィールドであるdynamic-data-mapping-form-field-typeの機能を取り込みます。 これにより、入力を FieldBase コンポーネントでラップして、Liferay のフィールドと一致するカスタム スライダー スタイルと構造を実現できます。
import {ReactFieldBase as FieldBase} from 'dynamic-data-mapping-form-field-type';
import React, {useState} from 'react';
エクスポートデフォルト関数スライダー ({... ブロックはフィールドを定義します。これは、パラメータ label name、 onChange、 predefinedValue、 readOnly、および valueを使用してインスタンス化されます。
export default function Slider({
label,
name,
onChange,
predefinedValue,
readOnly,
value,
...otherProps
}) {
const [currentValue, setCurrentValue] = useState(
value ? value : predefinedValue
);
これらのパラメーターの値は、その他のいくつかのパラメーターと共にフォームフィールドのHTML <input>タグを定義します。 重要なのは、ユーザーが選択できるmaxとminの値は、現在ハードコードされていることです。 これは後で変更します。 フィールドのvalueは三項演算子を用いて定義されており、値が入力されていれば、それを使用します。 それ以外の場合は、初期値を使用します。
ユーザーがスライダーをドラッグすると、 onInput イベント リスナーが起動し、スライダーの現在の値が設定され、 onChange 関数が呼び出されて、親コンポーネントに変更が通知されます。
onInput={(event) => {
setCurrentValue(event.target.value);
onChange(event);
}}
フォームフィールドにカスタム設定を追加する
現在、スライダーフィールドの最大値と最小値の設定はハードコーディングされていますが、設定可能であればよりよいでしょう。 フォームフィールドにカスタム設定を追加するには、
DDMFormFieldTypeSettingsクラスを追加し、DDMFormFieldTypeにメソッドを追加して、バックエンドを調整します。DDMFormFieldTemplateContextContributorを追加し、Slider.es.jsで定義される設定方法を更新して、フロントエンドを新しい設定のレンダリングに適合させます。
バックエンドでのカスタム設定のサポート
フォームフィールドの設定は DDMTypeSettingsクラスで定義されます。また、 @DDMFormアノテーションを使ってフィールドのサイドバーに表示されるフォームも定義されます。 それから、 DDMFormFieldType自体が新しい設定定義について知っている必要があるので、デフォルトのフィールド設定フォームを表示しないようにします。 DDMFormFieldContextContributorクラスが新しい設定をReactコンポーネントに送信し、エンドユーザーに表示します。
-
C2P9DDMFormFieldTypeSettingsJava クラスをcom.acme.c2p9.internal.dynamic.data.mapping.form.field.typeパッケージに追加します。package com.acme.c2p9.internal.dynamic.data.mapping.form.field.type; import com.liferay.dynamic.data.mapping.annotations.DDMForm; import com.liferay.dynamic.data.mapping.annotations.DDMFormField; import com.liferay.dynamic.data.mapping.annotations.DDMFormLayout; import com.liferay.dynamic.data.mapping.annotations.DDMFormLayoutColumn; import com.liferay.dynamic.data.mapping.annotations.DDMFormLayoutPage; import com.liferay.dynamic.data.mapping.annotations.DDMFormLayoutRow; import com.liferay.dynamic.data.mapping.form.field.type.DefaultDDMFormFieldTypeSettings; @DDMForm @DDMFormLayout( paginationMode = com.liferay.dynamic.data.mapping.model.DDMFormLayout.TABBED_MODE, value = { @DDMFormLayoutPage( title = "%basic", value = { @DDMFormLayoutRow( { @DDMFormLayoutColumn( size = 12, value = { "label", "predefinedValue", "required", "tip" } ) } ) } ), @DDMFormLayoutPage( title = "%advanced", value = { @DDMFormLayoutRow( { @DDMFormLayoutColumn( size = 12, value = { "dataType", "min", "max", "name", "showLabel", "repeatable", "type", "validation", "visibilityExpression" } ) } ) } ) } ) public interface C2P9DDMFormFieldTypeSettings extends DefaultDDMFormFieldTypeSettings { @DDMFormField( label = "%max-value", properties = "placeholder=%enter-the-top-limit-of-the-range", type = "numeric" ) public String max(); @DDMFormField( label = "%min-value", properties = "placeholder=%enter-the-bottom-limit-of-the-range", type = "numeric" ) public String min(); } -
各設定には、
labelとplaceholderの2つの言語キーがあります。c2p9-impl/src/main/resources/content/Language.propertiesを開き、以下の行を追加してください。max-value=Maximum Value min-value=Minimum Value enter-the-bottom-limit-of-the-range=Enter the bottom limit of the range. enter-the-top-limit-of-the-range=Enter the top limit of the range. -
getDDMFormFieldTypeSettingsメソッドを追加/オーバーライドすることにより、DDMFormFieldTypeクラスを更新します。@Override public Class<? extends DDMFormFieldTypeSettings> getDDMFormFieldTypeSettings() { return C2P9DDMFormFieldTypeSettings.class; }
フロントエンドでのカスタム設定のサポート
フロントエンドでは、ユーザーが入力した最小値と最大値をサポートするために Slider.es.js を更新する必要があります。また、フロントエンドがバックエンドから設定値を受け取ることができるように、 DDMTemplateContextContributor を更新する必要があります。
-
C2P9DDMFormFieldTemplateContextContributorクラスをcom.acme.c2p9.internal.dynamic.data.mapping.form.field.typeパッケージに作成します。package com.acme.c2p9.internal.dynamic.data.mapping.form.field.type; import com.liferay.dynamic.data.mapping.form.field.type.DDMFormFieldTemplateContextContributor; import com.liferay.dynamic.data.mapping.model.DDMFormField; import com.liferay.dynamic.data.mapping.render.DDMFormFieldRenderingContext; import java.util.HashMap; import java.util.Map; import org.osgi.service.component.annotations.Component; @Component( property = "ddm.form.field.type.name=c2p9-slider", service = DDMFormFieldTemplateContextContributor.class ) public class C2P9DDMFormFieldTemplateContextContributor implements DDMFormFieldTemplateContextContributor { @Override public Map<String, Object> getParameters( DDMFormField ddmFormField, DDMFormFieldRenderingContext ddmFormFieldRenderingContext) { Map<String, Object> parameters = new HashMap<>(); parameters.put("max", (String)ddmFormField.getProperty("max")); parameters.put("min", (String)ddmFormField.getProperty("min")); return parameters; } } -
Slider.es.jsのJavaScriptコンポーネントを更新し、ハードコードされた最小値と最大値を削除し、代わりにユーザーが値を入力できるようにします。 ファイルの全内容は以下の通りです。import {ReactFieldBase as FieldBase} from 'dynamic-data-mapping-form-field-type'; import React, {useState} from 'react'; export default function Slider({ label, max, min, name, onChange, predefinedValue, readOnly, value, ...otherProps }) { const [currentValue, setCurrentValue] = useState( value ? value : predefinedValue ); return ( <FieldBase label={label} name={name} predefinedValue={predefinedValue} {...otherProps} > <input className="ddm-field-slider form-control slider" disabled={readOnly} id="myRange" max={max} min={min} name={name} onInput={(event) => { setCurrentValue(event.target.value); onChange(event); }} type="range" value={currentValue ? currentValue : predefinedValue} /> </FieldBase> ); }; -
フォームフィールドモジュールを再デプロイします。 処理が完了したら(コンソールで STOPPED → STARTED)、Liferay を再起動します。
./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)docker container restart $(docker ps -lq) -
フォームのスライダーフィールドを再度テストします。 今回は、フィールドのサイドバー設定の[詳細設定]タブで、最小値と最大値の設定を変えて試してみてください。

Liferay DXP 2025.Q2 向け C2P9 スライダーの適応
C2P9 スライダーサンプルは、Liferay DXP 2025.Q1/Portal GA132 を対象としています。 AMD Loaderの廃止により、Liferay DXP 2025.Q2 にいくつかの変更を加える必要があります。 フォーム設定を追加した後、次の変更を加えます。
-
c2p9-impl/package.jsonファイルを次の内容に置き換えます。{ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "esbuild": "^0.20.2" }, "main": "./", "name": "dynamic-data-mapping-form-field-type-c2p9-slider", "scripts": { "build": "esbuild ./src/main/resources/META-INF/resources/C2P9/Slider.es.js --outfile=./build/resources/main/META-INF/resources/__liferay__/index.js --bundle --format=esm --external:react --external:react-dom --external:@liferay/dynamic-data-mapping-form-field-type --loader:.js=jsx" }, "version": "1.0.0" } -
c2p9-impl/src/main/resources/META-INF/resources/C2P9/Slider.es.jsファイルを次の内容に置き換えます。import React, {useState} from "react"; import {ReactFieldBase} from "@liferay/dynamic-data-mapping-form-field-type"; export function Slider({ label, name, onChange, predefinedValue, readOnly, value, ...otherProps }) { const [currentValue, setCurrentValue] = useState( value ? value : predefinedValue ); return ( <ReactFieldBase label={label} name={name} predefinedValue={predefinedValue} {...otherProps} > <input className="ddm-field-slider form-control slider" disabled={readOnly} id="myRange" max={100} min={1} name={name} onInput={(event) => { setCurrentValue(event.target.value); onChange(event); }} type="range" value={currentValue ? currentValue : predefinedValue} /> </ReactFieldBase> ); }; -
C2P9DDMFormFieldTypeクラスを開き、メソッドgetModuleNameを次のメソッドに置き換えます。@Override public String getESModule() { return "{Slider} from dynamic-data-mapping-form-field-type-c2p9-slider"; }