Skip to content

Commit ee579b4

Browse files
ArgoZhangcwtalentkonw_nothing
authored
feat(Modal): add OnClosing parameter (#7632)
* fix: Select component value incorrectly changes when external StateHasChanged is called during search * chore: bump version 10.3.1-beta06 * feat(Modal): add OnClosing parameter * doc: 代码格式化 * feat: 增加 OnClosingCallback 注册注销方法 * doc: 文档格式化 * chore: bump vesion 10.3.1 * test: 增加单元测试 --------- Co-Authored-By: cwtalent <31028918+cwtalent@users.noreply.github.com> Co-authored-by: konw_nothing <13763803956@chinalco.com.cn>
1 parent 3508958 commit ee579b4

7 files changed

Lines changed: 132 additions & 19 deletions

File tree

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>10.3.1-beta06</Version>
4+
<Version>10.3.1</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/Dialog/Dialog.razor

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
@namespace BootstrapBlazor.Components
1+
@namespace BootstrapBlazor.Components
22
@inherits BootstrapComponentBase
33

4-
<Modal @ref="_modal" IsBackdrop="_isBackdrop" IsKeyboard="@_isKeyboard" IsFade="@_isFade"
5-
OnShownAsync="@_onShownAsync" OnCloseAsync="@_onCloseAsync" class="@ClassString">
4+
<Modal @ref="_modal" IsBackdrop="_isBackdrop" IsKeyboard="@_isKeyboard" IsFade="_isFade"
5+
OnShownAsync="_onShownAsync" OnCloseAsync="_onCloseAsync"
6+
class="@ClassString">
67
@for (var index = 0; index < DialogParameters.Keys.Count; index++)
78
{
89
if (index != 0 && index == DialogParameters.Keys.Count - 1)

src/BootstrapBlazor/Components/Dialog/Dialog.razor.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ public partial class Dialog : IDisposable
1717

1818
[NotNull]
1919
private Modal? _modal = null;
20-
21-
[NotNull]
2220
private Func<Task>? _onShownAsync = null;
23-
24-
[NotNull]
2521
private Func<Task>? _onCloseAsync = null;
2622

2723
private readonly Dictionary<Dictionary<string, object>, (bool IsKeyboard, bool IsBackdrop, Func<Task>? OnCloseCallback)> DialogParameters = [];

src/BootstrapBlazor/Components/Modal/Modal.razor.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ public partial class Modal
8383
[Parameter]
8484
public Func<Task>? OnCloseAsync { get; set; }
8585

86+
/// <summary>
87+
/// <para lang="zh">关闭之前回调方法 返回 true 时关闭弹窗 返回 false 时阻止关闭弹窗</para>
88+
/// <para lang="en">Callback Method Before Closing. Return true to close, false to prevent closing</para>
89+
/// </summary>
90+
[Parameter]
91+
public Func<Task<bool>>? OnClosingAsync { get; set; }
92+
8693
/// <summary>
8794
/// <para lang="zh">获得后台关闭弹出窗口的设置</para>
8895
/// <para lang="en">Gets the background close popup setting</para>
@@ -190,6 +197,21 @@ public async Task CloseCallback()
190197
}
191198
}
192199

200+
/// <summary>
201+
/// <para lang="zh">弹出窗口关闭前回调方法,由 JSInvoke 调用</para>
202+
/// <para lang="en">Callback method when the popup before close, called by JSInvoke</para>
203+
/// </summary>
204+
[JSInvokable]
205+
public async Task<bool> BeforeCloseCallback()
206+
{
207+
var result = true;
208+
if (OnClosingAsync != null)
209+
{
210+
result = await OnClosingAsync();
211+
}
212+
return result;
213+
}
214+
193215
/// <summary>
194216
/// <para lang="zh">切换弹出窗口状态的方法</para>
195217
/// <para lang="en">Method to toggle the popup state</para>
@@ -214,7 +236,14 @@ public async Task Show()
214236
/// <para lang="zh">关闭当前弹出窗口的方法</para>
215237
/// <para lang="en">Method to close the current popup</para>
216238
/// </summary>
217-
public Task Close() => InvokeVoidAsync("execute", Id, "hide");
239+
public async Task Close()
240+
{
241+
var result = await BeforeCloseCallback();
242+
if (result)
243+
{
244+
await InvokeVoidAsync("execute", Id, "hide");
245+
}
246+
}
218247

