Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@page "/toast-service"
@using BlazorBootstrap.Demo.RCL.Components.Pages.Demos.Toasts

@attribute [Route(pageUrl)]
@layout DemosMainLayout

<DemosPageHeadSection PageUrl="@pageUrl"
PageTitle="@pageTitle"
PageDescription="@pageDescription"
MetaTitle="@metaTitle"
MetaDescription="@metaDescription"
ImageUrl="@imageUrl" />

<DocsLink Href="@DemoRouteConstants.Docs_URL_ToastService" />

<div class="mt-3">Use Blazor ToastService to publish application-level toast notifications from anywhere in your app.</div>

<Section Size="HeadingSize.H2" Name="How to setup" PageUrl="@pageUrl" Link="how-to-setup">
<div class="mb-3">1. Add the <code>Toasts</code> component in <code>MainLayout.razor</code> page as shown below.</div>
<Demo Type="typeof(Toasts_Demo_06_Global_Toasts_Service_For_Application_01)" ShowCodeOnly="true" />

<Callout Color="CalloutColor.Success">
Set the <code>Toasts</code> component parameters as per your requirement.
</Callout>

<div class="mb-3">2. Inject <code>ToastService</code>, then call <code>Notify(...)</code> for the common single <code>Toasts</code> host scenario.</div>
<Demo Type="typeof(Toasts_Demo_06_Global_Toasts_Service_For_Application_02)" ShowCodeOnly="true" />

<div class="mt-3">3. Use <code>await NotifyAsync(...)</code> when multiple toast subscribers may be registered and the caller needs to await all handlers.</div>
<Demo Type="typeof(global::BlazorBootstrap.Demo.RCL.Components.Pages.Demos.Services.ToastService.ToastService_Demo_01_NotifyAsync_With_Multiple_Subscribers)" />
</Section>

