代码改变世界

Sortable List using ASP.net MVC and jQuery

2010-02-21 10:28  AnyKoro  阅读(1526)  评论(0编辑  收藏  举报

Sorting is one of the most basic and essential algorithm form the early days of computation, In this article we will be discussing about sorting a list of items in a web application, this is one of the common feature given to the administrators, for example sorting the products in an e-commerce application, FAQ list etc etc, I am using the feature list as an example, typically this will be like features of a product which a web admin would like to sort.

 

Code:

Lets start by opening an ASP.net MVC application.

TDD:

ASP.net MVC gives a lot of emphasis on test driven development and its always a good practice to write a test before implementing any solution which will serve as a guideline for us at the time of implementing the functionality so lets add a test which will ensure that all the items are sorted according to their position after the list is sorted.

 

[TestMethod]
public void PositionsShouldBeSortedAfterSortList()
{
    var features = new List<Feature>
                   {
                       new Feature {Id = 1, Description = "Feature1", Position = 1},
                       new Feature {Id = 2, Description = "Feature2", Position = 3},
                       new Feature {Id = 3, Description = "Feature3", Position = 7},
                       new Feature {Id = 4, Description = "Feature4", Position = 2},
                       new Feature {Id = 5, Description = "Feature5", Position = 11}
                   };
 
    var modifiedOrder = new List<int> { 3, 2, 4, 1, 5 };
    var sortedPositions = features
                            .OrderBy(f => f.Position)
                            .Select(f => f.Position).ToList();
 
    FeatureService.SortList(features, modifiedOrder, sortedPositions);
 
    var lastPosition = 0;
 
    foreach (var order in modifiedOrder)
    {
        var position = features.Single(f => f.Id == order).Position;
        Assert.IsTrue(position > lastPosition);
        lastPosition = position;
    }
}

Database:

lets add a database to our application then add the table to hold the features of the product, the schema would be similar to this

the important thing to notice in this table is the “Position” column which will hold the sort position of each item, on the first sight it would seem like a nice candidate for an Identity key but bear in mind that this field is updateable, so implement your own mechanism to decide the value of position when creating a new item. Add few items to this table.

 

Model:

you can create the model with any technology of your preference, for this example lets implement the model using Linq2Sql, the generated model will be like this

Controller:

to keep it simple lets modify the index action of the Home Controller to fetch the list items and hand it over to the View

public ActionResult Index()
{
    var db = new DbDataContext();
    var features = db.Features.OrderBy( f => f.Position);
    return View(features);
}

View:

<ul id="features">
    <%foreach (var feature in (IEnumerable<Feature>)Model)
      { %>
    <li id='<%="feature_" + feature.Id %>'>
        <%=feature.Description%></li>
    <%} %>
</ul> 

the key thing to notice here is the id of the “li” element it should have an underscore within it which will be used by the library to fetch the ids in the order in which they were sorted

JavaScript:

we will be using the jQuery and the jQuery.ui libraries, jQuery is already a part of the ASP.net MVC project template so it will be in the Scripts folder, download the jQuery.ui script and add it to the Script folder and add a reference in your Site.Master.

<script src="<%=ResolveUrl("~/Scripts/jquery-1.3.2.min.js") %>" type="text/javascript"></script>
<script src="<%=ResolveUrl("~/Scripts/jquery-ui.js") %>" type="text/javascript"></script>

in the index page add the following javascript

<script type="text/javascript">
        $(function() {           
            $('#features').sortable();
        });   
</script> 

this will make the list sortable in the client side, but the user experience is not so good and the important piece of saving the position information to the database is still not implemented, to add a better user experience lets add an image to each list item which will be used as an handle to drag our items, its not a good idea to add this image in the markup as its not semantic so we can harness the power of jQuery to add this image, after that lets add some more options to the sortable method to improve the UX.

<script type="text/javascript">
    $(function() {
        $('<img src=<%=ResolveClientUrl("~/Content/arrow.png")%> alt= "move" class="handle" />').prependTo('#features li');
        $('#features').sortable({
            opacity: 0.7,
            revert: true,
            scroll: true,
            handle: '.handle'
            }
        });
    });   
</script> 

CSS:

lets add some CSS to style the list

ul#features{
    list-style: none;
 
}
 
ul#features li{
    margin: 0.5em;
}
 
ul#features li img.handle{
    cursor: move;
    width: 10px;
    height: 10px;
} 

Saving to DB:

we come to the final part of saving the positions back to the db, for this we need to implement the ajax callback method in the sortable function called update

$('#features').sortable({
                opacity: 0.7,
                revert: true,
                scroll: true,
                handle: '.handle',
                update: function(sorted) {
                    $.ajax({
                        url: '/Home/UpdatePosition',
                        type: "POST",
                        data: { 'positions': $(this).sortable('toArray') }
                    });
                }
            }); 

after this lets add an action method in the home controller to listen to this ajax call

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdatePosition(string[] positions)
{
    var db = new DbDataContext();
 
    var modifiedOrder = new List<int>(GetModifiedArrayIndexes(positions));
    var features = db.Features.ToList();
    var sortedPositions = db.Features.OrderBy(f => f.Position).Select(f => f.Position).ToList();
 
    FeatureService.SortList(features, modifiedOrder, sortedPositions);
 
    db.SubmitChanges();
 
    return new EmptyResult();
}
 
private IEnumerable<int> GetModifiedArrayIndexes(IEnumerable<string> positions)
{
    foreach (var position in positions)
        yield return Convert.ToInt32(position.Substring(position.IndexOf('_') + 1));
}        
 
public static class FeatureService
{
    public static void SortList(ICollection<Feature> features, IList<int> modifiedOrder, IList<int> originalPositions)
    {
        for (var i = 0; i < features.Count; i++)
        {
            var feature = features.FirstOrDefault(h => h.Id == modifiedOrder[i]);
            feature.Position = originalPositions[i];
        }
    }
} 

this method will save the positions of the list item as and when we change the sort order, that’s it we have successfully implemented the sortable list.

 

download the source code