# 機能設計書 63-オブジェクトウィジェット

## 概要

本ドキュメントは、Decap CMSのオブジェクトウィジェット（Object Widget）の機能設計を定義する。オブジェクトウィジェットは、ネストされたフィールドグループを表示・編集するためのUIコンポーネントである。

### 本機能の処理概要

オブジェクトウィジェットは、複数のフィールドを1つのグループとしてまとめ、ネストされたデータ構造を管理するウィジェットである。単一フィールドまたは複数フィールドのグループを定義可能で、折りたたみ/展開の制御やsummaryテンプレートによるラベル表示をサポートする。

**業務上の目的・背景**：CMSコンテンツでは、住所（国、都道府県、市区町村、番地）やSEO情報（title、description、image）のように、関連する複数のフィールドを1つのグループとしてまとめることが多い。オブジェクトウィジェットにより、こうしたネストされたデータ構造を視覚的にグループ化して管理できる。

**機能の利用シーン**：エントリ編集画面でフィールドタイプが`object`に設定されているフィールドに対して利用される。SEOメタデータ、著者情報、アドレス情報など、構造化されたデータの入力に使用される。

**主要な処理内容**：
1. ネストされたフィールド群のEditorControl描画
2. 折りたたみ/展開のトグル制御
3. summaryテンプレートによる折りたたみ時のラベル表示
4. 子ウィジェットの再帰的バリデーション
5. リストウィジェット内での使用時のforListモード対応
6. i18n対応（isFieldDuplicate、isFieldHidden）

**関連システム・外部連携**：リストウィジェット（decap-cms-widget-list）と密接に連携し、リスト項目の個々の入力コンポーネントとして使用される。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | 既存エントリのオブジェクトフィールド編集 |
| 7 | エントリ新規作成画面 | 主画面 | 新規エントリのオブジェクトフィールド入力 |

## 機能種別

UI入力コンポーネント / データグルーピング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field | ImmutableMap | Yes | フィールド定義（fields/field, collapsed, summary等） | - |
| value | Map / node / bool | No | 現在のオブジェクト値 | デフォルトはMap() |
| onChangeObject | function | Yes | オブジェクト値変更コールバック | - |
| editorControl | elementType | Yes | ネストしたエディタコントロールコンポーネント | - |
| resolveWidget | function | Yes | ウィジェット解決関数 | - |
| forList | boolean | No | リストウィジェット内での使用フラグ | - |
| forID | string | No | フィールド一意識別子 | - |

### 入力データソース

- 画面入力: 各ネストフィールドのユーザー入力
- フィールド定義: field/fieldsによるオブジェクト構造定義

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| value | ImmutableMap | オブジェクト値（各フィールド名をキーとしたMap） |

### 出力先

- Redux Store: エントリのフィールド値として保存

## 処理フロー

### 処理シーケンス

```
1. ウィジェット初期化
   └─ field.get('collapsed', false)で初期折りたたみ状態を設定
2. フィールド描画
   └─ renderFields()でmultiFields（複数）またはsingleField（単一）を描画
3. 各フィールド描画
   └─ controlFor()でEditorControlコンポーネントを生成（hiddenウィジェットはスキップ）
4. 折りたたみ/展開
   └─ handleCollapseToggle()で状態切替（forListの場合はprops.collapsedを使用）
5. バリデーション
   └─ validate()で子ウィジェット（childRefs）を再帰的に検証
6. 値変更
   └─ onChangeObject経由で親コンポーネントに通知
```

### フローチャート

