Skip to content

dispatch の実装サンプル ​

第3版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

dispatch の仕組みで説明した命令を、目的別に組み合わせます。サーバースクリプトは応答を作り、ブラウザ側のスクリプトがその命令を実行します。同じ JavaScript でも、配置する場所が違います。

本体 1.5.8.1 の JavaScript を DOM 環境で動かして命令の挙動を確認したサンプルです。プリザンター実機での動作確認結果ではありません。サーバーとの連携例は、タイトル項目がある検証用テーブルの通常の編集画面で試してください。

画面の値と送信バッファを明示的に揃える ​

SetValue で画面を更新し、SetFormData で応答処理に渡された送信バッファへ同じ値を登録します。通常の単一選択の Set が発火させる change に依存せず、反映先を明示する組み合わせです。

サーバースクリプトの実行条件を「画面表示の前」にします。既存レコードの編集画面だけを対象にするため、コードでも条件を絞ります。

js
if (context.Controller === 'items' && context.Action === 'edit') {
    const id = context.ReferenceType + '_Title';
    const value = '応答で設定したタイトル';
    context.AddResponse('SetValue', '#' + id, value);
    context.AddResponse('SetFormData', id, value);
}

編集画面を開くと、タイトル入力欄と $p.data.MainForm の該当キーを確認できます。

js
const $title = $p.getControl('Title');
console.log($title.val());
console.log($p.getData($title)[$title.attr('id')]);

この応答だけで DB のタイトルを保存するわけではありません。保存値と比較するには、更新操作と再読み込みが必要です。別の応答に ClearFormData やフィールドの差し替えがあれば、その後の値も調べます。初回表示時の応答に渡されるデータは #MainForm のバッファです(初回の呼び出し)。条件名と URL の関係は contextを参照してください。

非表示のまま値を変えたい場合 ​

SetValue は値の設定後に hideField も呼びます。Options を省略すると Hide が true ではないため、対象フィールドの hidden クラスを外します。非表示を維持する場合は、上の SetValue の行を次に置き換えます。

js
context.AddResponse(
    'SetValue',
    '#' + id,
    value,
    JSON.stringify({ Hide: true })
);

id と value は上のサーバーコードで定義した変数です。対象は入力要素、表示を制御する要素はその ID に Field を付けたものです(SetValue、hideField)。

サーバーの結果を独自のブラウザ関数へ渡す ​

サーバーで作った状態を SetMemory に積み、直後の Invoke で表示します。自作のサンプルでは JSON 文字列で受け渡しし、ブラウザ側で解析します。

ブラウザ側のスクリプトを新規・編集画面に配置します。関数の定義を on_editor_load の中に入れず、トップレベルに置きます。初回の応答から呼ばれる時点で関数が必要だからです。

js
$p.manualRenderHint = function (json) {
    const request = JSON.parse(json);
    const state = JSON.parse($p.manualDispatchState);
    const $field = $p.getField('Title');
    if (!$field || $field.length !== 1) return;

    let $hint = $('#ManualDispatchHint');
    if ($hint.length === 0) {
        $hint = $('<div/>').attr('id', 'ManualDispatchHint').insertAfter($field);
    }
    $hint.text(request.text + ' / ' + state.stage);
};

サーバースクリプトの実行条件を「画面表示の前」にして、次を配置します。最初のサンプルとは別に試せます。

js
if (context.Controller === 'items' && context.Action === 'edit') {
    context.AddResponse(
        'SetMemory',
        'manualDispatchState',
        JSON.stringify({ stage: '確認待ち' })
    );
    context.AddResponse(
        'Invoke',
        'manualRenderHint',
        JSON.stringify({ text: 'サーバーから受け取った状態' })
    );
}

タイトルの下に サーバーから受け取った状態 / 確認待ち と表示します。SetMemory を後ろへ移すと、関数はその応答の新しい状態をまだ読めません。Invoke の対象は $p の直接のキーです。manualRenderHint を指定し、$p.manualRenderHint という文字列は指定しません(命令の実装、Invoke)。

表示には .text() を使うので、受け取った文字列を HTML として挿入しません。この関数は同期処理です。Invoke は Promise の完了を待たないため、後続命令が非同期処理の結果に依存する構成には使えません。

フィールド差し替え後に説明を付け直す ​

タイトルのフィールド内に独自の説明を置く例です。応答でフィールドが置き換わると、その中の説明も失われるため、現在の DOM を取得して作り直します。Invoke は UI の再適用より前に実行されます。再適用後に処理したい場合は after_set を使います。

以下はブラウザ側のスクリプトです。初回表示には on_editor_load、URL を伴う応答には after_set を使います。

js
$p.manualRebuildTitleNote = function () {
    const $field = $p.getField('Title');
    if (!$field || $field.length !== 1) return;
    if ($field.find('.manual-title-note').length === 0) {
        $('<small/>')
            .addClass('manual-title-note')
            .text('タイトルは内容が分かる名前を入力してください。')
            .appendTo($field);
    }
};

