Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions exclusion.dic
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@ dotx
Modbus
Protocol
vditor
alertdialog
blazorbootstrap
17 changes: 13 additions & 4 deletions src/BootstrapBlazor.Server/Components/Samples/Messages.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@page "/message"
@page "/message"
@inject IStringLocalizer<Messages> Localizer
@inject MessageService MessageService

Expand All @@ -20,8 +20,18 @@ private MessageService? MessageService { get; set; }
});</Pre>

<DemoBlock Title="@Localizer["MessagesNormalTitle"]" Introduction="@Localizer["MessagesNormalIntro"]" Name="Normal">
<button class="btn btn-primary" @onclick="@ShowMessage">@Localizer["MessagesMessagePrompt"]</button>
<Message @ref="Message" Placement="Placement.Bottom" />
<section ignore class="row form-inline g-3">
<div class="col-12">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Placement"></BootstrapInputGroupLabel>
<RadioList @bind-Value="@_placement" Items="@_items"></RadioList>
</BootstrapInputGroup>
</div>
<div class="col-12">
<button class="btn btn-primary" @onclick="@ShowMessage">@Localizer["MessagesMessagePrompt"]</button>
</div>
</section>
<Message @ref="Message" Placement="@Placement"></Message>
</DemoBlock>

<DemoBlock Title="@Localizer["MessagesAsyncTitle"]" Introduction="@Localizer["MessagesAsyncIntro"]" Name="Async">
Expand Down Expand Up @@ -70,7 +80,6 @@ private MessageService? MessageService { get; set; }

<DemoBlock Title="@Localizer["MessagesTemplateTitle"]" Introduction="@Localizer["MessagesTemplateIntro"]" Name="Template">
<button class="btn btn-primary" @onclick="@ShowTemplateMessage">@Localizer["MessagesTemplatePrompt"]</button>
<Message @ref="Message1" Placement="Placement.Bottom" />
</DemoBlock>

<DemoBlock Title="@Localizer["MessagesShowModeTitle"]" Introduction="@Localizer["MessagesShowModeIntro"]" Name="ShowMode">
Expand Down
21 changes: 13 additions & 8 deletions src/BootstrapBlazor.Server/Components/Samples/Messages.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
Expand All @@ -18,13 +18,20 @@ public sealed partial class Messages

private readonly MessageOption _option = new();

private long _count = 0;

private string _placement = "Top";

private readonly List<SelectedItem> _items = [new SelectedItem("Top", "Top"), new SelectedItem("Bottom", "Bottom")];

private Placement Placement => _placement == "Top" ? Placement.Top : Placement.Bottom;

private async Task ShowMessage()
{
Message.SetPlacement(Placement.Top);
await MessageService.Show(new MessageOption()
{
Content = "This is a reminder message"
});
Content = $"This is a reminder message {_count++}"
}, Message);
}

private async Task ShowAsyncMessage()
Expand Down Expand Up @@ -97,7 +104,7 @@ private async Task ShowBottomMessage()
{
await MessageService.Show(new MessageOption()
{
Content = $"This is a reminder message - {DateTime.Now:mm:ss}",
Content = $"This is a reminder message - {_count++}",
Icon = "fa-solid fa-circle-info",
}, Message1);
}
Expand All @@ -111,13 +118,11 @@ await MessageService.Show(new MessageOption()
});
}

private int lastCount = 0;

private Task ShowLastOnlyMessage() => MessageService.Show(new MessageOption()
{
ShowShadow = true,
ShowMode = MessageShowMode.Single,
Content = lastCount++.ToString()
Content = $"This is a reminder message - {_count++}"
});

private static AttributeItem[] GetAttributes() =>
Expand Down
64 changes: 17 additions & 47 deletions src/BootstrapBlazor/Components/Message/Message.razor
Original file line number Diff line number Diff line change
@@ -1,59 +1,29 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]

<div id="@Id" class="@ClassString" style="@StyleName" role="alert">
@if (Placement == Placement.Top)
@foreach (var item in GetMessages())
{
foreach (var item in _messages)
{
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
@if (!string.IsNullOrEmpty(item.Icon))
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
@if (!string.IsNullOrEmpty(item.Icon))
{
<i class="@item.Icon"></i>
}
<div>
@if (item.ChildContent != null)
{
<i class="@item.Icon"></i>
@item.ChildContent
}
<div>
@if (item.ChildContent != null)
{
@item.ChildContent
}
else
{
@item.Content
}
</div>
@if (item.ShowDismiss)
else
{
<button type="button" class="btn-close" aria-label="close"></button>
@item.Content
}
</div>
}
}
else
{
for (var index = _messages.Count; index > 0; index--)
{
var item = _messages[index - 1];
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
@if (!string.IsNullOrEmpty(item.Icon))
{
<i class="@item.Icon"></i>
}
<div>
@if (item.ChildContent != null)
{
@item.ChildContent
}
else
{
@item.Content
}
</div>
@if (item.ShowDismiss)
{
<button type="button" class="btn-close" aria-label="close"></button>
}
</div>
}
@if (item.ShowDismiss)
{
<button type="button" class="btn-close" aria-label="close"></button>
}
</div>
}
</div>
33 changes: 25 additions & 8 deletions src/BootstrapBlazor/Components/Message/Message.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
Expand Down Expand Up @@ -55,7 +55,7 @@ protected override void OnInitialized()
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(Clear));
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop);

private static string? GetAutoHideString(MessageOption option) => option.IsAutoHide ? "true" : null;

