プリザンターのサーバスクリプト「context.AddResponse」のMethodを詳しくみてみる

## はじめに
プリザンターのサーバスクリプトの関数の1つである「context.AddResponse」、[マニュアルページ](https://pleasanter.org/ja/manual/server-script-context-add-response)はあるものの記載されている内容はかなりシンプルで、何がどこまで出来るの？というところまでは記載がありません。今回は実装から中身を読み解いていきます。

## 実装を見てみる
早速実装をみてみましょう。サーバスクリプト(ClearScript)で呼び出される部分は下記になります。

```csharp:Implem.Pleasanter/Libraries/ServerScripts/ServerScriptModelContext.cs
        public void AddResponse(
            string method,
            string target = null,
            object value = null,
            string options = null)
        {
            ResponseCollection.Add(
                method: method,
                target: target,
                value: value,
                options: options);
        }
```
https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/Libraries/ServerScripts/ServerScriptModelContext.cs#L165-L176

ここでは受け取った値を`ResponseCollection.Add`に渡しているだけなので、`ResponseCollection.Add`を見てみます。

```csharp:Implem.Pleasanter/Libraries/Responses/ResponseCollection.cs
        public ResponseCollection Add(
            string method,
            string target = null,
            object value = null,
            string options = null)
        {
            Add(new Response(
                method: method,
                target: target,
                value: value != null
                    ? value
                    : string.Empty,
                options: options));
            return this;
        }
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/Libraries/Responses/ResponseCollection.cs#L41-L55

ざっくりと説明してしまうと、`ResponseCollection`に`context.AddResponse`でセットされた値を追加していっています。最終的に`ResponseCollection`にセットされた値はJSONにシリアライズされブラウザに送られます。
ブラウザ側で送られた`ResponseCollection`を受け取っているのはJavaScriptで書かれたライブラリです。その実装部を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/_dispatch.js
$p.setByJsonElement = function (jsonElement, data, $control) {
    var method = jsonElement.Method;
    var target = jsonElement.Target;
    var value = jsonElement.Value;
    var options = jsonElement.Options !== undefined
        ? JSON.parse(jsonElement.Options)
        : {};
    switch (method) {
        case 'Html':
            $(target).html(value);
            break;
        case 'ReplaceAll':
            $(value).replaceAll(target);
            break;
        case 'Message':
            $p.setMessage(target, value);
            break;
        case 'Href':
            $control.addClass('no-send');
            $p.transition(value);
            break;
        case 'PushState':
            history.pushState(target, '', value);
            break;
        case 'Set':
            var $target = $p.getControl(target);
            if (!$target) {
                $target = $(target);
            }
            $p.set($target, value);
            break;
        case 'SetData':
            $p.setData($(target));
            break;
        case 'SetFormData':
            data[target] = value;
            break;
        case 'SetMemory':
            $p[target] = value;
            break;
        case 'Append':
            $(target).append(value);
            break;
        case 'Prepend':
            $(target).prepend(value);
            break;
        case 'After':
            if ($(target).length !== 0) {
                $(target).after(value);
            } else {
                $control.after(value);
            }
            break;
        case 'Before':
            if ($(target).length !== 0) {
                $(target).before(value);
            } else {
                $control.before(value);
            }
            break;
        case 'InsertText':
            $p.insertText($(target), value);
            break;
        case 'Remove':
            $(target).remove();
            break;
        case 'Attr':
            var json = JSON.parse(value);
            $(target).attr(json.Name, json.Value);
            break;
        case 'RemoveAttr':
            $(target).removeAttr(value);
            break;
        case 'Css':
            var json = JSON.parse(value);
            $(target).css(json.Name, json.Value);
            break;
        case 'Focus':
            if (target === '') {
                $('#' + data.ControlId).focus();
            } else {
                $(target).focus();
            }
            break;
        case 'SetValue':
            $p.setValue($(target), value);
            $p.hideField(target, options);
            break;
        case 'ClearFormData':
            $p.clearData(target, data, value);
            break;
        case 'CloseDialog':
            $p.clearMessage();
            if (target !== undefined) { 
                if ($(target).hasClass('ui-dialog-content')) {
                    $(target).dialog('close');
                }
            } else {
                $('.ui-dialog-content').dialog('close');
            }
            break;
        case 'Paging':
            $p.paging(target);
            break;
        case 'Toggle':
            $(target).toggle(value === '1');
            break;
        case 'Trigger':
            $(target).trigger(value);
            break;
        case 'Invoke':
            $p[target](value);
            break;
        case 'Events':
            $p.execEvents(target, '');
            break;
        case 'WindowScrollTop':
            $(window).scrollTop(value);
            break;
        case 'ScrollTop':
            $(target).scrollTop(value);
            break;
        case 'LoadScroll':
            $p.loadScroll();
            break;
        case 'FocusMainForm':
            $p.focusMainForm();
            break;
        case 'Disabled':
            $(target).prop('disabled', value);
        case 'Log':
            if (value) {
                console.log(value);
            }
            break;
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/_dispatch.js#L26-L162

ここまで来ると何か見たことがあるものがありませんか？そうですswitch文で処理されている`method`です。
この`method`がマニュアルに記載されている引数`method`そのものになります。

## `method`を細かく見ていく
では実際にそれぞれの`method`でどのような処理が行われているか見てみましょう。

### Html
`$(target).html(value);`とある通り、jQueryの`html`メソッドそのままです。`target`で指定された要素の中身を`value`で書き換えます。

### ReplaceAll ※マニュアルに記載あり
`$(target).replaceAll(value);`とある通り、jQueryの`replaceAll`メソッドそのままです。`target`で指定された要素ごと`value`で書き換えます。

### Message
`$p.setMessage(target, value);`なので、[スクリプトの`$p.setMessage`](https://pleasanter.org/ja/manual/script-set-message)になります。

### Href
`$control.addClass('no-send');`で現在の画面内容をサーバに対して送信しない様にしたあとに、`$p.transition`を呼んでいます。`$p.transition`の実装は下記のようになっています。

```js:Implem.Pleasanter/wwwroot/scripts/navigation.js
$p.transition = function (url) {
    try {
        location.href = url;
    } catch (e) {
        if (e.number !== -2147467259) {
            throw e;
        }
    }
};
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/navigation.js#L39-L37

`location.href`に`value`を渡しています。`location.href`はJavaScriptの標準プロパティなので、mdn web docsを参照してください。[location: href プロパティ](https://developer.mozilla.org/ja/docs/Web/API/Location/href)

### PushState
`history.pushState`を呼び出しています。JavaScriptの標準メソッドなので、mdn web docsを参照してください。[History: pushState() メソッド](https://developer.mozilla.org/ja/docs/Web/API/History/pushState)

### Set ※マニュアルに記載あり
指定された要素に`$p.set`で値をセットしています。ポイントとなるのは詳しくは`$p.getControl`で標準項目の取得を行い、取得出来なければそのままで要素が使用されるという部分です。`$p.set`については[マニュアル](https://pleasanter.org/ja/manual/script-set)を参照してください。

### SetData
指定された要素に`$p.setData`で値をセットしています。`$p.setData`はマニュアルに記載がないので簡単に紹介します。実装を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/_data.js
$p.setData = function ($control, data) {
    var controlId = $control.attr('id');
    if (!$control.hasClass('not-send')) {
        if (data === undefined) {
            data = $p.getData($control);
        }
        $p.setGridTimestamp($control, data);
        switch ($control.prop('type')) {
            case 'checkbox':
                data[controlId] = $control.prop('checked');
                break;
            case 'radio':
                if ($control.prop('checked')) {
                    var name = $control.attr('name');
                    data[name] = $control.val();
                    $('[id="' + name + '"]').val($control.val());
                    $('[id="' + name + '-error"]').remove();
                }
                break;
            default:
                switch ($control.prop('tagName')) {
                    case 'SPAN':
                        data[controlId] = $control.attr('data-value') !== undefined
                            ? $control.attr('data-value')
                            : $control.text();
                        break;
                    case 'SELECT':
                        if ($control.attr('multiple')) {
                            $p.setMultiSelectData($control);
                        } else {
                            data[controlId] = $control.val();
                        }
                        break;
                    case 'OL':
                        if ($control.hasClass('control-selectable')) {
                            data[controlId] = $p.toJson($control.find('li.ui-selected'));
                            if ($control.hasClass('send-all')) {
                                data[controlId + 'All'] = $p.toJson($control.find('li'));
                            }
                        } else {
                            data[controlId] = $p.toJson($control.find('li'));
                        }
                        break;
                    case 'TABLE':
                        data[controlId] = JSON.stringify($control
                            .find('.select')
                            .filter(':checked')
                            .map(function () {
                                return $(this).closest('.grid-row').attr('data-id');
                            })
                            .toArray());
                        break;
                    case 'P':
                        if ($control.hasClass('control-slider')) {
                            data[controlId] = $control.attr('data-value');
                        } else {
                            data[controlId] = $control.val();
                        }
                        break;
                    default:
                        data[controlId] = $control.val();
                        break;
                }
                break;
        }
    }
}
```
https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/_data.js#L67-L133

指定された要素が`not-send`という属性を持つときのみ`data`という連想配列にセットされているデータを詰め替えています。この`data`というのはプリザンターのブラウザからサーバに対して送るデータを保持しているものになります。UIや`$p.set`などでデータの変更を行うときに裏で動いている関数となります。通常は使うことがないので詳細は割愛します。

### SetFormData
前項の`SetData`と類似ではあるのですが、コントロールを直接指定しているという部分が異なります。通常は使うことがないので詳細は割愛します。

### SetMemory
`$p`という連想配列にデータをセットします。メソッド名の示すとおり一時的にデータを保管する用途で使用されます。任意の関数を作成して一時的に格納するということにも使用可能で、その場合は`Invoke`で発火させます。

### Append
`$(target).append(value);`とある通り、jQueryの`append`メソッドそのままです。`value`を`target`の末尾に追加します。

### Prepend
`$(target).prepend(value);`とある通り、jQueryの`prepend`メソッドそのままです。`value`を`target`の先頭に追加します。

### After
`$(target).after(value);`とある通り、jQueryの`after`メソッドそのままです。`target`の直後に要素を追加します。

### Before
`$(target).before(value);`とある通り、jQueryの`before`メソッドそのままです。`target`の直前に要素を追加します。

### InsertText
`$p.insertText`を呼び出しています。`$p.insertText`の実装を見てみましょう。

```js:
$p.insertText = function ($control, value) {
    var body = $control.get(0);
    body.focus();
    var start = body.value;
    var caret = body.selectionStart;
    var next = caret + value.length;
    body.value = start.substr(0, caret) + value + start.substr(caret);
    body.setSelectionRange(next, next);
    $p.setData($control);
    if (!$control.is(':visible')) {
        $p.showMarkDownViewer($control);
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/markdown.js#L146-L158

指定された要素の末尾で文字列を追記して、送信データに組み入れる処理を行っています。説明項目などに文字列を追記する時に使用します。

### Remove
`$(target).remove();`とある通り、jQueryの`remove`メソッドそのままです。`target`をまるごと削除します。

### Attr
`$(target).attr(json.Name, json.Value);`とある通り、jQueryの`attr`メソッドそのままです。`target`に属性を追加します。使い方に若干癖があり、`value`に対して`{Name:'',Value:''}`というJSONをシリアライズしたものを格納する必要があります。

### RemoveAttr
` $(target).removeAttr(value);`とある通り、jQueryの`removeAttr`メソッドそのままです。`target`から指定属性を削除します。

### Css
`$(target).css(value);`とある通り、jQueryの`css`メソッドそのままです。`target`に対してCSSを適用します。使い方に若干癖があり、`value`に対して`{Name:'',Value:''}`というJSONをシリアライズしたものを格納する必要があります。

### Focus
`$(target).focus();`とある通り、jQueryの`focus`メソッドそのままです。`target`に対してファーカスを当てる操作を行います。

### SetValue
`$p.setValue($(target), value);`と`$p.hideField(target, options);`が連続実行されています。まずは`$p.setValue`の実装を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/_view.js
$p.setValue = function ($control, value) {
    switch ($control.prop('type')) {
        case 'checkbox':
            $control.prop('checked', value);
            break;
        case 'radio':
            $control.val([value]);
            break;
        case 'textarea':
            $control.val(value);
            $p.showMarkDownViewer($control);
            break;
        default:
            switch ($control.prop('tagName')) {
                case 'SELECT':
                    if ($control.attr('multiple')) {
                        $p.selectMultiSelect($control, value);
                    } else {
                        $control.val(value);
                    }
                    break;
                case 'SPAN':
                    $control.html(value);
                case 'TIME':
                    $control.html(value);
                    $control.attr('datetime', value);
                    break;
                default:
                    if ($control.hasClass('radio-value')) {
                        // type="radio"のチェック変更
                        $('input[name="' + $control.attr('id') + '"]').val([value]);
                    }
                    $control.val(value);
                    break;
            }
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/_view.js#L1-L36

やっていることはとても簡単で指定要素を`value`で書き換えています。`$p.set`と似たような処理ではあるのですが、差違としては`$p.setData`を叩いていない（=サーバに送るデータとして処理しない）部分になります。

次は`$p.hideField`を見てみましょう。
```js:Implem.Pleasanter/wwwroot/scripts/visibility.js
$p.hideField = function (target, options) {
    $field = $(target + 'Field');
    if (options.Hide && !$field.hasClass('hidden')) {
        $field.addClass('hidden');
    } else if (options.Hide !== true && $field.hasClass('hidden')) {
        $field.removeClass('hidden');
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/visibility.js#L1-L8

項目の表示・非表示を判断して、表示・非表示を再設定しています。`$p.setValue`で表示・非表示が外れることがあるので、この関数で復旧させています。

画面上の要素で表示名などのデータ以外の項目を書き換えたいときに使用します。

### ClearFormData
`$p.clearData`を呼び出しています。実装を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/_data.js
$p.clearData = function (target, data, type) {
    if (!data) {
        data = $p.getData($('.main-form'));
    }
    if (target === undefined) {
        for (controlId in data) {
            if (!$('#' + controlId).hasClass('control-selectable')) {
                Delete(controlId);
            }
        }
    } else if (type === 'startsWith') {
        for (controlId in data) {
            if (controlId.indexOf(target) === 0) {
                Delete(controlId);
            }
        }
    } else {
        if (target in data) {
            Delete(target);
        } else if ($(target).length !== 0) {
            Delete($(target).attr('id'));
        }
    }
    function Delete(key) {
        if (type === 'ignoreView') {
            if (key.indexOf('View') === 0) {
                return;
            }
        }
        delete data[key];
    }
};
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/_data.js#L167-L198

`$p.setData`とは反対に`data`にセットされたデータを削除(=サーバに送信しないデータにする)を行っています。通常は使用することはないため割愛します。

### CloseDialog
開かれているダイアログを閉じるために使用します。通常は使用することはないため割愛します。

### Paging
`$p.paging`を呼び出しています。実装を見てみましょう。

```js:
$p.paging = function (selector) {
    if ($('.ui-dialog:visible').length > 0) {
        return;
    }
    var $control = $(selector);
    var $offset = $(selector + 'Offset');
    if ($control.length) {
        if ($(window).scrollTop() + $(window).height() >= $control.offset().top + $control.height()) {
            if ($offset.val() !== '-1') {
                $p.setData($offset);
                $offset.val('-1');
                $p.send($control);
            }
        }
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/scroll.js#L15-L30

一覧項目などでサイトの下部までスクロールした際に次の項目を読み出してくるときに使用します。通常は使用することはないため割愛します。

### Toggle
`$(target).toggle(value === '1');`とある通り、jQueryの`toggle`メソッドそのままです。指定された要素の表示・非表示を切替ます。`value`が`1`かそうでないかで`true/false`を指定しているので、`true/false`を直接指定出来ないことに注意が必要です。

### Trigger
`$(target).trigger(value);`とある通り、jQueryの`trigger`メソッドそのままです。指定された要素に対してトリガーを発火させることができます。サーバスクリプト側から選択肢一覧のモーダルを表示させたり、一覧メニューを表示させたりとUI操作でかなり強力な操作が可能です。

### Invoke
`$p[target](value);`と書いてある通り、`$p`にセットされている任意の関数を実行出来ます。`SetMemory`でセットして`Invoke`で発火させるという使い方になります。

### Events
`$p.execEvents`を呼び出しています。実装を見てみましょう。

```js:
$p.execEvents = function (event, args) {
    var result = exec(event);
    var $control = args.$control;
    if ($control) {
        result = exec(event + '_' + $control.attr('id')) && result;
        if ($control.attr('id') !== $control.attr('data-action')) {
            result = exec(event + '_' + $control.attr('data-action')) && result;
        }
    }
    return result;
    function exec(name) {
        if ($p.events[name] !== undefined) {
            return ($p.events[name](args) === false) ? false : true;
        }
        return true;
    }
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/_event.js#L13-L29

かなり強力な機能で、任意の関数を実行することが可能ですが、実行対象は`$p.events`に格納されている関数になるので、使いどころがむずかいしいところがあります。

### WindowScrollTop
`$(window).scrollTop(value);`とある通り、jQueryの`scrollTop`メソッドそのままです。`value`に指定された数値の位置までスクロールします。

### ScrollTop
`$(target).scrollTop(value);`とある通り、jQueryの`scrollTop`メソッドそのままです。`value`に指定された数値の位置までスクロールします。`WindowScrollTop`との違いは、ウインドウではなく要素が指定されているところになります。

### LoadScroll
`$p.loadScroll`を呼んでいるので、`$p.loadScroll`の実装を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/scroll.js
$p.loadScroll = function () {
    window.scroll($p.scrollX, $p.scrollY);
}
```
https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/scroll.js#L6-L8

さらに別の`$p.scrollX`と`$p.scrollY`も出てきたので、こちらの実装も見てみます。

```js:Implem.Pleasanter/wwwroot/scripts/scroll.js
$p.saveScroll = function () {
    $p.scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
    $p.scrollY = document.documentElement.scrollTop || document.body.scrollTop;
}

$p.clearScroll = function () {
    $p.scrollX = 0;
    $p.scrollY = 0;
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/scroll.js#L1-L4

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/scroll.js#L10-L13

これは現在のスクロール位置を取得しています。
画面の再ロードがおこなわれた際に元々の表示位置に画面をスクロールするのに使用します。通常は使用することはない思います。

### FocusMainForm
` $p.focusMainForm`が呼び出されています。実装を見てみましょう。

```js:Implem.Pleasanter/wwwroot/scripts/focus.js
$p.focusMainForm = function () {
    $('#FieldSetGeneral').find('[class^="control-"]').each(function () {
        if (!$(this).is(':hidden') &&
            !$(this).hasClass('control-text') &&
            !$(this).hasClass('control-markup')) {
            $(this).focus();
            return false;
        }
    });
}
```

https://github.com/Implem/Implem.Pleasanter/blob/59441e3203d8ba637d1511e5689eb17b3bad330b/Implem.Pleasanter/wwwroot/scripts/focus.js#L1-L10

編集画面中の先頭のアイテムにフォーカスを当てる動作をしています。通常は使用することはないと思います


### Disabled
`$(target).prop('disabled', value);`とある通り、jQueryの`prop`メソッドそのままです。指定要素に`disabled`を付与したり外したりといった操作が可能です。

### Log
`value`に値が格納されていれば`console.log`で値が出力されます。`context.Log`と同じ働きをします。

## まとめ
今回は`context.AddResponse`の中身を見てみました。マニュアルには記載がないけどかなり使える`method`が複数あります。`Href`や`WindowScrollTop`などかなり使いどころがある機能なので、是非使ってみてください。