Tree コントロール
Tree は、商品カテゴリ、組織図、フォルダー、地域などの階層データを表示するコントロールです。小規模で変更の少ないデータはページ構造内で管理でき、業務システムが管理するデータは API からルートと子ノードを読み込めます。検索、ノード単位の操作、ドラッグによる並べ替えにも対応します。
1. 構成方法を選ぶ
| 用途 | 推奨構成 |
|---|---|
| ページ作成者が管理する少数の固定項目 | 静的な Tree Nodes |
| 業務システムが管理する階層 | Tree Data Source |
| 追加、編集、削除、コンテキスト操作 | 動的データソース + Node Actions |
| 並べ替えや別階層への移動 | 動的データソース + onDrop |
1.1 Tree:静的な商品カテゴリ
静的サンプルでは、Set Page Context で [[treeNode.name]] を selectedCategory に保存してから、ProTable の reloadTable を呼び出します。ProTable の Query Context では、category パラメーターをこの値に割り当てます。
1.2 Tree02:動的読み込みと階層編集
Tree02 はページ表示時にルートを読み込み、ディレクトリを展開したときだけ parentId を使って直下の子ノードを取得します。サーバー検索、詳細表示、追加、編集、削除、ドラッグ結果の保存も確認できます。まずルート取得を検証し、遅延読み込み、検索、クリック連携、ノード操作、ドラッグの順に設定すると、問題を切り分けやすくなります。
2. Tree を追加する
- Page Designer で対象ページを開き、Screen を選択します。
- Navigation Tree の子要素として Tree を追加します。
- 静的データでは Tree Nodes、API データでは Data Source に Tree Data Source を追加します。
- ProTable、Form などの本文コンテンツは Panels に配置します。
Navigation Tree が左側のナビゲーション領域になり、Panels と Grids が本文領域になります。同じレイアウトを Grid で作り直す必要はありません。
3. Tree の基本設定
| プロパティ | 用途 |
|---|---|
| Title | Tree の上に表示する任意のタイトル |
| Width | PC での幅。狭い画面では 1 行全体に切り替わる |
| Show Search | 検索欄を表示する |
| Checkable | チェックボックスを表示する |
| Default Expand All | 初回表示時に読み込み済みノードを展開する |
| Node Draggable | ドラッグを許可して onDrop を発火する |
4. 静的ノード
Tree Nodes に TreeNode を追加し、Title、Value、Disabled、Selectable、Checkable、Is Leaf、Icon、Extra Info を設定します。Value は Tree 全体で一意かつ安定した値にしてください。後続の操作は Value でノードを識別します。
5. 動的データソース
Data Source に TreeDataSource を追加し、最初にルート取得を設定します。
| 設定 | Tree02 の例 |
|---|---|
| Root Data URL | /spark/api/sample/navigation/tree/nodes |
| Request Method | GET |
| Response List Path | data.nodes |
| Title Field | label |
| Value Field | id |
| Children Field | children |
| Leaf Field | leaf |
| Extra Field | count |
5.1 Test でリクエストを確認する
Test をクリックします。Test、デザインキャンバス、実行ページは同じ Tree リクエスト生成処理とレスポンス解析処理を使います。このため、URL、バックエンドデータ、Response List Path、フィールド対応のどこに問題があるかを結果から直接判断できます。
プラットフォームは次のヘッダーを自動で付与します。
X-Spark-Page-Environment: DEV
X-Spark-Page-Render-Mode: DESIGN_PREVIEW
Data Source に手動で追加する必要はありません。全体の仕様はページリクエストコンテキストとバックエンドデバッグを参照してください。
バックエンドでは共有契約でヘッダーを解析し、列挙値で設計プレビューを判定します。
SparkPageRequestContext context = SparkPageRequestHeaders.resolve(
environment, renderMode);
boolean developmentPreview = context != null
&& context.getEnvironment() == SparkPageEnvironment.DEV
&& context.getRenderMode() == SparkPageRenderMode.DESIGN_PREVIEW;
手書きの文字列や独自パラメーターで同じ判定を重複させないでください。プレビュー用データには本番の秘密情報、個人情報、機密データを含めないでください。
5.2 一括読み込みと遅延読み込み
小規模な Tree は children に全階層を含められます。大規模な Tree では Child Nodes URL と Parent Parameter を設定します。非リーフを展開すると、現在の Value が parentId などのパラメーターで送信されます。子がまだ未取得でも、ディレクトリは leaf: false を返す必要があります。
6. 検索
静的データや一括読み込み済みデータではローカル検索を利用できます。遅延読み込みでは Search URL と検索パラメーターを設定し、未取得の枝もサーバーで検索します。検索結果には一致したノードだけでなく、その祖先パスも含めてください。
7. 現在ノードとイベントコンテキスト
| イベント | 主なデータ |
|---|---|
onClick | treeNode, selectedTreeNode |
onCheck | treeCheck.checkedKeys, treeCheck.checkedNodes |
onExpand | treeExpand.expandedKeys, treeExpand.node |
onLoad | treeLoad.node, treeLoad.children |
onDrop | treeDrop.dragNode, treeDrop.dropNode, treeDrop.position |
よく使うパスは treeNode.value、treeNode.title、treeNode.isLeaf、treeNode.data.category です。Node Action は現在ノードを更新しますが、Tree の onClick は同時に発火しません。
8. ノード操作を設定する
Node Actions に TreeNodeAction を追加し、タイトル、共通 Icon ピッカー、トリガー、色の意味、表示条件、無効化条件を設定します。More ボタンと PC の右クリックは同じ操作一覧を使用します。タッチ端末では長押しではなく More を使います。
PUT /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
POST /spark/api/sample/navigation/tree/nodes
DELETE /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
Interaction は Tree ではなく Tree Node Action 自体に設定し、リクエスト成功後にだけ reloadTree を呼び出します。項目を非表示にしても権限制御にはならないため、バックエンドでも必ず検証してください。
9. ドラッグ結果を保存する
Node Draggable を有効にすると、画面上で移動した後に onDrop が発火します。treeDrop.position は BEFORE、INSIDE、AFTER のいずれかです。保存するには Send Request を追加します。
POST /spark/api/sample/navigation/tree/reorder
dragId = [[treeDrop.dragNode.key]]、dropId = [[treeDrop.dropNode.key]]、position = [[treeDrop.position]] を送ります。失敗または中断時、Tree は移動前の構造に戻ります。バックエンドではノード、親、循環参照、権限、同時更新を確認してください。
10. ノード選択で本文を更新する
静的 Tree の onClick では、まず [[treeNode.name]] を selectedCategory に書き込み、次に ProTable の reloadTable を呼びます。Tree02 では [[treeNode.value]] を selectedNodeId、[[treeNode.data.category]] を selectedCategory に保存してから再読み込みします。順序を逆にすると、前回の値で検索されます。
11. コンポーネント関数
| Function | 動作 |
|---|---|
reloadTree | ルートを再取得し、有効な選択は保持する |
expandAll | 読み込み済みの全ディレクトリを展開する |
collapseAll | 全ディレクトリを閉じる |
expandAll は、未取得の枝をすべて自動読み込みする機能ではありません。
12. Java バックエンド例
Tree02 の契約は Java 17 と Spring Boot 4 を対象とします。ノード DTO には少なくとも id、label、parentId、leaf、children を含めます。書き込みリクエストは parentId、label、category、並べ替えリクエストは dragId、dropId、position を受け取ります。
| 用途 | メソッドとパス |
|---|---|
| ルート、子ノード、検索 | GET /spark/api/sample/navigation/tree/nodes |
| 子ノード追加 | POST /spark/api/sample/navigation/tree/nodes |
| ノード編集 | PUT /spark/api/sample/navigation/tree/nodes/{nodeId} |
| ノード削除 | DELETE /spark/api/sample/navigation/tree/nodes/{nodeId} |
| ドラッグ保存 | POST /spark/api/sample/navigation/tree/reorder |
検索サービスは標準コンテキストに応じて再現可能なプレビューデータまたは対象環境の Repository を選べます。
Map<String, NodeRecord> source = isDevelopmentPreview(context)
? designPreviewNodes : nodes;
String keyword = StringUtils.trimToNull(query);
if (keyword != null) {
return children(source, null).stream()
.map(root -> filterTree(source, root, keyword))
.filter(Objects::nonNull)
.toList();
}
return children(source, StringUtils.trimToNull(parentId)).stream()
.map(node -> toDto(source, node))
.toList();
追加、編集、削除、ドラッグでは、ノード、親、循環参照、権限をサーバー側で再検証します。ユーザー向けエラーは LatticeRuntimeException(Message.code(...)) とサーバーの i18n リソースを使い、Service で文字列を組み立てないでください。親と同階層の順序は同一トランザクションで更新します。
右側の ProTable は通常の検索条件に nodeId を追加できます。カテゴリノードは category を使い、業務ノードはバックエンドが nodeId を適切な条件に変換します。
13. 検証とトラブルシューティング
データ取得、選択連携、ノード操作、ドラッグ保存の順に確認します。最初に Test でルートとフィールド対応を検証し、展開と検索、複数ノードの選択、書き込み操作へ進んでください。
| 症状 | 最初に確認する項目 |
|---|---|
| Test が失敗する | URL、メソッド、接続、認証、CORS |
| HTTP 200 だがノードが 0 件 | Response List Path が配列を指しているか |
| 件数はあるがタイトルがない | Title Field、Value Field、実際の JSON 名 |
| ディレクトリを展開できない | Leaf Field が誤って true になっていないか |
| 違うノードでも同じ一覧になる | Page Context の実行順と Query Context のパス |
| 再読み込みで順序が戻る | onDrop のパラメーターとバックエンド保存 |