@code {
private const string pageUrl = DemoRouteConstants.Demos_URL_ToastService;
private const string pageTitle = "Blazor Toast Service";
private const string pageDescription = "Use Blazor Bootstrap toast service to publish application-level toast notifications from anywhere in your app.";
private const string metaTitle = "Blazor Toast Service";
private const string metaDescription = "Use Blazor Bootstrap toast service to publish application-level toast notifications from anywhere in your app.";
private const string imageUrl = DemoScreenshotSrcConstants.Demos_URL_Toasts;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<div class="d-flex flex-column gap-3">
<div>This example registers two <code>Toasts</code> hosts on the same page. Clicking the button publishes one message to both hosts and awaits both subscribers.</div>

<div>
<Button Color="ButtonColor.Primary" Disabled="@isNotifying" @onclick="NotifyBothHostsAsync">
@(isNotifying ? "Sending..." : "Notify both hosts")
</Button>
</div>

<div class="text-body-secondary">@statusMessage</div>

@* These local Toasts hosts intentionally add extra ToastService subscribers on this page
so NotifyAsync can demonstrate awaiting multiple handlers for a single published message. *@
<Toasts class="p-3" AutoHide="true" Delay="4000" Placement="ToastsPlacement.TopLeft" />
<Toasts class="p-3" AutoHide="true" Delay="4000" Placement="ToastsPlacement.BottomRight" />
</div>

@code {
[Inject]
protected ToastService ToastService { get; set; } = default!;

private bool isNotifying;

private string statusMessage = "Click the button to publish the same toast to two hosts.";

private async Task NotifyBothHostsAsync()
{
isNotifying = true;
statusMessage = "Publishing to both toast hosts...";

await ToastService.NotifyAsync(new ToastMessage(
ToastType.Info,
"Toast Service",
$"NotifyAsync delivered this message to both toast hosts at {DateTime.Now:T}."));

statusMessage = $"NotifyAsync completed after both toast hosts handled the message at {DateTime.Now:T}.";
isNotifying = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
</Section>

<Section Size="HeadingSize.H2" Name="Stack Length" PageUrl="@pageUrl" Link="stack-length">
<div>Blazor Toasts component shows a maximum of 5 toasts by default. If you add a new toast to the existing list, the first toast gets deleted like <b>FIFO</b> (First In First Out). Change the maximum capacity according to your need by using the <code>StackLength</code> parameter.</div>
<div class="mt-3">In the below example, StackLength is set to 3. It shows a maximum of 3 toast messages at any time.</div>
<div>Blazor Toasts component shows a maximum of 5 toasts by default. If you add a new toast to the existing list, the oldest rendered toast is dismissed first in <b>FIFO</b> (First In First Out) order. Change the maximum capacity according to your need by using the <code>StackLength</code> parameter.</div>
<div class="mt-3">In the below example, StackLength is set to 3. It shows a maximum of 3 toast messages at any time, and older visible toasts finish their hide transition before they are removed from the stack.</div>
<Demo Type="typeof(Toasts_Demo_05_StackLength)" />
</Section>

Expand All @@ -61,6 +61,9 @@
</Callout>
<div class="mb-3">2. Inject <code>ToastService</code>, then call the <code>Notify(...)</code> method as shown below.</div>
<Demo Type="typeof(Toasts_Demo_06_Global_Toasts_Service_For_Application_02)" ShowCodeOnly="true" />
<Callout Color="CalloutColor.Info">
Use <code>Notify(...)</code> for the common single <code>Toasts</code> host scenario. Use <code>await NotifyAsync(...)</code> when multiple toast subscribers are registered and the caller needs to await all handlers.
</Callout>
</Section>

@code {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
MetaDescription="@metaDescription"
ImageUrl="@imageUrl" />

<DemoLink Href="@DemoRouteConstants.Demos_URL_PreloadService" />
<DemoLink Href="@DemoRouteConstants.Demos_URL_ToastService" />

<Section Class="p-0" Size="HeadingSize.H3" Name="Screenshot" PageUrl="@pageUrl" Link="screenshot">
<img src="@imageUrl" class="img-fluid" alt="@metaTitle" />
Expand All @@ -18,6 +18,11 @@
<DocxTable TItem="ToastService" DocType="DocType.Methods" />
</Section>

<Section Class="p-0" Size="HeadingSize.H3" Name="Usage Notes" PageUrl="@pageUrl" Link="usage-notes">
<div>Use <code>Notify(...)</code> for the common single <code>Toasts</code> host scenario.</div>
<div class="mt-3">Use <code>await NotifyAsync(...)</code> when multiple toast subscribers may be registered and the caller needs to await all handlers.</div>
</Section>

<Section Class="p-0" Size="HeadingSize.H3" Name="ToastMessage Properties" PageUrl="@pageUrl" Link="toast-message-properties">
<DocxTable TItem="ToastMessage" DocType="DocType.Properties" />
</Section>
Expand All @@ -30,8 +35,8 @@
private const string componentName = nameof(ToastService);
private const string pageUrl = DemoRouteConstants.Docs_URL_ToastService;
private const string pageTitle = componentName;
private const string pageDescription = $"This documentation provides a comprehensive reference for the <code>{componentName}</code> component, guiding you through its configuration options.";
private const string metaTitle = $"Blazor {componentName} Component";
private const string metaDescription = $"This documentation provides a comprehensive reference for the {componentName} component, guiding you through its configuration options.";
private const string pageDescription = $"This documentation provides a comprehensive reference for the <code>{componentName}</code> service, guiding you through its usage.";
private const string metaTitle = $"Blazor {componentName} Service";
private const string metaDescription = $"This documentation provides a comprehensive reference for the {componentName} service, guiding you through its usage.";
private const string imageUrl = DemoScreenshotSrcConstants.Demos_URL_Toasts;
}
20 changes: 13 additions & 7 deletions blazorbootstrap/Components/Toasts/Toasts.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing)
Messages = null;

if (ToastService is not null)
ToastService.OnNotify -= OnNotify;
ToastService.OnNotify -= OnNotifyAsync;
}

await base.DisposeAsyncCore(disposing);
Expand All @@ -21,12 +21,12 @@ protected override async ValueTask DisposeAsyncCore(bool disposing)
protected override void OnInitialized()
{
if (ToastService is not null)
ToastService.OnNotify += OnNotify;
ToastService.OnNotify += OnNotifyAsync;

base.OnInitialized();
}

private void OnNotify(ToastMessage toastMessage)
private async Task OnNotifyAsync(ToastMessage toastMessage)
{
if (toastMessage is null)
return;
Expand All @@ -35,7 +35,7 @@ private void OnNotify(ToastMessage toastMessage)

Messages.Add(toastMessage);

StateHasChanged();
await InvokeAsync(StateHasChanged);
}

private void OnToastHiddenAsync(ToastEventArgs args)
Expand Down Expand Up @@ -70,8 +70,11 @@ private async Task OnToastShownAsync(ToastEventArgs args)
{
if (message is not null)
{
Messages.Remove(message);
if (!string.IsNullOrWhiteSpace(message.ElementId))
if (string.IsNullOrWhiteSpace(message.ElementId))
Messages.Remove(message);
else
// Keep rendered toasts in the DOM until Bootstrap completes the hide transition.
// Removing them first can dispose the element before toast.js raises hidden.
await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.hide", message.ElementId);
}
}
Expand Down Expand Up @@ -153,10 +156,13 @@ private async Task OnToastShownAsync(ToastEventArgs args)
/// <para>
/// Default value is 5.
/// </para>
/// <para>
/// When the limit is exceeded, the oldest rendered toasts are dismissed first.
/// </para>
/// </summary>
[AddedVersion("1.0.0")]
[DefaultValue(5)]
[Description("Gets or sets the toast container maximum capacity.")]
[Description("Gets or sets the toast container maximum capacity. When the limit is exceeded, the oldest rendered toasts are dismissed first.")]
[Parameter]
public int StackLength { get; set; } = 5;

Expand Down
36 changes: 33 additions & 3 deletions blazorbootstrap/Services/ToastService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
namespace BlazorBootstrap;

/// <summary>
/// Provides a simple publish/subscribe mechanism for displaying toast messages from anywhere in the application.
/// </summary>
/// <remarks>
/// Register a <see cref="Toasts" /> host in the render tree to display messages published by this service.
/// </remarks>
public class ToastService
{
#region Events

internal event Action<ToastMessage> OnNotify = default!;
internal event Func<ToastMessage, Task> OnNotify = default!;

#endregion

Expand All @@ -13,12 +19,36 @@ public class ToastService
/// <summary>
/// Notifies subscribers of a new toast message event.
/// </summary>
/// <remarks>If no subscribers are registered, this method has no effect. This method is typically used to
/// display transient notifications to users.</remarks>
/// <remarks>If no subscribers are registered, this method has no effect. Use this overload for the common
/// fire-and-forget scenario where a single <see cref="Toasts" /> host is registered. If you need to await
/// multiple subscribers, use <see cref="NotifyAsync(ToastMessage)" /> instead.</remarks>
/// <param name="toastMessage">The toast message to be delivered to all registered listeners. Cannot be null.</param>
[AddedVersion("1.0.0")]
[Description("Notifies subscribers of a new toast message event.")]
public void Notify(ToastMessage toastMessage) => OnNotify?.Invoke(toastMessage);

/// <summary>
/// Notifies all subscribers of a new toast message event and awaits their completion.
/// </summary>
/// <remarks>If no subscribers are registered, this method has no effect. Use this overload when multiple
/// subscribers may be attached and the caller needs to observe completion or failures from each handler.</remarks>
/// <param name="toastMessage">The toast message to be delivered to all registered listeners. Cannot be null.</param>
/// <returns>A task that represents the asynchronous notification operation.</returns>
[AddedVersion("4.0.0")]
[Description("Notifies all subscribers of a new toast message event and awaits their completion.")]
public async Task NotifyAsync(ToastMessage toastMessage)
{
// Multicast delegate Invoke returns only the last Task, so enumerate subscribers when every handler must complete.
Delegate[] subscribers = OnNotify?.GetInvocationList() ?? Array.Empty<Delegate>();
if (subscribers.Length > 0)
{
await Task.WhenAll(subscribers.Select(d =>
{
Func<ToastMessage, Task> subscriber = (Func<ToastMessage, Task>)d;
return subscriber.Invoke(toastMessage);
}));
}
}

#endregion
}
8 changes: 6 additions & 2 deletions docs/docs/05-components/toasts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,9 @@ Change the Blazor Toasts placement according to your need. The default placement

### Stack Length

Blazor Toasts component shows a maximum of 5 toasts by default. If you add a new toast to the existing list, the first toast gets deleted like FIFO (First In First Out). Change the maximum capacity according to your need by using the **StackLength** parameter.
Blazor Toasts component shows a maximum of 5 toasts by default. If you add a new toast to the existing list, the oldest rendered toast is dismissed first in FIFO (First In First Out) order. Change the maximum capacity according to your need by using the **StackLength** parameter.

In the below example, StackLength is set to 3. It shows a maximum of 3 toast messages at any time.
In the below example, StackLength is set to 3. It shows a maximum of 3 toast messages at any time, and older visible toasts finish their hide transition before they are removed from the stack.

```cshtml {1} showLineNumbers
<Toasts class="p-3" Messages="messages" AutoHide="true" StackLength="3" Placement="ToastsPlacement.TopRight" />
Expand Down Expand Up @@ -314,6 +314,10 @@ Set the `Toasts` component parameters as per your requirement.

2. Inject `ToastService`, then call the `Notify(...)` method as shown below.

:::tip
Use `Notify(...)` for the common single `Toasts` host scenario. If your app registers multiple toast subscribers and you need to await all handlers, call `await ToastService.NotifyAsync(...)` instead.
:::

```cshtml {} showLineNumbers
@code {

Expand Down
Loading