# 機能設計書 64-Ajaxダイアログ

## 概要

本ドキュメントは、LEGACY CMSにおけるAjaxダイアログ機能の設計を記述する。本機能はDojo Toolkitのdijit.Dialogウィジェットを使用して、編集・削除・確認などの操作をモーダルダイアログで表示する。

### 本機能の処理概要

本機能は、ページ遷移なしでユーザーに操作確認やフォーム入力を求めるモーダルダイアログを提供する。Ajax通信でサーバーからコンテンツを取得し、ダイアログ内に表示することで、スムーズなユーザー体験を実現する。

**業務上の目的・背景**：CMSの管理画面では、記事の保存、削除確認、アセット選択など、頻繁にユーザーとのインタラクションが発生する。ページ遷移を伴う操作は作業の流れを中断させるため、本機能により操作の継続性を維持し、管理効率を向上させる。

**機能の利用シーン**：
- 記事・ページ・イベントの保存確認ダイアログ
- コンテンツ削除の確認ダイアログ
- 記事・イベントの公開確認ダイアログ
- 新規カテゴリ・ロール作成のフォームダイアログ
- アセット選択ダイアログ（画像挿入、リンク挿入）
- コメント承認・削除の確認ダイアログ
- スライド編集・削除のダイアログ

**主要な処理内容**：
1. ユーザーがダイアログ起動ボタンをクリック
2. getDialog()またはpostDialog()関数を呼び出し
3. Dojo xhrGet/xhrPostでサーバーにリクエスト
4. サーバーからHTMLコンテンツを受信
5. dijit.Dialogのcontentに設定して表示
6. ダイアログ内のボタン操作でさらなるAjax通信または画面遷移

**関連システム・外部連携**：Dojo Toolkit（dijit.Dialog、dojo.xhrGet、dojo.xhrPost）と連携してAjaxダイアログ機能を実現する。

**権限による制御**：ダイアログを起動するボタン自体がACLに基づいて表示/非表示制御される。ダイアログ内の操作もサーバーサイドで権限チェックを行う。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 36 | 記事編集画面 | 主画面 | 保存・削除・公開ダイアログ表示 |
| 37 | 記事新規作成画面 | 主画面 | アセット選択ダイアログ表示 |
| 61 | ページ編集画面 | 主画面 | 保存・削除・公開ダイアログ表示 |
| 62 | ページ新規作成画面 | 主画面 | アセット選択ダイアログ表示 |
| 83 | 画像挿入画面 | 主画面 | 画像選択ダイアログの表示 |
| 84 | リンク挿入画面 | 主画面 | リンク選択ダイアログの表示 |
| 88 | ローテーター編集画面 | 参照画面 | スライド削除ダイアログへの遷移 |
| 89 | スライド編集画面 | 参照画面 | スライド画像選択ダイアログ |
| 90 | スライド新規作成画面 | 参照画面 | スライド画像選択ダイアログ |

## 機能種別

UI操作 / Ajax通信 / モーダルダイアログ

## 入力仕様

### 入力パラメータ

#### getDialog()関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | string | Yes | サーバーへのリクエストURL | 有効なURL形式 |
| title | string | Yes | ダイアログのタイトル | 文字列 |
| refresh | string | No | 更新対象のContentPane ID | 文字列（dijit ID） |

#### postDialog()関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | string | Yes | サーバーへのPOST先URL | 有効なURL形式 |
| form | string | Yes | 送信するフォームのID | 文字列（フォームID） |
| title | string | Yes | ダイアログのタイトル | 文字列 |
| refresh | string | No | 1の場合detailsResponseを更新 | '1'または未指定 |

### 入力データソース

- ボタンのonClick属性で指定されるJavaScript関数呼び出し
- フォームIDで指定されるHTMLフォーム要素

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| dialogContent | HTML | ダイアログ内に表示するHTMLコンテンツ |
| dialogTitle | string | ダイアログのタイトルテキスト |

### 出力先

- dijit.Dialogウィジェットのcontentプロパティ
- dijit.Dialogウィジェットのtitleプロパティ

## 処理フロー

### 処理シーケンス

