概要
メッセージダイアログを自作し、その応答を取得します。
Razor Componentならではの興味深い書き方になっていると思います。
環境
VS2022のBlazor Appテンプレートからプロジェクトを作成します。
Render modeをInteractive WebAssembly
とするRazor componentを利用します。
選定基準に記載のように、デプロイ環境に大きな制約が出るため、基本的には、WebAssemblyが無難と考えています。
実装
プロジェクト名はBlazorAppTipsで進めていきます。
メッセージダイアログの作成については、Modalの書き方をベースに、JavaScriptを利用する形で実現しています。
- Dialogを呼び出すページを追加
@page "/dialog-caller"
@rendermode InteractiveWebAssembly
@inject IJSRuntime JSRuntime
<h3>DialogCaller</h3>
<button id="open-button" @onclick="OpenDialog">Open</button>
<MessageDialog @ref="messageDialog" />
@code {
private MessageDialog messageDialog = default!;
private async Task OpenDialog()
{
await JSRuntime.InvokeVoidAsync("toggle", true);
var result = await messageDialog.GetResult();
if (result == MessageDialog.Result.Yes)
Console.WriteLine("Yes");
else
Console.WriteLine("No");
}
}
Style
#open-button {
cursor: pointer;
border: 1px solid #000;
margin: 10px;
}
- Dialogを追加
@inject IJSRuntime JSRuntime
<div id="overlay" @onclick="Close" />
<div id="dialog">
<div id="container">
<button @onclick="OnYes">Yes</button>
<button @onclick="OnNo">No</button>
</div>
</div>
@code {
public enum Result
{
Yes,
No
}
private TaskCompletionSource<Result>? tsc;
public Task<Result> GetResult()
{
tsc = new TaskCompletionSource<Result>();
return tsc.Task;
}
private async Task OnYes()
{
if (tsc is not null && !tsc.Task.IsCompleted)
tsc.SetResult(Result.Yes);
await Close();
}
private async Task OnNo()
{
if (tsc is not null && !tsc.Task.IsCompleted)
tsc.SetResult(Result.No);
await Close();
}
private async Task Close()
{
await JSRuntime.InvokeVoidAsync("toggle", false);
}
}
Style
#dialog {
opacity: 0;
visibility: hidden;
transition: all 0.3s ease-out;
position: fixed;
top: 50%;
left: 50%;
z-index: 1000;
transform: translate(-50%, -50%);
width: 30%;
height: 30%;
border: 1px solid black;
background-color: lightblue;
}
#container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
button {
width:80px;
height:30px;
margin:0 10px;
border: 1px solid #000;
text-align: center;
}
#overlay {
opacity: 0;
visibility: hidden;
transition: all 0.3s ease-out;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
background-color: lightgray;
}
- JavaScriptを追加
<body>
@* 以下のscriptを追加 *@
<script>
// 表示処理
function toggle(isOpen) {
// エレメントを取得
const dialog = document.getElementById('dialog');
const overlay = document.getElementById('overlay');
// Scrollを固定するためのエレメント
const body = document.body;
if (isOpen) {
dialog.style.opacity = 1;
dialog.style.visibility = 'visible';
overlay.style.opacity = 1;
overlay.style.visibility = 'visible';
body.style.overflow = 'hidden';
} else {
dialog.style.opacity = 0;
dialog.style.visibility = 'hidden';
overlay.style.opacity = 0;
overlay.style.visibility = 'hidden';
body.style.overflow = 'auto';
}
}
</script>
<body>
確認
http://localhost:[your-port]/dialog-caller
にアクセスしてみてください。
結果はBrowserのコンソールに出力されます。
ポイント
<MessageDialog @ref="messageDialog" />
作成したDialogに対して、Element Referenceを追加し、@code
内でMessageDialog
インスタンスを自由に扱えるようにします。
このアクセス権限の付与により、Dialog内のボタン押下後のアクションに何かしらの干渉を行うことができます。
今回のサンプルはawait messageDialog.GetResult()
の応答を待ち、DialogCaller
内で結果を受けて分岐をしていますが、例えば、Callbackを渡して、MessageDialog
内でボタン押下後のアクションにそのCallbackを実行するという方法も可能です。
TaskCompletionSource
Task
オブジェクトを任意で生成できます。今回はMessageDialog
の応答を受けて、呼び元のDialogCaller
でアクションを実行するという方式にしたかったので利用しています。
GetResult
が呼ばれたタイミングで、メンバ変数のtsc
にTaskCompletionSource
のインスタンスを格納し呼び元に戻します。SetResult
により値が格納されることで、tsc
がResolveされ、呼び元のawait
が完了します。
ScriptとStyle
今回のようにJavaScriptを使う場合は、<script></script>
が呼ばれるタイミングも考慮してあげる必要があります。App.razor
に置くことで重複を防いでいます。仮に、MessageDialog
に置いた場合、このComponentが呼ばれる度に<script></script>
が追加されてしまいます。
Styleもインラインで各場合、同様の注意が必要ですが、BlazorのCSS isolationという仕組みによって回避しています。
最後に
なぜ、TaskCompletionSource
を採用したかというと、直感的で分かりやすいからです。
共通ライブラリ・Componentになり得るものは使う側の技術スタックなども考慮して作成していく必要がありますね。