219248
/// <summary>
220249
/// <para lang="zh">设置标题文本的方法</para>
@@ -247,4 +276,33 @@ public void UnRegisterShownCallback(IComponent component)
247276
{
248277
_shownCallbackCache.TryRemove(component, out _);
249278
}
279+
280+
/// <summary>
281+
/// <para lang="zh">注册弹出窗口关闭前调用的回调方法,允许自定义逻辑来决定是否继续关闭操作</para>
282+
/// <para lang="en">Registers a callback that is invoked asynchronously when a closing event is triggered, allowing custom logic to determine whether the closing operation should proceed.</para>
283+
/// </summary>
284+
/// <param name="onClosingCallback">
285+
/// <para lang="zh">返回包含布尔值的任务的函数。当关闭事件发生时执行该回调,返回 <see langword="true"/> 允许继续关闭操作,返回 <see langword="false"/> 取消关闭操作</para>
286+
/// <para lang="en">A function that returns a task containing a Boolean value. The callback is executed when the closing event
287+
/// occurs, and should return <see langword="true"/> to allow the closing operation to continue, or <see
288+
/// langword="false"/> to cancel it.</para>
289+
/// </param>
290+
public void RegisterOnClosingCallback(Func<Task<bool>> onClosingCallback)
291+
{
292+
OnClosingAsync += onClosingCallback;
293+
}
294+
295+
/// <summary>
296+
/// <para lang="zh">注销弹出窗口关闭前调用的回调方法</para>
297+
/// <para lang="en">Unregisters a previously registered callback that is invoked when a closing event occurs.</para>
298+
/// </summary>
299+
/// <param name="onClosingCallback">
300+
/// <para lang="zh">要从关闭事件中移除的回调函数。该函数应返回一个布尔值的任务,指示是否继续关闭操作</para>
301+
/// <para lang="en">The callback function to remove from the closing event. The function should return a task that evaluates to a
302+
/// Boolean value indicating whether the closing operation should proceed.</para>
303+
/// </param>
304+
public void UnRegisterOnClosingCallback(Func<Task<bool>> onClosingCallback)
305+
{
306+
OnClosingAsync -= onClosingCallback;
307+
}
250308
}

src/BootstrapBlazor/Components/Modal/Modal.razor.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Data from "../../modules/data.js"
1+
import Data from "../../modules/data.js"
22
import EventHandler from "../../modules/event-handler.js"
33

44
export function init(id, invoke, shownCallback, closeCallback) {
@@ -73,15 +73,22 @@ export function init(id, invoke, shownCallback, closeCallback) {
7373
}
7474
}
7575

