{"id":49972,"date":"2024-01-12T10:05:00","date_gmt":"2024-01-12T18:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=49972"},"modified":"2024-02-07T15:20:06","modified_gmt":"2024-02-07T23:20:06","slug":"introducing-blazor-sortable","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-blazor-sortable\/","title":{"rendered":"Sortable Lists for Blazor using a SortableJS Component"},"content":{"rendered":"<p>A common feature for web apps is sortable lists. <a href=\"https:\/\/github.com\/SortableJS\/Sortable\">SortableJS<\/a> is one of my favorite JavaScript libraries and I missed it when developing with Blazor. To remedy this, I decided to wrap SortableJS to make it a Blazor component, named <a href=\"https:\/\/blazorsortable.theurlist.com\">Bazor Sortable<\/a>, that I have made open source on GitHub that I think you will love. In this post I will walk you through how to add it into your own Blazor web apps. <\/p>\n<blockquote>\n<p>Note: Blazor Sortable is an open-source community component and not an official component from Microsoft. The Fluent UI for Blazor team has integrated a sortable component for Fluent UI for Blazor. You can try the <a href=\"https:\/\/www.fluentui-blazor.net\/SortableList\">Fluent UI Sortable Demo<\/a> today.<\/p>\n<\/blockquote>\n<p>Check the demo out here: <a href=\"https:\/\/blazorsortable.theurlist.com\">https:\/\/blazorsortable.theurlist.com<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/02\/simple-list.gif\" alt=\"A screenshot of the blazor sortable demos\" \/><\/p>\n<p>Every Friday, <a href=\"https:\/\/twitter.com\/jongalloway\">Jon Galloway<\/a> (you&#8217;ve never heard of him but he&#8217;s cool trust) and I work on rebuilding a real app called <a href=\"https:\/\/theurlist.com\">theurlist.com<\/a> in Blazor. The stream is called &#8220;<a href=\"https:\/\/dotnet.microsoft.com\/live\/burke-learns-blazor\">Burke Learns Blazor<\/a>&#8221; on Twitch and .NET YouTube (<a href=\"https:\/\/www.youtube.com\/@dotnet\/\">Like and Subscribe!<\/a>). And we&#8217;d love for you to join us. Mostly because we need all the help we can get with this thing because I have no idea what I&#8217;m doing.<\/p>\n<p>We ended up needing a sortable list component for this rebuild, and while there are a few &#8220;Blazor Sortable&#8221; examples out there, I kinda had my heart set on <a href=\"https:\/\/github.com\/SortableJS\/Sortable\">SortableJS<\/a>. SortableJS is a brilliant library for building sortable lists of items with virtually every feature you could need &#8211; sorting, sorting between lists, cloning items, filtering, custom animation easing, lumbar support. OK &#8211; not that last one, but that&#8217;s, like, that&#8217;s the only thing it doesn&#8217;t have.<\/p>\n<p>So with a little help from Steve Sanderson, we built a simple abstraction on SortableJS that you can drop in and use in your own apps. Let&#8217;s take a look at how to use and customize Blazor Sortable for your own Blazor apps.<\/p>\n<h3>Using Blazor Sortable<\/h3>\n<p>The GitHub repo for Blazor Sortable contains the source code for the sortable list as well as demos. For your own project, all you need is the <code>Shared\/SortableList.razor<\/code>, <code>Shared\/SortableList.razor.css<\/code> and <code>Shared\/SortableList.razor.js<\/code> files.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/02\/sortable-components.png\" alt=\"a screenshot of the files needed for Blazor Sortable in GitHub\" \/><\/p>\n<p>The <code>SortableList<\/code> component is a generic component that takes a list of items and <code>SortableItemTemplate<\/code> that defines how to render each item in the sortable list. For instance, let&#8217;s say that you have a list of books that looks like this&#8230;<\/p>\n<pre><code class=\"language-csharp\">public class Book\n{\n    public string Title { get; set; } = \"\";\n    public string Author { get; set; }  = \"\";\n    public int Year { get; set; }\n}\n\npublic List&lt;Book&gt; books = new List&lt;Book&gt;\n{\n    new Book { Title = \"The Very Hungry Caterpillar\", Author = \"Eric Carle\", Year = 1969 },\n    new Book { Title = \"Where the Wild Things Are\", Author = \"Maurice Sendak\", Year = 1963 },\n    new Book { Title = \"Goodnight Moon\", Author = \"Margaret Wise Brown\", Year = 1947 },\n    new Book { Title = \"The Cat in the Hat\", Author = \"Dr. Seuss\", Year = 1957 },\n    new Book { Title = \"Charlotte's Web\", Author = \"E.B. White\", Year = 1952 },\n    new Book { Title = \"Harry Potter and the Sorcerer's Stone\", Author = \"J.K. Rowling\", Year = 1997 },\n    new Book { Title = \"The Lion, the Witch and the Wardrobe\", Author = \"C.S. Lewis\", Year = 1950 },\n    new Book { Title = \"Matilda\", Author = \"Roald Dahl\", Year = 1988 },\n    new Book { Title = \"The Giving Tree\", Author = \"Shel Silverstein\", Year = 1964 },\n    new Book { Title = \"Oh, the Places You'll Go!\", Author = \"Dr. Seuss\", Year = 1990 }\n};<\/code><\/pre>\n<p>You can render this list in a <code>SortableList<\/code> like this&#8230;<\/p>\n<pre><code class=\"language-html\">&lt;div&gt;\n    &lt;SortableList Items=\"books\" Context=\"book\"&gt;\n        &lt;SortableItemTemplate&gt;\n            &lt;div class=\"book\"&gt;\n                &lt;p&gt;@book.Title&lt;\/p&gt;\n            &lt;\/div&gt;\n        &lt;\/SortableItemTemplate&gt;\n    &lt;\/SortableList&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/02\/sortable-book-example.png\" alt=\"a screenshot of a sortable list of books\" \/><\/p>\n<p>The <code>SortableList<\/code> component will render the list of items using the <code>SortableItemTemplate<\/code> and then make the list sortable using SortableJS. The <code>Context<\/code> parameter is used to define the name of the variable that will be used to represent each item in the list. In this case, the <code>Context<\/code> is <code>book<\/code> and so each item in the list will be represented by a variable called <code>book<\/code>.<\/p>\n<p>However, if you were to try and drag and drop items around at this point, you would notice that whenever you drop one, it just goes back to where it was before. That&#8217;s because we haven&#8217;t told the <code>SortableList<\/code> what to do when the list is sorted. We do that by handling the <code>OnUpdate<\/code> event and doing the sorting ourselves.<\/p>\n<pre><code class=\"language-html\">&lt;div&gt;\n    &lt;SortableList Items=\"books\" Context=\"book\" OnUpdate=\"@SortList\"&gt;\n        &lt;SortableItemTemplate&gt;\n            &lt;div class=\"book\"&gt;\n                &lt;p&gt;@book.Title&lt;\/p&gt;\n            &lt;\/div&gt;\n        &lt;\/SortableItemTemplate&gt;\n    &lt;\/SortableList&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<pre><code class=\"language-csharp\">...\npublic void SortList((int oldIndex, int newIndex) indices)\n{\n    \/\/ deconstruct the tuple\n    var (oldIndex, newIndex) = indices;\n\n    var items = this.books;\n    var itemToMove = items[oldIndex];\n    items.RemoveAt(oldIndex);\n\n    if (newIndex &lt; items.Count)\n    {{\n        items.Insert(newIndex, itemToMove);\n    }}\n    else\n    {{\n        items.Add(itemToMove);\n    }}\n}<\/code><\/pre>\n<p>The <code>OnUpdate<\/code> event handler will be called whenever the list is sorted. It will pass a tuple containing the old index and the new index of the item that was moved. In the <code>SortList<\/code> method, we deconstruct the tuple into two variables and then use those to move the item in the list. <\/p>\n<p>It&#8217;s SUPER important that you never ever mutate DOM that Blazor controls. Blazor keeps an internal copy of the DOM and if you change it with something like JavaScript, you will get bizarre results since the page state will be out of sync with Blazor&#8217;s internal state. So what we do behind the scenes here is cancel the JavaScript move so that the item doesn&#8217;t actually move on the page. Then we move the item in the list and Blazor will re-render the list with the new order.<\/p>\n<h3>A More Complex Example<\/h3>\n<p>SortableJS is a very powerful library and it can do a lot more than just sort lists. It can also sort between lists, clone items, filter items, and more. The <code>SortableList<\/code> component supports many of these features. Let&#8217;s take a look at a more complex example &#8211; sorting between two lists&#8230;<\/p>\n<pre><code class=\"language-html\">&lt;div&gt;\n    &lt;div class=\"container\"&gt;\n        &lt;div class=\"columns\"&gt;\n            &lt;div class=\"column\"&gt;\n                &lt;h3&gt;Books&lt;\/h3&gt;\n                &lt;SortableList Items=\"books\" Context=\"book\" OnRemove=\"@AddToFavoriteList\" Group=\"favorites\"&gt;\n                    &lt;SortableItemTemplate&gt;\n                        &lt;div class=\"book\"&gt;\n                            &lt;p&gt;@book.Title&lt;\/p&gt;\n                        &lt;\/div&gt;\n                    &lt;\/SortableItemTemplate&gt;\n                &lt;\/SortableList&gt;\n            &lt;\/div&gt;\n            &lt;div class=\"column\"&gt;\n                &lt;h3&gt;Favorite Books&lt;\/h3&gt;\n                &lt;SortableList Items=\"favoriteBooks\" Context=\"book\" OnRemove=\"@RemoveFromFavoriteList\" Group=\"favorites\"&gt;\n                    &lt;SortableItemTemplate&gt;\n                        &lt;div class=\"book\"&gt;\n                            &lt;p&gt;@book.Title&lt;\/p&gt;\n                        &lt;\/div&gt;\n                    &lt;\/SortableItemTemplate&gt;\n                &lt;\/SortableList&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<p>In this example, we have two lists &#8211; a list of all books and a list of favorite books. They are linked together via the <code>Group<\/code> property. <\/p>\n<p>We want to be able to drag and drop books from the list of all books to the list of favorite books. To do that, we need to handle the <code>OnRemove<\/code> event for both lists. <\/p>\n<pre><code class=\"language-csharp\">public void AddToFavoriteList((int oldIndex, int newIndex) indices)\n{\n    var (oldIndex, newIndex) = indices;\n\n    var book = books[oldIndex];\n    favoriteBooks.Insert(newIndex, book);\n    books.RemoveAt(oldIndex);\n}\n\npublic void RemoveFromFavoriteList((int oldIndex, int newIndex) indices)\n{\n    var (oldIndex, newIndex) = indices;\n\n    var book = favoriteBooks[oldIndex];\n    books.Insert(newIndex, book);\n    favoriteBooks.RemoveAt(oldIndex);\n}<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/02\/blazor-sortable.gif\" alt=\"a screenshot of multiple lists linked together\" \/><\/p>\n<h3>Styling the SortableList<\/h3>\n<p>By default, the <code>SortableList<\/code> contains some default styling that hides the &#8220;ghost&#8221; element while dragging. This will give you a gap between items as you are dragging. Without this style change, the item itself is shown as the drop target. This is a little weird because it means that the item you are dragging is also the item you are dropping on. But if that&#8217;s your jam, you can just override the styles in the <code>SortableList.razor.css<\/code> file or just don&#8217;t include it at all.<\/p>\n<p>Since all of the content rendered inside of a <code>SortableList<\/code> is rendered inside of a <code>SortableItemTemplate<\/code> child, you always have to use the &#8220;::deep&#8221; modifier for any changes to take effect. <\/p>\n<p>If you style the <code>SortableList<\/code> from a parent page\/component (i.e. Index.razor.css) you MUST wrap the <code>SortableList<\/code> in a container element and use the &#8220;::deep&#8221; modifier as well. If you don&#8217;t do this, your styles won&#8217;t take effect and you&#8217;ll be sad and confused and mad at me for making this component. This is a Blazor thing, not a SortableJS thing. You can read more about scope styles in the <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/blazor\/components\/css-isolation?view=aspnetcore-7.0#child-component-support\">ASP.NET Core docs<\/a>.<\/p>\n<p>I feel like nobody is going to read that last paragraph and there will be much wailing and gnashing of teeth. But I tried. I&#8217;m sorry in advance.<\/p>\n<h2>Why not HTML5 Drag and Drop?<\/h2>\n<p>Fair question and one that I certainly looked into before going to a JavaScript solution. The long and short of it is that the native HTML5 support for drag and drop simply isn&#8217;t robust enough for a decent sortable. For instance, there is no way to style much of the behaviour of the drag and drop. It looks&#8230;goofy&#8230;and there isn&#8217;t anything you can really do about it. It also has <a href=\"https:\/\/caniuse.com\/?search=drag\">pretty flaky support<\/a> across browsers. There are some essential properties that only work in Chrome.<\/p>\n<p>All of that said, SortableJS actually will try and use HTML5 drag and drop and fallback to a JavaScript solution on platforms like iOS. However, you still lose control over the styling and you get the goofy looking drag and drop. So I&#8217;ve got HTML5 <strong>turned off<\/strong> on the <code>SortableList<\/code>. If you want it back on, go into the <code>SortableList.razor.razor.js<\/code> file and remove the <code>forceFallback: true<\/code> attribute. I should probably make this a setting at some point.<\/p>\n<h2>Get Blazor Sortable<\/h2>\n<p>Check out <a href=\"https:\/\/blazorsortable.theurlist.com\">Blazor Sortable<\/a> and let us know what you think! You can do a lot with it, including cloning items, disabling sorting on certain items, specifying drag handles and more. We haven&#8217;t implemented every single feature of SortableJS. Yet. Pull requests are welcome! \ud83d\ude09<\/p>\n<blockquote>\n<p>Blazor Sortable is an open-source community project. <\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Blazor Sortable is a new a open source community Blazor component for creating sortable lists of items using SortableJS.<\/p>\n","protected":false},"author":8550,"featured_media":50421,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,197,7251,756],"tags":[7502,7205,58],"class_list":["post-49972","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnet","category-blazor","category-csharp","tag-aspnet","tag-blazor","tag-csharp"],"acf":[],"blog_post_summary":"<p>Blazor Sortable is a new a open source community Blazor component for creating sortable lists of items using SortableJS.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/49972","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/8550"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=49972"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/49972\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/50421"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=49972"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=49972"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=49972"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}