Blazor使用(一)封装Bootstrap5的模态框实现互操作
一、引入JS
要实现Bootstrap组件的功能,需要引用Bootstrap.js,依赖@popperjs/core
官方给出的方案
<body> <h1>Hello, modularity!</h1> <button id="popoverButton" type="button" class="btn btn-primary btn-lg" class="btn btn-lg btn-danger" data-bs-toggle="popover" title="ESM in Browser" data-bs-content="Bang!">Custom popover</button> <script async src="https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js" crossorigin="anonymous"></script> <script type="importmap"> { "imports": { "@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js", "bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.esm.min.js" } } </script> <script type="module"> import * as bootstrap from 'bootstrap' new bootstrap.Popover(document.getElementById('popoverButton')) </script>
</body>
但是无法加载popperjs和bootstrap.js,可以将这两个文件放在wwwroot再通过相对路径引用。注意“@”的转义
改为如下:
<script async src="https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js" crossorigin="anonymous"></script> <script type="importmap"> { "imports": { "@("@popperjs/core")": "/js/popperjs/popper.min.js", "bootstrap": "/js/bootstrap/bootstrap.esm.min.js" } } </script> <script type="module"> import * as bootstrap from 'bootstrap' </script>
二、实现组件模板
MyModal.razor
<div class="modal fade" id="@Id" @ref="ModalRef" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> @if(ModalHeader == null){ <h1 class="modal-title fs-5" id="modalLabel"> @(Title ?? "Modal Title") </h1> } else { @ModalHeader } </div> <div class="modal-body"> @if(ModalBody != null) { @ModalBody } </div> <div class="modal-footer"> @if(ModalFooter == null) { <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button> <button type="button" class="btn btn-primary"> Save Changes </button> } else{ @ModalFooter } </div> </div> </div> </div> @code { private ElementReference ModalRef { get; set; } [Parameter] public string? Id { get; set; } [Parameter] public string Title { get; set; } = "Modal Title"; [Parameter] public RenderFragment? ModalHeader { get; set; } [Parameter] public RenderFragment? ModalBody { get; set; } [Parameter] public RenderFragment? ModalFooter { get; set; } [Parameter] public EventCallback<EventArgs> OnHide { get; set; } [Parameter] public EventCallback<EventArgs> OnHidden { get; set; } [Parameter] public EventCallback<EventArgs> OnHidePrevented { get; set; } [Parameter] public EventCallback<EventArgs> OnShow { get; set; } [Parameter] public EventCallback<EventArgs> OnShown { get; set; } private IJSObjectReference? _MyModalModule { get; set; } [JSInvokable] public Task HandleEvent(string eventName) { return eventName switch { "show" => OnShow.InvokeAsync(new EventArgs()), "shown" => OnShown.InvokeAsync(new EventArgs()), "hide" => OnHide.InvokeAsync(new EventArgs()), "hidden" => OnHidden.InvokeAsync(new EventArgs()), "hidePrevented" => OnHidePrevented.InvokeAsync(new EventArgs()), _ => throw new NotSupportedException() }; } [Inject] private IJSRuntime? _JS { get; set; } private IJSObjectReference? _JSModalObject { get; set; } protected override async Task OnAfterRenderAsync(bool firstRender) { if(firstRender) { _MyModalModule = await _JS!.InvokeAsync<IJSObjectReference>( "import", "./Components/MyModal.razor.js"); var instance = DotNetObjectReference.Create<MyModal>(this); @* await _MyModalModule.InvokeVoidAsync("registryEvent", ModalRef, instance); *@ _JSModalObject = await _MyModalModule.InvokeAsync<IJSObjectReference>("registryEvent", ModalRef, instance); } await base.OnAfterRenderAsync(firstRender); } public async Task Show() { await _MyModalModule!.InvokeVoidAsync("showModal", _JSModalObject); } public async Task Hide() { await _MyModalModule!.InvokeVoidAsync("hideModal", _JSModalObject); } public async ValueTask DisposeAsync() { if(_MyModalModule != null) { await _MyModalModule.DisposeAsync(); } } }
MyModal.razor.js
三、对接Modal的事件
在Razor组件渲染完成后,调用JS注册Modal的事件,并且在JS中触发Razor组件的事件。
使用DotNetObjectReference创建Razor组件实例的引用,传递到JS。
MyModal.razor
private IJSObjectReference? _MyModalModule { get; set; }
[Inject]
private IJSRuntime? _JS { get; set; }
private IJSObjectReference? _JSModalObject { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) { if(firstRender) {
// 加载组件JS _MyModalModule = await _JS!.InvokeAsync<IJSObjectReference>( "import", "./Components/MyModal.razor.js");
// 构建要传递给JS的.NET对象引用 var instance = DotNetObjectReference.Create<MyModal>(this);
// 调用JS方法,返回Modal的JS对象的引用 _JSModalObject = await _MyModalModule.InvokeAsync<IJSObjectReference>("registryEvent", ModalRef, instance); } await base.OnAfterRenderAsync(firstRender); }
调用JS注册事件,将组件的HTML引用传递给JS。同时也可以将构建的modal对象传递回Razor组件。
MyModal.razor.js
// import bootstrap
import * as bootstrap from 'bootstrap' let modal; /** * 注册事件 * @param {HTMLElement} el */ export function registryEvent(el, dotnetInstance) { modal = new bootstrap.Modal(el); // BootStrap的构造方法 el.addEventListener("hide.bs.modal", (e) => { // 在HTML元素上监听事件 handleBsModalHide(e, dotnetInstance); }) el.addEventListener("hidden.bs.modal", (e) => { handleBsModalHidden(e, dotnetInstance); }) el.addEventListener("hidePrevented.bs.modal", (e) => { handleBsModalHidePrevented(e, dotnetInstance); }) el.addEventListener("show.bs.modal", (e) => { handleBsModalShow(e, dotnetInstance); }) el.addEventListener("shown.bs.modal", (e) => { handleBsModalShown(e, dotnetInstance); }) return modal; // 返回.NET }
const handleBsModalShow = (e, i) => { i.invokeMethodAsync("HandleEvent", "show") } const handleBsModalShown = (e, i) => { i.invokeMethodAsync("HandleEvent", "shown") } const handleBsModalHide = (e, i) => { i.invokeMethodAsync("HandleEvent", "hide") } const handleBsModalHidden = (e, i) => { i.invokeMethodAsync("HandleEvent", "hidden") } const handleBsModalHidePrevented = (e, i) => { i.invokeMethodAsync("HandleEvent", "hidePrevented") }
事件处理
[Parameter]
public EventCallback<EventArgs> OnHide { get; set; }
[Parameter]
public EventCallback<EventArgs> OnHidden { get; set; }
[Parameter]
public EventCallback<EventArgs> OnHidePrevented { get; set; }
[Parameter]
public EventCallback<EventArgs> OnShow { get; set; }
[Parameter]
public EventCallback<EventArgs> OnShown { get; set; }
// 时间参数也可以自定义
[JSInvokable]
public Task HandleEvent(string eventName)
{
return eventName switch {
"show" => OnShow.InvokeAsync(new EventArgs()), "shown" => OnShown.InvokeAsync(new EventArgs()), "hide" => OnHide.InvokeAsync(new EventArgs()), "hidden" => OnHidden.InvokeAsync(new EventArgs()), "hidePrevented" => OnHidePrevented.InvokeAsync(new EventArgs()), _ => throw new NotSupportedException()
};
}
Razor组件上实现监听
<MyModal Id="@modalId" @ref="modalRef" OnHidden="@(() => Clear())"> </MyModal>
四、从.NET调用Modal的JS方法
封装Modal的hide(),show()方法,传入Modal的实例,实现操作。
MyModal.razor.js
export function showModal(component) { component.show(); } export function hideModal(component) { component.hide(); }
根据在上面拿到的返回的Modal的实例的引用,在.NET中进行调用
MyModal.razor
public async Task Show() { await _MyModalModule!.InvokeVoidAsync("showModal", _JSModalObject); } public async Task Hide() { await _MyModalModule!.InvokeVoidAsync("hideModal", _JSModalObject); }
使用
<MyModal Id="@modalId" @ref="modalRef" OnHidden="@(() => Clear())"> <ModalFooter> <button type="button" @onclick="@(async () => await OnSave())" class="btn btn-primary"> 保存 </button> </ModalFooter> </MyModal> @code { public const string modalId = "settingModal"; private MyModal? modalRef { get; set; } private async Task OnSave()
{ if(modalRef != null){ await modalRef.Hide();
}
} }