はじめに
プリザンターの拡張スクリプトを書いていると、ユーザーにメッセージを見せたり、確認を取ったり、テキストを入力してもらう場面がよくあります。標準の window.alert や window.confirm は見た目がブラウザ依存で少し味気ないですし、外部のダイアログライブラリを組み込むのも手間がかかります。
実はプリザンターには jQuery UI が組み込まれていて、.dialog() メソッドがそのまま使えます。v2 テーマではデートピッカーが flatpickr に置き換わるなど jQuery UI 離れが進んでいますが、ダイアログ機能についてはテーマバージョンを問わず jquery-ui.min.js と jquery-ui.min.css が読み込まれており、$p.openDialog / $p.closeDialog も内部で .dialog() を呼んでいます。v2 テーマの style.scss でも .ui-dialog に対して CSS カスタムプロパティでスタイルを当てているため、ダイアログは引き続き jQuery UI ベースで動作します。
ただ、毎回 HTML を組み立てて .dialog() のオプションを書くのは面倒です。
この記事では、jQuery UI の .dialog() をラップして $p.modal.alert()・$p.modal.confirm()・$p.modal.prompt() のように呼び出せる軽量なラッパーを拡張スクリプトで作ってみます。外部ライブラリは一切不要で、プリザンター標準の jQuery UI だけで動作します。v1・v2 どちらのテーマでも利用できます。
仕組みを整理する
ラッパーの設計方針を整理します。
| 項目 | 内容 |
|---|---|
| 依存ライブラリ | なし(プリザンター標準の jQuery / jQuery UI のみ) |
| 名前空間 |
$p.modal に各メソッドを追加 |
| メソッド |
alert / confirm / prompt / show の 4 種類 |
| 戻り値 | すべて jQuery Deferred(done / fail でコールバック) |
| スタイル | 拡張スタイルで jQuery UI のデフォルトテーマを上書き |
メソッド一覧
| メソッド | 用途 | ボタン | 戻り値 |
|---|---|---|---|
$p.modal.alert(msg) |
情報・警告の表示 | OK |
done で OK 押下後の処理 |
$p.modal.confirm(msg) |
操作の確認 | OK / キャンセル |
done(OK) / fail(キャンセル) |
$p.modal.prompt(msg) |
テキスト入力 | OK / キャンセル |
done(value)(入力値) / fail(キャンセル) |
$p.modal.show(opt) |
任意の HTML を表示 | 自由に設定 |
done(result) / fail
|
実装してみよう
拡張スタイルと拡張スクリプトの 2 ファイルで構成します。
| 拡張機能 | 役割 |
|---|---|
| 拡張スタイル | jQuery UI ダイアログの見た目を調整 |
| 拡張スクリプト |
$p.modal 名前空間にメソッドを追加 |
拡張スタイル
拡張スタイルとして App_Data/Parameters/ExtendedStyles/ に配置します。前半は v1 テーマ向けの固定値スタイルで、後半の v2 テーマ向けセクションで CSS カスタムプロパティ(--base-bg / --base-text / --btn-positive-bg 等)による上書きを行います。v2 テーマでは :root にカスタムプロパティが定義されているため自動でテーマの配色に切り替わり、v1 テーマではカスタムプロパティが未定義のため前半の固定値がそのまま使われます。
/* --- モーダルラッパー専用スタイル --- */
/* ── 共通 / v1テーマ向け(固定値) ─────────────── */
/* オーバーレイ */
.modal-wrapper-overlay {
background: rgba(0, 0, 0, 0.5) !important;
z-index: 10000 !important;
}
/* ダイアログ本体 */
.ui-dialog.modal-wrapper {
border: none;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
padding: 0;
z-index: 10001;
font-family: inherit;
}
/* タイトルバー */
.ui-dialog.modal-wrapper .ui-dialog-titlebar {
background: #fff;
border: none;
border-bottom: 1px solid #e0e0e0;
border-radius: 8px 8px 0 0;
padding: 16px 20px;
font-size: 16px;
font-weight: bold;
color: #333;
}
/* 閉じるボタン非表示 */
.ui-dialog.modal-wrapper .ui-dialog-titlebar-close {
display: none;
}
/* コンテンツ */
.ui-dialog.modal-wrapper .ui-dialog-content {
padding: 20px;
font-size: 14px;
line-height: 1.7;
color: #444;
}
/* メッセージテキスト */
.ui-dialog.modal-wrapper .mw-message {
white-space: pre-wrap;
word-break: break-word;
}
/* 入力欄 */
.ui-dialog.modal-wrapper .mw-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
margin-top: 12px;
color: #333;
background: #fff;
}
.ui-dialog.modal-wrapper .mw-input:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
/* ボタン領域 */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane {
border-top: 1px solid #e0e0e0;
padding: 12px 20px;
background: #fafafa;
border-radius: 0 0 8px 8px;
margin-top: 0;
}
/* ボタン共通 */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button {
border: none;
border-radius: 4px;
padding: 8px 24px;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
margin-left: 8px;
}
/* プライマリボタン(OK) */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary {
background: #1976d2;
color: #fff;
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary:hover {
background: #1565c0;
}
/* セカンダリボタン(キャンセル) */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-secondary {
background: #e0e0e0;
color: #333;
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-secondary:hover {
background: #bdbdbd;
}
/* ── v2テーマ向け(CSSカスタムプロパティで自動切替) ── */
/* v2テーマ(cerulean / green-tea / mandarin / midnight)では
:root にカスタムプロパティが定義されるため以下で上書きされます。
v1テーマではカスタムプロパティが未定義のため
上の固定値がそのまま使われます。 */
.ui-dialog.modal-wrapper {
box-shadow: 0 8px 32px var(--base-shadow, rgba(0, 0, 0, 0.25));
}
.ui-dialog.modal-wrapper .ui-dialog-titlebar {
background: var(--base-bg, #fff);
border-bottom-color: var(--base-border, #e0e0e0);
color: var(--base-text, #333);
}
.ui-dialog.modal-wrapper .ui-dialog-content {
color: var(--base-text, #444);
}
.ui-dialog.modal-wrapper .mw-input {
border-color: var(--base-border, #ccc);
color: var(--base-text, #333);
background: var(--base-bg, #fff);
}
.ui-dialog.modal-wrapper .mw-input:focus {
border-color: var(--primaryColor, #1976d2);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane {
border-top-color: var(--base-border, #e0e0e0);
background: var(--base-bg-light, #fafafa);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary {
background: var(--btn-positive-bg, #1976d2);
color: var(--btn-positive-label, #fff);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary:hover {
background: var(--btn-positive-hover, #1565c0);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-secondary {
background: var(--btn-normal-bg, #e0e0e0);
color: var(--btn-normal-label, #333);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-secondary:hover {
background: var(--btn-normal-hover, #bdbdbd);
}
ポイントをまとめます。
-
.modal-wrapperクラスでスコープを限定し、プリザンター標準のダイアログに影響しないようにしています - jQuery UI が自動生成する
.ui-dialog/.ui-dialog-titlebar/.ui-dialog-buttonpaneを上書きしてモダンな見た目に変更しています - 前半は v1 テーマ向けの固定値スタイルです。v1 テーマでは CSS カスタムプロパティが未定義のため、この固定値がそのまま適用されます
- 後半の v2 テーマ向けセクションで CSS カスタムプロパティ(
--base-bg/--base-text/--btn-positive-bg等)による上書きを行います。v2 テーマ(cerulean / green-tea / mandarin / midnight)では:rootにカスタムプロパティが定義されているため、テーマの配色に自動で切り替わります -
.modal-wrapper-overlayクラスで jQuery UI のオーバーレイ(背景の半透明マスク)をカスタマイズします -
z-index: 10000以上にすることで、プリザンター標準の UI 要素より前面に表示されます - 閉じるボタン(×)は非表示にし、明示的にボタン操作で閉じるようにしています
拡張スクリプト
拡張スクリプトとして App_Data/Parameters/ExtendedScripts/ に配置します。
$(function () {
// ─── $p.modal 名前空間 ─────────────────────────────
$p.modal = {};
// ─── 共通:ダイアログ生成 ──────────────────────────
function createDialog(title, $content, buttons, options) {
var dfd = $.Deferred();
var $dlg = $('<div></div>').append($content);
var dialogClass = 'modal-wrapper'
+ (options && options.dialogClass ? ' ' + options.dialogClass : '');
$dlg.dialog($.extend({
title: title,
modal: true,
width: 420,
resizable: false,
closeOnEscape: false,
dialogClass: dialogClass,
buttons: buttons(dfd, $dlg),
create: function () {
// オーバーレイにカスタムクラスを付与
$(this).closest('.ui-dialog')
.prev('.ui-widget-overlay')
.addClass('modal-wrapper-overlay');
},
close: function () {
$(this).dialog('destroy').remove();
if (dfd.state() === 'pending') dfd.reject();
}
}, options || {}));
return dfd.promise();
}
// ─── alert ─────────────────────────────────────────
$p.modal.alert = function (message, title) {
var $content = $('<div class="mw-message"></div>').text(message);
return createDialog(title || 'メッセージ', $content, function (dfd, $dlg) {
return [
{
text: 'OK',
class: 'mw-primary',
click: function () {
dfd.resolve();
$dlg.dialog('close');
}
}
];
});
};
// ─── confirm ───────────────────────────────────────
$p.modal.confirm = function (message, title) {
var $content = $('<div class="mw-message"></div>').text(message);
return createDialog(title || '確認', $content, function (dfd, $dlg) {
return [
{
text: 'キャンセル',
class: 'mw-secondary',
click: function () {
dfd.reject();
$dlg.dialog('close');
}
},
{
text: 'OK',
class: 'mw-primary',
click: function () {
dfd.resolve();
$dlg.dialog('close');
}
}
];
});
};
// ─── prompt ────────────────────────────────────────
$p.modal.prompt = function (message, title, defaultValue) {
var $content = $('<div></div>')
.append($('<div class="mw-message"></div>').text(message))
.append(
$('<input type="text" class="mw-input">')
.val(defaultValue || '')
);
return createDialog(title || '入力', $content, function (dfd, $dlg) {
return [
{
text: 'キャンセル',
class: 'mw-secondary',
click: function () {
dfd.reject();
$dlg.dialog('close');
}
},
{
text: 'OK',
class: 'mw-primary',
click: function () {
dfd.resolve($dlg.find('.mw-input').val());
$dlg.dialog('close');
}
}
];
}, {
open: function () {
var $input = $(this).find('.mw-input');
$input.trigger('focus');
$input.on('keydown', function (e) {
if (e.key === 'Enter') {
$(this).closest('.ui-dialog')
.find('.mw-primary')
.trigger('click');
}
});
}
});
};
// ─── show(汎用) ──────────────────────────────────
$p.modal.show = function (options) {
var opt = $.extend({
title: 'ダイアログ',
content: '',
width: 420,
buttons: []
}, options);
var $content = typeof opt.content === 'string'
? $('<div></div>').html(opt.content)
: opt.content;
return createDialog(opt.title, $content, function (dfd, $dlg) {
if (!opt.buttons.length) {
return [{
text: '閉じる',
class: 'mw-secondary',
click: function () {
dfd.resolve();
$dlg.dialog('close');
}
}];
}
return opt.buttons.map(function (btn) {
return {
text: btn.text,
class: btn.class || 'mw-secondary',
click: function () {
if (btn.action) {
btn.action(dfd, $dlg);
} else {
dfd.resolve(btn.text);
$dlg.dialog('close');
}
}
};
});
}, { width: opt.width, dialogClass: opt.dialogClass || '' });
};
});
各処理のポイントを見ていきましょう。
共通関数 createDialog
すべてのメソッドの中核となる関数です。
| 引数 | 型 | 説明 |
|---|---|---|
title |
string | ダイアログのタイトル |
$content |
jQuery | ダイアログ本文の jQuery オブジェクト |
buttons |
function | Deferred と $dlg を受け取り、ボタン配列を返す関数 |
options |
object | jQuery UI の追加オプション |
$.Deferred() を使って非同期の結果を返します。ボタンが押されたら resolve(OK)または reject(キャンセル)を呼び、呼び出し側は .done() / .fail() で後続処理を書けます。
close イベントでは destroy と remove で DOM をクリーンアップします。Escape キーや×ボタンで閉じた場合も reject されるため、明示的にキャンセル扱いになります。
$p.modal.alert
メッセージと OK ボタンだけのシンプルなダイアログです。OK を押すと done が呼ばれます。
$p.modal.confirm
OK とキャンセルの 2 ボタン構成です。OK で done、キャンセルで fail が呼ばれます。
$p.modal.prompt
テキスト入力欄付きのダイアログです。OK で入力値が done に渡されます。Enter キーで OK ボタンを押せるようにしています。
$p.modal.show
任意の HTML コンテンツとボタンを自由に設定できる汎用メソッドです。ボタンを省略すると「閉じる」ボタンだけが表示されます。
使い方
ここからは実際の利用例を紹介します。拡張スクリプトやスクリプト項目の中で $p.modal を呼び出します。
メッセージを表示する
$p.modal.alert('処理が完了しました。');
タイトルを変えることもできます。
$p.modal.alert('入力内容に不備があります。', '入力エラー');
確認してから処理を実行する
$p.modal.confirm('この操作は取り消せません。実行しますか?')
.done(function () {
// OK が押された場合の処理
$p.send($('#UpdateButton'));
});
fail でキャンセル時の処理も書けます。
$p.modal.confirm('レコードを削除しますか?', '削除確認')
.done(function () {
$p.send($('#DeleteButton'));
})
.fail(function () {
console.log('キャンセルされました');
});
テキストを入力してもらう
$p.modal.prompt('コメントを入力してください', 'コメント追加')
.done(function (value) {
// 入力された値を分類項目にセット
$p.set($p.getControl('ClassA'), value);
});
初期値を指定することもできます。
$p.modal.prompt('理由を入力してください', '却下理由', '内容不備のため')
.done(function (value) {
$p.set($p.getControl('DescriptionA'), value);
$p.send($('#UpdateButton'));
});
自由なコンテンツを表示する
$p.modal.show({
title: 'レコード情報',
width: 500,
content:
'<table style="width:100%; border-collapse:collapse;">' +
'<tr><th style="text-align:left; padding:4px;">ID</th>' +
'<td style="padding:4px;">' + $p.getControl('ResultId').val() + '</td></tr>' +
'<tr><th style="text-align:left; padding:4px;">状態</th>' +
'<td style="padding:4px;">' + $p.getControl('Status').val() + '</td></tr>' +
'</table>',
buttons: [
{ text: 'コピー', class: 'mw-primary', action: function (dfd, $dlg) {
navigator.clipboard.writeText($p.getControl('ResultId').val());
dfd.resolve('copied');
$dlg.dialog('close');
}
},
{ text: '閉じる', class: 'mw-secondary' }
]
}).done(function (result) {
if (result === 'copied') {
$p.modal.alert('ID をクリップボードにコピーしました。');
}
});
イベントと組み合わせる
$p.events と組み合わせて、ボタンクリック時の確認ダイアログとして使えます。
$p.events.on_editor_load = function () {
// 更新ボタンにハンドラを追加
$('#UpdateButton').on('click', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
$p.modal.confirm('変更内容を保存しますか?', '保存確認')
.done(function () {
// 確認後に送信
$p.send($('#UpdateButton'));
});
return false;
});
};
カスタマイズ
ダイアログの幅を変える
alert・confirm・prompt のデフォルト幅は 420px です。拡張スクリプト内の createDialog 関数の width: 420 を変更するか、show メソッドの width オプションで個別に指定します。
ボタンの色を変える
v1 テーマ向けの固定値を変更します。v2 テーマではテーマの配色が自動で適用されるため、v2 テーマ向けセクションのフォールバック値もあわせて変更します。
/* v1テーマ向け(固定値) */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary {
- background: #1976d2;
+ background: #2e7d32;
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary:hover {
- background: #1565c0;
+ background: #1b5e20;
}
/* v2テーマ向け(CSS変数のフォールバック値もあわせて変更) */
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary {
- background: var(--btn-positive-bg, #1976d2);
+ background: var(--btn-positive-bg, #2e7d32);
}
.ui-dialog.modal-wrapper .ui-dialog-buttonpane button.mw-primary:hover {
- background: var(--btn-positive-hover, #1565c0);
+ background: var(--btn-positive-hover, #1b5e20);
}
タイトルバーの背景色を変える
/* v1テーマ向け(固定値) */
.ui-dialog.modal-wrapper .ui-dialog-titlebar {
- background: #fff;
+ background: #e3f2fd;
}
/* v2テーマ向け(CSS変数のフォールバック値もあわせて変更) */
.ui-dialog.modal-wrapper .ui-dialog-titlebar {
- background: var(--base-bg, #fff);
+ background: var(--base-bg, #e3f2fd);
}
ブラウザ標準ダイアログとの比較
ブラウザ標準のダイアログと比較した場合の違いを整理します。
| 項目 | ブラウザ標準 | $p.modal |
|---|---|---|
| 見た目 | ブラウザ依存 | CSS でカスタマイズ可能 |
| 処理の流れ | 同期(ページが止まる) | 非同期(done / fail コールバック) |
| 入力の種類 | テキストのみ | HTML で自由に構成可能 |
| 複数ボタン | 対応・非対応(2 種類まで) | 自由に追加可能 |
| 外部ライブラリ | 不要 | 不要(jQuery UI を使用) |
| 他のダイアログとの共存 | 非対応(ブロック) | 対応 |
まとめ
- プリザンターに組み込まれている jQuery UI の
.dialog()をラップして、$p.modal.alert/confirm/prompt/showの 4 メソッドを提供するラッパーを作りました - 外部ライブラリを追加せずに、拡張スクリプトと拡張スタイルの 2 ファイルだけで動作します
- v1・v2 どちらのテーマでも利用できます。v1 テーマでは固定値のスタイルが適用され、v2 テーマでは CSS カスタムプロパティによりテーマの配色に自動で切り替わります
-
$.Deferredを使った非同期 API なので、done/failで直感的にコールバックを書けます -
$p.modal.showで任意の HTML コンテンツとボタンを自由に構成でき、汎用的に使えます - CSS を変更するだけで見た目をカスタマイズできます
-
$p.eventsと組み合わせることで、ボタン操作の確認ダイアログとしても活用できます