diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/TabsDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/TabsDocumentation.razor index e1372d53d..1f61ee62e 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/TabsDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/TabsDocumentation.razor @@ -69,28 +69,28 @@

- - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
Event NameDescription
Event NameDescription
OnHidingThis event fires when a new tab is to be shown (and thus the previous active tab is to be hidden).
OnHiddenThis event fires after a new tab is shown (and thus the previous active tab is hidden).
OnShowingThis event fires on tab show, but before the new tab has been shown.
OnShownThis event fires on tab show after a tab has been shown.
OnHidingThis event fires when a new tab is to be shown (and thus the previous active tab is to be hidden).
OnHiddenThis event fires after a new tab is shown (and thus the previous active tab is hidden).
OnShowingThis event fires on tab show, but before the new tab has been shown.
OnShownThis event fires on tab show after a tab has been shown.
@@ -123,6 +123,13 @@ +
+
+ In the following example, we are removing the inactive tab by name. The focus will not be lost with this active tab. +
+ +
+ @code { private const string pageUrl = RouteConstants.Demos_Tabs_Documentation; private const string pageTitle = "Blazor Tabs"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/Tabs_Demo_15_Remove_Inactive_Tab_by_Name.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/Tabs_Demo_15_Remove_Inactive_Tab_by_Name.razor new file mode 100644 index 000000000..e9fd91b4c --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Tabs/Tabs_Demo_15_Remove_Inactive_Tab_by_Name.razor @@ -0,0 +1,55 @@ +
+ + + + @foreach (var customer in customers) + { + + +
+
+ This is the placeholder content for the @customer.CustomerName tab. +
+
+
+
+ } +
+
+
+
+
+ +
+ +@code { + Tabs tabsRef = default!; + + int count = 1; + private List customers = default!; + + protected override void OnInitialized() + { + customers = new() { + new(1, "Marvin Klein"), + new(2, "Vikram Reddy"), + new(3, "Bandita PA"), + new(4, "Dan H"), + new(5, "Joe JJ"), + new(6, "Rose KK") + }; + count = customers.Count; + } + + private void RemoveCustomerVikram() + { + var customer = customers.FirstOrDefault(c => c.CustomerName == "Vikram Reddy"); + if (customer != null) + { + customers.Remove(customer); + tabsRef.RemoveTabByName(customer.CustomerId.ToString()); + } + } +} diff --git a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs index e5386389f..6a16ea1ca 100644 --- a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs +++ b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs @@ -8,6 +8,8 @@ public abstract class BlazorBootstrapComponentBase : ComponentBase, IDisposable, private bool isDisposed; + internal Queue> queuedTasks = new(); + #endregion #region Methods @@ -15,17 +17,17 @@ public abstract class BlazorBootstrapComponentBase : ComponentBase, IDisposable, /// protected override async Task OnAfterRenderAsync(bool firstRender) { - IsRenderComplete = true; + // process queued tasks + while (queuedTasks.TryDequeue(out var taskToExecute)) + await taskToExecute.Invoke(); - await base.OnAfterRenderAsync(firstRender); + IsRenderComplete = true; } /// protected override void OnInitialized() { Id ??= IdUtility.GetNextId(); - - base.OnInitialized(); } public static string BuildClassNames(params (string? cssClass, bool when)[] cssClassList) diff --git a/blazorbootstrap/Components/Grid/Grid.razor.cs b/blazorbootstrap/Components/Grid/Grid.razor.cs index 2474f2cb9..57fe01434 100644 --- a/blazorbootstrap/Components/Grid/Grid.razor.cs +++ b/blazorbootstrap/Components/Grid/Grid.razor.cs @@ -35,8 +35,6 @@ public partial class Grid : BlazorBootstrapComponentBase private int pageSize; - private Queue> queuedTasks = new(); - private bool requestInProgress = false; private HashSet selectedItems = new(); @@ -56,18 +54,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } await base.OnAfterRenderAsync(firstRender); - - // process queued tasks - while (true) - { - if (queuedTasks.Count == 0) - break; - - var taskToExecute = queuedTasks.Dequeue(); - - if (taskToExecute is not null) - await taskToExecute.Invoke(); - } } protected override void OnInitialized() diff --git a/blazorbootstrap/Components/Tabs/Tabs.razor.cs b/blazorbootstrap/Components/Tabs/Tabs.razor.cs index f19354983..a97a5babb 100644 --- a/blazorbootstrap/Components/Tabs/Tabs.razor.cs +++ b/blazorbootstrap/Components/Tabs/Tabs.razor.cs @@ -14,7 +14,7 @@ public partial class Tabs : BlazorBootstrapComponentBase private bool showLastTab = false; - private List? tabs = new(); + private List tabs = new(); #endregion @@ -23,7 +23,8 @@ public partial class Tabs : BlazorBootstrapComponentBase /// protected override async ValueTask DisposeAsyncCore(bool disposing) { - if (disposing) tabs = null; + if (disposing && tabs is not null) + tabs = null!; await base.DisposeAsyncCore(disposing); } @@ -45,7 +46,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } // Show next available tab - if (removedTabIndex > -1) + if (removedTabIndex > -1 || removedTabIndex == -99) { await ShowNextAvailableTabAsync(removedTabIndex); removedTabIndex = -1; @@ -69,8 +70,8 @@ protected override async Task OnInitializedAsync() [JSInvokable] public async Task bsHiddenTab(string activeTabId, string previousActiveTabId) { - var activeTab = tabs?.FirstOrDefault(x => x.Id == activeTabId); - var previousActiveTab = tabs?.FirstOrDefault(x => x.Id == previousActiveTabId); + var activeTab = tabs.FirstOrDefault(x => x.Id == activeTabId); + var previousActiveTab = tabs.FirstOrDefault(x => x.Id == previousActiveTabId); var args = new TabsEventArgs(activeTab?.Name!, activeTab?.Title!, previousActiveTab?.Name!, previousActiveTab?.Title!); await OnHidden.InvokeAsync(args); @@ -79,8 +80,8 @@ public async Task bsHiddenTab(string activeTabId, string previousActiveTabId) [JSInvokable] public async Task bsHideTab(string activeTabId, string previousActiveTabId) { - var activeTab = tabs?.FirstOrDefault(x => x.Id == activeTabId); - var previousActiveTab = tabs?.FirstOrDefault(x => x.Id == previousActiveTabId); + var activeTab = tabs.FirstOrDefault(x => x.Id == activeTabId); + var previousActiveTab = tabs.FirstOrDefault(x => x.Id == previousActiveTabId); var args = new TabsEventArgs(activeTab?.Name!, activeTab?.Title!, previousActiveTab?.Name!, previousActiveTab?.Title!); await OnHiding.InvokeAsync(args); @@ -89,8 +90,8 @@ public async Task bsHideTab(string activeTabId, string previousActiveTabId) [JSInvokable] public async Task bsShownTab(string activeTabId, string previousActiveTabId) { - var activeTab = tabs?.FirstOrDefault(x => x.Id == activeTabId); - var previousActiveTab = tabs?.FirstOrDefault(x => x.Id == previousActiveTabId); + var activeTab = tabs.FirstOrDefault(x => x.Id == activeTabId); + var previousActiveTab = tabs.FirstOrDefault(x => x.Id == previousActiveTabId); var args = new TabsEventArgs(activeTab?.Name!, activeTab?.Title!, previousActiveTab?.Name!, previousActiveTab?.Title!); await OnShown.InvokeAsync(args); @@ -99,8 +100,8 @@ public async Task bsShownTab(string activeTabId, string previousActiveTabId) [JSInvokable] public async Task bsShowTab(string activeTabId, string previousActiveTabId) { - var activeTab = tabs?.FirstOrDefault(x => x.Id == activeTabId); - var previousActiveTab = tabs?.FirstOrDefault(x => x.Id == previousActiveTabId); + var activeTab = tabs.FirstOrDefault(x => x.Id == activeTabId); + var previousActiveTab = tabs.FirstOrDefault(x => x.Id == previousActiveTabId); var args = new TabsEventArgs(activeTab?.Name!, activeTab?.Title!, previousActiveTab?.Name!, previousActiveTab?.Title!); await OnShowing.InvokeAsync(args); @@ -112,34 +113,23 @@ public async Task bsShowTab(string activeTabId, string previousActiveTabId) /// Returns the cuurent active . public Tab GetActiveTab() => activeTab; - /// - /// Initializes the most recently added tab, optionally displaying it. - /// - /// Specifies whether to display the tab after initialization. - [Obsolete("This method is obseolete. Use `ShowRecentTabAsync` method instead.")] - public void InitializeRecentTab(bool showTab) - { - if (showTab) showLastTab = true; - } - /// /// Removes the tab by index. /// /// - /// public void RemoveTabByIndex(int tabIndex) { - if (!tabs?.Any() ?? true) return; - - if (tabIndex < 0 || tabIndex >= tabs!.Count) throw new IndexOutOfRangeException(); - - var tab = tabs[tabIndex]; + var tab = tabs.ElementAtOrDefault(tabIndex); + if (tab is null) + return; - if (tab is null) return; + // If active tab is removed then select the next available tab. + if (activeTab.Id == tab.Id) + removedTabIndex = tabIndex; + else + removedTabIndex = -99; - tabs!.Remove(tab); - - removedTabIndex = tabIndex; + tabs.Remove(tab); } /// @@ -148,17 +138,18 @@ public void RemoveTabByIndex(int tabIndex) /// public void RemoveTabByName(string tabName) { - if (!tabs?.Any() ?? true) return; - - var tabIndex = tabs!.FindIndex(x => x.Name == tabName); - + var tabIndex = tabs.FindIndex(x => x.Name == tabName); if (tabIndex == -1) return; var tab = tabs[tabIndex]; - tabs!.Remove(tab); + // If active tab is removed then select the next available tab. + if (activeTab.Id == tab.Id) + removedTabIndex = tabIndex; + else + removedTabIndex = -99; - removedTabIndex = tabIndex; + tabs.Remove(tab); } /// @@ -166,9 +157,9 @@ public void RemoveTabByName(string tabName) /// public async Task ShowFirstTabAsync() { - if (!tabs?.Any() ?? true) return; - - var tab = tabs!.FirstOrDefault(x => !x.Disabled); + var tab = tabs.FirstOrDefault(x => !x.Disabled); + if (tab is null) + return; if (tab is { Disabled: false }) await ShowTabAsync(tab); @@ -179,9 +170,9 @@ public async Task ShowFirstTabAsync() /// public async Task ShowLastTabAsync() { - if (!tabs?.Any() ?? true) return; + if (tabs.Count == 0) return; - var tab = tabs!.LastOrDefault(x => !x.Disabled); + var tab = tabs.LastOrDefault(x => !x.Disabled); if (tab is { Disabled: false }) await ShowTabAsync(tab); @@ -198,7 +189,7 @@ public async Task ShowLastTabAsync() /// The zero-based index of the element to get or set. public async Task ShowTabByIndexAsync(int tabIndex) { - if (!tabs?.Any() ?? true) return; + if (tabs.Count == 0) return; if (tabIndex < 0 || tabIndex >= tabs!.Count) throw new IndexOutOfRangeException(); @@ -214,9 +205,9 @@ public async Task ShowTabByIndexAsync(int tabIndex) /// The name of the tab to select. public async Task ShowTabByNameAsync(string tabName) { - if (!tabs?.Any() ?? true) return; + if (tabs.Count == 0) return; - var tab = tabs!.LastOrDefault(x => x.Name == tabName && !x.Disabled); + var tab = tabs.LastOrDefault(x => x.Name == tabName && !x.Disabled); if (tab is not null) await ShowTabAsync(tab); @@ -224,7 +215,7 @@ public async Task ShowTabByNameAsync(string tabName) internal void AddTab(Tab tab) { - tabs!.Add(tab); + tabs.Add(tab); if (tab is { Active: true, Disabled: false }) activeTab = tab; @@ -237,9 +228,9 @@ internal void AddTab(Tab tab) /// internal async Task SetDefaultActiveTabAsync() { - if (!tabs?.Any() ?? true) return; + if (tabs.Count == 0) return; - activeTab ??= tabs!.FirstOrDefault(x => !x.Disabled)!; + activeTab ??= tabs.FirstOrDefault(x => !x.Disabled)!; if (activeTab is not null) await ShowTabAsync(activeTab); @@ -249,15 +240,20 @@ internal async Task SetDefaultActiveTabAsync() private async Task ShowNextAvailableTabAsync(int removedTabIndex) { - if (!tabs?.Any() ?? true) return; + if (tabs.Count == 0) return; - if (removedTabIndex < 0 || removedTabIndex > tabs!.Count) throw new IndexOutOfRangeException(); + // Inactive tab is removed, just show the active tab. + if (removedTabIndex == -99) + { + await ShowTabAsync(activeTab); + return; + } var tabIndex = 0; - if (removedTabIndex == tabs!.Count) - tabIndex = tabs!.Count - 1; - else if (removedTabIndex < tabs!.Count) + if (removedTabIndex == tabs.Count) + tabIndex = tabs.Count - 1; + else if (removedTabIndex < tabs.Count) tabIndex = removedTabIndex; var tab = tabs[tabIndex]; @@ -271,10 +267,12 @@ private async Task ShowTabAsync(Tab tab) if (!isDefaultActiveTabSet) isDefaultActiveTabSet = true; - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tabs.show", tab.Id); + queuedTasks.Enqueue(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tabs.show", tab.Id)); if (tab?.OnClick.HasDelegate ?? false) await tab.OnClick.InvokeAsync(new TabEventArgs(tab!.Name, tab.Title)); + + activeTab = tab!; } #endregion diff --git a/docs/docs/05-components/tabs.mdx b/docs/docs/05-components/tabs.mdx index 4712a6593..fd50827c7 100644 --- a/docs/docs/05-components/tabs.mdx +++ b/docs/docs/05-components/tabs.mdx @@ -680,4 +680,72 @@ In the following example, we are deleting tabs dynamically. Ensure that the **@k } ``` -[See demo here.](https://demos.blazorbootstrap.com/tabs#remove-dynamic-tabs) \ No newline at end of file +[See demo here.](https://demos.blazorbootstrap.com/tabs#remove-dynamic-tabs) + +### Remove inactive tab + +In the following example, we are removing the inactive tab by name. The focus will not be lost with this active tab. + +Blazor Tabs Component - Remove inactive tab + +```cshtml {} showLineNumbers +
+ + + + @foreach (var customer in customers) + { + + +
+
+ This is the placeholder content for the @customer.CustomerName tab. +
+
+
+
+ } +
+
+
+
+
+ +
+``` + +```cs {} showLineNumbers +@code { + Tabs tabsRef = default!; + + int count = 1; + private List customers = default!; + + protected override void OnInitialized() + { + customers = new() { + new(1, "Marvin Klein"), + new(2, "Vikram Reddy"), + new(3, "Bandita PA"), + new(4, "Dan H"), + new(5, "Joe JJ"), + new(6, "Rose KK") + }; + count = customers.Count; + } + + private void RemoveCustomerVikram() + { + var customer = customers.FirstOrDefault(c => c.CustomerName == "Vikram Reddy"); + if (customer != null) + { + customers.Remove(customer); + tabsRef.RemoveTabByName(customer.CustomerId.ToString()); + } + } +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/tabs#remove-inactive-tab) \ No newline at end of file