10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/BlazorSortable.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/BlazorSortable.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.002.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSortable", "BlazorSortable.csproj", "{41997150-7264-49CD-AB05-B7E9C34C2A7D}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {8BC5FBD3-9B58-42CE-A559-156D5A0D0791}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Item.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorSortable;
2 |
3 |
4 | public class Item
5 | {
6 | public int Id { get; set; }
7 | public string Name { get; set; } = "";
8 |
9 | public bool Disabled { get; set; } = false;
10 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 The Urlist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
4 | @Body
5 |
6 |
--------------------------------------------------------------------------------
/Pages/Index.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 |
3 | @using BlazorSortable.Shared.Demos
4 |
5 | @inject IJSRuntime JS
6 |
7 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Web;
2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
3 | using BlazorSortable;
4 |
5 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
6 | builder.RootComponents.Add("#app");
7 | builder.RootComponents.Add("head::after");
8 |
9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
10 |
11 | await builder.Build().RunAsync();
12 |
--------------------------------------------------------------------------------
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "iisExpress": {
4 | "applicationUrl": "http://localhost:14975",
5 | "sslPort": 44313
6 | }
7 | },
8 | "profiles": {
9 | "http": {
10 | "commandName": "Project",
11 | "dotnetRunMessages": true,
12 | "launchBrowser": true,
13 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
14 | "applicationUrl": "http://localhost:5219",
15 | "environmentVariables": {
16 | "ASPNETCORE_ENVIRONMENT": "Development"
17 | }
18 | },
19 | "https": {
20 | "commandName": "Project",
21 | "dotnetRunMessages": true,
22 | "launchBrowser": true,
23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
24 | "applicationUrl": "https://localhost:7263;http://localhost:5219",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | },
29 | "IIS Express": {
30 | "commandName": "IISExpress",
31 | "launchBrowser": true,
32 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
33 | "environmentVariables": {
34 | "ASPNETCORE_ENVIRONMENT": "Development"
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Blazor Sortable
2 |
3 | An implementation of the [SortableJS library](https://sortablejs.github.io/Sortable/) for Blazor. This example shows you how to reorder elements within a list using drag and drop.
4 |
5 | [View Demos](https://mango-flower-090e9130f.4.azurestaticapps.net/)
6 |
7 | ## Prerequisites
8 |
9 | - [dotnet 7](https://dotnet.microsoft.com/download/dotnet/7.0)
10 |
11 | ## Running Locally
12 |
13 | 1. Clone this repository.
14 | 1. Run the project using `dotnet watch`.
15 |
16 | ## How to use it in your own project
17 |
18 | 1. Add SortableJS to your `index.html` file. You can either download from the [SortableJS website](https://sortablejs.github.io/Sortable/) or use a CDN...
19 |
20 |
21 |
22 | 1. Add the `SortableList.razor`, `SortableList.razor.css` and `SortableList.razor.js` files to your project.
23 | 1. Add the SortableList component to your page and define the SortableItemTemplate...
24 |
25 |
26 |
27 |
28 |
@item.Name
29 |
30 |
31 |
32 |
33 | - Items: The list of items to be displayed. Can be of any type.
34 | - OnUpdate: The method to be called when the list is updated.
35 | - Context: The name of the variable to be used in the template.
36 |
37 | The SortableItemTemplate can contain any markup or components that you want.
38 |
39 | ## API
40 |
41 | ### Properties
42 |
43 | All proerties are optional and have default values.
44 |
45 | `Id`: String (Default: GUID): The id of the HTML element that will be turned into a sortable. If you don't supply this, a random id (GUID) will be generated for you.
46 |
47 | `Group`: String (Default: null) - Used for dragging between lists. Set the group to the same value on both lists to enable.
48 |
49 | `Pull`: String (Default: null) - Used when you want to clone elments into a list instead of moving them. Set `Pull` to "clone".
50 |
51 | `Put`: Boolean (Default: True) - Set to false to disable dropping from another list onto the current list.
52 |
53 | `Sort`: Boolean (Default: True) - Disable sorting within a list by setting to `false`.
54 |
55 | `Handle` (Default: ""): The CSS class you want the sortable to respect as the drag handle.
56 |
57 | `Filter` (Default: ""): The CSS class you want to use to identify elements that cannot be sorted or moved.
58 |
59 | `ForceFallback`: Boolean (Default: True) - Enable HTML5 drag and drop by setting to `false`.
60 |
61 | ### Methods
62 |
63 | `OnUpdate` (Optional): The method to be called when the list is updated. You can name this method whatever you like, but it expects a `oldIndex` and `newIndex` parameters when the drag and drop occurs.
64 |
65 | `OnRemove` (Optional): The method to be called when an item is removed from a list. This method is useful for handling the drop even between multiple lists. Like `OnUpdate`, it expects `oldIndex` and `newIndex` parameters.
66 |
67 | You will need to handle all events yourself. **If you don't handle any events, no sort or move will happen** as Blazor needs to make the changes to the underlying data structures so it can re-render the list.
68 |
69 | Here is an example of how to reorder your list when the OnUpdate is fired...
70 |
71 | private void SortList((int oldIndex, int newIndex) indices)
72 | {
73 | var (oldIndex, newIndex) = indices;
74 |
75 | var items = this.items;
76 | var itemToMove = items[oldIndex];
77 | items.RemoveAt(oldIndex);
78 |
79 | if (newIndex < items.Count)
80 | {
81 | items.Insert(newIndex, itemToMove);
82 | }
83 | else
84 | {
85 | items.Add(itemToMove);
86 | }
87 |
88 | StateHasChanged();
89 | }
90 |
--------------------------------------------------------------------------------
/Shared/Demos/Cloning.razor:
--------------------------------------------------------------------------------
1 |
2 |
Cloning
3 |
Cloning is enabled by the "Pull='Clone'" property. This allows cloning of an item by dropping it into a shared list.
46 |
47 |
48 |
49 | @code {{
50 | private void ListOneRemove((int oldIndex, int newIndex) indices)
51 | {{
52 | var item = items1[indices.oldIndex];
53 | var clone = item;
54 |
55 | items1.Remove(items1[indices.oldIndex]);
56 | }}
57 |
58 | private void ListTwoRemove((int oldIndex, int newIndex) indices)
59 | {{
60 | var item = items2[indices.oldIndex];
61 | var clone = item;
62 |
63 | items2.Remove(items2[indices.oldIndex]);
64 | }}
65 | }}
66 | ";
67 |
68 | public List items1 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
69 |
70 | public List items2 = Enumerable.Range(11, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
71 |
72 | private void ListOneRemove((int oldIndex, int newIndex) indices)
73 | {
74 | // get the item at the old index in list 1
75 | var item = items1[indices.oldIndex];
76 |
77 | var clone = item;
78 |
79 | // add it to the new index in list 2
80 | items2.Insert(indices.newIndex, clone);
81 | }
82 |
83 | private void ListTwoRemove((int oldIndex, int newIndex) indices)
84 | {
85 | // get the item at the old index in list 2
86 | var item = items2[indices.oldIndex];
87 |
88 | // make a copy
89 | var clone = item;
90 |
91 | // add it to the new index in list 1
92 | items1.Insert(indices.newIndex, clone);
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/Shared/Demos/DisablingSorting.razor:
--------------------------------------------------------------------------------
1 |
2 |
Disabling Sorting
3 |
You can disable sorting with the `Sort` option set to `false`. You can also disable dropping items on a list by setting the `Put` to `false`. In the example below, you can drag from list 1 to list 2, but not from list 2 to list 1. You can sort list 2, but not list 1.
Drag handles are specified by the `Handle` property which specifies which CSS class denotes the drag handle. In the example below, the items can only be sorted using the drag handle itself.
In the list below, you cannot drag the item in red. This is because it is filtered out with a filter CSS class. Pass the name of this class to the `Filter` property to filter out an item.
29 |
30 |
31 |
32 | @code {{
33 | private void SortList((int oldIndex, int newIndex) indices)
34 | {{
35 | // deconstruct the tuple
36 | var (oldIndex, newIndex) = indices;
37 |
38 | var items = this.items;
39 | var itemToMove = items[oldIndex];
40 | items.RemoveAt(oldIndex);
41 |
42 | if (newIndex < items.Count)
43 | {{
44 | items.Insert(newIndex, itemToMove);
45 | }}
46 | else
47 | {{
48 | items.Add(itemToMove);
49 | }}
50 |
51 | StateHasChanged();
52 | }}
53 | }}";
54 |
55 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
56 |
57 | // on initialized, set a random item in the list to disabled
58 | protected override void OnInitialized()
59 | {
60 | var random = new Random();
61 | var randomIndex = random.Next(0, items.Count);
62 | items[randomIndex].Disabled = true;
63 | }
64 |
65 |
66 | private void SortList((int oldIndex, int newIndex) indices)
67 | {
68 | // deconstruct the tuple
69 | var (oldIndex, newIndex) = indices;
70 |
71 | var items = this.items;
72 | var itemToMove = items[oldIndex];
73 | items.RemoveAt(oldIndex);
74 |
75 | if (newIndex < items.Count)
76 | {
77 | items.Insert(newIndex, itemToMove);
78 | }
79 | else
80 | {
81 | items.Add(itemToMove);
82 | }
83 |
84 | StateHasChanged();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Shared/Demos/Filtering.razor.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-urlist/BlazorSortable/5b9ee8db115a296f09638ddc8b4c95e2ad6afe57/Shared/Demos/Filtering.razor.css
--------------------------------------------------------------------------------
/Shared/Demos/ForceFallback.razor:
--------------------------------------------------------------------------------
1 |
2 |
Force Fallback to JavaScript
3 |
Due to HTML5 drag and drop not being universally supported and the loss of control over the styling forceFallback is set to true by default for this library so the HTML5 drag and drop is not used. If preferred, you can set the 'ForceFallback' to false to get SortableJS's default behavior. The list on the left has 'ForceFallback' set to true, and the list on the right has ForceFallback set to false.
45 |
46 |
47 |
48 | @code {{
49 | public List items = Enumerable.Range(1, 10).Select(i => new Item {{ Id = i, Name = $""Item {{i}}"" }}).ToList();
50 |
51 | public List items2 = Enumerable.Range(1, 10).Select(i => new Item {{ Id = i, Name = $""Item {{i}}"" }}).ToList();
52 |
53 | private void SortList((int oldIndex, int newIndex) indices)
54 | {{
55 | // deconstruct the tuple
56 | var (oldIndex, newIndex) = indices;
57 |
58 | var items = this.items;
59 | var itemToMove = items[oldIndex];
60 | items.RemoveAt(oldIndex);
61 |
62 | if (newIndex < items.Count)
63 | {{
64 | items.Insert(newIndex, itemToMove);
65 | }}
66 | else
67 | {{
68 | items.Add(itemToMove);
69 | }}
70 |
71 | StateHasChanged();
72 | }}
73 | }}
74 | ";
75 |
76 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
77 |
78 | public List items2 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
79 |
80 | private void SortList((int oldIndex, int newIndex) indices)
81 | {
82 | // deconstruct the tuple
83 | var (oldIndex, newIndex) = indices;
84 |
85 | var items = this.items;
86 | var itemToMove = items[oldIndex];
87 | items.RemoveAt(oldIndex);
88 |
89 | if (newIndex < items.Count)
90 | {
91 | items.Insert(newIndex, itemToMove);
92 | }
93 | else
94 | {
95 | items.Add(itemToMove);
96 | }
97 |
98 | StateHasChanged();
99 | }
100 | }
--------------------------------------------------------------------------------
/Shared/Demos/SharedLists.razor:
--------------------------------------------------------------------------------
1 |
2 |
Shared Lists
3 |
Shared lists are lists where items can be dragged from one list to the other and vice-versa. Providing the same "Group" string name for both lists is what links them together. Note that when an item is dragged into a different list, it assumes the visual style of that list. This is because Blazor controls the rendering of the list items.
45 |
46 |
47 |
48 | @code {{
49 | private void ListOneRemove((int oldIndex, int newIndex) indices)
50 | {{
51 | // get the item at the old index in list 1
52 | var item = items1[indices.oldIndex];
53 |
54 | // add it to the new index in list 2
55 | items2.Insert(indices.newIndex, item);
56 |
57 | // remove the item from the old index in list 1
58 | items1.Remove(items1[indices.oldIndex]);
59 | }}
60 |
61 | private void ListTwoRemove((int oldIndex, int newIndex) indices)
62 | {{
63 | // get the item at the old index in list 2
64 | var item = items2[indices.oldIndex];
65 |
66 | // add it to the new index in list 1
67 | items1.Insert(indices.newIndex, item);
68 |
69 | // remove the item from the old index in list 2
70 | items2.Remove(items2[indices.oldIndex]);
71 | }}
72 | }}
73 | ";
74 |
75 | public List items1 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
76 |
77 | public List items2 = Enumerable.Range(11, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList();
78 |
79 | private void ListOneRemove((int oldIndex, int newIndex) indices)
80 | {
81 | // get the item at the old index in list 1
82 | var item = items1[indices.oldIndex];
83 |
84 | // add it to the new index in list 2
85 | items2.Insert(indices.newIndex, item);
86 |
87 | // remove the item from the old index in list 1
88 | items1.Remove(items1[indices.oldIndex]);
89 | }
90 |
91 | private void ListTwoRemove((int oldIndex, int newIndex) indices)
92 | {
93 | // get the item at the old index in list 2
94 | var item = items2[indices.oldIndex];
95 |
96 | // add it to the new index in list 1
97 | items1.Insert(indices.newIndex, item);
98 |
99 | // remove the item from the old index in list 2
100 | items2.Remove(items2[indices.oldIndex]);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Shared/Demos/SimpleList.razor:
--------------------------------------------------------------------------------
1 |