diff --git a/BlazorBootstrap.Demo.Hosted/Client/wwwroot/appsettings.json b/BlazorBootstrap.Demo.Hosted/Client/wwwroot/appsettings.json index 0958b9250..0e9252144 100644 --- a/BlazorBootstrap.Demo.Hosted/Client/wwwroot/appsettings.json +++ b/BlazorBootstrap.Demo.Hosted/Client/wwwroot/appsettings.json @@ -10,6 +10,6 @@ "twitter": "//twitter.com/blazorbootstrap", "github_issues": "//github.com/vikramlearning/blazorbootstrap/issues", "github_discussions": "//github.com/vikramlearning/blazorbootstrap/discussions", - "stackoverflow": "//stackoverflow.com/questions/tagged/blazorbootstrap" + "stackoverflow": "//stackoverflow.com/questions/tagged/blazor-bootstrap" } } \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj index e31c8c240..e91aafa65 100644 --- a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj +++ b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj @@ -19,6 +19,10 @@ + + + + diff --git a/BlazorBootstrap.Demo.RCL/Pages/Charts/ChartsDocumentation.razor b/BlazorBootstrap.Demo.RCL/Pages/Charts/ChartsDocumentation.razor index 8ea486248..e886222fb 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Charts/ChartsDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Charts/ChartsDocumentation.razor @@ -6,7 +6,7 @@

Blazor Charts

- Blazor Bootstrap charts are well-designed chart components on top of Chart.js to visualize data. It contains a rich UI gallery of charts that cater to all charting scenarios. Its high performance helps render large amounts of data quickly. + Blazor Bootstrap charts are well-designed chart components on top of Chart.js to visualize data. It contains a rich UI gallery of charts that cater to all charting scenarios. Its high performance helps render large amounts of data quickly.
diff --git a/BlazorBootstrap.Demo.RCL/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Pages/Index.razor index d8f8225e5..286601cb2 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Index.razor @@ -67,7 +67,7 @@
@@ -97,7 +97,7 @@
@@ -122,7 +122,7 @@
@@ -157,7 +157,7 @@
@@ -172,7 +172,12 @@
+
@@ -202,7 +207,7 @@
@@ -221,7 +226,7 @@
@@ -231,12 +236,12 @@
@@ -260,7 +265,7 @@
diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableListDocumentation.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableListDocumentation.razor new file mode 100644 index 000000000..8a88ba3c3 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableListDocumentation.razor @@ -0,0 +1,75 @@ +@page "/sortable-list" + +@title + + + +

Blazor Sortable List

+
+ The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists. +
+ +@* *@ + + +
+ + + +
+

To drag-and-drop an item from one list to the other and vice versa, set the Group parameter for all the lists. Providing the same Group name for the lists is what links them together.

+

In the below example, both lists use the same Group.

