4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Blazor】メッセージダイアログから応答を受け取る

Last updated at Posted at 2024-03-08

概要

メッセージダイアログを自作し、その応答を取得します。

Razor Componentならではの興味深い書き方になっていると思います。

環境

VS2022のBlazor Appテンプレートからプロジェクトを作成します。

Render modeをInteractive WebAssemblyとするRazor componentを利用します。

選定基準に記載のように、デプロイ環境に大きな制約が出るため、基本的には、WebAssemblyが無難と考えています。

実装

プロジェクト名はBlazorAppTipsで進めていきます。

メッセージダイアログの作成については、Modalの書き方をベースに、JavaScriptを利用する形で実現しています。

  • Dialogを呼び出すページを追加
BlazorAppTips.Client/Pages/Dialog/DialogCaller.razor
@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

BlazorAppTips.Client/Pages/Dialog/DialogCaller.razor.css
#open-button {
    cursor: pointer;
    border: 1px solid #000;
    margin: 10px;
}
  • Dialogを追加
BlazorAppTips.Client/Pages/Dialog/MessageDialog.razor
@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

BlazorAppTips.Client/Pages/Dialog/MessageDialog.razor.css
#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を追加
BlazorAppTips/Components/App.razor
<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が呼ばれたタイミングで、メンバ変数のtscTaskCompletionSourceのインスタンスを格納し呼び元に戻します。SetResultにより値が格納されることで、tscがResolveされ、呼び元のawaitが完了します。

ScriptとStyle

今回のようにJavaScriptを使う場合は、<script></script>が呼ばれるタイミングも考慮してあげる必要があります。App.razorに置くことで重複を防いでいます。仮に、MessageDialogに置いた場合、このComponentが呼ばれる度に<script></script>が追加されてしまいます。

Styleもインラインで各場合、同様の注意が必要ですが、BlazorのCSS isolationという仕組みによって回避しています。

最後に

なぜ、TaskCompletionSourceを採用したかというと、直感的で分かりやすいからです。

共通ライブラリ・Componentになり得るものは使う側の技術スタックなども考慮して作成していく必要がありますね。

リポジトリ

BlazorAppTips

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?