/// <summary> /// 弹出带结果的对话框 /// </summary> /// <param name="service">DialogService 服务实例</param> /// <param name="option">对话框参数</param> /// <param name="dialog">指定弹窗组件 默认为 null 使用 <see cref="BootstrapBlazorRoot"/> 组件内置弹窗组件</param> /// <returns></returns> public static async Task <DialogResult> ShowModal <TDialog>(this DialogService service, ResultDialogOption option, Dialog?dialog = null) where TDialog : IComponent, IResultDialog { IResultDialog?resultDialog = null; var result = DialogResult.Close; option.BodyTemplate = builder => { var index = 0; builder.OpenComponent(index++, typeof(TDialog)); if (option.ComponentParamters != null) { foreach (var p in option.ComponentParamters) { builder.AddAttribute(index++, p.Key, p.Value); } } builder.AddComponentReferenceCapture(index++, com => resultDialog = (IResultDialog)com); builder.CloseComponent(); }; option.FooterTemplate = BootstrapDynamicComponent.CreateComponent <ResultDialogFooter>(new Dictionary <string, object?> { [nameof(ResultDialogFooter.ButtonCloseText)] = option.ButtonCloseText, [nameof(ResultDialogFooter.ButtonNoText)] = option.ButtonNoText, [nameof(ResultDialogFooter.ButtonYesText)] = option.ButtonYesText, [nameof(ResultDialogFooter.ShowCloseButton)] = option.ShowCloseButton, [nameof(ResultDialogFooter.ButtonCloseColor)] = option.ButtonCloseColor, [nameof(ResultDialogFooter.ButtonCloseIcon)] = option.ButtonCloseIcon, [nameof(ResultDialogFooter.OnClickClose)] = new Func <Task>(async() => { result = DialogResult.Close; if (option.OnCloseAsync != null) { await option.OnCloseAsync(); } }), [nameof(ResultDialogFooter.ShowYesButton)] = option.ShowYesButton, [nameof(ResultDialogFooter.ButtonYesColor)] = option.ButtonYesColor, [nameof(ResultDialogFooter.ButtonYesIcon)] = option.ButtonYesIcon, [nameof(ResultDialogFooter.OnClickYes)] = new Func <Task>(async() => { result = DialogResult.Yes; if (option.OnCloseAsync != null) { await option.OnCloseAsync(); } }), [nameof(ResultDialogFooter.ShowNoButton)] = option.ShowNoButton, [nameof(ResultDialogFooter.ButtonNoColor)] = option.ButtonNoColor, [nameof(ResultDialogFooter.ButtonNoIcon)] = option.ButtonNoIcon, [nameof(ResultDialogFooter.OnClickNo)] = new Func <Task>(async() => { result = DialogResult.No; if (option.OnCloseAsync != null) { await option.OnCloseAsync(); } }) }).Render(); var closeCallback = option.OnCloseAsync; option.OnCloseAsync = async() => { if (resultDialog != null && await resultDialog.OnClosing(result)) { await resultDialog.OnClose(result); if (closeCallback != null) { await closeCallback(); } // Modal 与 ModalDialog 的 OnClose 事件陷入死循环 // option.OnClose -> Modal.Close -> ModalDialog.Close -> ModalDialog.OnClose -> option.OnClose option.OnCloseAsync = null; await option.Dialog.Close(); option.ReturnTask.SetResult(result); } else { result = DialogResult.Close; } }; await service.Show(option, dialog); return(await option.ReturnTask.Task); }
public void Show_Ok() { #region Show var cut = Context.RenderComponent <BootstrapBlazorRoot>(pb => { pb.AddChildContent <MockDialogTest>(); }); var dialog = cut.FindComponent <MockDialogTest>().Instance.DialogService; var closed = false; cut.InvokeAsync(() => dialog.Show(new DialogOption() { BodyTemplate = builder => builder.AddContent(0, "Test-BodyTemplate"), HeaderTemplate = builder => builder.AddContent(0, "Test-HeaderTemplate"), FooterTemplate = builder => builder.AddContent(0, "Test-FooterTemplate"), Class = "test-class", ShowMaximizeButton = true, OnCloseAsync = () => { closed = true; return(Task.CompletedTask); } })); // 全屏按钮 Assert.Contains("btn-maximize", cut.Markup); // 代码覆盖模板单元测试 Assert.Contains("Test-BodyTemplate", cut.Markup); Assert.Contains("Test-HeaderTemplate", cut.Markup); Assert.Contains("Test-FooterTemplate", cut.Markup); Assert.Contains("test-class", cut.Markup); // 测试关闭逻辑 var modal = cut.FindComponent <Modal>(); cut.InvokeAsync(() => modal.Instance.Close()); Assert.True(closed); // 测试 Component 赋值逻辑 cut.InvokeAsync(() => dialog.Show(new DialogOption() { Component = BootstrapDynamicComponent.CreateComponent <Button>(), BodyTemplate = null })); Assert.Contains("class=\"btn btn-primary\"", cut.Markup); modal = cut.FindComponent <Modal>(); cut.InvokeAsync(() => modal.Instance.Close()); // 测试 Component 与 BodyTemplate 均为 null 逻辑 cut.InvokeAsync(() => dialog.Show(new DialogOption() { Component = null, BodyTemplate = null })); cut.InvokeAsync(() => modal.Instance.Close()); #endregion #region ShowSearchDialog // 无按钮回调赋值 var option = new SearchDialogOption <Foo>() { Title = "Test-SearchDialogTitle", Model = new Foo(), ItemsPerRow = 2, RowType = RowType.Inline, LabelAlign = Alignment.Left, ResetButtonText = null, QueryButtonText = null, Items = null }; cut.InvokeAsync(() => dialog.ShowSearchDialog(option)); // 重置按钮委托为空 null var button = cut.FindComponents <Button>().First(b => b.Instance.Text == "重置"); cut.InvokeAsync(() => button.Instance.OnClickWithoutRender !.Invoke()); // 搜索按钮委托为空 cut.InvokeAsync(() => dialog.ShowSearchDialog(option)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "查询"); cut.InvokeAsync(() => button.Instance.OnClickWithoutRender !.Invoke()); // 重置按钮 var reset = false; option.OnResetSearchClick = () => { reset = true; return(Task.CompletedTask); }; cut.InvokeAsync(() => dialog.ShowSearchDialog(option)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "重置"); cut.InvokeAsync(() => button.Instance.OnClickWithoutRender !.Invoke()); Assert.True(reset); // 搜索按钮 var search = false; option.DialogBodyTemplate = foo => builder => builder.AddContent(0, foo.Name); option.OnSearchClick = () => { search = true; return(Task.CompletedTask); }; cut.InvokeAsync(() => dialog.ShowSearchDialog(option)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "查询"); cut.InvokeAsync(() => button.Instance.OnClickWithoutRender !.Invoke()); Assert.True(search); #endregion #region ShowEditDialog // 无按钮回调赋值 var editOption = new EditDialogOption <Foo>() { Model = new Foo(), ItemsPerRow = 2, RowType = RowType.Inline, ItemChangedType = ItemChangedType.Add, LabelAlign = Alignment.Left, ShowLabel = true }; cut.InvokeAsync(() => dialog.ShowEditDialog(editOption)); cut.InvokeAsync(() => modal.Instance.Close()); // 设置关闭回调 closed = false; editOption.OnCloseAsync = () => { closed = true; return(Task.CompletedTask); }; cut.InvokeAsync(() => dialog.ShowEditDialog(editOption)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "关闭"); cut.InvokeAsync(() => button.Instance.OnClickWithoutRender !.Invoke()); Assert.True(closed); // 设置保存回调 var saved = false; editOption.ShowLoading = true; editOption.OnEditAsync = context => { saved = true; return(Task.FromResult(true)); }; var model = new Foo() { Name = "Test" }; var parameters = new Dictionary <string, object?>() { ["Field"] = "Name", ["FieldExpression"] = model.GenerateValueExpression() }; var item = new EditorItem <Foo, string>(); cut.InvokeAsync(() => item.SetParametersAsync(ParameterView.FromDictionary(parameters))); editOption.Items = new IEditorItem[] { item }; editOption.Model = model; cut.InvokeAsync(() => dialog.ShowEditDialog(editOption)); var form = cut.Find("form"); form.Submit(); Assert.True(saved); // 测试 DialogBodyTemplate editOption.DialogBodyTemplate = foo => builder => builder.AddContent(0, "test"); cut.InvokeAsync(() => dialog.ShowEditDialog(editOption)); form.Submit(); // Modal is Null editOption.Model = null; Assert.ThrowsAsync <InvalidOperationException>(() => cut.InvokeAsync(() => dialog.ShowEditDialog(editOption))); cut.InvokeAsync(() => cut.Find(".btn-close").Click()); #endregion #region ShowModal var result = false; var resultOption = new ResultDialogOption() { ComponentParamters = new Dictionary <string, object>() { [nameof(MockModalDialog.Value)] = result, [nameof(MockModalDialog.ValueChanged)] = EventCallback.Factory.Create <bool>(this, b => result = b) } }; // 点击的是 Yes 按钮 cut.InvokeAsync(() => dialog.ShowModal <MockModalDialog>(resultOption)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "确认"); cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync()); Assert.True(result); // 点击的是 No 按钮 result = true; resultOption = new ResultDialogOption() { ComponentParamters = new Dictionary <string, object>() { [nameof(MockModalDialog.Value)] = result, [nameof(MockModalDialog.ValueChanged)] = EventCallback.Factory.Create <bool>(this, b => result = b) } }; cut.InvokeAsync(() => dialog.ShowModal <MockModalDialog>(resultOption)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "取消"); cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync()); Assert.False(result); // 点击关闭按钮 resultOption = new ResultDialogOption() { ShowCloseButton = true, ComponentParamters = new Dictionary <string, object>() { [nameof(MockModalDialog.Value)] = result, [nameof(MockModalDialog.ValueChanged)] = EventCallback.Factory.Create <bool>(this, b => result = b) }, OnCloseAsync = () => Task.CompletedTask }; cut.InvokeAsync(() => dialog.ShowModal <MockModalDialog>(resultOption)); button = cut.FindComponents <Button>().First(b => b.Instance.Text == "关闭"); cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync()); #endregion #region 弹窗中的弹窗测试 cut.InvokeAsync(() => dialog.Show(new DialogOption() { // 弹窗中按钮 BodyTemplate = BootstrapDynamicComponent.CreateComponent <Button>(new Dictionary <string, object?>() { [nameof(Button.OnClickWithoutRender)] = () => { // 继续弹窗 dialog.Show(new DialogOption() { BodyTemplate = builder => { builder.AddContent(0, "Test"); } }); return(Task.CompletedTask); } }).Render() })); // 弹出第二个弹窗 var buttonInDialog = cut.Find(".btn-primary"); buttonInDialog.Click(); Assert.Equal(2, cut.FindComponents <ModalDialog>().Count); // 关闭第二个弹窗 var btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); Assert.Equal(1, cut.FindComponents <ModalDialog>().Count); // 关闭第一个弹窗 btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); Assert.Equal(0, cut.FindComponents <ModalDialog>().Count); #endregion #region 全屏弹窗 cut.InvokeAsync(() => dialog.Show(new DialogOption() { FullScreenSize = FullScreenSize.Large })); Assert.Contains("modal-fullscreen-lg-down", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); #endregion #region IsCenter cut.InvokeAsync(() => dialog.Show(new DialogOption() { IsCentered = true })); Assert.Contains("modal-dialog-centered", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); cut.InvokeAsync(() => dialog.Show(new DialogOption() { IsCentered = false })); Assert.DoesNotContain("modal-dialog-centered", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); #endregion #region IsKeyboard cut.InvokeAsync(() => dialog.Show(new DialogOption() { IsKeyboard = true })); Assert.Contains("data-bs-keyboard=\"true\"", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); cut.InvokeAsync(() => dialog.Show(new DialogOption() { IsKeyboard = false })); Assert.DoesNotContain("data-bs-keyboard\"false\"", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); #endregion #region ShowHeaderCloseButton cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowHeaderCloseButton = true })); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowHeaderCloseButton = false })); Assert.DoesNotContain("btn-close", cut.Markup); btnClose = cut.FindAll(".btn-secondary")[cut.FindAll(".btn-secondary").Count - 1]; btnClose.Click(); #endregion #region ShowPrintButton cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowPrintButton = true })); Assert.Contains("btn-print", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowPrintButton = false })); Assert.DoesNotContain("btn-print", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowPrintButton = true, ShowPrintButtonInHeader = true, PrintButtonText = "Print-Test" })); Assert.Contains("btn-print", cut.Markup); Assert.Contains("Print-Test", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); #endregion #region ShowSaveButton cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowSaveButton = true, SaveButtonText = "Save-Test", CloseButtonText = "Close-Test", BodyContext = "Test" })); Assert.Contains("Save-Test", cut.Markup); Assert.Contains("Close-Test", cut.Markup); btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1]; btnClose.Click(); #endregion #region OnSaveAsync var save = false; cut.InvokeAsync(() => dialog.Show(new DialogOption() { ShowSaveButton = true, IsAutoCloseAfterSave = true, IsDraggable = false, OnSaveAsync = () => { save = true; return(Task.FromResult(save)); } })); btnClose = cut.FindAll(".btn-primary")[cut.FindAll(".btn-primary").Count - 1]; btnClose.Click(); Assert.True(save); #endregion #region ShowSaveDialog cut.InvokeAsync(() => dialog.ShowSaveDialog <MockDialogTest>("Title", () => Task.FromResult(true), p => { }, op => op.Class = "test")); modal.FindAll("button")[modal.FindAll("button").Count - 1].Click(); cut.InvokeAsync(() => dialog.ShowSaveDialog <MockDialogTest>("Title")); modal.FindAll("button")[modal.FindAll("button").Count - 1].Click(); #endregion #region ShowValidateFormDialog cut.InvokeAsync(() => dialog.ShowValidateFormDialog <MockValidateFormDialog>("ValidateFormDialog")); var btn = cut.Find(".btn-close"); cut.InvokeAsync(() => btn.Click()); Func <DialogOption, Dictionary <string, object?> > parameterFactory = op => new Dictionary <string, object?>(); Action <DialogOption> configureOption = op => op.Class = "ValidateFormDialog-Class"; cut.InvokeAsync(() => dialog.ShowValidateFormDialog <MockValidateFormDialog>("ValidateFormDialog", parameterFactory, configureOption)); btn = cut.Find(".btn-close"); cut.InvokeAsync(() => btn.Click()); #endregion #region ShowCloseDialog Action <Dictionary <string, object?> > closeDialogParameterFactory = op => new Dictionary <string, object?>(); cut.InvokeAsync(() => dialog.ShowCloseDialog <MockValidateFormDialog>("CloseDialog", closeDialogParameterFactory, configureOption)); btn = cut.Find(".btn-close"); cut.InvokeAsync(() => btn.Click()); cut.InvokeAsync(() => dialog.ShowCloseDialog <MockValidateFormDialog>("CloseDialog")); btn = cut.Find(".btn-close"); cut.InvokeAsync(() => btn.Click()); #endregion }