Expand Down Expand Up @@ -85,8 +85,19 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
}
}

private List<MessageOption> GetMessages()
{
var messages = new List<MessageOption>(_messages);
if (Placement == Placement.Bottom)
{
messages.Reverse();
}

return messages;
}

/// <summary>
/// 设置 Toast 容器位置方法
/// 设置 容器位置方法
/// </summary>
/// <param name="placement"></param>
public void SetPlacement(Placement placement)
Expand All @@ -105,33 +116,39 @@ private async Task Show(MessageOption option)
if (!_messages.Contains(option))
{
_messages.Add(option);
_msgId = GetItemId(option);
}
_msgId = GetItemId(option);
await InvokeAsync(StateHasChanged);
}

/// <summary>
/// 清除 Message 方法 由 JSInvoke 触发
/// </summary>
[JSInvokable]
public Task Clear()
public void Clear(string id)
{
_messages.Clear();
var option = _messages.Find(i => GetItemId(i) == id);
if (option != null)
{
_messages.Remove(option);
}

StateHasChanged();
return Task.CompletedTask;
}

/// <summary>
/// OnDismiss 回调方法 由 JSInvoke 触发
/// </summary>
/// <param name="id"></param>
[JSInvokable]
public async Task Dismiss(string id)
public async ValueTask Dismiss(string id)
{
var option = _messages.Find(i => GetItemId(i) == id);
if (option is { OnDismiss: not null })
{
await option.OnDismiss();
_messages.Remove(option);
StateHasChanged();
}
}

Expand Down
27 changes: 12 additions & 15 deletions src/BootstrapBlazor/Components/Message/Message.razor.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Data from "../../modules/data.js"
import Data from "../../modules/data.js"
import EventHandler from "../../modules/event-handler.js"

export function init(id, invoke, callback) {
export function init(id, invoke) {
const el = document.getElementById(id)
const msg = { el, invoke, callback, items: [] }
const msg = { el, invoke, items: [] }
Data.set(id, msg)
}

Expand Down Expand Up @@ -46,28 +46,25 @@ export function show(id, msgId) {
el.classList.add('show');

const close = () => {
EventHandler.off(el, 'click')
el.classList.remove('show');
const hideHandler = setTimeout(function () {
clearTimeout(hideHandler);
el.classList.add("d-none");

msg.items.pop();
if (msg.items.length === 0) {
msg.invoke.invokeMethodAsync(msg.callback);
}
}, 500);
msg.items = msg.items.filter(i => i.el.id !== msgId);
msg.invoke.invokeMethodAsync("Clear", msgId);
};

EventHandler.on(el, 'click', '.btn-close', e => {
EventHandler.on(el, 'click', '.btn-close', async e => {
e.preventDefault();
e.stopPropagation();

const alert = e.delegateTarget.closest('.alert');
if (alert) {
EventHandler.off(el, 'click')
alert.classList.add("d-none");

const alertId = alert.getAttribute('id');
msg.invoke.invokeMethodAsync('Dismiss', alertId);
msg.items = msg.items.filter(i => i.el.id !== alertId);
await msg.invoke.invokeMethodAsync('Dismiss', alertId);
}
close();
});
}

Expand Down
49 changes: 44 additions & 5 deletions test/UnitTest/Components/MessageTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Options;

namespace UnitTest.Components;

Expand Down Expand Up @@ -57,10 +58,8 @@ await cut.InvokeAsync(() =>
Assert.NotNull(alert.Id);

var message = cut.FindComponent<Message>();
await message.Instance.Dismiss(alert.Id);
await cut.InvokeAsync(() => message.Instance.Dismiss(alert.Id));
Assert.True(dismiss);

await cut.InvokeAsync(() => message.Instance.Clear());
}

[Fact]
Expand Down Expand Up @@ -98,7 +97,7 @@ await cut.InvokeAsync(() => service.Show(new MessageOption()

await cut.Instance.Dismiss(alert.Id);
await cut.Instance.Dismiss("test_id");
await cut.InvokeAsync(() => cut.Instance.Clear());
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));

await cut.InvokeAsync(() => service.Show(new MessageOption()
{
Expand Down Expand Up @@ -133,4 +132,44 @@ await cut.InvokeAsync(() => service.Show(new MessageOption()
ShowMode = MessageShowMode.Single
}, cut.Instance));
}

[Fact]
public async Task ForceDelay_Ok()
{
var service = Context.Services.GetRequiredService<MessageService>();
var cut = Context.RenderComponent<Message>();
var option = new MessageOption()
{
Content = "Test Content",
IsAutoHide = false,
ShowDismiss = true,
Icon = "fa-solid fa-font-awesome",
ForceDelay = true,
Delay = 2000
};
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
Assert.Contains("data-bb-delay=\"2000\"", cut.Markup);

var alert = cut.Find(".alert");
Assert.NotNull(alert);
Assert.NotNull(alert.Id);
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));

option.ForceDelay = false;
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
Assert.Contains("data-bb-delay=\"4000\"", cut.Markup);
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));

// 更新 Options 值
var options = Context.Services.GetRequiredService<IOptionsMonitor<BootstrapBlazorOptions>>();
options.CurrentValue.MessageDelay = 1000;
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
Assert.Contains("data-bb-delay=\"1000\"", cut.Markup);
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));

options.CurrentValue.MessageDelay = 0;
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
Assert.Contains("data-bb-delay=\"1000\"", cut.Markup);
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
}
}
Loading