76+
modal.close = async () => {
77+
const close = await invoke.invokeMethodAsync("BeforeCloseCallback");
78+
if (close) {
79+
modal.hide();
80+
}
81+
}
82+
7683
modal.handlerKeyboardAndBackdrop = () => {
7784
if (!modal.hook_keyboard_backdrop) {
7885
modal.hook_keyboard_backdrop = true;
7986

80-
modal.handlerEscape = e => {
87+
modal.handlerEscape = async e => {
8188
if (e.key === 'Escape') {
8289
const keyboard = el.getAttribute('data-bs-keyboard')
8390
if (keyboard === 'true') {
84-
modal.hide()
91+
modal.close();
8592
}
8693
}
8794
}
@@ -91,7 +98,7 @@ export function init(id, invoke, shownCallback, closeCallback) {
9198
if (e.target.closest('.modal-dialog') === null) {
9299
const backdrop = el.getAttribute('data-bs-backdrop')
93100
if (backdrop !== 'static') {
94-
modal.hide()
101+
modal.close();
95102
}
96103
}
97104
})

src/BootstrapBlazor/Components/Modal/ModalDialog.razor

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
}
2323
@if (ShowPrintButtonInHeader)
2424
{
25-
<PrintButton Color="PrintButtonColor" class="btn-print" Text="@PrintButtonText" Icon="@PrintButtonIcon"></PrintButton>
25+
<PrintButton Color="PrintButtonColor" Text="@PrintButtonText" Icon="@PrintButtonIcon"
26+
class="btn-print"></PrintButton>
2627
}
2728
@if (ShowExportPdfButtonInHeader)
2829
{
@@ -32,11 +33,13 @@
3233
}
3334
@if (ShowMaximizeButton)
3435
{
35-
<Button Color="Color.None" class="btn-maximize" aria-label="@MaximizeAriaLabel" OnClick="@OnToggleMaximize" Icon="@MaximizeIconString"></Button>
36+
<Button Color="Color.None" OnClick="@OnToggleMaximize" Icon="@MaximizeIconString"
37+
class="btn-maximize" aria-label="@MaximizeAriaLabel"></Button>
3638
}
3739
@if (ShowHeaderCloseButton)
3840
{
39-
<Button Color="Color.None" class="btn-close" aria-label="Close" OnClickWithoutRender="@OnClickCloseAsync"></Button>
41+
<Button Color="Color.None" class="btn-close" aria-label="Close"
42+
OnClickWithoutRender="@OnClickCloseAsync"></Button>
4043
}
4144
</div>
4245
</div>
@@ -58,11 +61,13 @@
5861
}
5962
@if (ShowCloseButton)
6063
{
61-
<Button Color="Color.Secondary" Text="@CloseButtonText" Icon="@CloseButtonIcon" OnClickWithoutRender="OnClickCloseAsync"></Button>
64+
<Button Color="Color.Secondary" Text="@CloseButtonText" Icon="@CloseButtonIcon"
65+
OnClickWithoutRender="OnClickCloseAsync"></Button>
6266
}
6367
@if (ShowPrintButton)
6468
{
65-
<PrintButton Color="PrintButtonColor" class="btn-print" Text="@PrintButtonText" Icon="@PrintButtonIcon"></PrintButton>
69+
<PrintButton Color="PrintButtonColor" Text="@PrintButtonText" Icon="@PrintButtonIcon"
70+
class="btn-print"></PrintButton>
6671
}
6772
@if (ShowExportPdfButton)
6873
{
@@ -72,7 +77,8 @@
7277
}
7378
@if (ShowSaveButton)
7479
{
75-
<Button Color="Color.Primary" Text="@SaveButtonText" Icon="@SaveButtonIcon" IsAsync="true" OnClickWithoutRender="OnClickSave"></Button>
80+
<Button Color="Color.Primary" Text="@SaveButtonText" Icon="@SaveButtonIcon"
81+
IsAsync="true" OnClickWithoutRender="OnClickSave"></Button>
7682
}
7783
@if (FooterTemplate != null)
7884
{

test/UnitTest/Components/ModalTest.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,51 @@ public async Task RegisterShownCallback_Ok()
172172
Assert.True(component.Instance.Pass);
173173
}
174174

175+
[Fact]
176+
public async Task OnClosingAsync_Ok()
177+
{
178+
var closing = false;
179+
var cut = Context.Render<Modal>(builder =>
180+
{
181+
builder.Add(a => a.OnClosingAsync, async () =>
182+
{
183+
closing = true;
184+
await Task.Yield();
185+
return true;
186+
});
187+
builder.AddChildContent<ModalDialog>(pb =>
188+
{
189+
190+
});
191+
});
192+
await cut.InvokeAsync(() => cut.Instance.Close());
193+
Assert.True(closing);
194+
}
195+
196+
[Fact]
197+
public async Task RegisterOnClosingAsync_Ok()
198+
{
199+
var closing = false;
200+
var cut = Context.Render<Modal>(builder =>
201+
{
202+
builder.AddChildContent<ModalDialog>(pb =>
203+
{
204+
205+
});
206+
});
207+
208+
var closingHandler = async () =>
209+
{
210+
closing = true;
211+
await Task.Yield();
212+
return true;
213+
};
214+
cut.Instance.RegisterOnClosingCallback(closingHandler);
215+
await cut.InvokeAsync(() => cut.Instance.Close());
216+
cut.Instance.UnRegisterOnClosingCallback(closingHandler);
217+
Assert.True(closing);
218+
}
219+
175220
private class MockComponent : ComponentBase
176221
{
177222
public bool Value { get; set; }

0 commit comments

Comments
 (0)