複数のスクリプトが競合してしまう原因とその解決法

## はじめに

Pleasanterで複数のスクリプトを適用した際に、スクリプトの競合が発生することがあります。本記事では、複数のスクリプトを適用した際に、片方のスクリプトの動作が無効化されてしまう現象とその解決策について解説します。

## 発生した問題

貸出明細テーブルの `分類A`に応じて、親テーブル（総在庫管理テーブル）から `現在在庫数`や`貸出可能数`などの値を取得するスクリプトを複数適用したところ、

1. それぞれのスクリプトを単独で適用すると正常に動作する。
2. 両方のスクリプトを適用すると、どちらか一方しか効果が現れない。
3. コンソールを見ると、APIリクエストは発生しているものの、片方のスクリプトの処理が上書きされているように見える。

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116202/58f7ec52-a686-4326-aee0-94cb2b32b75b.png)
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116202/300b2300-b7f7-46b3-9fff-507de92bc2dc.png)
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116202/ac8b0bb8-1189-4c28-a1f8-743dd02e3f0d.png)


この問題が発生する原因は、**`$p.events.on_editor_load` の上書き**にありました。

## 原因
`$p.events.on_editor_load` に関数を設定することで、画面のロード時に特定の処理を実行できます。しかし、**異なるスクリプトで `$p.events.on_editor_load` を定義すると、後から適用されたスクリプトが前のスクリプトを上書きしてしまう** ため、最初のスクリプトの処理が実行されなくなります。

例えば、以下の2つのスクリプトを適用した場合、

### **スクリプト①（NumBを取得する処理）**
```javascript
$p.events.on_editor_load = function () {
    getParentData();
    $p.getControl('NumB').attr('disabled', true);  // 完全に入力不可
}
```

### **スクリプト②（NumCを取得する処理）**
```javascript
$p.events.on_editor_load = function () {
    getParentData();
    $p.getControl('NumC').attr('disabled', true);  // 完全に入力不可
}
```

この場合、スクリプト②の `$p.events.on_editor_load` がスクリプト①の処理を上書きしてしまい、スクリプト①が動作しなくなります。

## 解決策

### **1. `$p.events.on_editor_load` の複数登録を許容する書き方にする**

スクリプトを競合させずに両方の処理を実行するには、 **既存の `$p.events.on_editor_load` に処理を追加する** ように書き換えます。

```javascript
let originalOnEditorLoad = $p.events.on_editor_load || function () {};

$p.events.on_editor_load = function () {
    originalOnEditorLoad(); // 既存の処理を保持
    getParentDataForNumB();
    getParentDataForNumC();
};
```

この方法を使えば、スクリプト①とスクリプト②が競合せず、両方の処理が実行されるようになります。

### **2. `getParentData` の関数名を変えてスクリプト間の衝突を回避する**

もともと、両方のスクリプトで `getParentData()` という関数名を使っていたため、関数の定義が上書きされる問題も発生していました。解決策として、それぞれのスクリプトの関数名を変更することで、別々の処理として認識させます。

#### **修正後のスクリプト①（NumBを取得する処理）**
```javascript
$p.events.on_editor_load = function () {
    getParentDataForNumB();
    $p.getControl('NumB').attr('disabled', true);  // 完全に入力不可
}

function getParentDataForNumB() {
    let classAValue = $p.getControl('ClassA').val();
    if (classAValue) {
        $p.apiGet({
            'id': classAValue,
            'done': function (data) {
                let numBValue = data.Response.Data[0].NumHash?.NumB || data.Response.Data[0]?.NumB;
                $p.set($p.getControl('NumB'), numBValue);
            }
        });
    }
}
```

