Draggable Sorting and Saving Changes using MVC and jQuery UI

We’ve been posting a lot of MVC and jQuery lately and I guess you have to get used to it for a couple of months more as this the technology that I am using currently in most of my projects. For today’s session I will be discussing on how to make a sortable list which you can drag and pass that sorted list information back to your server.

Not let’s make some assumptions.

Lets say you want a list to be sortable in your web project, usually Old school UI’s do it the Sharepoint way like this

or the old windows way like this

and I can’t blame developers who did this as this is the easy way to do it for a web UI, I myself made some sorting like this before. But now thanks to jQuery and jQuery UI things are much better and tasks like this just needs an element id and call the .sortable() method.

Now before we start lets dissect first what we need, and here it is

  1. A view which we call SortingOptions
  2. A view model to define the Sorting Options, we call the parent view model as UserPreferenceViewModel and the child view model which contains the details of each item on the list as UserPreferenceDetailViewModel
  3. A controller which contains two actions, InitializeModifyView which populates your view model and SaveUserPreferenceDetails which saves the ordering of the list

Lets start.

First here is how your View Model should look like for this example, I think the property names are self-explanatory.

public class UserPreferenceViewModel 
{
    public int Id { get; set; }
    public IList<UserPreferenceDetailViewModel> UserPreferenceDetails { get; set; }
}
public class UserPreferenceDetailViewModel 
{
    public int Id { get; set; }
    public string Description { get; set; }
    public bool IsEnabled { get; set; }
    public int ColumnOrder { get; set; }
}

Next is the View. There will be 3 main sections that I want to cover here, first is to import the jQuery and jQuery UI Scripts

<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.ui.core.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.ui.widget.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.ui.mouse.js")" type="text/javascript"></script>   
<script src="@Url.Content("~/Scripts/jquery.ui.sortable.js")" type="text/javascript"></script>

You need all of these references for the sortable function to work, it’s the documented dependencies you will see in the jQuery UI site. If you don’t have the jQuery UI you can get it here, the downloads can be customized to what you need. You can also use NuGet but there is a catch, it will work but the jQuery dependency listed is between versions 1.4.4 and 1.6(as of this posting date)

which will automatically download the 1.4 if you have a different version of jQuery. And yes the jQuery UI will work in 1.7 thats what I am using.

Now let’s go to the UI elements, it’s a simple <ul> and <li>’s with id’s so we can refer to them easily. You will also notice that we will be looping to all the UserPreferenceDetails to show that on the <li>element. The span will show the image and there will be a checkbox if you want to enable/disable the list item. There will be also button for saving the users preference.

@model UserPreferenceViewModel
@using YourNamespace;
@{
    Layout = null;
}

<div id="modify-view-command">
    <p class="message information">Drag the columns up and down according to the order you want, you can choose the items you want to enable/disable by ticking the check box</p>
    <button id="save-view-preferences" class="t-button">
        Save Changes</button>
</div>

<div id="modify-view-elements">
    <ul id="sortable">
        @foreach (UserPreferenceDetailViewModel m in Model.UserPreferenceDetails)
        {
            <li><span id="sort-updown"></span>
                @Html.CheckBox("chkEnabled" + m.Id, m.IsEnabled, new { @class = "EnabledFlag" })
                @m.Description
                @Html.Hidden("hdnId-" + m.Id, m.Id, new { @class = "ItemId" })
            </li>
        }
    </ul>
</div>

Finally the jQuery that makes everything happen, here you now refer to the sortable element which is our <li>,this is what makes it happen when you apply a sortable() method on it. You will also notice that we are passing an array to our Action / Controller and you need to make sure the names are exactly the same as your View Model in this case the UserPreferenceDetail, MVC will automatically then map this so you don’t need to interpret the data, all you need is to pass it as a JSON String. Also on that array you will pass the index which is the sorted index value of the list, this will take care of your Sorting Order value.

<script type="text/javascript">

$(function () {
    $("#sortable").sortable();
    $("#sortable").disableSelection();
    $("#save-view-preferences").click(saveUserPreferenceDetails);
});

function saveUserPreferenceDetails() {
    var userViewPreferenceDetails = new Array();
    $("#sortable li").each(function (index) {
        var userPreferenceItem = new Object();
        userPreferenceItem.Id = $(this).find($(".ItemId")).val();
        userPreferenceItem.IsEnabled = $(this).find($(".EnabledFlag")).attr("checked") == "checked" ? true : false;
        userPreferenceItem.ColumnOrder = index;
        userPreferenceDetails.push(userPreferenceItem);
    });
    $.ajax({
        type: 'POST',
        url: '/YourActionName/SaveUserViewPreferenceDetails',
        data: JSON.stringify(userPreferenceDetails),
        dataType: 'json',
        contentType: 'application/json; charset=utf-8',
        success: function () {
            window.location.replace("http://whereyouwantogoafter.com");
        }
    });
}