```
1. ユーザーがダイアログ起動ボタンをクリック
   └─ onClick="getDialog('/admin/xxx/yyy/','Dialog Title');"

2. 共通JavaScript関数の実行
   └─ getDialog() または postDialog() を呼び出し

3. ローディング表示
   └─ ダイアログにローディングインジケータを表示

4. Ajax通信の実行
   └─ dojo.xhrGet/xhrPost でサーバーにリクエスト

5. サーバーサイド処理
   └─ コントローラーでHTML断片を生成（レイアウト無効）

6. レスポンス受信
   └─ 受信したHTMLをダイアログのcontentに設定

7. ダイアログ表示
   └─ dijit.byId('ajaxDialog').show()

8. ユーザー操作
   ├─ [確認] さらなるAjax通信でデータ更新
   ├─ [キャンセル] ダイアログを閉じる
   └─ [遷移] goTo()で画面遷移
```

### フローチャート

```mermaid
flowchart TD
    A[ボタンクリック] --> B{GET or POST?}
    B -->|GET| C[getDialog実行]
    B -->|POST| D[postDialog実行]
    C --> E[ローディング表示]
    D --> E
    E --> F[Ajax通信]
    F --> G[サーバー処理]
    G --> H[HTML断片生成]
    H --> I[レスポンス受信]
    I --> J[ダイアログcontent設定]
    J --> K[ダイアログ表示]
    K --> L{ユーザー操作}
    L -->|確認| M[確認処理のAjax通信]
    L -->|キャンセル| N[ダイアログを閉じる]
    L -->|遷移| O[画面遷移]
    M --> P[結果表示]
    P --> Q[ダイアログ閉じる/更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-64-01 | 確認ダイアログ必須 | 削除・公開操作は確認ダイアログを表示 | 削除・公開ボタンクリック時 |
| BR-64-02 | 保存結果表示 | 保存処理後は結果メッセージをダイアログに表示 | 保存ボタンクリック時 |
| BR-64-03 | バリデーションエラー表示 | サーバーサイドバリデーションエラーはダイアログ内に表示 | フォーム送信時 |
| BR-64-04 | レイアウト無効化 | ダイアログ用レスポンスはレイアウトを無効化 | Ajax応答時 |
| BR-64-05 | 画面更新オプション | 操作完了後に親画面の特定領域を更新可能 | detailsResponse指定時 |
| BR-64-06 | FCKeditor同期 | postDialog実行前にFCKeditorの内容をフォームに同期 | エディタ使用時 |

### 計算ロジック

本機能には特別な計算ロジックはない。

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

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

ダイアログ自体はUI機能であり、直接的なDB操作は行わない。ダイアログ経由で呼び出されるサーバー処理によりDB操作が発生する。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 記事保存 | articles | UPDATE | 記事データの更新 |
| 記事削除 | articles, tags, comments | DELETE | 記事と関連データの削除 |
| 記事公開 | articles | UPDATE | ステータスをpublishedに変更 |
| カテゴリ作成 | articles_categories | INSERT | 新規カテゴリの追加 |

### テーブル別操作詳細

ダイアログ経由の操作は、呼び出し先のコントローラーアクションに依存する。詳細は各機能の設計書を参照。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Ajax通信エラー | ネットワーク障害、サーバーエラー | エラーメッセージをダイアログ内に表示 |
| - | バリデーションエラー | フォーム入力値が不正 | エラーメッセージをダイアログ内に表示 |
| - | 権限エラー | 操作権限がない | 権限エラー画面へフォワード |

### リトライ仕様

Ajax通信エラー時はユーザーが再度ボタンをクリックすることで再試行可能。自動リトライは実装されていない。

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

ダイアログ自体はトランザクションを使用しない。ダイアログ経由のDB操作は各機能のトランザクション仕様に従う。

## パフォーマンス要件

- ダイアログ表示までの応答時間は1秒以内を目標
- ローディングインジケータによりユーザーに処理中であることを通知

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

- ダイアログ経由の操作もサーバーサイドで権限チェックを実施
- CSRFトークンは実装されていないため、将来的な対策が推奨
- ダイアログ内のHTMLはサーバーで生成するため、XSSリスクは低い

## 備考

- 本機能はDojo Toolkit（dijit.Dialog）を使用したカスタム実装
- getDialog()はGETリクエスト用、postDialog()はPOSTリクエスト用
- doDialog()はクライアントサイドバリデーション付きのPOSTリクエスト
- FCKeditorとの連携時はMyFCKObject.UpdateEditorFormValue()でフォーム同期が必要

---

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

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

### 推奨読解順序

#### Step 1: クライアントサイドのダイアログ関数を理解する

まず、ダイアログ表示のためのJavaScript関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | common.js | `public/_scripts/admin/common.js` | getDialog(), postDialog(), doDialog()関数 |

**読解のコツ**: Dojo xhrGetとxhrPostの使い方、dijit.Dialogへのコンテンツ設定方法を理解する。

**主要処理フロー**:
1. **82-83行目**: ローディングメッセージ定義
   ```javascript
   var lmessage = "<div style=\"text-align:center; padding:8px;height:100%\"><img src=\"/_styles/admin/images/ajax_loader.gif\" style=\"vertical-align: middle\"/></div>";
   ```
2. **86-107行目**: postDialog()関数 - POSTリクエスト用
   ```javascript
   function postDialog(url,form,title,refresh) {
       dijit.byId("ajaxDialog").attr("content",lmessage);
       dojo.xhrPost({
           url: url,
           load: function(response, ioArgs){
               dijit.byId("ajaxDialog").attr("content",response);
               dijit.byId("ajaxDialog").attr("title",title);
               if(refresh == '1') {
                   dijit.byId("detailsResponse").refresh();
               }
               dijit.byId("ajaxDialog").show();
               return response;
           },
           error: function(response, ioArgs){...},
           form:form
       });
   }
   ```
3. **133-142行目**: getDialog()関数 - GETリクエスト用
   ```javascript
   function getDialog(url,title,refresh) {
       dijit.byId("ajaxDialog").setHref(url);
       dijit.byId("ajaxDialog").attr("title",title);
       if(typeof(refresh) != 'undefined') {
           dijit.byId(refresh).refresh();
       }
       dijit.byId("ajaxDialog")._position();
       dijit.byId("ajaxDialog").show();
   }
   ```

#### Step 2: ビューでのダイアログ定義を理解する

ビューテンプレートでダイアログがどのように定義されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | manage.phtml | `application/modules/admin/views/scripts/articles/manage.phtml` | dijit.Dialog定義とボタンのonClick |

**主要処理フロー**:
1. **16行目**: dijit.Dialogのrequire
   ```javascript
   dojo.require("dijit.Dialog");
   ```
2. **32行目**: ダイアログ要素の定義
   ```html
   <div dojoType="dijit.Dialog" id="ajaxDialog" title="Information"></div>
   ```
3. **41-42行目**: ダイアログ起動ボタン
   ```html
   <button dojoType="dijit.form.Button" ... onClick="getDialog('/admin/articles/new/','New Article');">New Article</button>
   ```
4. **128行目**: 削除ダイアログ呼び出し
   ```html
   <a onclick="getDialog('/admin/articles/delete/id/<?php echo $article['article_id']; ?>/','Delete Article');" style="cursor: pointer;">Delete</a>
   ```

#### Step 3: サーバーサイドのダイアログ応答を理解する

コントローラーでダイアログ用のHTMLがどのように生成されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ArticlesController.php | `application/modules/admin/controllers/ArticlesController.php` | deleteAction(), saveAction()での応答生成 |

**主要処理フロー**:
1. **82-83行目**: レイアウト・ビュー無効化
   ```php
   $this->_helper->layout()->disableLayout();
   $this->_helper->viewRenderer->setNoRender(true);
   ```
2. **93-98行目**: 削除完了時のHTML出力
   ```php
   echo '<p class="Spacer"></p>
         <div class="cUpd">Article Deleted</div>
         <p class="Spacer"></p>
         <div class="cFormDS">
             <button dojoType="dijit.form.Button" ... onClick="dijit.byId(\'ajaxDialog\').hide();location.reload(true);">Close</button>
         </div>';
   ```
3. **102-108行目**: 削除確認時のHTML出力
   ```php
   echo '<p class="Spacer"></p>
         <div class="cUpd">Are you sure you want to delete this article?</div>
         <p class="Spacer"></p>
         <div class="cFormDS">
             <button dojoType="dijit.form.Button" ... onClick="getDialog(\'/admin/articles/delete/id/'.$id.'/confirm/1/\',\'Delete Article\');">Delete</button>
             <button dojoType="dijit.form.Button" ... onClick="dijit.byId(\'ajaxDialog\').hide();">Cancel</button>
         </div>';
   ```

#### Step 4: 編集画面でのダイアログ使用を理解する

編集画面でのダイアログ使用パターンを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | edit.phtml | `application/modules/admin/views/scripts/articles/edit.phtml` | 保存・公開ダイアログの呼び出し |

**主要処理フロー**:
- **43行目**: ダイアログ定義
  ```html
  <div dojoType="dijit.Dialog" id="ajaxDialog" title="Information"></div>
  ```
- エディットフォームからのpostDialog呼び出し（ボタン部分は別ファイルで定義）

#### Step 5: FCKeditorとの連携を理解する

リッチテキストエディタとダイアログの連携を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | common.js | `public/_scripts/admin/common.js` | MyFCKObjectの定義 |

**主要処理フロー**:
- **66-75行目**: FCKeditor同期関数
  ```javascript
  function MyFCKClass() {
      this.UpdateEditorFormValue = function() {
          for ( i = 0; i < parent.frames.length; ++i )
              if ( parent.frames[i].FCK )
                  parent.frames[i].FCK.UpdateLinkedField();
      };
  }
  var MyFCKObject = new MyFCKClass();
  ```

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

```
[ユーザー操作]
    │
    └─ ボタンクリック (onClick)
           │
           ├─ getDialog(url, title, refresh)
           │      │
           │      ├─ dijit.byId("ajaxDialog").setHref(url)  [GETリクエスト]
           │      ├─ dijit.byId("ajaxDialog").attr("title", title)
           │      └─ dijit.byId("ajaxDialog").show()
           │
           └─ postDialog(url, form, title, refresh)
                  │
                  ├─ dijit.byId("ajaxDialog").attr("content", lmessage)  [ローディング]
                  │
                  └─ dojo.xhrPost({url, form})
                         │
                         └─ [サーバーサイド]
                                │
                                ├─ $this->_helper->layout()->disableLayout()
                                ├─ $this->_helper->viewRenderer->setNoRender(true)
                                │
                                └─ echo 'HTMLコンテンツ'
                                       │
                                       └─ [クライアント受信]
                                              │
                                              ├─ dijit.byId("ajaxDialog").attr("content", response)
                                              └─ dijit.byId("ajaxDialog").show()
