TheChaseMan's Frenetic SoapBox

Always looking for better ways to do things...

Sunday, February 13, 2011 #

Once you get past all of the cool MVC simple-mode demos which always have things like typing dates into textboxes, no Ajax, etc...you start to dig into what I personally find to be the most work with MVC: enriching the UI. WebForms can kind of spoil us with things like update panels, Ajax control toolkit, or 3rd party solutions such as Telerik Ajax Controls. I've burned many hours with UI enhancements using jQuery. Yes, this is a confession not a complaint. For example, autocomplete is a fairly simple concept; however, this also involves changing up style sheets, or adding additional behavior to examples you might find on Google such as what I've illustrated below: filtering the autocomplete list by employee title.

 I'm used to cranking out WebForms apps very quickly, whereas MVC has been a bit more tedious. So I thought I'd post some things I worked on yesterday that will hopefully save other people some time. jQuery Autocomplete and Dropdownlist replacements are very nice plugins. Here's some code that leverages both of them.


The View

@model AdventureWorks.Common.SearchEmployeeViewModel
<h2>Search Employees</h2>
<
link href="@Url.Content("~/Content/jquery.autocomplete.css")" rel="stylesheet" type="text/css" />
<
script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<
script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<
link href="@Url.Content("~/Content/jquery.dropdownReplacement-0.5.css")" rel="stylesheet" type="text/css" />
<
script src="@Url.Content("~/Scripts/jquery.scrollTo-1.4.2.js")" type="text/javascript"></script>
<
script src="@Url.Content("~/Scripts/jquery.bgiframe.min.js")" type="text/javascript"></script>
<
script src="@Url.Content("~/Scripts/jquery.dropdownReplacement-0.5.js")" type="text/javascript"></script>
<
script src="@Url.Content("~/Scripts/jquery.autocomplete.min.js")" type="text/javascript"></script>

<!-- this should go in its own js file... -->
<script type="text/javascript">
   
$(document).ready(function () {
        $(
"#Title").dropdownReplacement({ selectCssWidth: 600 });
        $(
"input#LastName").autocomplete('@Url.Action("Find")', { cacheLength: 0, delay: 500, extraParams: { title: function () { return $("#Title").val(); } } });
    });
</script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(
true)
   
<fieldset>
       
<legend>SearchEmployeeViewModel</legend>
           
<div class="editor-label">@Html.LabelFor(model => model.LastName)</div>
           
<div class="editor-field">
               
@Html.TextBoxFor(model => model.LastName, htmlAttributes: new { style = "width: 300px " })
                @Html.ValidationMessageFor(model => model.LastName)
           
</div>
           
<div class="editor-label">@Html.LabelFor(model => model.Title)</div>
           
<div class="editor-field">
               
@Html.DropDownListFor(model => model.Title, new SelectList(Model.TitleList))
                @Html.ValidationMessageFor(model => model.Title)
           
</div>
           
<p><input type="submit" value="Search" /></p>
   
</fieldset>
}


The Controller

using System.Web.Mvc;
using AdventureWorks.BusinessInterface;
using AdventureWorks.Common;

namespace MvcDemo.Controllers {
   
public class EmployeeController : Controller {
       
EmployeeBusinessInterface _employeeBusinessInterface = new EmployeeBusinessInterface();

       
public ActionResult Search() {
           
var model = new SearchEmployeeViewModel();
            model.TitleList = _employeeBusinessInterface.GetTitleList();
           
return View(model);
        }

        public ActionResult Find(string q, int limit, string title) {
           
string[] lastNames = _employeeBusinessInterface.FindLastNames(q, title, limit);
           
return Content(string.Join("\n", lastNames));
        }
    }
}


The Model

using System.Collections.Generic;
using System.Linq;

namespace AdventureWorks.BusinessInterface {
   
public class EmployeeBusinessInterface {
        DataAccess.
AdventureWorksEntities _adventureWorksEntities = new DataAccess.AdventureWorksEntities();
       
public string[] FindLastNames(string q, string title, int limit) {
           
return _adventureWorksEntities.Employees.Where(e => e.Title == title && e.Contact.LastName.StartsWith(q))
                                                    .Select(e => e.Contact.LastName)
                                                    .Distinct()
                                                    .Take(limit)
                                                    .ToArray();
        }

        public List<string> GetTitleList() {
           
return _adventureWorksEntities.Employees.Select(e => e.Title).Distinct().OrderBy(name => name).ToList();
        }
    }
}


The ViewModel

using System.Collections.Generic;

namespace AdventureWorks.Common {
   
public class SearchEmployeeViewModel {
       
public string LastName { get; set; }
       
public string FirstName { get; set; }
        
public string Title { get; set; }
       
public List<string> TitleList { get; set; }
    }
}

So now we have a really nice dropdown where you can set the height and scroll unlike a regular HTML Select control.


And our Autocomplete assists the user with the search.


As an aside, I'm starting to change my mind about needing a Common, BI, DAL, but I definitely think the ViewModel and MVVM pattern is the way to go using only strongly-typed Views. I'm also playing around with putting some of the current logic for Common, BI into the ViewModel class with the goal of keeping the Controller code as clean as possible. This means the ViewModel class will be encapsulating the logic necessary to call into the Entity Framework (in this case) classes to fill itself with data, handle validation with annotations, etc eliminating the need to have a bunch of library projects. In this example the Controller calls to the BI to fill the ViewModel class with data, the jQuery Autocomplete Controller method basically does the same thing to get a list of names. Seems to work pretty well so far.

posted @ 10:02 AM | Feedback (6)