### **3. 2つのスクリプトを統合する**
```javascript
$p.events.on_editor_load = function () {
    getParentDataForNumB();  // NumBの取得処理
    getParentDataForNumC();  // NumCの取得処理

    $p.getControl('NumB').attr('disabled', true);  // NumB を編集不可
    $p.getControl('NumC').attr('disabled', true);  // NumC を編集不可
};

// 貸出明細テーブル (B) の「分類A」の値が変更されたら呼び出す
$(document).on('change', '#' + $p.getControl('ClassA')[0].id, function () {
    getParentDataForNumB();
    getParentDataForNumC();
});

// **NumBの取得処理**
function getParentDataForNumB() {
    let classAValue = $p.getControl('ClassA').val();

    if (classAValue) {
        console.log("取得するレコードID (NumB):", classAValue);

        $p.apiGet({
            'id': classAValue,
            'done': function (data) {
                console.log("APIレスポンス (NumB):", data);

                if (data.Response && data.Response.Data && data.Response.Data.length > 0) {
                    let numBValue = data.Response.Data[0].NumHash?.NumB || data.Response.Data[0]?.NumB;
                    console.log("取得した数値B:", numBValue);

                    $p.set($p.getControl('NumB'), numBValue);
                } else {
                    console.warn("レスポンスデータが空 (NumB)");
                    $p.set($p.getControl('NumB'), "データなし");
                }
            },
            'fail': function (data) {
                console.error("APIリクエスト失敗 (NumB):", data);
            }
        });
    } else {
        console.log("分類Aが未設定のため、数値Bをクリア");
        $p.set($p.getControl('NumB'), '');
    }
}

// **NumCの取得処理**
function getParentDataForNumC() {
    let classAValue = $p.getControl('ClassA').val();

    if (classAValue) {
        console.log("取得するレコードID (NumC):", classAValue);

        $p.apiGet({
            'id': classAValue,
            'done': function (data) {
                console.log("APIレスポンス (NumC):", data);

                if (data.Response && data.Response.Data && data.Response.Data.length > 0) {
                    let numCValue = data.Response.Data[0].NumHash?.NumD || data.Response.Data[0]?.NumD;
                    console.log("取得した数値C:", numCValue);

                    $p.set($p.getControl('NumC'), numCValue);
                } else {
                    console.warn("レスポンスデータが空 (NumC)");
                    $p.set($p.getControl('NumC'), "データなし");
                }
            },
            'fail': function (data) {
                console.error("APIリクエスト失敗 (NumC):", data);
            }
        });
    } else {
        console.log("分類Aが未設定のため、数値Cをクリア");
        $p.set($p.getControl('NumC'), '');
    }
}

// **保存時に NumB, NumC をクリア**
$p.events.before_send = function () {
    console.log("更新時に NumB, NumC をクリア");
    $p.set($p.getControl('NumB'), null);
    $p.set($p.getControl('NumC'), null);
};

```

#### **修正後のスクリプト②（NumCを取得する処理）**
```javascript
$p.events.on_editor_load = function () {
    getParentDataForNumC();
    $p.getControl('NumC').attr('disabled', true);  // 完全に入力不可
}

function getParentDataForNumC() {
    let classAValue = $p.getControl('ClassA').val();
    if (classAValue) {
        $p.apiGet({
            'id': classAValue,
            'done': function (data) {
                let numCValue = data.Response.Data[0].NumHash?.NumC || data.Response.Data[0]?.NumC;
                $p.set($p.getControl('NumC'), numCValue);
            }
        });
    }
}
```

このように、それぞれの関数名をユニークにすることで、別々の処理として扱えるようになります。

## まとめ

複数のスクリプトが競合してしまう原因として、

1. `$p.events.on_editor_load` の上書き
2. `getParentData()` の関数名の競合

が挙げられます。

これらの問題を解決するには、

- `$p.events.on_editor_load` に関数を追加する形に変更する
- 別々の関数名を使用することで処理の競合を防ぐ

といった対応を行うことで、スクリプトの競合を回避できます。

Pleasanterで複数のスクリプトを併用する場合は、上書きされないような構造にすることが重要です。