</script>

Now for the Actions. InitializeModifyView is a simple as popualting the view model and SaveUserPreferenceDetails takes care of the saving. And if you notice it accepts a list of UserPreferenceDetailViewModel and thats the one that we created as an array and stringify from our jQuery code.

[HttpPost]
public JsonResult InitializeModifyView()
{
    var viewModel = yourQuery.PopulateViewModel();

    return Json(new { viewHtml = RenderRazorViewToString("SortingOptions", viewModel) }); 
}

public virtual void SaveUserViewPreferenceDetails(List<UserPreferenceDetailViewModel> savedItems)
{
    foreach (var item in savedItems)
    {
        yourTasks.SaveUserPreferenceDetail(item);
    }
}

Easy enough? well imagine life without jquery!

How to render MVC View on a Modal Popup Window

You might be wondering how to place an MVC View easily on a pop-up window like the image above that’s why you are in this page now?

Well you a bit lucky as I will tell you how easy is this to execute, but first of all let me tell you that I am using the Window Control from Telerik Extensions for ASP.NET MVC to make my life easy and not re-invent the wheel and if you don’t have problems with that then read ahead.

Now lets start.  Lets pretend we want an application to set up a new client Account and what we want is when a user clicks a link create account it will pop up a window for account creation.  So like any other MVC Application you need your Model which in this case we will call “AccountSetupViewModel” which is a model for setting up accounts, a View for the pop up which we can call “NewAccount.cshtml” and Controller which we can call “SetupController”.  We also need some JavaScript file to separate our JavaScript commands to others for a cleaner implementation.

Lets first make our ViewModel in AccountSetupViewModel.cs under Controllers -> ViewModels -> Setup folder, we will make it simple so it will only contain AccountCode and AccountName

public class AccountSetupViewModel
{
    [Required]
    public string AccountCode { getset; }

    [Required]
    public string AccountName { getset; }

}

Now lets create a query to execute with firstName and lastName as a parameter and name it as GetAccountViewModel which we will place in  AccountSetupQuery.cs under Controllers -> Queries -> Setup, you can also create an interface for it if you wish.  This method will combine the firstName and lastName and sets the AccountName viewModel.

public AccountSetupViewModel GetAccountViewModel(string firstName, string lastName)
{
    var viewModel = new AccountSetupViewModel();

    viewModel.AccountCode = "RSM";
    viewModel.AccountName = firstName + " " + lastName;

    return viewModel;
}

Now lets create our Controller called SetupController.cs just in the Controllers directory and create a method called “GetNewAccountViewHtml”, this will be the method that will output a JsonResult to render our view

[HttpPost]
public JsonResult GetNewAccountViewHtml(string firstName, string lastName)
{
    string viewHtml = string.Empty;
    var viewModel = accountSetupQuery.GetAccountViewModel(firstName, lastName);

    viewHtml = RenderRazorViewToString("NewAccount", viewModel);

    var hashtable = new Hashtable();
    hashtable["viewHtml"] = viewHtml;

    return Json(hashtable);
}

You notice we have a method called RenderRazorViewToString, like how it’s called its purpose is to Render the MVC Razor View to string.   We will be using the MVC engine to render the view model as HTML so we can easily place it on the pop-up window.  You can place it in the controller but best if there is a separate class for this as you will  definitely reuse this a lot.

private string RenderRazorViewToString(string viewName, object model)
{
    ViewData.Model = model;

    using (var stringWriter = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, stringWriter);
        viewResult.View.Render(viewContext, stringWriter);
        viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);

        return stringWriter.GetStringBuilder().ToString();
    }
}

Next lets create a view and call it “NewAccount.cshtml” and place it in Views -> Setup, this is the view that we will be using on the pop-up window.

@model CI5.Web.Mvc.Controllers.ViewModels.Setup.AccountSetupViewModel
@using Telerik.Web.Mvc.UI;
@{
    Layout = null;
}

This is an MVC view in a pop up <br />
Account Code : @Model.AccountCode <br />
Account Name : @Model.AccountName

Now you have all of  the contents you need for that pop-up, now lets create a view to call the pop-up we can use “Default.cshtml” under View -> Setup Folder in this instance.  On your view it will be simple as registering JavaScript on an a link to pop up the window so put this on your view.

