複数のWijmo FlexGridへ大量データを高速に非同期表示する処理


JavaScriptのモダンな非同期処理を駆使して\*\*「複数のデータソースから並列でデータを取得し、完了したものから順次Wijmo FlexGridに表示する」\*\*という、高速かつユーザーフレンドリーな実装方法をまとめる。

## 1\. バックエンドの準備 (`@RestController`)

バックエンド側でJSON形式のデータを返すAPIエンドポイントが用意されていることを前提とする。
Spring Bootでは、以下のように`@RestController`を用いて簡単に実装できる。

`com/example/demo/controller/ApiController.java`

```java
@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/customers")
    public List<CustomerDto> getCustomers() {
        // 顧客データを取得する重い処理（例: 1秒かかる）
        // Thread.sleep(1000); 
        return customerService.findAll();
    }

    @GetMapping("/products")
    public List<ProductDto> getProducts() {
        // 製品データを取得する非常に重い処理（例: 3秒かかる）
        // Thread.sleep(3000);
        return productService.findAll();
    }
}
```

## 3\. フロントエンド実装: HTMLの準備

まず、Wijmoライブラリを読み込み、FlexGridをホストするための`div`要素をHTMLに配置する。

`templates/grid-view.html`

```html
<!DOCTYPE html>
<html>
<head>
    <title>Wijmo FlexGrid Parallel Loading</title>
    <link href="https://cdn.grapecity.com/wijmo/5.20231.888/styles/wijmo.min.css" rel="stylesheet" />
    <script src="https://cdn.grapecity.com/wijmo/5.20231.888/controls/wijmo.min.js"></script>
    <script src="https://cdn.grapecity.com/wijmo/5.20231.888/controls/wijmo.grid.min.js"></script>
</head>
<body>
    <h1>ダッシュボード</h1>

    <h2>顧客リスト（読み込み時間: 短）</h2>
    <div id="customerGrid" style="height: 300px; margin-bottom: 20px;"></div>
    
    <h2>製品在庫（読み込み時間: 長）</h2>
    <div id="productGrid" style="height: 300px;"></div>

    <script>
        // このセクションにJavaScriptコードを記述する
    </script>
</body>
</html>
```

## 4\. フロントエンド実装: JavaScriptによる並列・順次データバインド

複数のAPIリクエストを並列で実行し、完了したものから順次グリッドを更新するロジックを実装する。

### Step 1: 共通化されたデータバインド関数の作成

まず、再利用性を考慮し、データ取得からバインドまでの一連の処理を一つの関数にまとめる。

```javascript
/**
 * 複数のWijmo FlexGridに対し、それぞれ対応するAPIからデータを並列で非同期に取得し、
 * 【完了したものから順次】itemsSourceに設定する。
 *
 * @param {Array<object>} gridConfigs - グリッドとAPIの設定情報の配列
 * @param {wijmo.grid.FlexGrid} gridConfigs[].grid - データ設定対象のFlexGridインスタンス
 * @param {string} gridConfigs[].apiUrl - データ取得先のAPIのURL
 */
function bindDataToGridsSequentially(gridConfigs) {
    // 1. まず、すべてのグリッドのローディング表示を開始する
    gridConfigs.forEach(config => {
        config.grid.itemsSource = [];      // データソースを空にする
        config.grid.isLoading = true;      // Wijmoのローディング表示を有効化
    });

    // 2. 各グリッドに対して、データ取得処理を並列で開始する
    // forEachはawaitしないため、ループはすぐに完了し、
    // 各fetchはバックグラウンドで並列に実行される。
    gridConfigs.forEach(config => {
        fetch(config.apiUrl)
            .then(response => {
                if (!response.ok) {
                    // HTTPステータスが200番台でない場合はエラーとする
                    throw new Error(`API request failed for ${config.apiUrl} with status ${response.status}`);
                }
                return response.json();
            })
            .then(data => {
                // ★成功した場合、即座に対応するグリッドのitemsSourceを更新
                config.grid.itemsSource = data;
                console.log(`[Success] Grid updated from ${config.apiUrl}`);
            })
            .catch(error => {
                // ★失敗した場合も、即座にエラー処理を行う
                console.error(`[Failure] Could not load data for grid from ${config.apiUrl}:`, error);
                // ここで、対象グリッドの隣にエラーメッセージを表示するなどのUIフィードバックも可能
            })
            .finally(() => {
                // ★成功・失敗にかかわらず、そのグリッドのローディング表示を解除する
                config.grid.isLoading = false;
            });
    });
}
```

### Step 2: 関数の呼び出し

ページの読み込み完了後、FlexGridのインスタンスを生成し、作成した共通関数を呼び出してデータロードを開始する。

```javascript
// 上記のbindDataToGridsSequentially関数の定義に続けて記述

document.addEventListener('DOMContentLoaded', () => {

    // 1. 各FlexGridのインスタンスを生成する
    const customerGrid = new wijmo.grid.FlexGrid('#customerGrid', {
        isReadOnly: true,
        autoGenerateColumns: false,
        columns: [
            { binding: 'id', header: '顧客ID' },
            { binding: 'name', header: '顧客名' },
            { binding: 'country', header: '国' }
        ]
    });

    const productGrid = new wijmo.grid.FlexGrid('#productGrid', {
        isReadOnly: true,
        autoGenerateColumns: false,
        columns: [
            { binding: 'productName', header: '製品名' },
            { binding: 'stock', header: '在庫数' },
            { binding: 'price', header: '単価', format: 'c0' }
        ]
    });

    // 2. グリッドとAPIの組み合わせを定義した設定配列を作成する
    const gridConfigs = [
        { grid: customerGrid, apiUrl: '/api/customers' },
        { grid: productGrid,  apiUrl: '/api/products' }
    ];

    // 3. 共通関数を呼び出して、データの並列ロードとバインドを開始する
    console.log('各グリッドのデータロードを開始します...');
    bindDataToGridsSequentially(gridConfigs);
});
```