```

### データフロー図

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

ボタンクリック ──────▶ getDialog() / postDialog()
                              │
                              ▼
                       [Ajax通信]
                              │
                              ▼
                     [サーバー処理]
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
             [確認画面]            [結果画面]
                    │                   │
                    ▼                   ▼
        ┌───────────────┐      dijit.Dialog.hide()
        │ Delete/Confirm│      location.reload()
        │    Button     │
        └───────────────┘
                    │
                    ▼
              [Ajax通信]
                    │
                    ▼
              [DB更新処理]
                    │
                    ▼
              [結果表示]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| common.js | `public/_scripts/admin/common.js` | JavaScript | getDialog(), postDialog(), doDialog()関数定義 |
| manage.phtml (articles) | `application/modules/admin/views/scripts/articles/manage.phtml` | テンプレート | 記事一覧画面、ダイアログ定義例 |
| edit.phtml (articles) | `application/modules/admin/views/scripts/articles/edit.phtml` | テンプレート | 記事編集画面、ダイアログ使用例 |
| manage.phtml (events) | `application/modules/admin/views/scripts/events/manage.phtml` | テンプレート | イベント一覧画面 |
| edit.phtml (events) | `application/modules/admin/views/scripts/events/edit.phtml` | テンプレート | イベント編集画面 |
| manage.phtml (pages) | `application/modules/admin/views/scripts/pages/manage.phtml` | テンプレート | ページ一覧画面 |
| edit.phtml (pages) | `application/modules/admin/views/scripts/pages/edit.phtml` | テンプレート | ページ編集画面 |
| edit.phtml (rotators) | `application/modules/admin/views/scripts/rotators/edit.phtml` | テンプレート | ローテーター編集画面 |
| manage.phtml (users) | `application/modules/admin/views/scripts/users/manage.phtml` | テンプレート | ユーザー一覧画面 |
| ArticlesController.php | `application/modules/admin/controllers/ArticlesController.php` | ソース | 記事管理、ダイアログ応答生成 |
| UsersController.php | `application/modules/admin/controllers/UsersController.php` | ソース | ユーザー管理、ダイアログ応答生成 |
| EventsController.php | `application/modules/admin/controllers/EventsController.php` | ソース | イベント管理、ダイアログ応答生成 |
| PagesController.php | `application/modules/admin/controllers/PagesController.php` | ソース | ページ管理、ダイアログ応答生成 |
| RotatorsController.php | `application/modules/admin/controllers/RotatorsController.php` | ソース | ローテーター管理、ダイアログ応答生成 |
| Dialog.js | `public/_scripts/dojo/dijit/Dialog.js` | JavaScript | Dojo dijit.Dialogウィジェット |