<li class="newclient"><a href="#newClient" title="" onclick="javascript:AccountSetupForm.displayPopUpWindow('Raymund', 'Macaalay');">New Client</a></li>

Then using a javascript file that initializes on load of the “_SiteLayout.cshtml

<script src="@Url.Content("~/Scripts/AccountSetup.js")" type="text/javascript"></script>

which we call “AccountSetup.js” located in Scripts folder we will create a function trigger the Telerik window pop up.

var AccountSetupForm = (function () {
    return {
        init: function () {
        },
        displayPopUpWindow: function (firstName, lastName) {

            var postData = {
                firstName: firstName,
                lastName: lastName
            };

            $.post("/Setup/GetNewAccountViewHtml", postData, function (data) {
                $.telerik.window.create({
                    title: "Sample Window",
                    html: unescape(data.viewHtml),
                    modal: true,
                    resizable: false,
                    visible: false,
                    width: 500,
                    height: 200
                })
            .data('tWindow').center().open();
            });
        }
    };

})();

$(document).ready(function () {
    AccountSetupForm.init();
});

So for a full view on how this is structured please refer to the image below.  I highlighted what was used on the codes above.

Simple Implementation of MVC Cascading Ajax Drop Down

Don’t you miss developing the traditional way? where you can just use Update Panels and Auto Post backs, it was so easy to develop the UI before where you can see it in the design view and interact with the objects in a rich manner. But that was the past, now we have MVC where its presents us a different way of developing things the proper way.  If you are starting to use MVC like me then  you might be thinking how do I make cascading drop downs without the Auto Post back and Update Panels then read further as this is how I implemented it in the most easiest way.

Lets start! Lets say you have the following structure below.

You need to have models for each of them

Now you need a controller to perform all of the operations, Lets call it CascadingDropDown.cs, we need 3 Actions Results one for the Index which will Load the page and populate the Category DropDown, Select Category to populate Sub Category Drop Down and Select Sub Category to populate Products Drop Down.  If you notice the name might be confusing that is because it is named after an Action Result not action to perform, which means what Action was invoked for this to happen.  Below is a sample code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    using Models;

    public class CascadingDropDownController : Controller
    {
        public ActionResult Index()
        {
            ProductCatalog productCatalog = new ProductCatalog();
            productCatalog.Categories = ProductCatalog.GetCategories();

            return View(productCatalog);
        }

        [HttpPost]
        public ActionResult SelectCategory(int? selectedCategoryId)
        {
            ProductCatalog productCatalog = new ProductCatalog();
            productCatalog.SubCategories = new List<SubCategory>();

            if (selectedCategoryId.HasValue)
            {
                productCatalog.SubCategories = (from s in ProductCatalog.GetSubCategories()
                                                where s.CategoryId == selectedCategoryId
                                                orderby s.Name
                                                select s).ToList();
            }

            return PartialView("SubCategoriesUserControl", productCatalog);

        }

        [HttpPost]
        public ActionResult SelectSubCategory(int? selectedSubCategoryId)
        {
            ProductCatalog productCatalog = new ProductCatalog();
            productCatalog.Products = new List<Product>();

            if (selectedSubCategoryId.HasValue)
            {
                productCatalog.Products = (from s in ProductCatalog.GetProducts()
                                           where s.SubCategoryId == selectedSubCategoryId
                                           orderby s.Name
                                           select s).ToList();
            }

            return PartialView("ProductsUserControl", productCatalog);

        }
    }
}

Take note we use partial view on the SelectCategory and SelectSubCategory as we will send a partial view to the response.  While on the Index we need the full rendering to the response.  Now you have your controller we need to create those views.  We need 1 view for Index and 3 partial views for the 3 dropdowns.

Now lets create the Index view, all you have to do is to right-click on the Index method on your controller then you can start coding.  This view will display all of the dropdowns.

Index.cshtml

@model MvcApplication1.Models.ProductCatalog
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<table cellpadding="0" cellspacing="4" border="0">
    <tr>
        <td>Category&nbsp;</td>
        <td>&nbsp;:</td>
        <td>@Html.Partial("CategoriesUserControl", Model)</td>
    </tr>
    <tr>
        <td>Sub - Category&nbsp;</td>
        <td>&nbsp;:</td>
        <td><div id="SubCategories">@Html.Partial("SubCategoriesUserControl", Model)</div></td>
    </tr>
    <tr>
        <td>Products&nbsp;</td>
        <td>&nbsp;:</td>
        <td><div id="Products">@Html.Partial("ProductsUserControl", Model)</div></td>
    </tr>