$p.events.on_editor_load = function () {
    $p.manualRebuildTitleNote();
};

$p.events.after_set = function (args) {
    const htmlMethods = ['Html', 'ReplaceAll', 'Append', 'Prepend', 'After', 'Before'];
    if (args.json.some(function (command) {
        return htmlMethods.indexOf(command.Method) !== -1;
    })) {
        $p.manualRebuildTitleNote();
    }
};

このコードを繰り返し呼んでも、同じフィールド内に説明を重複追加しません。初回の setByJson は URL が渡されないため after_set が呼ばれず、初回用のイベントが必要です。応答時には全命令・apply・applyValidator の後で after_set が呼ばれます(実行順、編集画面の初回イベント)。

既存のイベントへそのまま代入すると、その処理を上書きします。既存処理があるテーブルでは 複数処理の登録のヘルパーを使って、この2つの処理を追加してください。

保存せずに命令を比較する実験用フォーム ​

通常の編集画面の開発者ツールで、次のブラウザ用コードを実行します。専用のフォームと送信バッファを作り、レコードの入力欄とは別に動きを比べます。送信を防ぐため、フォームの submit を止めます。

js
if ($('#ManualDispatchForm').length === 0) {
    $('<form id="ManualDispatchForm">' +
        '<div id="ManualDispatchTextField">' +
        '<input id="ManualDispatchText" type="text" value="初期値"></div>' +
        '<select id="ManualDispatchChoice">' +
        '<option value="a">A</option><option value="b">B</option></select>' +
        '</form>')
        .on('submit', function (event) { event.preventDefault(); })
        .appendTo('body');
}
$p.manualDispatchLab = function (commands) {
    const data = $p.getData($('#ManualDispatchText'));
    $p.setByJson(undefined, 'post', data, $('#ManualDispatchText'), true, commands);
    return data;
};

URL を undefined にしているため、before_set と after_set は呼ばれません。true はイベント引数用の値であり、この呼び出しを非同期にする指定ではありません。HTML 更新の命令を加えると本体の UI 再適用も呼ばれるため、ここでは値とバッファの命令だけを試します。

SetValue だけではバッファに入らない ​

js
delete $p.getData($('#ManualDispatchText')).ManualDispatchText;
const result = $p.manualDispatchLab([
    { Method: 'SetValue', Target: '#ManualDispatchText', Value: '画面だけ' }
]);
console.log($('#ManualDispatchText').val()); // 画面だけ
console.log(result.ManualDispatchText); // undefined

$p.manualDispatchLab([
    { Method: 'SetFormData', Target: 'ManualDispatchText', Value: '送信だけ' }
]);
console.log($('#ManualDispatchText').val()); // 画面だけ
console.log(result.ManualDispatchText); // 送信だけ

result はバッファそのものの参照なので、次の命令による変更も読み取れます。

SetData は現在の DOM の値を登録する ​

js
const result = $p.manualDispatchLab([
    { Method: 'SetData', Target: '#ManualDispatchText' }
]);
console.log(result.ManualDispatchText); // 画面だけ

$p.manualDispatchLab([
    { Method: 'ClearFormData', Target: 'ManualDispatchText' }
]);
console.log(result.ManualDispatchText); // undefined
console.log($('#ManualDispatchText').val()); // 画面だけ

前の実験の続きで実行します。ClearFormData はキーを消しますが入力欄を消しません。このあと SetData を実行すれば、同じ値をまた登録できます。SetData は対象要素のフォームを探し、SetFormData は setByJson に渡した data を直接操作します。この例では両方が同じフォームを指しています(データ操作、SetData、ClearFormData)。

通常の単一選択で change の有無を比べる ​

js
let changes = 0;
$('#ManualDispatchChoice')
    .off('change.manualDispatchLab')
    .on('change.manualDispatchLab', function () { changes += 1; });

$p.manualDispatchLab([
    { Method: 'SetValue', Target: '#ManualDispatchChoice', Value: 'b' }
]);
console.log(changes); // 0

$p.manualDispatchLab([
    { Method: 'Set', Target: '#ManualDispatchChoice', Value: 'a' }
]);
console.log(changes); // 1
console.log($p.data.ManualDispatchForm.ManualDispatchChoice); // a

検索付き・複数選択ではない通常の単一選択の例です。Set はコントロールの解決後に $p.set を呼び、この種類の選択欄では change を発火させてから setData を実行します(Set の対象解決、単一選択とバッファ登録)。

実験を終えたら、フォームとこの例で追加した関数・バッファを削除します。

js
$('#ManualDispatchForm').remove();
delete $p.data.ManualDispatchForm;
delete $p.manualDispatchLab;

関連ページ ​

変更履歴

第3版記事のリスト内の空行を整理して表示間隔を統一
第2版AddResponseの用途別実例と確認手順を追加
第1版dispatchの応答処理と実装サンプルを詳しく解説