Day 12: Implement Search Using Inline Forms and AJAX

This is an installment in a 30 day series on Bootstrap and the MVC Framework. To see more, check out Day 0 for an index.

It had to happen; at some point we were going to need to let our users enter some data! Well, that time has come, so let’s start by adding a handy-dandy search form to our Person page.

Forms will need a few cues on how to properly render themselves and take part in Booststrap’s style party of awesome. Let’s get a search form going and start filtering our results.

There are actually a few styles of forms that you can get going. A standard styling gives you label-over-control type layout, horizontal forms give you label-beside-control layout and inline styling gives you controls without labels side-by-each continuously in the row. That’s the one we’ll go with to generate our simple search form:

image

Create a partial view under the Views\Person folder. You can make it an empty one, and call it _PersonSearchForm.cshtml.  Paste in the following code:

<hr/>
<div class=”container”>
<div class=”pull-right”>
<form class=”form-inline” role=”form”>
<div class=”form-group”>
<label class=”sr-only” for=”search-text”>Email address</label>
<input type=”text” class=”form-control” id=”search-text” placeholder=”Enter Search Text”>
</div>
<button type=”button” class=”btn btn-success” id=”search-btn”>Search</button>
</form>
</div>
</div>

We have a few things going on here:

  • The HR tag is just a style thing and not required. It makes the search form look more “balanced” vertically on the form.
  • There is a DIV that acts as a container, keeping our content separate from the rest of the page. It is a wrapper for a “pull-right” styled DIV that moves our search bar over to the right hand side of the page.
  • The form is given a class of “form-inline”. This is the first important part of making our controls and labels show up correctly.
  • Inside that FORM element we have a DIV with a class of “form-group”. This lets Bootstrap know (or rather, the browser through Bootstrap’s CSS) that these controls are related in some way. Specifically, we have a label for an input.
  • Because this is an “inline” form, we’re using the “sr-only” class on the label to eat the display and keep the visuals tidy. “SR” stands for “Screen Reader”; this is an accessibility tag.

When the form data comes calling, we’re going to need someone to answer the phone on the controller side.

The Controller’s Search Method

In your PersonController class, add the following public method:

public ActionResult SearchPeople(string searchText)
{
var term = searchText.ToLower();
var result = _people
.Where(p =>
p.FirstName.ToLower().Contains(term) ||
p.LastName.ToLower().Contains(term)
);

<span class="kwrd">return</span> PartialView(<span class="str">"_SearchPeople"</span>, result);

}

This accepts a string parameter and finds any matches where FirstName or LastName match what the user entered, then it returns via a call to PartialView to generate the result. We’re using a partial because we don’t want to have to reload the entire page each time the user clicks the search button.

A quick note: the astute reader will note that that this simplified method of search won’t pass the Turkey Test. If you work with different cultures, this is a great side-read, and you’ll need to approach the problem in a different way.

Later in the series we’ll cover best practices around accessing and filtering data, but this approach will suffice for now, using the local static collection of data that we built yesterday.

When we call PartialView the MVC Framework doesn’t attempt to resolve a layout, so we just get the meat that lives in the cshtml file itself and as processed by the view engine. Rendered via the controller, we have to pass in our data as a parameter. If you were rendering a partial via a View (with an HtmlHelper) the partial could ‘inherit’ the parent page’s model and use that to render your content. The partial we need should be located in Views\Person\ and called _SearchPeople.cshtml and the code looks like the following:

@model IEnumerable<SimpleSite.Models.Person>

@Html.DisplayForModel(Model)

@if (!Model.Any())
{
<h3>Sorry!</h3>
<p>Looks like there’s no results for that person.</p>
}

Partial in place, controller set up to search…off to the core view.

Updating the View

Back in Views\Person\Index.cshtml there isn’t a lot we have to do to get our form to display. Update the code so it reads as follows:

@model IEnumerable<SimpleSite.Models.Person>

@{ Html.RenderPartial(“_PersonSearchForm”); }

<div id=”people-data”>
@Html.DisplayForModel(Model)
</div>

I have updated the view from yesterday by wrapping the data with a DIV that acts as a container. We’ll use that later when we AJAX up the page. Press CTRL+F5 to see the updated view in action.

image

This handles the display aspect, but we need some script in place to handle the button click and make the AJAX call, finally updating the page with the search results. Add a script section to the bottom of the page as follows:

@section scripts
{
<script type=”text/javascript”>
$(function () {
// it’s lonely here…
});
</script>
}

Today’s Bonus Content: Sections are defined in the layout page that is used by your view. You can find them in Views\Shared_Layout.cshtml. These sections can be required or optional per your needs. The default template defines only the scripts section, but you may often wish to include something in the page header or footer. Oooo! Sounds like another blog post!

Now the script doesn’t do anything quite yet, except give us a place to land. What you see above is called a “self-executing anonymous method”, which is a good term to know if you want to sound smart around your boss. Basically, jQuery will make sure any code in this block is executed in a cross-browser friendly way after the page is finished loading.

Replace that lonely comment with the following code:

$(“#search-btn”).click(function () {
var searchTerm = $(“#search-text”).val();
$.get(“SearchPeople”, { searchText: searchTerm })
.success(function(data) {
$(“#people-data”).html(data);
});
});

If you’re not familiar with JavaScript or the patterns that jQuery uses, here’s little breakdown of what is happening:

  • A click event handler is setup for the element in the DOM that has an ID of “search-btn”.
  • The event handler is an anonymous method that invokes the jQuery get() method, passing in the action that we’re targeting and the data that we’re trying to pass in.
  • The data we’re passing in is read from the form and assigned to the searchText key
  • When you pass in parameters from calls like this, you need to make sure spelling and case are identical to avoid rapid hair loss. And null values.
  • The get() method follows the “promise” pattern, and you get to register a callback when the search is completed. Here, we use the success callback.
  • Our anonymous callback is invoked when the AJAX completes successfully and it updates the data container (“people-data”) with the HTML that is returned from our controller.

Try typing in some search terms from your Person\Index page.

image

Bazinga!

Next Steps

Our search form looks fine but wouldn’t be ideal for data entry. Tomorrow we’ll look at the other two variants of forms and wire up a view (and our controller) to allow users to create new people.