</table>

Now lets create partial views and for those who does not know how just right-click on the parent folder where your Index view is and Select Add -> View then tick the “create as a partial view”.

Now for the codes:

CategoriesUserControl.cshtml

@model MvcApplication1.Models.ProductCatalog
@using (Ajax.BeginForm("SelectCategory""CascadingDropDown"new AjaxOptions { UpdateTargetId = "SubCategories" }))
{ 
    @Html.DropDownListFor(
            m => m.SelectedCategoryId,
            new SelectList(Model.Categories, "Id""Name"),
           string.Empty
        )
}
<script type="text/javascript">
    $('#SelectedCategoryId').change(function () {
        $(this).parents('form').submit();
    });
</script>

SubCategoriesUserControl.cshtml

@model MvcApplication1.Models.ProductCatalog
@if (Model.SubCategories != null && Model.SubCategories.Count() > 0)
{
    using (Ajax.BeginForm("SelectSubCategory""CascadingDropDown"new AjaxOptions { UpdateTargetId = "Products" }))
    { 
    @Html.HiddenFor(m => m.SelectedCategoryId)
    @Html.DropDownListFor(
            m => m.SelectedSubCategoryId,
            new SelectList(Model.SubCategories, "Id""Name"),
            string.Empty
            )
    }
}
<script type="text/javascript">
    $('#SelectedSubCategoryId').change(function () {
        $(this).parents('form').submit();
    });   
</script>

ProductsUserControl.cshtml

@model MvcApplication1.Models.ProductCatalog
@if (Model.Products != null && Model.Products.Count() > 0)
{
    @Html.DropDownList(
        "Products",
            new SelectList(Model.Products, "Id""Name"),
            string.Empty
            )
}

If you notice instead of Html.BeginForm we use Ajax.BeginForm using the parameters below.

  • actionName will be name of your method in your controller
  • controllerName will be theame of your controller
  • AjaxOptions as the name says AjaxOptions, at this point we only need the UpdateTargetId which we defined as <div> in the index.cshtml file.

If you notice there are jQuery scripts below CategoriesUserControl.cshtml and SubCategoriesUserControl.cshtml, they will handle the auto postback on the partially rendered HTML.

Also on your _Layout.cshtml add this on the header if you haven’t done it yet

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

Note : If you are using ASP.Net MVC3 then by default it uses unobtrusive jquery so you need to remove all MicrosoftAjax scripts and use only those two above otherwise the onchange event will open a new page instead of partially rendering it.

Note : If you are using Telerik MVC Controls then you only need the unobtrusive ajax the other script is already referenced by default if you are using Telerik.

Make sure also on your web.config you have the following lines.

<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>

At this point you have done everything you need and your ready to roll.

If you need the full source code you can download it here (http://www.codeproject.com/KB/Blogs/258172/CascadingDropDownMVC.zip).

Using jQuery with Sharepoint 2010

Several days back I was asked a question in this blog relating to using JavaScript in a Sharepoint, it ended up with multiple questions and one was using jQuery in Sharepoint, as usual I searched my own blog for answers becuase that is one of my main reasons having this is to store information like this that I may need in the future, a reference for what I had done which I may be able to use again someday.  Anyways as I was searching and I found out that I havent posted this article, I thought I already have made one before but no, so this will be my chance.

So how would you use jQuery in Sharepoint? You can follow this easy steps and you will be ready to go in minutes

1. Download jQuery

Download latest verion of jQuery here  http://docs.jquery.com/Downloading_jQuery

2. Save it in an accessible location in Sharepoint

Using Sharepoint Designer I added a Folder called “Scripts” inside the “Style Library” of the root level of the site. I then added the latest jQuery File into that folder.

3. Place a reference on that jQuery from your code

The best place for this will be your master page so that it can be used anywhere in your site, so open the master page (by default SharePoint 2010 uses v4.master) you are using in Advanced mode then add your javascript reference like such.

<script src="/Style%20Library/Scripts/jquery-1.6.2.js" type="text/javascript"></script>

you place that inside the <head> tag

Save that master page and you are ready to go

4. Test if its working

If you are using FireFox open up firebug or if you are using IE you can press F12.  Go to the script tab to check whether your javascript reference is there, then on the console window try wether that reference works by typing something like this.

$("#MSO_ContentTable").text("Welcome to Sharepoint jQuery")

you should get a result something like this

Follow

Get every new post delivered to your Inbox.

Join 773 other followers