+
+ + +
+ In the following example, all three lists use the same group. +
+ + + +
+ By setting Pull="SortableListPullMode.Clone", you can enable item cloning. Drag an item from one list to another to create a copy that stays in the original list. +
+ + + +
+ You can disable list sorting by setting AllowSorting="false". In the example below, the list cannot be sorted. +
+ + + +
+ The Handle parameter specifies the CSS class that denotes the drag handle. In the example below, items can only be sorted by dragging the handle itself. +
+ + + +
+ Try dragging the red-backgrounded item. You won't be able to, as it's disabled using the DisableItem parameter. +
+ + + + +@*
+ *@ + +
Nested list sorting is not currently supported. We will add this feature in upcoming releases.
+
+ + +
+ + + +
+ + +@code { + private string pageUrl = "/sortable-list"; + private string title = "Blazor Sortable List Component"; + private string description = "The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists."; + private string imageUrl = "https://i.imgur.com/bfzP8Yi.png"; +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_01_Examples.razor new file mode 100644 index 000000000..c39ae034e --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_01_Examples.razor @@ -0,0 +1,26 @@ + + + @item.Name + + + +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_A.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_A.razor new file mode 100644 index 000000000..1fd3be545 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_A.razor @@ -0,0 +1,84 @@ +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+ +@code { + public List employeeList1 = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(6, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList1Remove(SortableListEventArgs args) + { + // get the item at the old index in list 1 + var item = employeeList1[args.OldIndex]; + + // add it to the new index in list 2 + employeeList2.Insert(args.NewIndex, item); + + // remove the item from the old index in list 1 + employeeList1.Remove(employeeList1[args.OldIndex]); + } + + private void OnEmployeeList2Remove(SortableListEventArgs args) + { + // get the item at the old index in list 2 + var item = employeeList2[args.OldIndex]; + + // add it to the new index in list 1 + employeeList1.Insert(args.NewIndex, item); + + // remove the item from the old index in list 2 + employeeList2.Remove(employeeList2[args.OldIndex]); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_B_Three_Lists.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_B_Three_Lists.razor new file mode 100644 index 000000000..65771013e --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_02_Shared_Lists_B_Three_Lists.razor @@ -0,0 +1,116 @@ +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+ +@code { + public List employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList3 = Enumerable.Range(30, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList3Update(SortableListEventArgs args) + { + var itemToMove = employeeList3[args.OldIndex]; + + employeeList3.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList3.Count) + employeeList3.Insert(args.NewIndex, itemToMove); + else + employeeList3.Add(itemToMove); + } + + private void OnEmployeeListRemove(SortableListEventArgs args) + { + Employee? item = default!; + + // get the item at the old index + if (args.FromListName == "empList1") + item = employeeList1[args.OldIndex]; + else if (args.FromListName == "empList2") + item = employeeList2[args.OldIndex]; + else + item = employeeList3[args.OldIndex]; + + // add it to the new index + if (args.ToListName == "empList1") + employeeList1.Insert(args.NewIndex, item); + else if (args.ToListName == "empList2") + employeeList2.Insert(args.NewIndex, item); + else + employeeList3.Insert(args.NewIndex, item); + + // remove the item from the old index + if (args.FromListName == "empList1") + employeeList1.Remove(employeeList1[args.OldIndex]); + else if (args.FromListName == "empList2") + employeeList2.Remove(employeeList2[args.OldIndex]); + else + employeeList3.Remove(employeeList3[args.OldIndex]); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_03_Cloning.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_03_Cloning.razor new file mode 100644 index 000000000..5f424d1e3 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_03_Cloning.razor @@ -0,0 +1,82 @@ +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+ +@code { + public List employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList1Remove(SortableListEventArgs args) + { + // get the item at the old index in list 1 + var item = employeeList1[args.OldIndex]; + + var clone = item with {}; + + // add it to the new index in list 2 + employeeList2.Insert(args.NewIndex, clone); + } + + private void OnEmployeeList2Remove(SortableListEventArgs args) + { + // get the item at the old index in list 2 + var item = employeeList2[args.OldIndex]; + + var clone = item with { }; + + // add it to the new index in list 1 + employeeList1.Insert(args.NewIndex, clone); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_04_Disable_Sorting.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_04_Disable_Sorting.razor new file mode 100644 index 000000000..d54eb114d --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_04_Disable_Sorting.razor @@ -0,0 +1,18 @@ + + + @item.Name + + + +@code { + public List items = Enumerable.Range(1, 5).Select(i => new Employee { Id = i, Name = $"Item {i}" }).ToList(); + + public class Employee + { + public int Id { get; set; } + public string? Name { get; set; } + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_05_Handle.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_05_Handle.razor new file mode 100644 index 000000000..2246db783 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_05_Handle.razor @@ -0,0 +1,33 @@ + + + +
+
+
@item.Name
+
+
+ +
+ +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_06_Disable_Item.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_06_Disable_Item.razor new file mode 100644 index 000000000..df50c737d --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_06_Disable_Item.razor @@ -0,0 +1,28 @@ + + + @item.Name + + + +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_07_Nested_Sortables.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_07_Nested_Sortables.razor new file mode 100644 index 000000000..0664795bd --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_07_Nested_Sortables.razor @@ -0,0 +1,101 @@ + + + @item.Name + + @if (item?.Children?.Any() ?? false) + { + + + @childItem.Name + + + } + + + +@code { + public List employees = new List + { + new(){ Id = 1, Name = "Employee 1"}, + new(){ Id = 2, Name = "Employee 2", Children = new List + { + new(){ Id = 21, Name = "Employee 21"}, + new(){ Id = 22, Name = "Employee 22", Children= new List + { + new(){ Id = 221, Name = "Employee 221"}, + new(){ Id = 222, Name = "Employee 222"}, + new(){ Id = 223, Name = "Employee 223"}, + } + }, + new(){ Id = 23, Name = "Employee 23"}, + } + }, + new(){ Id = 3, Name = "Employee 3"}, + new(){ Id = 4, Name = "Employee 4", Children = new List + { + new(){ Id = 41, Name = "Employee 41"}, + new(){ Id = 42, Name = "Employee 42"}, + new(){ Id = 43, Name = "Employee 43"}, + } + }, + new(){ Id = 5, Name = "Employee 5"}, + }; + + private void OnEmployeeListAdd(SortableListEventArgs args, List fromList) + { + Console.WriteLine($"OnEmployeeListAdd >> OldIndex: {args.OldIndex}, NewIndex: {args.NewIndex}, Count: {fromList.Count}"); + + //var itemToMove = employees[args.OldIndex]; + + // employees.RemoveAt(args.OldIndex); + + // if (args.NewIndex < employees.Count) + // employees.Insert(args.NewIndex, itemToMove); + // else + // employees.Add(itemToMove); + } + + private void OnEmployeeListRemove(SortableListEventArgs args, List fromList) + { + Console.WriteLine($"OnEmployeeListRemove >> OldIndex: {args.OldIndex}, NewIndex: {args.NewIndex}, Count: {fromList.Count}"); + + //var itemToMove = employees[args.OldIndex]; + + // employees.RemoveAt(args.OldIndex); + + // if (args.NewIndex < employees.Count) + // employees.Insert(args.NewIndex, itemToMove); + // else + // employees.Add(itemToMove); + } + + private void OnEmployeeListUpdate(SortableListEventArgs args, List fromList) + { + Console.WriteLine($"OnEmployeeListUpdate >> OldIndex: {args.OldIndex}, NewIndex: {args.NewIndex}, Count: {fromList.Count}"); + + var itemToMove = fromList[args.OldIndex]; + + fromList.RemoveAt(args.OldIndex); + + if (args.NewIndex < fromList.Count) + fromList.Insert(args.NewIndex, itemToMove); + else + fromList.Add(itemToMove); + } + + public class Employee + { + public int Id { get; set; } + public string? Name { get; set; } + public List? Children { get; set; } + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_08_Dynamic_Data.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_08_Dynamic_Data.razor new file mode 100644 index 000000000..a319674e1 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_08_Dynamic_Data.razor @@ -0,0 +1,40 @@ + + + @item.Name + + + + + +@code { + public bool isLoading = false; + public List employees = null!; + + private async Task LoadDataAsync() + { + isLoading = true; + await Task.Delay(3000); + employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + isLoading = false; + await base.OnInitializedAsync(); + } + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_09_Empty_Data.razor b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_09_Empty_Data.razor new file mode 100644 index 000000000..19335244f --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/SortableList/SortableList_Demo_09_Empty_Data.razor @@ -0,0 +1,13 @@ + + + @item.Name + + + +@code { + public List items = null!; + + public record Employee(int Id, string? Name); +} diff --git a/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs index f88fc2deb..26b3a5edb 100644 --- a/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs @@ -67,6 +67,7 @@ private IEnumerable GetNavItems() new (){ Id = "519", Text = "Script Loader", Href = "/script-loader", IconName = IconName.CodeSlash, ParentId = "5" }, new (){ Id = "520", Text = "Sidebar", Href = "/sidebar", IconName = IconName.LayoutSidebar, ParentId = "5" }, new (){ Id = "521", Text = "Sidebar 2", Href = "/sidebar2", IconName = IconName.ListNested, ParentId = "5" }, + new (){ Id = "521", Text = "Sortable List", Href = "/sortable-list", IconName = IconName.ArrowsMove, ParentId = "5" }, new (){ Id = "522", Text = "Spinner", Href = "/spinners", IconName = IconName.ArrowRepeat, ParentId = "5" }, new (){ Id = "523", Text = "Tabs", Href = "/tabs", IconName = IconName.WindowPlus, ParentId = "5" }, new (){ Id = "524", Text = "Toasts", Href = "/toasts", IconName = IconName.ExclamationTriangleFill, ParentId = "5" }, diff --git a/BlazorBootstrap.Demo.Server/appsettings.json b/BlazorBootstrap.Demo.Server/appsettings.json index 5254d6987..235139476 100644 --- a/BlazorBootstrap.Demo.Server/appsettings.json +++ b/BlazorBootstrap.Demo.Server/appsettings.json @@ -17,6 +17,6 @@ "twitter": "//twitter.com/blazorbootstrap", "github_issues": "//github.com/vikramlearning/blazorbootstrap/issues", "github_discussions": "//github.com/vikramlearning/blazorbootstrap/discussions", - "stackoverflow": "//stackoverflow.com/questions/tagged/blazorbootstrap" + "stackoverflow": "//stackoverflow.com/questions/tagged/blazor-bootstrap" } } diff --git a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json index 4cc66ebff..179ef37f6 100644 --- a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json +++ b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json @@ -10,6 +10,6 @@ "twitter": "//twitter.com/blazorbootstrap", "github_issues": "//github.com/vikramlearning/blazorbootstrap/issues", "github_discussions": "//github.com/vikramlearning/blazorbootstrap/discussions", - "stackoverflow": "//stackoverflow.com/questions/tagged/blazorbootstrap" + "stackoverflow": "//stackoverflow.com/questions/tagged/blazor-bootstrap" } } \ No newline at end of file diff --git a/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html b/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html index 074afd5a3..3425593b7 100644 --- a/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html +++ b/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html @@ -53,6 +53,8 @@ + + diff --git a/blazorbootstrap/Components/SortableList/SortableList.razor b/blazorbootstrap/Components/SortableList/SortableList.razor new file mode 100644 index 000000000..46be99ef8 --- /dev/null +++ b/blazorbootstrap/Components/SortableList/SortableList.razor @@ -0,0 +1,46 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase +@typeparam TItem + +
+ @if (IsLoading) + { + if (LoadingTemplate is not null) + { +
@LoadingTemplate
+ } + else + { + + } + } + else if (Data?.Any() ?? false) + { + @foreach (var item in Data) + { + if (ItemTemplate is not null) + { + var disableItem = DisableItem?.Invoke(item) ?? false; + if (disableItem) // disable item + { +
@ItemTemplate(item)
+ } + else + { +
@ItemTemplate(item)
+ } + } + } + } + else + { + if (EmptyDataTemplate is not null) + { +
@EmptyDataTemplate
+ } + else + { +
@EmptyText
+ } + } +
diff --git a/blazorbootstrap/Components/SortableList/SortableList.razor.cs b/blazorbootstrap/Components/SortableList/SortableList.razor.cs new file mode 100644 index 000000000..faa270f54 --- /dev/null +++ b/blazorbootstrap/Components/SortableList/SortableList.razor.cs @@ -0,0 +1,196 @@ +namespace BlazorBootstrap; + +/// +/// Represents a sortable list component. +/// +/// The type of items in the list. +public partial class SortableList : BlazorBootstrapComponentBase +{ + #region Fields and Constants + + /// + /// A cancellation token source for managing asynchronous operations. + /// + private CancellationTokenSource cancellationTokenSource = default!; + + /// + /// A DotNetObjectReference that allows JavaScript interop with this component. + /// + private DotNetObjectReference>? objRef; + + /// + /// A CSS selector used to filter disabled items. + /// + private string filter = ".bb-sortable-list-item-disabled"; + + #endregion + + #region Methods + + /// + protected override void BuildClasses() + { + this.AddClass("list-group"); + + base.BuildClasses(); + } + + protected override async Task OnInitializedAsync() + { + objRef ??= DotNetObjectReference.Create(this); + await base.OnInitializedAsync(); + + QueueAfterRenderAction(async () => await SortableListJsInterop.InitializeAsync(ElementId!, Name!, Handle!, Group!, AllowSorting, Pull.ToSortableListPullMode(), Put.ToSortableListPutMode(), filter, objRef!), new RenderPriority()); + } + + /// + protected override async ValueTask DisposeAsync(bool disposing) + { + if (disposing) Data = null!; + + await base.DisposeAsync(disposing); + } + + [JSInvokable] + public async Task OnAddJS(int oldIndex, int newIndex) + { + if (OnAdd.HasDelegate) + await OnAdd.InvokeAsync(new(oldIndex, newIndex)); + } + + [JSInvokable] + public async Task OnRemoveJS(int oldIndex, int newIndex, string fromListName, string toListName) + { + if (OnRemove.HasDelegate) + await OnRemove.InvokeAsync(new(oldIndex, newIndex, fromListName, toListName)); + } + + [JSInvokable] + public async Task OnUpdateJS(int oldIndex, int newIndex) + { + if (OnUpdate.HasDelegate) + await OnUpdate.InvokeAsync(new(oldIndex, newIndex)); + } + + #endregion + + #region Properties, Indexers + + /// + protected override bool ShouldAutoGenerateId => true; + + /// + /// Gets or sets a value indicating whether sorting is allowed for the list. + /// + [Parameter] + public bool AllowSorting { get; set; } = true; + + /// + /// Specifies the content to be rendered inside the . + /// + [Parameter] + public RenderFragment ChildContent { get; set; } = default!; + + /// + /// Gets or sets the items. + /// + [Parameter] + public List Data { get; set; } = default!; + + /// + /// Gets or sets a delegate that determines whether an item should be disabled. + /// + [Parameter] + public Func DisableItem { get; set; } = default!; + + /// + /// Gets or sets the CSS class applied to disabled items. + /// + [Parameter] + public string? DisabledItemCssClass { get; set; } = default!; + + /// + /// Specifies the template to render when there are no items to display in the list. + /// + [Parameter] + public RenderFragment EmptyDataTemplate { get; set; } = default!; + + /// + /// Gets or sets the text to display when there are no records in the list. + /// + [Parameter] + public string EmptyText { get; set; } = "No records to display"; + + /// + /// Gets or sets the group name associated with the list. + /// + [Parameter] + public string? Group { get; set; } + + /// + /// Gets or sets the CSS selector for the drag handle element. + /// + [Parameter] + public string? Handle { get; set; } + + /// + /// Gets or sets a value indicating whether the list is currently loading. + /// + [Parameter] + public bool IsLoading { get; set; } + + /// + /// Specifies the template used to render individual items in the list. + /// + [Parameter] + public RenderFragment? ItemTemplate { get; set; } + + /// + /// Specifies the template to render while the list data is loading. + /// + [Parameter] + public RenderFragment LoadingTemplate { get; set; } = default!; + + /// + /// Gets or sets the name of the component. + /// + [Parameter] + public string? Name { get; set; } + + /// + /// Gets or sets an event callback that fires when an item is added to the list. + /// + [Parameter] + public EventCallback OnAdd { get; set; } + + /// + /// Gets or sets an event callback that fires when an item is removed from the list. + /// + [Parameter] + public EventCallback OnRemove { get; set; } + + /// + /// Gets or sets an event callback that fires when an item is updated in the list. + /// + [Parameter] + public EventCallback OnUpdate { get; set; } + + /// + /// Gets or sets the pull mode for the sortable list. + /// + [Parameter] + public SortableListPullMode Pull { get; set; } + + /// + /// Gets or sets the put mode for the sortable list. + /// + [Parameter] + public SortableListPutMode Put { get; set; } + + /// + /// Provides JavaScript interop functionality for the Sortable List. + /// + [Inject] private SortableListJsInterop SortableListJsInterop { get; set; } = default!; + + #endregion +} diff --git a/blazorbootstrap/Components/SortableList/SortableList.razor.css b/blazorbootstrap/Components/SortableList/SortableList.razor.css new file mode 100644 index 000000000..5a41b28a9 --- /dev/null +++ b/blazorbootstrap/Components/SortableList/SortableList.razor.css @@ -0,0 +1,3 @@ +::deep .bb-sortable-list-handle { + cursor: grab !important; +} diff --git a/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs b/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs new file mode 100644 index 000000000..dbda19200 --- /dev/null +++ b/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs @@ -0,0 +1,38 @@ +namespace BlazorBootstrap; + +public class SortableListJsInterop : IAsyncDisposable +{ + #region Fields and Constants + + private readonly Lazy> moduleTask; + + #endregion + + #region Constructors + + public SortableListJsInterop(IJSRuntime jsRuntime) + { + moduleTask = new Lazy>(() => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.sortable-list.js").AsTask()); + } + + #endregion + + #region Methods + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + + public async Task InitializeAsync(string elementId, string elementName, string handle, string group, bool allowSorting, object pull, object put, string filter, object objRef) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("initialize", elementId, elementName, handle, group, allowSorting, pull, put, filter, objRef); + } + + #endregion +} diff --git a/blazorbootstrap/Config.cs b/blazorbootstrap/Config.cs index d4d9b3f17..75900126e 100644 --- a/blazorbootstrap/Config.cs +++ b/blazorbootstrap/Config.cs @@ -21,6 +21,7 @@ public static IServiceCollection AddBlazorBootstrap(this IServiceCollection serv serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); return serviceCollection; } diff --git a/blazorbootstrap/Enums/SortableListPullMode.cs b/blazorbootstrap/Enums/SortableListPullMode.cs new file mode 100644 index 000000000..d362a0840 --- /dev/null +++ b/blazorbootstrap/Enums/SortableListPullMode.cs @@ -0,0 +1,9 @@ +namespace BlazorBootstrap; + +public enum SortableListPullMode +{ + True, + False, + Clone, + //@Array +} diff --git a/blazorbootstrap/Enums/SortableListPutMode.cs b/blazorbootstrap/Enums/SortableListPutMode.cs new file mode 100644 index 000000000..fd3d9ce49 --- /dev/null +++ b/blazorbootstrap/Enums/SortableListPutMode.cs @@ -0,0 +1,8 @@ +namespace BlazorBootstrap; + +public enum SortableListPutMode +{ + True, + False, + //@Array +} diff --git a/blazorbootstrap/EventArguments/SortableListEventArgs.cs b/blazorbootstrap/EventArguments/SortableListEventArgs.cs new file mode 100644 index 000000000..6896744a8 --- /dev/null +++ b/blazorbootstrap/EventArguments/SortableListEventArgs.cs @@ -0,0 +1,31 @@ +namespace BlazorBootstrap; + +public class SortableListEventArgs : EventArgs +{ + #region Constructors + + public SortableListEventArgs(int oldIndex, int newIndex) + { + OldIndex = oldIndex; + NewIndex = newIndex; + } + + public SortableListEventArgs(int oldIndex, int newIndex, string fromListName, string toListName) + { + OldIndex = oldIndex; + NewIndex = newIndex; + FromListName = fromListName; + ToListName = toListName; + } + + #endregion + + #region Properties, Indexers + + public string? FromListName { get; } + public int NewIndex { get; } + public int OldIndex { get; } + public string? ToListName { get; } + + #endregion +} diff --git a/blazorbootstrap/Extensions/EnumExtensions.cs b/blazorbootstrap/Extensions/EnumExtensions.cs index b1683b599..10649c111 100644 --- a/blazorbootstrap/Extensions/EnumExtensions.cs +++ b/blazorbootstrap/Extensions/EnumExtensions.cs @@ -127,6 +127,23 @@ public static string ToCssString(this Unit value) => _ => string.Empty }; + public static object ToSortableListPullMode(this SortableListPullMode mode) => + mode switch + { + SortableListPullMode.True => true, + SortableListPullMode.False => false, + SortableListPullMode.Clone => "clone", + //SortableListPullMode.Array => "array" + }; + + public static object ToSortableListPutMode(this SortableListPutMode mode) => + mode switch + { + SortableListPutMode.True => true, + SortableListPutMode.False => false, + //SortableListPullMode.Array => "array" + }; + /// /// Gets the spinner color. /// diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.sortable-list.js b/blazorbootstrap/wwwroot/blazor.bootstrap.sortable-list.js new file mode 100644 index 000000000..d9365e8fa --- /dev/null +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.sortable-list.js @@ -0,0 +1,49 @@ +export function initialize(elementId, elementName, handle, group, allowSorting, pull, put, filter, dotNetHelper) { + let listGroupEl = document.getElementById(elementId); + if (listGroupEl == null) + return; + + if (Sortable) { + const sortable = Sortable.create(listGroupEl, { + animation: 150, + filter: '.bb-sortable-list-item-disabled', + group: { + name: group, + pull: pull, + put: put + }, + handle: handle, // handle's class + onAdd: (event) => { + event.item.remove(); + dotNetHelper.invokeMethodAsync('OnAddJS', event.oldDraggableIndex, event.newDraggableIndex); + }, + onRemove: (event) => { + if (event.pullMode === 'clone') { + event.clone.remove(); + } + + event.item.remove(); + event.from.insertBefore(event.item, event.from.childNodes[event.oldIndex]); + + let fromElName = ''; + let toElName = ''; + + let fromEl = document.getElementById(event.from.id); + if (fromEl) + fromElName = fromEl.getAttribute('name'); + + let toEl = document.getElementById(event.to.id); + if (toEl) + toElName = toEl.getAttribute('name'); + + dotNetHelper.invokeMethodAsync('OnRemoveJS', event.oldDraggableIndex, event.newDraggableIndex, fromElName, toElName); + }, + onUpdate: (event) => { + event.item.remove(); + event.to.insertBefore(event.item, event.to.childNodes[event.oldIndex]); + dotNetHelper.invokeMethodAsync('OnUpdateJS', event.oldDraggableIndex, event.newDraggableIndex); + }, + sort: allowSorting + }); + } +} diff --git a/docs/docs/05-components/sortable-list.mdx b/docs/docs/05-components/sortable-list.mdx new file mode 100644 index 000000000..ea3a1220f --- /dev/null +++ b/docs/docs/05-components/sortable-list.mdx @@ -0,0 +1,609 @@ +--- +title: Blazor Sortable List Component +description: The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists. +image: https://i.imgur.com/bfzP8Yi.png + +sidebar_label: Sortable List +sidebar_position: 23 +--- + +import CarbonAd from '/carbon-ad.mdx' + +# Blazor Sortable List + +The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists. + + + +Blazor Sortable List + +## Parameters + +| Name | Type | Default | Required | Description | Added Version | +|:--|:--|:--|:--|:--|:--| +| AllowSorting | bool | true | | Gets or sets a value indicating whether sorting is allowed for the list. | 2.2.0 | +| ChildContent | `RenderFragment` | | | Specifies the content to be rendered inside the `SortableList`. | 2.2.0 | +| Data | `List` | | | Gets or sets the items. | 2.2.0 | +| DisableItem | `Func` | | | Gets or sets a delegate that determines whether an item should be disabled. | 2.2.0 | +| DisabledItemCssClass | `string?` | | | Gets or sets the CSS class applied to disabled items. | 2.2.0 | +| EmptyDataTemplate | `RenderFragment` | | | Specifies the template to render when there are no items to display in the list. | 2.2.0 | +| EmptyText | string | `No records to display` | | Gets or sets the text to display when there are no records in the list. | 2.2.0 | +| Group | `string?` | | | Gets or sets the group name associated with the list. | 2.2.0 | +| Handle | `string?` | | | Gets or sets the CSS selector for the drag handle element. | 2.2.0 | +| IsLoading | bool | | | Gets or sets a value indicating whether the list is currently loading. | 2.2.0 | +| ItemTemplate | `RenderFragment?` | | | Specifies the template used to render individual items in the list. | 2.2.0 | +| LoadingTemplate | `RenderFragment` | | | Specifies the template to render while the list data is loading. | 2.2.0 | +| Name | `string?` | | Gets or sets the name of the `SortableList` component. | 2.2.0 | +| Pull | `SortableListPullMode` | | | Gets or sets the pull mode for the sortable list. | 2.2.0 | +| Put | `SortableListPutMode` | | | Gets or sets the put mode for the sortable list. | 2.2.0 | + +## Methods + +There are no public methods available. + +## Callback Events + +| Event | Description | Added Version | +|--|--|--| +| OnAdd | Gets or sets an event callback that fires when an item is added to the list. | 2.2.0 | +| OnRemove | Gets or sets an event callback that fires when an item is removed from the list. | 2.2.0 | +| OnUpdate | Gets or sets an event callback that fires when an item is updated in the list. | 2.2.0 | + +## Examples + +### Basic usage + +Blazor Sortable List - Basic usage + +```cshtml {} showLineNumbers + + + @item.Name + + +``` + +```cs {} showLineNumbers +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#examples) + +### Shared lists + +To drag-and-drop an item from one list to the other and vice versa, set the **Group** parameter for all the lists. +Providing the same **Group** name for the lists is what links them together. + +In the below example, both lists use the same **Group**. + +Blazor Sortable List - Shared lists + +```cshtml {} showLineNumbers +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+``` + +```cs {} showLineNumbers +@code { + public List employeeList1 = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(6, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList1Remove(SortableListEventArgs args) + { + // get the item at the old index in list 1 + var item = employeeList1[args.OldIndex]; + + // add it to the new index in list 2 + employeeList2.Insert(args.NewIndex, item); + + // remove the item from the old index in list 1 + employeeList1.Remove(employeeList1[args.OldIndex]); + } + + private void OnEmployeeList2Remove(SortableListEventArgs args) + { + // get the item at the old index in list 2 + var item = employeeList2[args.OldIndex]; + + // add it to the new index in list 1 + employeeList1.Insert(args.NewIndex, item); + + // remove the item from the old index in list 2 + employeeList2.Remove(employeeList2[args.OldIndex]); + } + + public record Employee(int Id, string? Name); +} +``` + +In the following example, all three lists use the same group. + +Blazor Sortable List - Shared lists - More than two lists + +```cshtml {} showLineNumbers +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+``` + +```cs {} showLineNumbers +@code { + public List employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList3 = Enumerable.Range(30, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList3Update(SortableListEventArgs args) + { + var itemToMove = employeeList3[args.OldIndex]; + + employeeList3.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList3.Count) + employeeList3.Insert(args.NewIndex, itemToMove); + else + employeeList3.Add(itemToMove); + } + + private void OnEmployeeListRemove(SortableListEventArgs args) + { + Employee? item = default!; + + // get the item at the old index + if (args.FromListName == "empList1") + item = employeeList1[args.OldIndex]; + else if (args.FromListName == "empList2") + item = employeeList2[args.OldIndex]; + else + item = employeeList3[args.OldIndex]; + + // add it to the new index + if (args.ToListName == "empList1") + employeeList1.Insert(args.NewIndex, item); + else if (args.ToListName == "empList2") + employeeList2.Insert(args.NewIndex, item); + else + employeeList3.Insert(args.NewIndex, item); + + // remove the item from the old index + if (args.FromListName == "empList1") + employeeList1.Remove(employeeList1[args.OldIndex]); + else if (args.FromListName == "empList2") + employeeList2.Remove(employeeList2[args.OldIndex]); + else + employeeList3.Remove(employeeList3[args.OldIndex]); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#shared-lists) + +### Cloning + +By setting `Pull="SortableListPullMode.Clone"`, you can enable item cloning. +Drag an item from one list to another to create a copy that stays in the original list. + +Blazor Sortable List - Cloning + +```cshtml {} showLineNumbers +
+
+ + + @item.Name + + +
+
+ + + @item.Name + + +
+
+``` + +```cs {} showLineNumbers +@code { + public List employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + public List employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeList1Update(SortableListEventArgs args) + { + var itemToMove = employeeList1[args.OldIndex]; + + employeeList1.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList1.Count) + employeeList1.Insert(args.NewIndex, itemToMove); + else + employeeList1.Add(itemToMove); + } + + private void OnEmployeeList2Update(SortableListEventArgs args) + { + var itemToMove = employeeList2[args.OldIndex]; + + employeeList2.RemoveAt(args.OldIndex); + + if (args.NewIndex < employeeList2.Count) + employeeList2.Insert(args.NewIndex, itemToMove); + else + employeeList2.Add(itemToMove); + } + + private void OnEmployeeList1Remove(SortableListEventArgs args) + { + // get the item at the old index in list 1 + var item = employeeList1[args.OldIndex]; + + var clone = item with {}; + + // add it to the new index in list 2 + employeeList2.Insert(args.NewIndex, clone); + } + + private void OnEmployeeList2Remove(SortableListEventArgs args) + { + // get the item at the old index in list 2 + var item = employeeList2[args.OldIndex]; + + var clone = item with { }; + + // add it to the new index in list 1 + employeeList1.Insert(args.NewIndex, clone); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#cloning) + +### Disable sorting + +You can disable list sorting by setting `AllowSorting="false"`. In the example below, the list cannot be sorted. + +Blazor Sortable List - Disable sorting + +```cshtml {} showLineNumbers + + + @item.Name + + +``` + +```cs {} showLineNumbers +@code { + public List items = Enumerable.Range(1, 5).Select(i => new Employee { Id = i, Name = $"Item {i}" }).ToList(); + + public class Employee + { + public int Id { get; set; } + public string? Name { get; set; } + } +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#disable-sorting) + +### Handle + +The **Handle** parameter specifies the CSS class that denotes the drag handle. In the example below, items can only be sorted by dragging the handle itself. + +Blazor Sortable List - Handle + +```cshtml {} showLineNumbers + + + +
+
+
@item.Name
+
+
+ +
+``` + +```cs {} showLineNumbers +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#handle) + +### Disable item + +Try dragging the red-backgrounded item. You won't be able to, as it's disabled using the **DisableItem** parameter. + +Blazor Sortable List - Disable item + +```cshtml {} showLineNumbers + + + @item.Name + + +``` + +```cs {} showLineNumbers +@code { + public List employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#disable-item) + +### Nested sortables + +:::note +Nested list sorting is not currently supported. We will add this feature in upcoming releases. +::: + +### Dynamic data + +Blazor Sortable List - Dynamic data + +```cshtml {} showLineNumbers + + + @item.Name + + + + +``` + +```cs {} showLineNumbers +@code { + public bool isLoading = false; + public List employees = null!; + + private async Task LoadDataAsync() + { + isLoading = true; + await Task.Delay(3000); + employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList(); + isLoading = false; + await base.OnInitializedAsync(); + } + + private void OnEmployeeListUpdate(SortableListEventArgs args) + { + var itemToMove = employees[args.OldIndex]; + + employees.RemoveAt(args.OldIndex); + + if (args.NewIndex < employees.Count) + employees.Insert(args.NewIndex, itemToMove); + else + employees.Add(itemToMove); + } + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#dynamic-data) + +### Empty data + +Blazor Sortable List - Empty data + +```cshtml {} showLineNumbers + + + @item.Name + + +``` + +```cs {} showLineNumbers +@code { + public List items = null!; + + public record Employee(int Id, string? Name); +} +``` + +[See demo here.](https://demos.blazorbootstrap.com/sortable-list#empty-data) diff --git a/docs/docs/05-components/spinners.mdx b/docs/docs/05-components/spinners.mdx index 0576578d5..dfe93ca3f 100644 --- a/docs/docs/05-components/spinners.mdx +++ b/docs/docs/05-components/spinners.mdx @@ -4,7 +4,7 @@ description: Visualize the loading state of a component or page using the Blazor image: https://i.imgur.com/G4wyEd6.png sidebar_label: Spinners -sidebar_position: 23 +sidebar_position: 24 --- import CarbonAd from '/carbon-ad.mdx' diff --git a/docs/docs/05-components/tabs.mdx b/docs/docs/05-components/tabs.mdx index 0c8a70f7e..e3d7cce2f 100644 --- a/docs/docs/05-components/tabs.mdx +++ b/docs/docs/05-components/tabs.mdx @@ -4,7 +4,7 @@ description: Documentation and examples for using Blazor Bootstrap Tabs componen image: https://i.imgur.com/KelXx6Z.png sidebar_label: Tabs -sidebar_position: 24 +sidebar_position: 25 --- import CarbonAd from '/carbon-ad.mdx' diff --git a/docs/docs/05-components/toasts.mdx b/docs/docs/05-components/toasts.mdx index 65e0aceb9..896d1f21e 100644 --- a/docs/docs/05-components/toasts.mdx +++ b/docs/docs/05-components/toasts.mdx @@ -4,7 +4,7 @@ description: Push notifications to your visitors with a toast, a lightweight and image: https://i.imgur.com/W1YkmJH.png sidebar_label: Toasts -sidebar_position: 25 +sidebar_position: 26 --- import CarbonAd from '/carbon-ad.mdx' diff --git a/docs/docs/05-components/tooltips.mdx b/docs/docs/05-components/tooltips.mdx index 405969bb2..009e8005b 100644 --- a/docs/docs/05-components/tooltips.mdx +++ b/docs/docs/05-components/tooltips.mdx @@ -4,7 +4,7 @@ description: Use Blazor Bootstrap tooltip component to add custom tooltips to yo image: https://i.imgur.com/uqvqb2i.jpg sidebar_label: Tooltips -sidebar_position: 26 +sidebar_position: 27 --- import CarbonAd from '/carbon-ad.mdx' diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index da2f53e47..753af47db 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -91,7 +91,7 @@ const config = { items: [ { label: 'Issues', href: 'https://github.com/vikramlearning/blazorbootstrap/issues', }, { label: 'Discussions', href: 'https://github.com/vikramlearning/blazorbootstrap/discussions', }, - //{ label: 'Stack Overflow', href: 'https://stackoverflow.com/questions/tagged/blazorbootstrap', }, + { label: 'Stack Overflow', href: 'https://stackoverflow.com/questions/tagged/blazor-bootstrap', }, { label: 'Twitter', href: 'https://twitter.com/blazorbootstrap', }, ], },