```mermaid
flowchart TD
    A[ObjectControl.render] --> B{multiFields or singleField?}
    B -->|multiFields| C[renderFields - 複数フィールド]
    B -->|singleField| D[renderFields - 単一フィールド]
    B -->|なし| E["h3: No field(s) defined"]
    C --> F[controlFor - 各フィールド]
    D --> F
    F --> G{widget === hidden?}
    G -->|Yes| H[null返却]
    G -->|No| I[EditorControl描画]
    I --> J[onChangeObject コールバック]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-63-01 | 折りたたみ制御 | collapsed（デフォルトfalse）で初期折りたたみ状態を制御 | 常時 |
| BR-63-02 | forListモード | forListがtrueの場合、ObjectWidgetTopBarを非表示にし、折りたたみはprops.collapsedを使用 | リストウィジェット内で使用時 |
| BR-63-03 | hiddenウィジェットスキップ | field.widgetが'hidden'の場合、controlFor()でnullを返す | hiddenウィジェットが定義されている場合 |
| BR-63-04 | summaryテンプレート | summaryフィールドにテンプレートを指定すると折りたたみ時のラベルをカスタマイズ可能 | summaryが定義されている場合 |
| BR-63-05 | i18n フィールド制御 | isFieldDuplicate/isFieldHiddenにより多言語対応時のフィールド表示を制御 | i18n設定がある場合 |

### 計算ロジック

- summaryテンプレート: `stringTemplate.compileStringTemplate(summary, null, '', value)`で値を展開

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（クライアントサイドのみの処理）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | フィールド未定義 | fields/fieldのいずれも定義されていない | "No field(s) defined for this widget"を表示 |
| - | バリデーションエラー | 子ウィジェットのバリデーション失敗 | 各フィールドのエラーメッセージを表示 |

### リトライ仕様

リトライ処理は行わない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- shouldComponentUpdateは常にtrueを返す（ネストウィジェットの更新を保証）

## セキュリティ考慮事項

- React標準のXSS対策が適用される

## 備考

- オブジェクトウィジェットはリストウィジェットの各項目の表示にも使用される重要な基盤コンポーネントである
- ObjectPreviewはfield.fieldsまたはfield.fieldの値をそのまま表示する簡素な実装

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `packages/decap-cms-widget-object/src/schema.js` | collapsed, i18nのスキーマプロパティ |

**読解のコツ**: オブジェクトウィジェットの値はImmutableのMap型で管理される。各ネストフィールドの名前がキー、値がバリューとなる。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `packages/decap-cms-widget-object/src/index.js` | name='object'として登録。controlComponent, previewComponent, schemaのエクスポート |

**主要処理フロー**:
1. **1-3行目**: ObjectControl, ObjectPreview, schemaをインポート
2. **5-13行目**: Widget関数でウィジェット定義を返却

#### Step 3: 制御コンポーネント（ObjectControl）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ObjectControl.js | `packages/decap-cms-widget-object/src/ObjectControl.js` | forListモードの分岐、validate()の再帰呼び出し、controlFor()のEditorControl生成 |

**主要処理フロー**:
- **57-62行目**: constructorでcollapsedの初期状態をfield設定から取得
- **79-94行目**: validate() - 子ウィジェットのchildRefsを反復して検証
- **96-142行目**: controlFor() - 各フィールドのEditorControlコンポーネントを生成
- **113-115行目**: hiddenウィジェットのスキップ
- **168-173行目**: renderFields() - multiFieldsまたはsingleFieldの描画分岐
- **175-180行目**: objectLabel() - summaryテンプレート展開
- **182-235行目**: render() - メインUI。forListの場合はObjectWidgetTopBarを非表示

#### Step 4: プレビューコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ObjectPreview.js | `packages/decap-cms-widget-object/src/ObjectPreview.js` | 簡素なプレビュー実装（field.fieldsまたはfield.fieldを表示） |

### プログラム呼び出し階層図

```
ObjectControl (ObjectControl.js)
    │
    ├─ render()
    │   ├─ ObjectWidgetTopBar → handleCollapseToggle()（forList時は非表示）
    │   └─ renderFields()
    │       └─ controlFor(field, key)
    │           ├─ widget === 'hidden' → null
    │           └─ EditorControl → onChangeObject
    │
    ├─ validate()
    │   └─ childRefs[name].validate() → 子ウィジェット検証
    │
    ├─ focus(path)
    │   └─ childRefs[fieldName].focus() → フォーカス委譲
    │
    └─ objectLabel()
        └─ stringTemplate.compileStringTemplate()
```

### データフロー図

```
[入力]                    [処理]                           [出力]

field定義 ────────▶ renderFields() ──▶ EditorControl群
  (fields/field)         │
                        ▼
ユーザー操作 ──────▶ onChangeObject(fieldName, value) ──▶ 親コンポーネント
  (各フィールド入力)                                          │
                                                             ▼
                                                      Redux Store (Map値)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-widget-object/src/index.js` | ソース | ウィジェット登録エントリーポイント |
| ObjectControl.js | `packages/decap-cms-widget-object/src/ObjectControl.js` | ソース | オブジェクト制御コンポーネント（メインロジック） |
| ObjectPreview.js | `packages/decap-cms-widget-object/src/ObjectPreview.js` | ソース | プレビューコンポーネント |
| schema.js | `packages/decap-cms-widget-object/src/schema.js` | ソース | ウィジェットスキーマ定義 |
