This is step two of applying filters to my contact list without refreshing the whole page. I will be using the partial view created in last week’s post.
I ran into a bit of trouble getting ajax working. It seems that starting in MVC 5 the jQuery plugin needed to make this form of ajax work is no longer included by default. In order to fix use NuGet and install jQuery.Unobtrusive.Ajax.
After the NuGet package is installed add a new bundle (or add to an existing bundle) for the jQuery unobtrusive files to the BundleConfig found in the App_Start folder.
bundles.Add(new ScriptBundle("~/bundles/jqueryunob").Include( "~/Scripts/jquery.unobtrusive*"));
If a new bundle was added then make sure to render the new bundle in the _Layout.cshtml file in the Views/Shared folder.
@Scripts.Render("~/bundles/jqueryunob")
The next step was to change the index action of contacts controller. The index action now needs to accept both gets and posts which can be accomplished via the AcceptVerbs attribute. The second change needed in the index action is to return a partial view instead of a view if the request is an ajax request.
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)] public ActionResult Index(string city, string search) { var cityList = new List<string>(); var cityDistinct = from c in db.Contacts orderby c.City select c.City; cityList.AddRange(cityDistinct.Distinct()); ViewBag.city = new SelectList(cityList); var contacts = from c in db.Contacts select c; if (!string.IsNullOrWhiteSpace(search)) { contacts = contacts.Where(c => c.Name.Contains(search)); } if (!string.IsNullOrWhiteSpace(city)) { contacts = contacts.Where(c => c.City == city); } if (Request.IsAjaxRequest()) { return PartialView("_ContactList", contacts); } return View(contacts); }
Reusing the index action is only one option to implement the needed changes. Another option would be to add another action to the controller that would return the partial view. In this case the common query code would be moved to a function.
The last set of changes needed are in the index view. Instead of using Html.BeginForm Ajax.BeginForm should be used. The section of the page that will be replaced via the ajax request needs to be moved to a div with an id. In the example below I added a div with the id of contactList. It is important that the div id match the UpdateTargetId in AjaxOptions.
@model IEnumerable<Contacts.Models.Contact> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @using (Ajax.BeginForm("Index", "Contacts", new AjaxOptions { UpdateTargetId = "contactList" })) { <p> City: @Html.DropDownList("city", ViewBag.city as SelectList, "All", new {@class = "city", onchange = "$(this.form).submit();"}) Name: @Html.TextBox("Search") <input type="submit" value="Filter"/> </p> } </p> <div id="contactList"> @{ Html.RenderPartial("_ContactList"); } </div>
One thing to make special note of is the that the onchange for the city drop down list has change from “this.form.submit();” to “$(this.form).submit();”. Without this change the request will not come through as an ajax request and the full page will refresh instead of just the contact list section. I wasted a lot of time trying to track down why a full page request was happening and the issue ended up being the way that the drop down list was submitting the form.