mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2024-12-05 13:39:39 +08:00
!3539 feat(#I61QLU): redesign modal component support dynamic load javascript
* chore: bump version 7.0.3-beta01 * test: 增加单元测试 * test: 增加代码覆盖率 * refactor: EditDialog 代码复用 * doc: 增加 ShowLoading 示例 * chore: 打包脚本 * feat: DialogBase 支持脚本动态加载 * refactor: 更新单元测试 * test: 增加单元测试 * test: 增加 Category 单元测试 * test: 更新单元测试 * test: 更新单元测试 * chore: 排除单元测试 * feat: 增加按钮参数 * test: 修复失败的单元测试 * test: 更新单元测试 * chore: 更新打包文件 * feat: 适配 Swal 组件 * feat: 增加 ShowHeader 参数 * refactor: 重构 Modal 组件 * refactor: 重构 Dialog 组件 * doc: 更新 Modal 示例 * doc: 更改资源文件 * doc: 更新示例 * feat: 增加 Modal 动态脚本 * chore: 更新资源文件
This commit is contained in:
parent
7b2a662c49
commit
2ed76dfa40
@ -1,8 +1,8 @@
|
||||
<h3>无限弹窗示例</h3>
|
||||
<h3>无限弹窗示例 @Title</h3>
|
||||
|
||||
<Tab>
|
||||
<TabItem Text="用户管理">
|
||||
<div>我是用户管理 @Value</div>
|
||||
<div>我是用户管理</div>
|
||||
<Button Text="弹窗" OnClick="@OnClickButton" />
|
||||
</TabItem>
|
||||
<TabItem Text="菜单管理">
|
||||
|
@ -9,29 +9,15 @@ namespace BootstrapBlazor.Shared.Components;
|
||||
/// </summary>
|
||||
public partial class DialogDemo
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private DialogService? DialogService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnInitialized 方法
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
Value = DateTime.Now.ToString();
|
||||
}
|
||||
private string Title { get; } = DateTime.Now.ToString();
|
||||
|
||||
private Task OnClickButton() => DialogService.Show(new DialogOption()
|
||||
{
|
||||
Title = $"弹窗 {Value}",
|
||||
Title = "Pop-up",
|
||||
Component = BootstrapDynamicComponent.CreateComponent<DialogDemo>()
|
||||
});
|
||||
}
|
||||
|
@ -349,10 +349,7 @@
|
||||
"P2": "A dialog box pops up, suitable for scenarios that require more customization",
|
||||
"P3": "Popup title",
|
||||
"P4": "popup text",
|
||||
"P5": "by setting",
|
||||
"P6": "component",
|
||||
"P7": "Parameter, whether to open the pop-up window is supported",
|
||||
"P8": "Please click the back button to set and then click the <b>popup</b> button to test the effect",
|
||||
"P5": "by setting <code>Modal</code> component <code>IsKeyboard</code> parameter, whether to open the pop-up window is supported <kbd>ESC</kbd>, Please click the back button to set and then click the <b>popup</b> button to test the effect",
|
||||
"P9": "Pop-ups",
|
||||
"P10": "Default popup",
|
||||
"P11": "I am the text in the pop-up window",
|
||||
@ -772,8 +769,7 @@
|
||||
"P74": "Fullscreen popup (< 1200px)",
|
||||
"P75": "Print button",
|
||||
"P76": "Show a print preview button on the <code>Header</code> by setting <code>ShowPrintButton</code>",
|
||||
"P77": "by setting",
|
||||
"P78": "Change the <b>Print Preview</b> button text",
|
||||
"P77": "by setting <code>PrintButtonText</code> change the <b>Print Preview</b> button text",
|
||||
"P79": "Click to open Dialog",
|
||||
"P80": "Full screen popup",
|
||||
"P81": "Show a window maximize button on the <code>Header</code> by setting <code>ShowMaximizeButton</code>",
|
||||
|
@ -350,10 +350,7 @@
|
||||
"P2": "弹出一个对话框,适合需要定制性更大的场景",
|
||||
"P3": "弹窗标题",
|
||||
"P4": "弹窗正文",
|
||||
"P5": "通过设置",
|
||||
"P6": "组件的",
|
||||
"P7": "参数,开启弹窗是否支持",
|
||||
"P8": "请点击后面按钮设置后再点击 <b>弹窗</b> 按钮测试效果",
|
||||
"P5": "通过设置 <code>Modal</code> 组件的 <code>IsKeyboard</code> 参数,开启弹窗是否支持 <kbd>ESC</kbd>,请点击后面按钮设置后再点击 <b>弹窗</b> 按钮测试效果",
|
||||
"P9": "弹窗",
|
||||
"P10": "默认弹窗",
|
||||
"P11": "我是弹窗内正文",
|
||||
@ -773,8 +770,7 @@
|
||||
"P74": "全屏弹窗(< 1200px)",
|
||||
"P75": "打印按钮",
|
||||
"P76": "通过设置 <code>ShowPrintButton</code> 使 <code>Header</code> 上显示一个打印预览按钮",
|
||||
"P77": "通过设置",
|
||||
"P78": "更改 <b>打印预览</b> 按钮文字",
|
||||
"P77": "通过设置 <code>PrintButtonText</code> 更改 <b>打印预览</b> 按钮文字",
|
||||
"P79": "点击打开 Dialog",
|
||||
"P80": "全屏弹窗",
|
||||
"P81": "通过设置 <code>ShowMaximizeButton</code> 使 <code>Header</code> 上显示一个窗口最大化按钮",
|
||||
|
@ -141,12 +141,12 @@ await DialogService.Show(op);</Pre>
|
||||
<Button OnClick="@OnSaveDialogClick">@Localizer["P71"]</Button>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["P72"]" Introduction="@Localizer["P73"]" Name="ModalDialog">
|
||||
<Button OnClick="@OnSizeDialogClick">@Localizer["P74"]</Button>
|
||||
<DemoBlock Title="@Localizer["P72"]" Introduction="@Localizer["P73"]" Name="Size">
|
||||
<Button OnClick="@OnSizeDialogClick">@((MarkupString)Localizer["P74"].Value)</Button>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["P75"]" Introduction="@Localizer["P76"]" Name="PrintDialog">
|
||||
<p>@Localizer["P77"] <code>@nameof(DialogOption.PrintButtonText)</code> @Localizer["P78"]</p>
|
||||
<p>@((MarkupString)Localizer["P77"].Value)</p>
|
||||
<Button OnClick="@OnPrintDialogClick">@Localizer["P79"]</Button>
|
||||
</DemoBlock>
|
||||
|
||||
|
@ -89,9 +89,8 @@ public sealed partial class Dialogs
|
||||
Title = "I am the popup created by the service",
|
||||
BodyTemplate = BootstrapDynamicComponent.CreateComponent<Button>(new Dictionary<string, object?>
|
||||
{
|
||||
[nameof(Button.ChildContent)] = new RenderFragment(builder => builder.AddContent(0, "我是服务创建的按钮"))
|
||||
})
|
||||
.Render()
|
||||
[nameof(Button.ChildContent)] = new RenderFragment(builder => builder.AddContent(0, "Button"))
|
||||
}).Render()
|
||||
});
|
||||
|
||||
private async Task Show()
|
||||
@ -148,7 +147,7 @@ public sealed partial class Dialogs
|
||||
// Modal 组件 ShownCallbackAsync 触发后调用 Option 实例的 ShownCallbackAsync
|
||||
[nameof(ShownCallbackDummy.ShownTodo)] = new Action<Func<Task>>(cb =>
|
||||
{
|
||||
option.ShownCallbackAsync = async () =>
|
||||
option.OnShownAsync = async () =>
|
||||
{
|
||||
await cb();
|
||||
};
|
||||
@ -270,7 +269,7 @@ public sealed partial class Dialogs
|
||||
{
|
||||
await DialogService.Show(new DialogOption()
|
||||
{
|
||||
Title = $"弹窗 {DateTime.Now}",
|
||||
Title = $"Multiple Pop-up",
|
||||
Component = BootstrapDynamicComponent.CreateComponent<DialogDemo>()
|
||||
});
|
||||
}
|
||||
@ -282,8 +281,14 @@ public sealed partial class Dialogs
|
||||
Title = "Edit popup",
|
||||
Model = new Foo(),
|
||||
RowType = RowType.Inline,
|
||||
ShowLoading = true,
|
||||
ItemsPerRow = 2,
|
||||
ItemChangedType = ItemChangedType.Update
|
||||
ItemChangedType = ItemChangedType.Update,
|
||||
OnEditAsync = async context =>
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
await DialogService.ShowEditDialog(option);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<h4>@Localizer["H2"]</h4>
|
||||
|
||||
<DemoBlock Title="@Localizer["P1"]" Introduction="@Localizer["P2"]" Name="Normal">
|
||||
<div class="modal d-block position-relative">
|
||||
<div class="modal d-block position-relative" style="z-index: 2;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<p>@Localizer["P5"] <code>Modal</code> @Localizer["P6"] <code>IsKeyboard</code> @Localizer["P7"] <kbd>ESC</kbd>,@Localizer["P8"]</p>
|
||||
<p>@((MarkupString)Localizer["P5"].Value)</p>
|
||||
<Button OnClick="async e => await Modal.Toggle()">@Localizer["P9"]</Button>
|
||||
<Button OnClick="OnClickKeyboard" Text="@($"Keyboard: {IsKeyboard}")" class="ms-3" />
|
||||
</div>
|
||||
@ -231,7 +231,7 @@
|
||||
|
||||
<DemoBlock Title="弹窗已显示回调方法" Introduction="通过设置 <code>ShownCallbackAsync</code> 回调委托,弹窗显示后回调此方法" Name="ShownCallbackAsync">
|
||||
<Button OnClick="@(async e => await ShownCallbackModal.Toggle())">弹窗</Button>
|
||||
<Modal @ref="ShownCallbackModal" ShownCallbackAsync="OnShownCallbackAsync">
|
||||
<Modal @ref="ShownCallbackModal" OnShownAsync="OnShownCallbackAsync">
|
||||
<ModalDialog Title="ShownCallbackAsync 回调示例">
|
||||
<BodyTemplate>
|
||||
<div>我是弹窗内正文</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.0.2</Version>
|
||||
<Version>7.0.3-beta01</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
|
@ -1,9 +1,9 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@inherits BootstrapComponentBase
|
||||
|
||||
<Modal AdditionalAttributes="@AdditionalAttributes" @ref="ModalContainer" IsKeyboard="@IsKeyboard" ShownCallbackAsync="@OnShownCallbackAsync">
|
||||
@foreach (var parameter in DialogParameters)
|
||||
<Modal AdditionalAttributes="@AdditionalAttributes" @ref="ModalContainer" IsBackdrop="IsBackdrop" IsKeyboard="@IsKeyboard" OnShownAsync="@OnShownAsync" OnCloseAsync="OnCloseAsync">
|
||||
@for (var index = 0; index < DialogParameters.Count; index++)
|
||||
{
|
||||
@RenderDialog(parameter)
|
||||
@RenderDialog(index, DialogParameters[index])
|
||||
}
|
||||
</Modal>
|
||||
|
@ -22,6 +22,8 @@ public partial class Dialog : IDisposable
|
||||
|
||||
private bool IsKeyboard { get; set; }
|
||||
|
||||
private bool IsBackdrop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DialogServices 服务实例
|
||||
/// </summary>
|
||||
@ -29,9 +31,13 @@ public partial class Dialog : IDisposable
|
||||
[NotNull]
|
||||
private DialogService? DialogService { get; set; }
|
||||
|
||||
private bool IsShowDialog { get; set; }
|
||||
[NotNull]
|
||||
private Func<Task>? OnShownAsync { get; set; }
|
||||
|
||||
private Func<Task>? ShownCallbackAsync { get; set; }
|
||||
[NotNull]
|
||||
private Func<Task>? OnCloseAsync { get; set; }
|
||||
|
||||
private Dictionary<string, object>? CurrentParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnInitialized 方法
|
||||
@ -53,27 +59,47 @@ public partial class Dialog : IDisposable
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (IsShowDialog)
|
||||
if (CurrentParameter != null)
|
||||
{
|
||||
IsShowDialog = false;
|
||||
await ModalContainer.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Show(DialogOption option)
|
||||
{
|
||||
ShownCallbackAsync = async () =>
|
||||
OnShownAsync = async () =>
|
||||
{
|
||||
if (option.ShownCallbackAsync != null)
|
||||
if (option.OnShownAsync != null)
|
||||
{
|
||||
await option.ShownCallbackAsync();
|
||||
await option.OnShownAsync();
|
||||
}
|
||||
};
|
||||
|
||||
OnCloseAsync = async () =>
|
||||
{
|
||||
// 回调 OnCloseAsync
|
||||
if (option.OnCloseAsync != null)
|
||||
{
|
||||
await option.OnCloseAsync();
|
||||
}
|
||||
|
||||
// 移除当前 DialogParameter
|
||||
if (CurrentParameter != null)
|
||||
{
|
||||
DialogParameters.Remove(CurrentParameter);
|
||||
|
||||
// 多弹窗支持
|
||||
CurrentParameter = DialogParameters.LastOrDefault();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
};
|
||||
|
||||
IsKeyboard = option.IsKeyboard;
|
||||
IsBackdrop = option.IsBackdrop;
|
||||
option.Dialog = ModalContainer;
|
||||
var parameters = option.ToAttributes();
|
||||
|
||||
var parameters = option.ToAttributes();
|
||||
var content = option.BodyTemplate ?? option.Component?.Render();
|
||||
if (content != null)
|
||||
{
|
||||
@ -115,50 +141,23 @@ public partial class Dialog : IDisposable
|
||||
parameters.Add(nameof(ModalDialog.SaveButtonText), option.SaveButtonText);
|
||||
}
|
||||
|
||||
parameters.Add(nameof(ModalDialog.OnClose), new Func<Task>(async () =>
|
||||
{
|
||||
// 回调 OnClose 方法
|
||||
// 移除当前对话框
|
||||
if (option.OnCloseAsync != null)
|
||||
{
|
||||
await option.OnCloseAsync();
|
||||
}
|
||||
DialogParameters.Remove(parameters);
|
||||
|
||||
// 支持多级弹窗
|
||||
await ModalContainer.CloseOrPopDialog();
|
||||
StateHasChanged();
|
||||
}));
|
||||
// 保存当前 Dialog 参数
|
||||
CurrentParameter = parameters;
|
||||
|
||||
// 添加 ModalDialog 到容器中
|
||||
DialogParameters.Add(parameters);
|
||||
if (DialogParameters.Count == 1)
|
||||
{
|
||||
IsShowDialog = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private RenderFragment RenderDialog(IEnumerable<KeyValuePair<string, object>> parameter) => builder =>
|
||||
private static RenderFragment RenderDialog(int index, IEnumerable<KeyValuePair<string, object>> parameter) => builder =>
|
||||
{
|
||||
builder.OpenComponent<ModalDialog>(0);
|
||||
builder.AddMultipleAttributes(1, parameter);
|
||||
builder.AddComponentReferenceCapture(2, dialog =>
|
||||
{
|
||||
var modal = (ModalDialog)dialog;
|
||||
ModalContainer.ShowDialog(modal);
|
||||
});
|
||||
builder.OpenComponent<ModalDialog>(100 + index);
|
||||
builder.AddMultipleAttributes(101 + index, parameter);
|
||||
builder.SetKey(parameter);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
|
||||
private async Task OnShownCallbackAsync()
|
||||
{
|
||||
if (ShownCallbackAsync != null)
|
||||
{
|
||||
await ShownCallbackAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose 方法
|
||||
/// </summary>
|
||||
|
@ -7,7 +7,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class DialogBase<TModel> : ComponentBase
|
||||
public abstract class DialogBase<TModel> : BootstrapModuleComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 EditModel 实例
|
||||
|
@ -65,6 +65,11 @@ public class DialogOption
|
||||
/// </summary>
|
||||
public bool IsKeyboard { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否支持点击遮罩关闭弹窗 默认 false
|
||||
/// </summary>
|
||||
public bool IsBackdrop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示 Footer 默认为 true
|
||||
/// </summary>
|
||||
@ -153,7 +158,7 @@ public class DialogOption
|
||||
/// <summary>
|
||||
/// 获得/设置 弹窗已显示时回调此方法
|
||||
/// </summary>
|
||||
public Func<Task>? ShownCallbackAsync { get; set; }
|
||||
public Func<Task>? OnShownAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 将参数转换为组件属性方法
|
||||
|
@ -1,11 +0,0 @@
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_form_load: function (el, method) {
|
||||
var $el = $(el);
|
||||
if (method === 'show')
|
||||
$el.addClass('show');
|
||||
else
|
||||
$el.removeClass('show');
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
@ -9,44 +9,21 @@
|
||||
@BodyTemplate(Model)
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
@if (FooterTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="OnCloseAsync" IsFixed>
|
||||
@FooterTemplate(Model)
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsTracking)
|
||||
{
|
||||
<Button Color="Color.Secondary" Icon="fa-solid fa-xmark" Text="@CloseButtonText" OnClickWithoutRender="OnClickClose" />
|
||||
}
|
||||
<Button Color="Color.Primary" ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" Text="@SaveButtonText" />
|
||||
}
|
||||
@RenderFooter
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditorForm TModel="TModel" Items="Items" ItemChangedType="ItemChangedType" ItemsPerRow="ItemsPerRow" RowType="RowType" LabelAlign="LabelAlign" ShowLabel="ShowLabel" ShowUnsetGroupItemsOnTop="ShowUnsetGroupItemsOnTop">
|
||||
<Buttons>
|
||||
@if (FooterTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="OnCloseAsync" IsFixed>
|
||||
@FooterTemplate(Model)
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsTracking)
|
||||
{
|
||||
<Button Color="Color.Secondary" Icon="fa-solid fa-xmark" Text="@CloseButtonText" OnClickWithoutRender="OnClickClose" />
|
||||
}
|
||||
<Button Color="Color.Primary" ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" Text="@SaveButtonText" />
|
||||
}
|
||||
@RenderFooter
|
||||
</Buttons>
|
||||
</EditorForm>
|
||||
}
|
||||
@if (ShowLoading)
|
||||
{
|
||||
<div class="form-loader" id="@Id">
|
||||
<Spinner Color="Color.Primary" />
|
||||
</div>
|
||||
}
|
||||
</ValidateForm>
|
||||
<div class="form-loader" @ref="SpinnerElement">
|
||||
<Spinner Color="Color.Primary" />
|
||||
</div>
|
||||
|
@ -10,10 +10,9 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// 编辑弹窗组件
|
||||
/// </summary>
|
||||
[JSModuleAutoLoader("edit-dialog")]
|
||||
public partial class EditDialog<TModel>
|
||||
{
|
||||
private ElementReference SpinnerElement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 保存回调委托
|
||||
/// </summary>
|
||||
@ -75,10 +74,6 @@ public partial class EditDialog<TModel>
|
||||
[NotNull]
|
||||
private IStringLocalizer<EditDialog<TModel>>? Localizer { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IJSRuntime? JSRuntime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnParametersSet 方法
|
||||
/// </summary>
|
||||
@ -114,7 +109,37 @@ public partial class EditDialog<TModel>
|
||||
{
|
||||
if (ShowLoading)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync(SpinnerElement, "bb_form_load", state ? "show" : "hide");
|
||||
await InvokeExecuteAsync(Id, state);
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment RenderFooter => builder =>
|
||||
{
|
||||
if (FooterTemplate != null)
|
||||
{
|
||||
builder.OpenComponent<CascadingValue<Func<Task>?>>(0);
|
||||
builder.AddAttribute(1, nameof(CascadingValue<Func<Task>?>.Value), OnCloseAsync);
|
||||
builder.AddAttribute(2, nameof(CascadingValue<Func<Task>?>.IsFixed), true);
|
||||
builder.AddAttribute(3, nameof(CascadingValue<Func<Task>?>.ChildContent), FooterTemplate(Model));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsTracking)
|
||||
{
|
||||
builder.OpenComponent<Button>(20);
|
||||
builder.AddAttribute(21, nameof(Button.Color), Color.Secondary);
|
||||
builder.AddAttribute(22, nameof(Button.Icon), "fa-solid fa-xmark");
|
||||
builder.AddAttribute(23, nameof(Button.Text), CloseButtonText);
|
||||
builder.AddAttribute(24, nameof(Button.OnClickWithoutRender), OnClickClose);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
builder.OpenComponent<Button>(30);
|
||||
builder.AddAttribute(31, nameof(Button.Color), Color.Primary);
|
||||
builder.AddAttribute(32, nameof(Button.Icon), "fa-solid fa-floppy-disk");
|
||||
builder.AddAttribute(33, nameof(Button.Text), SaveButtonText);
|
||||
builder.AddAttribute(34, nameof(Button.ButtonType), ButtonType.Submit);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// 查询弹窗组件
|
||||
/// </summary>
|
||||
[JSModuleNotInherited]
|
||||
public partial class SearchDialog<TModel>
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -1,126 +0,0 @@
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_modal_dialog: function (el, obj, method) {
|
||||
var $el = $(el);
|
||||
$el.data('bb_dotnet_invoker', { obj, method });
|
||||
|
||||
// monitor mousedown ready to drag dialog
|
||||
var originX = 0;
|
||||
var originY = 0;
|
||||
var dialogWidth = 0;
|
||||
var dialogHeight = 0;
|
||||
var pt = { top: 0, left: 0 };
|
||||
if ($el.hasClass('is-draggable')) {
|
||||
$el.find('.btn-maximize').click(function () {
|
||||
$button = $(this);
|
||||
var status = $button.attr('aria-label');
|
||||
if (status === "maximize") {
|
||||
$el.css({
|
||||
"marginLeft": "auto",
|
||||
"width": $el.width(),
|
||||
});
|
||||
}
|
||||
else {
|
||||
var handler = window.setInterval(function () {
|
||||
if ($el.attr('style')) {
|
||||
$el.removeAttr('style');
|
||||
}
|
||||
else {
|
||||
window.clearInterval(handler);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
$el.css({
|
||||
"marginLeft": "auto"
|
||||
});
|
||||
$el.find('.modal-header').drag(
|
||||
function (e) {
|
||||
originX = e.clientX || e.touches[0].clientX;
|
||||
originY = e.clientY || e.touches[0].clientY;
|
||||
|
||||
// 弹窗大小
|
||||
dialogWidth = $el.width();
|
||||
dialogHeight = $el.height();
|
||||
|
||||
// 偏移量
|
||||
pt.top = parseInt($el.css('marginTop').replace("px", ""));
|
||||
pt.left = parseInt($el.css('marginLeft').replace("px", ""));
|
||||
|
||||
$el.css({ "marginLeft": pt.left, "marginTop": pt.top });
|
||||
|
||||
// 固定大小
|
||||
$el.css("width", dialogWidth);
|
||||
this.addClass('is-drag');
|
||||
},
|
||||
function (e) {
|
||||
var eventX = e.clientX || e.changedTouches[0].clientX;
|
||||
var eventY = e.clientY || e.changedTouches[0].clientY;
|
||||
|
||||
newValX = pt.left + Math.ceil(eventX - originX);
|
||||
newValY = pt.top + Math.ceil(eventY - originY);
|
||||
|
||||
if (newValX <= 0) newValX = 0;
|
||||
if (newValY <= 0) newValY = 0;
|
||||
|
||||
if (newValX + dialogWidth < $(window).width()) {
|
||||
$el.css({ "marginLeft": newValX });
|
||||
}
|
||||
if (newValY + dialogHeight < $(window).height()) {
|
||||
$el.css({ "marginTop": newValY });
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
this.removeClass('is-drag');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
bb_modal: function (el, obj, method, callback) {
|
||||
var $el = $(el);
|
||||
|
||||
if (method === 'dispose') {
|
||||
$el.remove();
|
||||
}
|
||||
else if (method === 'init') {
|
||||
function keyHandler() {
|
||||
var e = event;
|
||||
if (e.key === 'Escape') {
|
||||
var $dialog = $el.find('.modal-dialog');
|
||||
var invoker = $dialog.data('bb_dotnet_invoker');
|
||||
if (invoker != null) {
|
||||
invoker.obj.invokeMethodAsync(invoker.method);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($el.closest('.swal').length === 0) {
|
||||
// move self end of the body
|
||||
$('body').append($el);
|
||||
}
|
||||
$el.on('shown.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.addEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
obj.invokeMethodAsync(callback);
|
||||
});
|
||||
$el.on('hide.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.removeEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (method !== 'hide' && method !== 'dispose') {
|
||||
var instance = bootstrap.Modal.getInstance(el);
|
||||
if (instance != null) {
|
||||
instance._config.keyboard = false;
|
||||
}
|
||||
}
|
||||
$el.modal(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
@ -1,7 +1,7 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@inherits BootstrapComponentBase
|
||||
@inherits BootstrapModuleComponentBase
|
||||
|
||||
<div @attributes="@AdditionalAttributes" class="@ClassString" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="@Backdrop" data-bs-keyboard="@KeyboardString" @ref="ModalElement">
|
||||
<div @attributes="@AdditionalAttributes" class="@ClassString" tabindex="-1" role="dialog" data-bs-backdrop="@Backdrop" data-bs-keyboard="@KeyboardString" id="@Id">
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
@ -5,15 +5,11 @@
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Modal 组件
|
||||
/// </summary>
|
||||
public partial class Modal : IAsyncDisposable
|
||||
[JSModuleAutoLoader(JSObjectReference = true)]
|
||||
public partial class Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 DOM 元素实例
|
||||
/// </summary>
|
||||
private ElementReference ModalElement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得 样式字符串
|
||||
/// </summary>
|
||||
@ -24,7 +20,7 @@ public partial class Modal : IAsyncDisposable
|
||||
/// <summary>
|
||||
/// 获得 ModalDialog 集合
|
||||
/// </summary>
|
||||
private List<ModalDialog> Dialogs { get; } = new(8);
|
||||
protected List<ModalDialog> Dialogs { get; } = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否后台关闭弹窗 默认 false
|
||||
@ -54,8 +50,22 @@ public partial class Modal : IAsyncDisposable
|
||||
/// 获得/设置 弹窗已显示时回调此方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Obsolete("Call OnShownAsync")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public Func<Task>? ShownCallbackAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 弹窗已显示时回调此方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<Task>? OnShownAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭弹窗回调委托
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<Task>? OnCloseAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得 后台关闭弹窗设置
|
||||
/// </summary>
|
||||
@ -63,7 +73,11 @@ public partial class Modal : IAsyncDisposable
|
||||
|
||||
private string? KeyboardString => IsKeyboard ? "true" : "false";
|
||||
|
||||
private JSInterop<Modal>? Interop { get; set; }
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Task ModuleInitAsync() => InvokeInitAsync(Id, nameof(ShownCallback), nameof(CloseCallback));
|
||||
|
||||
/// <summary>
|
||||
/// 添加对话框方法
|
||||
@ -71,140 +85,89 @@ public partial class Modal : IAsyncDisposable
|
||||
/// <param name="dialog"></param>
|
||||
internal void AddDialog(ModalDialog dialog)
|
||||
{
|
||||
if (!Dialogs.Any())
|
||||
{
|
||||
dialog.IsShown = true;
|
||||
}
|
||||
|
||||
Dialogs.Add(dialog);
|
||||
ResetShownDialog(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除对话框方法
|
||||
/// </summary>
|
||||
/// <param name="dialog"></param>
|
||||
internal void RemoveDialog(ModalDialog? dialog = null)
|
||||
internal void RemoveDialog(ModalDialog dialog)
|
||||
{
|
||||
if (dialog == null)
|
||||
// 移除当前弹窗
|
||||
Dialogs.Remove(dialog);
|
||||
|
||||
if (Dialogs.Any())
|
||||
{
|
||||
dialog = Dialogs.LastOrDefault();
|
||||
dialog?.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialogs.Remove(dialog);
|
||||
ResetShownDialog(Dialogs.Last());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示指定对话框方法
|
||||
/// </summary>
|
||||
/// <param name="dialog"></param>
|
||||
internal void ShowDialog(ModalDialog? dialog = null)
|
||||
private void ResetShownDialog(ModalDialog dialog)
|
||||
{
|
||||
dialog ??= Dialogs.LastOrDefault();
|
||||
if (dialog != null)
|
||||
// 保证新添加的 Dialog 为当前弹窗
|
||||
Dialogs.ForEach(d =>
|
||||
{
|
||||
Dialogs.ForEach(d => d.IsShown = d == dialog);
|
||||
}
|
||||
d.IsShown = d == dialog;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnAfterRenderAsync 方法
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
Interop ??= new(JSRuntime);
|
||||
await Interop.InvokeVoidAsync(this, ModalElement, "bb_modal", "init", nameof(Shown));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹窗已经弹出回调方法
|
||||
/// 弹窗已经弹出回调方法 JSInvoke 调用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[JSInvokable]
|
||||
public async Task Shown()
|
||||
public async Task ShownCallback()
|
||||
{
|
||||
if (ShownCallbackAsync != null)
|
||||
if (OnShownAsync != null)
|
||||
{
|
||||
await ShownCallbackAsync();
|
||||
await OnShownAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹窗已经关闭回调方法 JSInvoke 调用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[JSInvokable]
|
||||
public async Task CloseCallback()
|
||||
{
|
||||
// 移除当前弹窗
|
||||
var dialog = Dialogs.FirstOrDefault(d => d.IsShown);
|
||||
if (dialog != null)
|
||||
{
|
||||
Dialogs.Remove(dialog);
|
||||
}
|
||||
|
||||
// 多级弹窗支持
|
||||
if (Dialogs.Any())
|
||||
{
|
||||
ResetShownDialog(Dialogs.Last());
|
||||
}
|
||||
|
||||
if (OnCloseAsync != null)
|
||||
{
|
||||
await OnCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹窗状态切换方法
|
||||
/// </summary>
|
||||
public async ValueTask Toggle()
|
||||
{
|
||||
var dialog = Dialogs.FirstOrDefault();
|
||||
if (dialog != null)
|
||||
{
|
||||
dialog.IsShown = true;
|
||||
}
|
||||
|
||||
if (Interop != null)
|
||||
{
|
||||
await Interop.InvokeVoidAsync(this, ModalElement, "bb_modal", "toggle");
|
||||
}
|
||||
}
|
||||
public Task Toggle() => InvokeExecuteAsync(Id, "show");
|
||||
|
||||
/// <summary>
|
||||
/// 显示弹窗方法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async ValueTask Show()
|
||||
{
|
||||
var dialog = Dialogs.LastOrDefault();
|
||||
if (dialog != null)
|
||||
{
|
||||
Dialogs.ForEach(d => d.IsShown = dialog == d);
|
||||
}
|
||||
if (Interop != null)
|
||||
{
|
||||
await Interop.InvokeVoidAsync(this, ModalElement, "bb_modal", "show");
|
||||
}
|
||||
}
|
||||
public Task Show() => InvokeExecuteAsync(Id, "show");
|
||||
|
||||
/// <summary>
|
||||
/// 关闭当前弹窗方法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task Close()
|
||||
{
|
||||
var dialog = Dialogs.FirstOrDefault(d => d.IsShown);
|
||||
if (dialog != null)
|
||||
{
|
||||
await dialog.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
await CloseOrPopDialog();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部使用如果还有弹窗继续显示,如果没有弹窗关闭所有
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal async ValueTask CloseOrPopDialog()
|
||||
{
|
||||
if (Dialogs.Any())
|
||||
{
|
||||
ShowDialog();
|
||||
}
|
||||
else if (Interop != null)
|
||||
{
|
||||
// 全部关闭
|
||||
await Interop.InvokeVoidAsync(this, ModalElement, "bb_modal", "hide");
|
||||
}
|
||||
}
|
||||
public Task Close() => InvokeExecuteAsync(Id, "hide");
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Header 文字方法
|
||||
@ -213,40 +176,6 @@ public partial class Modal : IAsyncDisposable
|
||||
public void SetHeaderText(string text)
|
||||
{
|
||||
var dialog = Dialogs.FirstOrDefault(d => d.IsShown);
|
||||
if (dialog != null)
|
||||
{
|
||||
dialog.SetHeaderText(text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual async ValueTask DisposeAsyncCore(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 切换线程防止 JS 清理 DOM 后 C# 代码报错
|
||||
// https://gitee.com/LongbowEnterprise/BootstrapBlazor/issues/I4PKOC
|
||||
await Task.Delay(300);
|
||||
|
||||
// JS 清理 DOM
|
||||
if (Interop != null)
|
||||
{
|
||||
await Interop.InvokeVoidAsync(this, ModalElement, "bb_modal", "dispose");
|
||||
Interop.Dispose();
|
||||
Interop = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose 方法
|
||||
/// </summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisposeAsyncCore(true);
|
||||
GC.SuppressFinalize(this);
|
||||
dialog?.SetHeaderText(text);
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,39 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@inherits BootstrapComponentBase
|
||||
|
||||
<div class="@ClassName" role="document" @ref="DialogElement">
|
||||
<div class="@ClassName">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@if (HeaderTemplate != null)
|
||||
{
|
||||
@HeaderTemplate
|
||||
}
|
||||
else
|
||||
{
|
||||
<h5 class="modal-title flex-fill">@Title</h5>
|
||||
}
|
||||
<div class="modal-header-buttons">
|
||||
@if (HeaderToolbarTemplate != null)
|
||||
@if (ShowHeader)
|
||||
{
|
||||
<div class="modal-header">
|
||||
@if (HeaderTemplate != null)
|
||||
{
|
||||
@HeaderToolbarTemplate
|
||||
@HeaderTemplate
|
||||
}
|
||||
@if (ShowPrintButton && ShowPrintButtonInHeader)
|
||||
else
|
||||
{
|
||||
<PrintButton Color="Color.Primary" class="btn-print" Text="@PrintButtonText" />
|
||||
}
|
||||
@if (ShowMaximizeButton)
|
||||
{
|
||||
<Button Color="Color.None" class="btn-maximize" aria-label="@MaximizeAriaLabel" OnClick="@OnToggleMaximize" Icon="@MaximizeIcon"></Button>
|
||||
}
|
||||
@if (ShowHeaderCloseButton)
|
||||
{
|
||||
<Button Color="Color.None" class="btn-close" aria-label="Close" OnClickWithoutRender="@OnClickClose"></Button>
|
||||
<h5 class="modal-title flex-fill">@Title</h5>
|
||||
}
|
||||
<div class="modal-header-buttons">
|
||||
@if (HeaderToolbarTemplate != null)
|
||||
{
|
||||
@HeaderToolbarTemplate
|
||||
}
|
||||
@if (ShowPrintButton && ShowPrintButtonInHeader)
|
||||
{
|
||||
<PrintButton Color="Color.Primary" class="btn-print" Text="@PrintButtonText" />
|
||||
}
|
||||
@if (ShowMaximizeButton)
|
||||
{
|
||||
<Button Color="Color.None" class="btn-maximize" aria-label="@MaximizeAriaLabel" OnClick="@OnToggleMaximize" Icon="@MaximizeIcon"></Button>
|
||||
}
|
||||
@if (ShowHeaderCloseButton)
|
||||
{
|
||||
<Button Color="Color.None" class="btn-close" aria-label="Close" OnClickWithoutRender="@OnClickClose"></Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<CascadingValue Name="BodyContext" Value="@BodyContext" IsFixed="true">
|
||||
<div class="modal-body">
|
||||
@RenderBodyTemplate()
|
||||
|
@ -11,11 +11,6 @@ namespace BootstrapBlazor.Components;
|
||||
/// </summary>
|
||||
public partial class ModalDialog : IHandlerException, IDisposable
|
||||
{
|
||||
private ElementReference DialogElement { get; set; }
|
||||
|
||||
[NotNull]
|
||||
private JSInterop<ModalDialog>? Interop { get; set; }
|
||||
|
||||
private string MaximizeAriaLabel => MaximizeStatus ? "maximize" : "restore";
|
||||
|
||||
/// <summary>
|
||||
@ -109,6 +104,12 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
[Parameter]
|
||||
public bool ShowHeaderCloseButton { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示 Header 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowHeader { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示 Footer 默认为 true
|
||||
/// </summary>
|
||||
@ -157,11 +158,11 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
[Parameter]
|
||||
public RenderFragment? HeaderTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭弹窗回调委托
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<Task>? OnClose { get; set; }
|
||||
///// <summary>
|
||||
///// 获得/设置 关闭弹窗回调委托
|
||||
///// </summary>
|
||||
//[Parameter]
|
||||
//public Func<Task>? OnClose { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 保存按钮回调委托
|
||||
@ -194,7 +195,7 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
[NotNull]
|
||||
public Modal? Modal { get; set; }
|
||||
protected Modal? Modal { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
@ -208,9 +209,6 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
base.OnInitialized();
|
||||
|
||||
ErrorLogger?.Register(this);
|
||||
|
||||
Interop = new JSInterop<ModalDialog>(JSRuntime);
|
||||
|
||||
Modal.AddDialog(this);
|
||||
}
|
||||
|
||||
@ -226,21 +224,6 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
PrintButtonText ??= Localizer[nameof(PrintButtonText)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnAfterRenderAsync 方法
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
await Interop.InvokeVoidAsync(this, DialogElement, "bb_modal_dialog", nameof(Close));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Header 文字方法
|
||||
/// </summary>
|
||||
@ -251,16 +234,7 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task OnClickClose()
|
||||
{
|
||||
Modal.RemoveDialog(this);
|
||||
await Modal.CloseOrPopDialog();
|
||||
|
||||
if (OnClose != null)
|
||||
{
|
||||
await OnClose();
|
||||
}
|
||||
}
|
||||
private async Task OnClickClose() => await Modal.Close();
|
||||
|
||||
private bool MaximizeStatus { get; set; }
|
||||
|
||||
@ -285,13 +259,6 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close 方法 客户端按 ESC 键盘时调用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[JSInvokable]
|
||||
public Task Close() => OnClickClose();
|
||||
|
||||
private RenderFragment RenderBodyTemplate() => builder =>
|
||||
{
|
||||
builder.AddContent(0, _errorContent ?? BodyTemplate);
|
||||
@ -324,9 +291,7 @@ public partial class ModalDialog : IHandlerException, IDisposable
|
||||
if (disposing)
|
||||
{
|
||||
ErrorLogger?.UnRegister(this);
|
||||
|
||||
Interop.Dispose();
|
||||
Interop = null;
|
||||
Modal.RemoveDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ public abstract class PopupOptionBase
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否自动隐藏 默认 true 自动关闭
|
||||
/// 获得/设置 是否自动隐藏 默认 true 自动关闭 <see cref="SweetAlert"/> 默认 false
|
||||
/// </summary>
|
||||
public bool IsAutoHide { get; set; } = true;
|
||||
|
||||
|
@ -12,7 +12,7 @@ public class SwalOption : PopupOptionBase
|
||||
/// <summary>
|
||||
/// 获得/设置 相关弹窗实例
|
||||
/// </summary>
|
||||
internal Modal? Dialog { get; set; }
|
||||
internal Modal? Modal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 模态弹窗返回值任务实例
|
||||
@ -34,11 +34,6 @@ public class SwalOption : PopupOptionBase
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 相关连数据,多用于传值使用
|
||||
/// </summary>
|
||||
public object? BodyContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 ModalBody 组件
|
||||
/// </summary>
|
||||
@ -64,6 +59,31 @@ public class SwalOption : PopupOptionBase
|
||||
/// </summary>
|
||||
public RenderFragment? ButtonTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭按钮图标 默认 fa-solid fa-xmark
|
||||
/// </summary>
|
||||
public string? CloseButtonIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 确认按钮图标 默认 fa-solid fa-check
|
||||
/// </summary>
|
||||
public string? ConfirmButtonIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭按钮文字 默认为 关闭
|
||||
/// </summary>
|
||||
public string? CloseButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 确认按钮文字 默认为 确认
|
||||
/// </summary>
|
||||
public string? ConfirmButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 取消按钮文字 默认为 取消
|
||||
/// </summary>
|
||||
public string? CancelButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@ -76,18 +96,22 @@ public class SwalOption : PopupOptionBase
|
||||
/// 将参数转换为组件属性方法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, object?> ToAttributes()
|
||||
public Dictionary<string, object> ToAttributes()
|
||||
{
|
||||
var parameters = new Dictionary<string, object?>
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
[nameof(Size)] = Size.Medium,
|
||||
[nameof(ModalDialog.IsCentered)] = true,
|
||||
[nameof(ModalDialog.IsScrolling)] = false,
|
||||
[nameof(ModalDialog.ShowCloseButton)] = false,
|
||||
[nameof(ShowFooter)] = false,
|
||||
[nameof(ModalDialog.Title)] = Title,
|
||||
[nameof(BodyContext)] = BodyContext
|
||||
[nameof(ModalDialog.ShowHeader)] = false,
|
||||
[nameof(ModalDialog.ShowFooter)] = false
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Title))
|
||||
{
|
||||
parameters.Add(nameof(Title), Title);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@ -97,9 +121,9 @@ public class SwalOption : PopupOptionBase
|
||||
/// <param name="returnValue">模态弹窗返回值 默认为 true</param>
|
||||
public async Task Close(bool returnValue = true)
|
||||
{
|
||||
if (Dialog != null)
|
||||
if (Modal != null)
|
||||
{
|
||||
await Dialog.Close();
|
||||
await Modal.Close();
|
||||
}
|
||||
|
||||
if (IsModalConfirm)
|
||||
|
@ -4,11 +4,6 @@
|
||||
z-index: var(--bb-swal-zindex);
|
||||
}
|
||||
|
||||
.swal .modal-header,
|
||||
.swal .modal-header-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.swal2-icon,
|
||||
.swal2-actions {
|
||||
display: flex;
|
||||
|
@ -3,7 +3,7 @@
|
||||
@inject SwalService Swal
|
||||
|
||||
<div class="swal">
|
||||
<Modal AdditionalAttributes="@AdditionalAttributes" @ref="ModalContainer">
|
||||
<Modal @ref="ModalContainer" IsKeyboard="false" OnCloseAsync="OnCloseAsync">
|
||||
@RenderDialog()
|
||||
</Modal>
|
||||
</div>
|
||||
|
@ -31,7 +31,10 @@ public partial class SweetAlert : IDisposable
|
||||
private CancellationTokenSource? DelayToken { get; set; }
|
||||
|
||||
[NotNull]
|
||||
private Dictionary<string, object?>? DialogParameter { get; set; }
|
||||
private Dictionary<string, object>? DialogParameter { get; set; }
|
||||
|
||||
[NotNull]
|
||||
private Func<Task>? OnCloseAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnInitialized 方法
|
||||
@ -60,42 +63,47 @@ public partial class SweetAlert : IDisposable
|
||||
|
||||
if (IsAutoHide && Delay > 0)
|
||||
{
|
||||
if (DelayToken == null)
|
||||
{
|
||||
DelayToken = new CancellationTokenSource();
|
||||
}
|
||||
await DelayCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
async Task DelayCloseAsync()
|
||||
{
|
||||
DelayToken ??= new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
await Task.Delay(Delay, DelayToken.Token);
|
||||
await ModalContainer.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
if (!DelayToken.IsCancellationRequested)
|
||||
{
|
||||
// 自动关闭弹窗
|
||||
await ModalContainer.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task Show(SwalOption option)
|
||||
{
|
||||
IsAutoHide = option.IsAutoHide;
|
||||
Delay = option.Delay;
|
||||
|
||||
option.Dialog = ModalContainer;
|
||||
var parameters = option.ToAttributes();
|
||||
|
||||
parameters.Add(nameof(ModalDialog.OnClose), new Func<Task>(async () =>
|
||||
OnCloseAsync = () =>
|
||||
{
|
||||
if (IsAutoHide && DelayToken != null)
|
||||
{
|
||||
DelayToken.Cancel();
|
||||
DelayToken = null;
|
||||
}
|
||||
DialogParameter = null;
|
||||
await ModalContainer.CloseOrPopDialog();
|
||||
StateHasChanged();
|
||||
}));
|
||||
|
||||
// 移除当前 DialogParameter
|
||||
DialogParameter = null;
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
IsAutoHide = option.IsAutoHide;
|
||||
Delay = option.Delay;
|
||||
|
||||
option.Modal = ModalContainer;
|
||||
var parameters = option.ToAttributes();
|
||||
parameters.Add(nameof(ModalDialog.BodyTemplate), BootstrapDynamicComponent.CreateComponent<SweetAlertBody>(SweetAlertBody.Parse(option)).Render());
|
||||
|
||||
DialogParameter = parameters;
|
||||
@ -110,10 +118,7 @@ public partial class SweetAlert : IDisposable
|
||||
{
|
||||
var index = 0;
|
||||
builder.OpenComponent<ModalDialog>(index++);
|
||||
foreach (var p in DialogParameter)
|
||||
{
|
||||
builder.AddAttribute(index++, p.Key, p.Value);
|
||||
}
|
||||
builder.AddMultipleAttributes(index++, DialogParameter);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
@ -141,7 +146,7 @@ public partial class SweetAlert : IDisposable
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
@ -44,17 +44,11 @@ else
|
||||
<div class="swal2-actions">
|
||||
@if (ShowClose)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="@OnClickClose">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
<span>@(IsConfirm ? CancelButtonText : CloseButtonText)</span>
|
||||
</button>
|
||||
<Button Color="Color.Secondary" Icon="@CloseButtonIcon" Text="@InternalCloseButtonText" OnClickWithoutRender="OnClickClose" />
|
||||
}
|
||||
@if (IsConfirm)
|
||||
{
|
||||
<button type="button" class="btn btn-danger ms-3" @onclick="@OnClickConfirm">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
<span>@ConfirmButtonText</span>
|
||||
</button>
|
||||
<Button Color="Color.Danger" Icon="@ConfirmButtonIcon" Text="@ConfirmButtonText" OnClickWithoutRender="OnClickConfirm" class="ms-3" />
|
||||
}
|
||||
@ButtonTemplate
|
||||
</div>
|
||||
|
@ -11,10 +11,13 @@ namespace BootstrapBlazor.Components;
|
||||
/// </summary>
|
||||
public partial class SweetAlertBody
|
||||
{
|
||||
private string InternalCloseButtonText => IsConfirm ? CancelButtonText : CloseButtonText;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭按钮文字 默认为 关闭
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? CloseButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -67,17 +70,31 @@ public partial class SweetAlertBody
|
||||
[Parameter]
|
||||
public bool IsConfirm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭按钮图标 默认 fa-solid fa-xmark
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? CloseButtonIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 确认按钮图标 默认 fa-solid fa-check
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? ConfirmButtonIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 关闭按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action? OnClose { get; set; }
|
||||
public Func<Task>? OnCloseAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 确认按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action? OnConfirm { get; set; }
|
||||
public Func<Task>? OnConfirmAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示内容模板
|
||||
@ -120,13 +137,18 @@ public partial class SweetAlertBody
|
||||
[nameof(SweetAlertBody.ShowClose)] = option.ShowClose,
|
||||
[nameof(SweetAlertBody.IsConfirm)] = option.IsModalConfirm,
|
||||
[nameof(SweetAlertBody.ShowFooter)] = option.ShowFooter,
|
||||
[nameof(SweetAlertBody.OnClose)] = new Action(async () => await option.Close(false)),
|
||||
[nameof(SweetAlertBody.OnConfirm)] = new Action(async () => await option.Close(true)),
|
||||
[nameof(SweetAlertBody.OnCloseAsync)] = () => option.Close(false),
|
||||
[nameof(SweetAlertBody.OnConfirmAsync)] = () => option.Close(true),
|
||||
[nameof(SweetAlertBody.Title)] = option.Title,
|
||||
[nameof(SweetAlertBody.Content)] = option.Content,
|
||||
[nameof(SweetAlertBody.BodyTemplate)] = option.BodyTemplate,
|
||||
[nameof(SweetAlertBody.FooterTemplate)] = option.FooterTemplate,
|
||||
[nameof(SweetAlertBody.ButtonTemplate)] = option.ButtonTemplate
|
||||
[nameof(SweetAlertBody.ButtonTemplate)] = option.ButtonTemplate,
|
||||
[nameof(SweetAlertBody.CloseButtonIcon)] = option.CloseButtonIcon,
|
||||
[nameof(SweetAlertBody.ConfirmButtonIcon)] = option.ConfirmButtonIcon,
|
||||
[nameof(SweetAlertBody.CloseButtonText)] = option.CloseButtonText,
|
||||
[nameof(SweetAlertBody.CancelButtonText)] = option.CancelButtonText,
|
||||
[nameof(SweetAlertBody.ConfirmButtonText)] = option.ConfirmButtonText
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -139,9 +161,24 @@ public partial class SweetAlertBody
|
||||
CloseButtonText ??= Localizer[nameof(CloseButtonText)];
|
||||
CancelButtonText ??= Localizer[nameof(CancelButtonText)];
|
||||
ConfirmButtonText ??= Localizer[nameof(ConfirmButtonText)];
|
||||
|
||||
CloseButtonIcon ??= "fa-solid fa-xmark";
|
||||
ConfirmButtonIcon ??= "fa-solid fa-check";
|
||||
}
|
||||
|
||||
private void OnClickClose() => OnClose?.Invoke();
|
||||
private async Task OnClickClose()
|
||||
{
|
||||
if (OnCloseAsync != null)
|
||||
{
|
||||
await OnCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClickConfirm() => OnConfirm?.Invoke();
|
||||
private async Task OnClickConfirm()
|
||||
{
|
||||
if (OnConfirmAsync != null)
|
||||
{
|
||||
await OnConfirmAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,12 @@
|
||||
--bb-table-header-min-height: 37px;
|
||||
--bb-table-footer-font-weight: blod;
|
||||
--bb-table-footer-border-top: 2px solid #dee2e6;
|
||||
--bb-table-loader-bg: #f8f9fa;
|
||||
--bb-table-card-row-padding: .75rem .5rem;
|
||||
--bb-table-columnlist-max-height: var(--bb-dropdown-max-height);
|
||||
--bs-table-striped-bg: rgba(0,0,0,.05);
|
||||
--bs-table-hover-bg: rgba(0,0,0,.075);
|
||||
--bb-table-search-body-margin: 1rem;
|
||||
--bb-loader-bg: #f8f9fa;
|
||||
}
|
||||
|
||||
.table-container .table:not(.table-excel) .switch {
|
||||
@ -364,7 +364,7 @@
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bb-table-loader-bg);
|
||||
background-color: var(--bb-loader-bg);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
.form-control {
|
||||
form {
|
||||
--bb-loader-bg: #f8f9fa;
|
||||
}
|
||||
.form-control {
|
||||
--bb-form-control-padding: 0.375rem 0.75rem;
|
||||
padding: var(--bb-form-control-padding);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
@ -26,8 +26,7 @@ public static class DialogServiceExtensions
|
||||
[nameof(SearchDialog<TModel>.Items)] = option.Items ?? Utility.GenerateColumns<TModel>(item => item.Searchable),
|
||||
[nameof(SearchDialog<TModel>.OnResetSearchClick)] = new Func<Task>(async () =>
|
||||
{
|
||||
option.OnCloseAsync = null;
|
||||
option.Dialog.RemoveDialog();
|
||||
await option.Dialog.Close();
|
||||
if (option.OnResetSearchClick != null)
|
||||
{
|
||||
await option.OnResetSearchClick();
|
||||
@ -35,8 +34,7 @@ public static class DialogServiceExtensions
|
||||
}),
|
||||
[nameof(SearchDialog<TModel>.OnSearchClick)] = new Func<Task>(async () =>
|
||||
{
|
||||
option.OnCloseAsync = null;
|
||||
option.Dialog.RemoveDialog();
|
||||
await option.Dialog.Close();
|
||||
if (option.OnSearchClick != null)
|
||||
{
|
||||
await option.OnSearchClick();
|
||||
@ -70,8 +68,7 @@ public static class DialogServiceExtensions
|
||||
[nameof(EditDialog<TModel>.Items)] = option.Items ?? Utility.GenerateColumns<TModel>(item => item.Editable),
|
||||
[nameof(EditDialog<TModel>.OnCloseAsync)] = new Func<Task>(async () =>
|
||||
{
|
||||
option.Dialog.RemoveDialog();
|
||||
await option.Dialog.CloseOrPopDialog();
|
||||
await option.Dialog.Close();
|
||||
}),
|
||||
[nameof(EditDialog<TModel>.OnSaveAsync)] = new Func<EditContext, Task>(async context =>
|
||||
{
|
||||
@ -80,8 +77,7 @@ public static class DialogServiceExtensions
|
||||
var ret = await option.OnEditAsync(context);
|
||||
if (ret)
|
||||
{
|
||||
option.Dialog.RemoveDialog();
|
||||
await option.Dialog.CloseOrPopDialog();
|
||||
await option.Dialog.Close();
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -20,6 +20,6 @@ public static class SwalExtensions
|
||||
{
|
||||
option.IsModalConfirm = true;
|
||||
await service.Show(option, swal);
|
||||
return !option.IsModalConfirm || await option.ReturnTask.Task;
|
||||
return await option.ReturnTask.Task;
|
||||
}
|
||||
}
|
||||
|
@ -1292,18 +1292,6 @@
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_form_load: function (el, method) {
|
||||
var $el = $(el);
|
||||
if (method === 'show')
|
||||
$el.addClass('show');
|
||||
else
|
||||
$el.removeClass('show');
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_drawer: function (el, open) {
|
||||
@ -1850,133 +1838,6 @@
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_modal_dialog: function (el, obj, method) {
|
||||
var $el = $(el);
|
||||
$el.data('bb_dotnet_invoker', { obj, method });
|
||||
|
||||
// monitor mousedown ready to drag dialog
|
||||
var originX = 0;
|
||||
var originY = 0;
|
||||
var dialogWidth = 0;
|
||||
var dialogHeight = 0;
|
||||
var pt = { top: 0, left: 0 };
|
||||
if ($el.hasClass('is-draggable')) {
|
||||
$el.find('.btn-maximize').click(function () {
|
||||
$button = $(this);
|
||||
var status = $button.attr('aria-label');
|
||||
if (status === "maximize") {
|
||||
$el.css({
|
||||
"marginLeft": "auto",
|
||||
"width": $el.width(),
|
||||
});
|
||||
}
|
||||
else {
|
||||
var handler = window.setInterval(function () {
|
||||
if ($el.attr('style')) {
|
||||
$el.removeAttr('style');
|
||||
}
|
||||
else {
|
||||
window.clearInterval(handler);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
$el.css({
|
||||
"marginLeft": "auto"
|
||||
});
|
||||
$el.find('.modal-header').drag(
|
||||
function (e) {
|
||||
originX = e.clientX || e.touches[0].clientX;
|
||||
originY = e.clientY || e.touches[0].clientY;
|
||||
|
||||
// 弹窗大小
|
||||
dialogWidth = $el.width();
|
||||
dialogHeight = $el.height();
|
||||
|
||||
// 偏移量
|
||||
pt.top = parseInt($el.css('marginTop').replace("px", ""));
|
||||
pt.left = parseInt($el.css('marginLeft').replace("px", ""));
|
||||
|
||||
$el.css({ "marginLeft": pt.left, "marginTop": pt.top });
|
||||
|
||||
// 固定大小
|
||||
$el.css("width", dialogWidth);
|
||||
this.addClass('is-drag');
|
||||
},
|
||||
function (e) {
|
||||
var eventX = e.clientX || e.changedTouches[0].clientX;
|
||||
var eventY = e.clientY || e.changedTouches[0].clientY;
|
||||
|
||||
newValX = pt.left + Math.ceil(eventX - originX);
|
||||
newValY = pt.top + Math.ceil(eventY - originY);
|
||||
|
||||
if (newValX <= 0) newValX = 0;
|
||||
if (newValY <= 0) newValY = 0;
|
||||
|
||||
if (newValX + dialogWidth < $(window).width()) {
|
||||
$el.css({ "marginLeft": newValX });
|
||||
}
|
||||
if (newValY + dialogHeight < $(window).height()) {
|
||||
$el.css({ "marginTop": newValY });
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
this.removeClass('is-drag');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
bb_modal: function (el, obj, method, callback) {
|
||||
var $el = $(el);
|
||||
|
||||
if (method === 'dispose') {
|
||||
$el.remove();
|
||||
}
|
||||
else if (method === 'init') {
|
||||
function keyHandler() {
|
||||
var e = event;
|
||||
if (e.key === 'Escape') {
|
||||
var $dialog = $el.find('.modal-dialog');
|
||||
var invoker = $dialog.data('bb_dotnet_invoker');
|
||||
if (invoker != null) {
|
||||
invoker.obj.invokeMethodAsync(invoker.method);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($el.closest('.swal').length === 0) {
|
||||
// move self end of the body
|
||||
$('body').append($el);
|
||||
}
|
||||
$el.on('shown.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.addEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
obj.invokeMethodAsync(callback);
|
||||
});
|
||||
$el.on('hide.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.removeEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (method !== 'hide' && method !== 'dispose') {
|
||||
var instance = bootstrap.Modal.getInstance(el);
|
||||
if (instance != null) {
|
||||
instance._config.keyboard = false;
|
||||
}
|
||||
}
|
||||
$el.modal(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_notify_checkPermission: function (obj, method, requestPermission) {
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -19279,18 +19279,6 @@ return jQuery;
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_form_load: function (el, method) {
|
||||
var $el = $(el);
|
||||
if (method === 'show')
|
||||
$el.addClass('show');
|
||||
else
|
||||
$el.removeClass('show');
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_drawer: function (el, open) {
|
||||
@ -19837,133 +19825,6 @@ return jQuery;
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_modal_dialog: function (el, obj, method) {
|
||||
var $el = $(el);
|
||||
$el.data('bb_dotnet_invoker', { obj, method });
|
||||
|
||||
// monitor mousedown ready to drag dialog
|
||||
var originX = 0;
|
||||
var originY = 0;
|
||||
var dialogWidth = 0;
|
||||
var dialogHeight = 0;
|
||||
var pt = { top: 0, left: 0 };
|
||||
if ($el.hasClass('is-draggable')) {
|
||||
$el.find('.btn-maximize').click(function () {
|
||||
$button = $(this);
|
||||
var status = $button.attr('aria-label');
|
||||
if (status === "maximize") {
|
||||
$el.css({
|
||||
"marginLeft": "auto",
|
||||
"width": $el.width(),
|
||||
});
|
||||
}
|
||||
else {
|
||||
var handler = window.setInterval(function () {
|
||||
if ($el.attr('style')) {
|
||||
$el.removeAttr('style');
|
||||
}
|
||||
else {
|
||||
window.clearInterval(handler);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
$el.css({
|
||||
"marginLeft": "auto"
|
||||
});
|
||||
$el.find('.modal-header').drag(
|
||||
function (e) {
|
||||
originX = e.clientX || e.touches[0].clientX;
|
||||
originY = e.clientY || e.touches[0].clientY;
|
||||
|
||||
// 弹窗大小
|
||||
dialogWidth = $el.width();
|
||||
dialogHeight = $el.height();
|
||||
|
||||
// 偏移量
|
||||
pt.top = parseInt($el.css('marginTop').replace("px", ""));
|
||||
pt.left = parseInt($el.css('marginLeft').replace("px", ""));
|
||||
|
||||
$el.css({ "marginLeft": pt.left, "marginTop": pt.top });
|
||||
|
||||
// 固定大小
|
||||
$el.css("width", dialogWidth);
|
||||
this.addClass('is-drag');
|
||||
},
|
||||
function (e) {
|
||||
var eventX = e.clientX || e.changedTouches[0].clientX;
|
||||
var eventY = e.clientY || e.changedTouches[0].clientY;
|
||||
|
||||
newValX = pt.left + Math.ceil(eventX - originX);
|
||||
newValY = pt.top + Math.ceil(eventY - originY);
|
||||
|
||||
if (newValX <= 0) newValX = 0;
|
||||
if (newValY <= 0) newValY = 0;
|
||||
|
||||
if (newValX + dialogWidth < $(window).width()) {
|
||||
$el.css({ "marginLeft": newValX });
|
||||
}
|
||||
if (newValY + dialogHeight < $(window).height()) {
|
||||
$el.css({ "marginTop": newValY });
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
this.removeClass('is-drag');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
bb_modal: function (el, obj, method, callback) {
|
||||
var $el = $(el);
|
||||
|
||||
if (method === 'dispose') {
|
||||
$el.remove();
|
||||
}
|
||||
else if (method === 'init') {
|
||||
function keyHandler() {
|
||||
var e = event;
|
||||
if (e.key === 'Escape') {
|
||||
var $dialog = $el.find('.modal-dialog');
|
||||
var invoker = $dialog.data('bb_dotnet_invoker');
|
||||
if (invoker != null) {
|
||||
invoker.obj.invokeMethodAsync(invoker.method);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($el.closest('.swal').length === 0) {
|
||||
// move self end of the body
|
||||
$('body').append($el);
|
||||
}
|
||||
$el.on('shown.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.addEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
obj.invokeMethodAsync(callback);
|
||||
});
|
||||
$el.on('hide.bs.modal', function () {
|
||||
var keyboard = $el.attr('data-bs-keyboard') === "true";
|
||||
if (keyboard === true) {
|
||||
document.removeEventListener('keyup', keyHandler, false);
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (method !== 'hide' && method !== 'dispose') {
|
||||
var instance = bootstrap.Modal.getInstance(el);
|
||||
if (instance != null) {
|
||||
instance._config.keyboard = false;
|
||||
}
|
||||
}
|
||||
$el.modal(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
(function ($) {
|
||||
$.extend({
|
||||
bb_notify_checkPermission: function (obj, method, requestPermission) {
|
||||
|
File diff suppressed because one or more lines are too long
12
src/BootstrapBlazor/wwwroot/modules/edit-dialog.js
Normal file
12
src/BootstrapBlazor/wwwroot/modules/edit-dialog.js
Normal file
@ -0,0 +1,12 @@
|
||||
import BlazorComponent from "./base/blazor-component.js"
|
||||
|
||||
export class EditDialog extends BlazorComponent {
|
||||
_execute(args) {
|
||||
var show = args[0]
|
||||
if (show) {
|
||||
this._element.classList.add('show')
|
||||
} else {
|
||||
this._element.classList.remove('show')
|
||||
}
|
||||
}
|
||||
}
|
92
src/BootstrapBlazor/wwwroot/modules/modal.js
Normal file
92
src/BootstrapBlazor/wwwroot/modules/modal.js
Normal file
@ -0,0 +1,92 @@
|
||||
import BlazorComponent from "./base/blazor-component.js"
|
||||
import EventHandler from "./base/event-handler.js"
|
||||
|
||||
export class Modal extends BlazorComponent {
|
||||
_init() {
|
||||
this._invoker = this._config.arguments[0]
|
||||
this._invokerShownMethod = this._config.arguments[1]
|
||||
this._invokerCloseMethod = this._config.arguments[2]
|
||||
this._setEventListeners()
|
||||
}
|
||||
|
||||
_setEventListeners() {
|
||||
EventHandler.on(this._element, 'shown.bs.modal', () => {
|
||||
this._invoker.invokeMethodAsync(this._invokerShownMethod)
|
||||
})
|
||||
EventHandler.on(this._element, 'hide.bs.modal', () => {
|
||||
this._invoker.invokeMethodAsync(this._invokerCloseMethod)
|
||||
})
|
||||
|
||||
console.log('pop1')
|
||||
|
||||
this._pop = () => {
|
||||
console.log('pop2')
|
||||
if (this._modal) {
|
||||
this._modal._dialog.remove()
|
||||
this._modal.dispose()
|
||||
this._modal = null
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.paddingLeft = '';
|
||||
document.body.style.paddingRight = '';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
}
|
||||
EventHandler.on(window, 'popstate', this._pop)
|
||||
}
|
||||
|
||||
_execute(args) {
|
||||
const method = args[1]
|
||||
if (method === 'show') {
|
||||
this._show()
|
||||
} else if (method === 'hide') {
|
||||
this._hide()
|
||||
} else if (method === 'toggle') {
|
||||
this._toggle()
|
||||
}
|
||||
}
|
||||
|
||||
_show() {
|
||||
const dialogs = this._element.querySelectorAll('.modal-dialog')
|
||||
if (dialogs.length === 1) {
|
||||
const keyboard = this._element.getAttribute('data-bs-keyboard') === 'true'
|
||||
let backdrop = this._element.getAttribute('data-bs-backdrop')
|
||||
if (backdrop === null) {
|
||||
backdrop = true
|
||||
}
|
||||
if (!this._modal) {
|
||||
this._modal = bootstrap.Modal.getOrCreateInstance(this._element)
|
||||
}
|
||||
this._modal._config.keyboard = keyboard
|
||||
this._modal._config.backdrop = backdrop
|
||||
this._modal.show()
|
||||
} else {
|
||||
this._invoker.invokeMethodAsync(this._invokerShownMethod)
|
||||
}
|
||||
}
|
||||
|
||||
_hide() {
|
||||
const dialogs = this._element.querySelectorAll('.modal-dialog')
|
||||
if (dialogs.length === 1) {
|
||||
this._modal.hide()
|
||||
} else {
|
||||
this._invoker.invokeMethodAsync(this._invokerCloseMethod)
|
||||
}
|
||||
}
|
||||
|
||||
_toggle() {
|
||||
if (this._modal) {
|
||||
this._modal.toggle()
|
||||
} else {
|
||||
this._show()
|
||||
}
|
||||
}
|
||||
|
||||
_dispose() {
|
||||
EventHandler.off(this._element, 'shown.bs.modal')
|
||||
EventHandler.off(this._element, 'hide.bs.modal')
|
||||
EventHandler.off(window, 'popstate', this._pop)
|
||||
if (this._modal) {
|
||||
this._modal.dispose()
|
||||
}
|
||||
}
|
||||
}
|
@ -27,12 +27,14 @@ public class DialogTest : DialogTestBase
|
||||
FooterTemplate = builder => builder.AddContent(0, "Test-FooterTemplate"),
|
||||
Class = "test-class",
|
||||
ShowMaximizeButton = true,
|
||||
IsBackdrop = false,
|
||||
OnCloseAsync = () =>
|
||||
{
|
||||
closed = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}));
|
||||
Assert.Contains("data-bs-backdrop=\"static\"", cut.Markup);
|
||||
|
||||
// 全屏按钮
|
||||
Assert.Contains("btn-maximize", cut.Markup);
|
||||
@ -45,16 +47,22 @@ public class DialogTest : DialogTestBase
|
||||
|
||||
// 测试关闭逻辑
|
||||
var modal = cut.FindComponent<Modal>();
|
||||
cut.InvokeAsync(() => modal.Instance.Close());
|
||||
cut.InvokeAsync(async () =>
|
||||
{
|
||||
await modal.Instance.Close();
|
||||
await modal.Instance.CloseCallback();
|
||||
});
|
||||
Assert.True(closed);
|
||||
|
||||
// 测试 HeaderToolbarTemplate
|
||||
cut.InvokeAsync(() => dialog.Show(new DialogOption()
|
||||
{
|
||||
IsBackdrop = true,
|
||||
HeaderToolbarTemplate = builder => builder.AddContent(0, "Test-HeaderToolbarTemplate"),
|
||||
}));
|
||||
Assert.DoesNotContain("data-bs-backdrop", cut.FindComponent<Modal>().Markup);
|
||||
Assert.Contains("Test-HeaderToolbarTemplate", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.Close());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 测试 Component 赋值逻辑
|
||||
cut.InvokeAsync(() => dialog.Show(new DialogOption()
|
||||
@ -64,7 +72,7 @@ public class DialogTest : DialogTestBase
|
||||
}));
|
||||
Assert.Contains("class=\"btn btn-primary\"", cut.Markup);
|
||||
modal = cut.FindComponent<Modal>();
|
||||
cut.InvokeAsync(() => modal.Instance.Close());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 测试 Component 与 BodyTemplate 均为 null 逻辑
|
||||
cut.InvokeAsync(() => dialog.Show(new DialogOption()
|
||||
@ -72,7 +80,7 @@ public class DialogTest : DialogTestBase
|
||||
Component = null,
|
||||
BodyTemplate = null
|
||||
}));
|
||||
cut.InvokeAsync(() => modal.Instance.Close());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShownCallbackAsync
|
||||
@ -80,7 +88,7 @@ public class DialogTest : DialogTestBase
|
||||
var option1 = new DialogOption
|
||||
{
|
||||
BodyTemplate = builder => builder.AddContent(0, "Test-BodyTemplate"),
|
||||
ShownCallbackAsync = () =>
|
||||
OnShownAsync = () =>
|
||||
{
|
||||
shown = true;
|
||||
return Task.CompletedTask;
|
||||
@ -88,10 +96,9 @@ public class DialogTest : DialogTestBase
|
||||
};
|
||||
cut.InvokeAsync(() => dialog.Show(option1));
|
||||
modal = cut.FindComponent<Modal>();
|
||||
cut.InvokeAsync(() => modal.Instance.ShownCallbackAsync!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.ShownCallback());
|
||||
Assert.True(shown);
|
||||
var button = cut.FindComponents<Button>().First(b => b.Instance.Text == "关闭");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowSearchDialog
|
||||
@ -112,13 +119,15 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowSearchDialog(option));
|
||||
|
||||
// 重置按钮委托为空 null
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "重置");
|
||||
var button = cut.FindComponents<Button>().First(b => b.Instance.Text == "重置");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 搜索按钮委托为空
|
||||
cut.InvokeAsync(() => dialog.ShowSearchDialog(option));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "查询");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 重置按钮
|
||||
var reset = false;
|
||||
@ -130,6 +139,7 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowSearchDialog(option));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "重置");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.True(reset);
|
||||
|
||||
// 搜索按钮
|
||||
@ -143,6 +153,7 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowSearchDialog(option));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "查询");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.True(search);
|
||||
#endregion
|
||||
|
||||
@ -158,7 +169,7 @@ public class DialogTest : DialogTestBase
|
||||
ShowLabel = true
|
||||
};
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
cut.InvokeAsync(() => modal.Instance.Close());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 设置关闭回调
|
||||
closed = false;
|
||||
@ -168,8 +179,10 @@ public class DialogTest : DialogTestBase
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
// 点击关闭按钮
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "关闭");
|
||||
cut.InvokeAsync(() => button.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.True(closed);
|
||||
|
||||
// 设置保存回调
|
||||
@ -198,23 +211,53 @@ public class DialogTest : DialogTestBase
|
||||
var form = cut.Find("form");
|
||||
form.Submit();
|
||||
Assert.True(saved);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// edit dialog is tracking true
|
||||
editOption.IsTracking = true;
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
button = cut.FindComponents<Button>().FirstOrDefault(b => b.Instance.Text == "关闭");
|
||||
Assert.Null(button);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// edit dialog is tracking false
|
||||
editOption.IsTracking = false;
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
button = cut.FindComponents<Button>().FirstOrDefault(b => b.Instance.Text == "关闭");
|
||||
Assert.NotNull(button);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// Edit Dialog FooterTemplate
|
||||
editOption.DialogFooterTemplate = modal => builder => builder.AddContent(0, "footer-template");
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
cut.Contains("footer-template");
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// body template is not null
|
||||
editOption.DialogBodyTemplate = modal => builder => builder.AddContent(0, "body-template");
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
cut.Contains("body-template");
|
||||
cut.Contains("footer-template");
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 测试 DialogBodyTemplate
|
||||
editOption.DialogBodyTemplate = foo => builder => builder.AddContent(0, "test");
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
form.Submit();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// DisableAutoSubmitFormByEnter
|
||||
editOption.DisableAutoSubmitFormByEnter = true;
|
||||
cut.InvokeAsync(() => dialog.ShowEditDialog(editOption));
|
||||
cut.Contains("data-bb-dissubmit=\"true\"");
|
||||
form.Submit();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// Modal is Null
|
||||
editOption.Model = null;
|
||||
Assert.ThrowsAsync<InvalidOperationException>(() => cut.InvokeAsync(() => dialog.ShowEditDialog(editOption)));
|
||||
cut.InvokeAsync(() => cut.Find(".btn-close").Click());
|
||||
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowModal
|
||||
@ -244,6 +287,7 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowModal<MockModalDialog>(resultOption));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "Test-Yes");
|
||||
cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.True(result);
|
||||
|
||||
// 点击的是 No 按钮
|
||||
@ -259,6 +303,7 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowModal<MockModalDialog>(resultOption));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "取消");
|
||||
cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.False(result);
|
||||
|
||||
// 点击关闭按钮
|
||||
@ -275,10 +320,12 @@ public class DialogTest : DialogTestBase
|
||||
cut.InvokeAsync(() => dialog.ShowModal<MockModalDialog>(resultOption));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "关闭");
|
||||
cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
cut.InvokeAsync(() => dialog.ShowModal<MockModalDialogClosingFalse>(resultOption));
|
||||
button = cut.FindComponents<Button>().First(b => b.Instance.Text == "关闭");
|
||||
cut.InvokeAsync(() => button.Instance.OnClick.InvokeAsync());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region 弹窗中的弹窗测试
|
||||
@ -308,13 +355,11 @@ public class DialogTest : DialogTestBase
|
||||
Assert.Equal(2, cut.FindComponents<ModalDialog>().Count);
|
||||
|
||||
// 关闭第二个弹窗
|
||||
var btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.Equal(1, cut.FindComponents<ModalDialog>().Count);
|
||||
|
||||
// 关闭第一个弹窗
|
||||
btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
Assert.Equal(0, cut.FindComponents<ModalDialog>().Count);
|
||||
#endregion
|
||||
|
||||
@ -324,8 +369,7 @@ public class DialogTest : DialogTestBase
|
||||
FullScreenSize = FullScreenSize.Large
|
||||
}));
|
||||
Assert.Contains("modal-fullscreen-lg-down", cut.Markup);
|
||||
btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region IsCenter
|
||||
@ -334,16 +378,14 @@ public class DialogTest : DialogTestBase
|
||||
IsCentered = true
|
||||
}));
|
||||
Assert.Contains("modal-dialog-centered", cut.Markup);
|
||||
btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region IsKeyboard
|
||||
@ -352,16 +394,14 @@ public class DialogTest : DialogTestBase
|
||||
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(() => modal.Instance.CloseCallback());
|
||||
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowHeaderCloseButton
|
||||
@ -369,16 +409,14 @@ public class DialogTest : DialogTestBase
|
||||
{
|
||||
ShowHeaderCloseButton = true
|
||||
}));
|
||||
btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowPrintButton
|
||||
@ -387,16 +425,14 @@ public class DialogTest : DialogTestBase
|
||||
ShowPrintButton = true
|
||||
}));
|
||||
Assert.Contains("btn-print", cut.Markup);
|
||||
btnClose = cut.FindAll(".btn-close")[cut.FindAll(".btn-close").Count - 1];
|
||||
btnClose.Click();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
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(() => modal.Instance.CloseCallback());
|
||||
|
||||
cut.InvokeAsync(() => dialog.Show(new DialogOption()
|
||||
{
|
||||
@ -406,8 +442,7 @@ public class DialogTest : DialogTestBase
|
||||
}));
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowSaveButton
|
||||
@ -420,8 +455,7 @@ public class DialogTest : DialogTestBase
|
||||
}));
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region OnSaveAsync
|
||||
@ -437,43 +471,45 @@ public class DialogTest : DialogTestBase
|
||||
return Task.FromResult(save);
|
||||
}
|
||||
}));
|
||||
btnClose = cut.FindAll(".btn-primary")[cut.FindAll(".btn-primary").Count - 1];
|
||||
btnClose.Click();
|
||||
var btnClose = cut.FindComponents<Button>().First(i => i.Instance.Icon == "fa-solid fa-fw fa-floppy-disk");
|
||||
cut.InvokeAsync(() => btnClose.Instance.OnClickWithoutRender!.Invoke());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
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();
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowValidateFormDialog
|
||||
cut.InvokeAsync(() => dialog.ShowValidateFormDialog<MockValidateFormDialog>("ValidateFormDialog"));
|
||||
var btn = cut.Find(".btn-close");
|
||||
cut.InvokeAsync(() => btn.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
Dictionary<string, object?> parameterFactory(DialogOption op) => new();
|
||||
void ConfigureOption(DialogOption op) => op.Class = "ValidateFormDialog-Class";
|
||||
cut.InvokeAsync(() => dialog.ShowValidateFormDialog<MockValidateFormDialog>("ValidateFormDialog", parameterFactory, ConfigureOption));
|
||||
btn = cut.Find(".btn-close");
|
||||
cut.InvokeAsync(() => btn.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
|
||||
#region ShowCloseDialog
|
||||
cut.InvokeAsync(() => dialog.ShowCloseDialog<MockValidateFormDialog>("CloseDialog", null, ConfigureOption));
|
||||
btn = cut.Find(".btn-close");
|
||||
cut.InvokeAsync(() => btn.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
cut.InvokeAsync(() => dialog.ShowCloseDialog<MockValidateFormDialog>("CloseDialog"));
|
||||
btn = cut.Find(".btn-close");
|
||||
cut.InvokeAsync(() => btn.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
cut.InvokeAsync(() => dialog.ShowCloseDialog<MockValidateFormDialog>("CloseDialog", parameter =>
|
||||
{
|
||||
parameter.Add("Class", "test");
|
||||
}));
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
#endregion
|
||||
}
|
||||
|
||||
private class MockValidateFormDialog : ComponentBase
|
||||
{
|
||||
|
||||
[Parameter]
|
||||
public string? Class { get; set; }
|
||||
}
|
||||
|
||||
private class MockDialogTest : ComponentBase
|
||||
|
@ -77,6 +77,16 @@ public class ModalTest : BootstrapBlazorTestBase
|
||||
Assert.Equal("Test-Header", header.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetHeaderText_Null()
|
||||
{
|
||||
var cut = Context.RenderComponent<MockModal>(pb =>
|
||||
{
|
||||
pb.AddChildContent<ModalDialog>();
|
||||
});
|
||||
cut.Instance.TestSetHeaderText();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShownCallbackAsync_Ok()
|
||||
{
|
||||
@ -94,7 +104,7 @@ public class ModalTest : BootstrapBlazorTestBase
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.OpenComponent<MockModal>(0);
|
||||
builder.AddAttribute(1, nameof(Modal.ShownCallbackAsync), () =>
|
||||
builder.AddAttribute(1, nameof(Modal.OnShownAsync), () =>
|
||||
{
|
||||
Value = true;
|
||||
return Task.CompletedTask;
|
||||
@ -105,13 +115,15 @@ public class ModalTest : BootstrapBlazorTestBase
|
||||
|
||||
private class MockModal : Modal
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task Show_Test()
|
||||
{
|
||||
await base.Shown();
|
||||
await base.ShownCallback();
|
||||
}
|
||||
|
||||
public void TestSetHeaderText()
|
||||
{
|
||||
Dialogs.Clear();
|
||||
base.SetHeaderText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ namespace UnitTest.Components;
|
||||
public class SwalTest : SwalTestBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task Show_Ok()
|
||||
public void Show_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
@ -16,165 +16,161 @@ public class SwalTest : SwalTestBase
|
||||
|
||||
var swal = cut.FindComponent<MockSwalTest>().Instance.SwalService;
|
||||
|
||||
await cut.InvokeAsync(async () => await swal.Show(new SwalOption()
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
BodyTemplate = builder => builder.AddContent(0, "Test-BodyTemplate"),
|
||||
FooterTemplate = builder => builder.AddContent(0, "Test-FooterTemplate"),
|
||||
ButtonTemplate = builder => builder.AddContent(0, "Test-ButtonTemplate"),
|
||||
ShowFooter = true,
|
||||
ShowClose = true,
|
||||
BodyContext = null
|
||||
CloseButtonIcon = "test-close-icon",
|
||||
CloseButtonText = "test-button-text-Cancel"
|
||||
}));
|
||||
|
||||
// 代码覆盖模板单元测试
|
||||
Assert.Contains("Test-BodyTemplate", cut.Markup);
|
||||
Assert.Contains("Test-FooterTemplate", cut.Markup);
|
||||
Assert.Contains("Test-ButtonTemplate", cut.Markup);
|
||||
Assert.Contains("test-close-icon", cut.Markup);
|
||||
Assert.Contains("test-button-text-Cancel", cut.Markup);
|
||||
|
||||
// 测试关闭逻辑
|
||||
var modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
var modals = cut.FindComponents<Modal>();
|
||||
var modal = modals[modals.Count - 1];
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 测试 Category
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Eror",
|
||||
Category = SwalCategory.Error
|
||||
}));
|
||||
|
||||
Assert.Contains("swal2-x-mark-line-left", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
// 测试 Category
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Eror",
|
||||
Category = SwalCategory.Information
|
||||
}));
|
||||
Assert.Contains("swal2-info", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
//测试Content
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
// 测试 Category
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Eror",
|
||||
Category = SwalCategory.Warning
|
||||
}));
|
||||
Assert.Contains("swal2-warning", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 测试 Category
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Eror",
|
||||
Category = SwalCategory.Question
|
||||
}));
|
||||
Assert.Contains("swal2-question", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
//测试 Content
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Swal",
|
||||
}));
|
||||
|
||||
Assert.Contains("I am Swal", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
|
||||
//测试Title
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
//测试 Title
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Title",
|
||||
Title = "I am Title",
|
||||
}));
|
||||
|
||||
Assert.Contains("I am Title", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
|
||||
//测试Title
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
//测试 Title
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
ForceDelay = true,
|
||||
Delay = 1000
|
||||
Title = "I am Title",
|
||||
Content = "I am Swal",
|
||||
}));
|
||||
|
||||
modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
|
||||
//测试Title
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
ForceDelay = true,
|
||||
Delay = 1000,
|
||||
}));
|
||||
|
||||
modal = cut.FindComponent<Modal>();
|
||||
await cut.InvokeAsync(() => modal.Instance.Close());
|
||||
Assert.Contains("I am Title", cut.Markup);
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
//测试关闭按钮
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Swal",
|
||||
IsAutoHide = true,
|
||||
Delay = 1000
|
||||
}));
|
||||
|
||||
var button = cut.Find(".btn-secondary");
|
||||
button.Click();
|
||||
cut.InvokeAsync(() => button.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
//测试Modal取消
|
||||
var cancel = true;
|
||||
_ = Task.Run(() =>
|
||||
// auto close
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
cut.InvokeAsync(async () => cancel = await swal.ShowModal(new SwalOption()
|
||||
{
|
||||
Content = "I am Swal"
|
||||
}));
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
var cancelbutton = cut.Find(".btn-secondary");
|
||||
cancelbutton.Click();
|
||||
Assert.False(cancel);
|
||||
|
||||
//测试Modal确认
|
||||
var confirm = false;
|
||||
_ = Task.Run(() => cut.InvokeAsync(async () =>
|
||||
{
|
||||
confirm = await swal.ShowModal(new SwalOption()
|
||||
{
|
||||
Content = "I am Swal"
|
||||
});
|
||||
}));
|
||||
await Task.Delay(100);
|
||||
|
||||
var confirmbutton = cut.Find(".btn-danger");
|
||||
confirmbutton.Click();
|
||||
Assert.True(confirm);
|
||||
|
||||
cut.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.AddChildContent<Select<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.OnBeforeSelectedItemChange, item => Task.FromResult(true));
|
||||
pb.Add(a => a.SwalFooter, "Test-Swal-Footer");
|
||||
pb.Add(a => a.SwalCategory, SwalCategory.Question);
|
||||
pb.Add(a => a.SwalTitle, "Test-Swal-Title");
|
||||
pb.Add(a => a.SwalContent, "Test-Swal-Content");
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
{
|
||||
new SelectedItem("1", "Test1"),
|
||||
new SelectedItem("2", "Test2")
|
||||
});
|
||||
});
|
||||
});
|
||||
await cut.InvokeAsync(() => cut.Find(".dropdown-item").Click());
|
||||
Assert.Contains("Test-Swal-Title", cut.Markup);
|
||||
Assert.Contains("Test-Swal-Content", cut.Markup);
|
||||
Assert.Contains("Test-Swal-Footer", cut.Markup);
|
||||
|
||||
await cut.InvokeAsync(() => cut.Find(".swal2-actions button").Click());
|
||||
Assert.DoesNotContain("Test-Swal-Content", cut.Markup);
|
||||
|
||||
// 测试自动关闭
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am Swal",
|
||||
Content = "I am auto hide",
|
||||
IsAutoHide = true,
|
||||
Delay = 100
|
||||
}));
|
||||
while (cut.Markup.Contains("I am Swal"))
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
Thread.Sleep(150);
|
||||
// 弹窗显示
|
||||
cut.Contains("I am auto hide");
|
||||
Thread.Sleep(150);
|
||||
// 模拟关闭
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
cut.DoesNotContain("I am auto hide");
|
||||
|
||||
// 不关闭弹窗测试 Dispose
|
||||
await cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
// 模态框
|
||||
bool result = false;
|
||||
Task.Run(async () => await cut.InvokeAsync(async () =>
|
||||
{
|
||||
Content = "I am Swal",
|
||||
IsAutoHide = true,
|
||||
Delay = 1000
|
||||
result = await swal.ShowModal(new SwalOption()
|
||||
{
|
||||
Content = "I am Modal Swal",
|
||||
CancelButtonText = "test-cancel-text",
|
||||
ConfirmButtonIcon = "test-confirm-icon",
|
||||
ConfirmButtonText = "test-confirm-text"
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
var tick = DateTime.Now;
|
||||
while (!cut.Markup.Contains("test-cancel-text"))
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
if (DateTime.Now > tick.AddSeconds(1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
cut.Contains("test-cancel-text");
|
||||
cut.Contains("I am Modal Swal");
|
||||
cut.Contains("test-confirm-icon");
|
||||
cut.Contains("test-confirm-text");
|
||||
|
||||
// 触发确认按钮
|
||||
button = cut.Find(".btn-danger");
|
||||
cut.InvokeAsync(() => button.Click());
|
||||
cut.InvokeAsync(() => modal.Instance.CloseCallback());
|
||||
|
||||
// 自动隐藏时间未到时触发 Disposing
|
||||
cut.InvokeAsync(() => swal.Show(new SwalOption()
|
||||
{
|
||||
Content = "I am auto hide",
|
||||
IsAutoHide = true,
|
||||
Delay = 4000
|
||||
}));
|
||||
Thread.Sleep(150);
|
||||
// 弹窗显示
|
||||
cut.Contains("I am auto hide");
|
||||
var alert = cut.FindComponent<SweetAlert>();
|
||||
alert.Dispose();
|
||||
}
|
||||
|
||||
private class MockSwalTest : ComponentBase
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user