议题
当你在搜索某个具体事情的时候,可能输入会比较复杂(或需要很长时间),正当用户在试图输入正确的内容时,搜索并显示可能是用户输入的内容,让用户查找输入和搜索变的更加容易。
解决方案
使用JQuery自动完成插件,修改图书列表页面上的搜索功能,使用户在输入搜索时,立即显示搜索结果。
讨论
自动完成插件默认不包含在MVC项目的JQuery基本包中,所以,要做的第一件事是访问http://jquery.com去下载插件。这个插件包含两个必需文件,一个Javascript文件和一个CSS文件。将新下载的文件放置到“Scripts”文件夹中。将CSS文件放置到“Content”文件夹中。
这个秘诀也会介绍关于视图渲染部分的内容。为了提高访问性能,在全局共享视图中添加Ajax和不唐突Ajax文件以及名为“main”的CSS文件,以避免每次加载不同视图时都就要加载这些文件。并在共享视图里面添加RenderSection()方法。这将允许每个视图根据具体情况在<Head>标签中添加特需的Javascript或CSS文件。
修改Views/Shared/_Layout.cshtml,添加新方法RenderSection():
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"
type="text/javascript"></script>
@RenderSection("JavaScriptAndCSS", required: false)
</head>
<body>
<div class="page">
<div id="header">
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
@Html.Partial("_LogOnPartial")
[ @Html.ActionLink("English", "ChangeLanguage",
"Home", new { language = "en" }, null) ]
[ @Html.ActionLink("Français", "ChangeLanguage",
"Home", new { language = "fr" }, null) ]
</div>
<div id="menucontainer">
<ul id="menu">
<li>@Html.ActionLink("Home",
"Index", "Home")</li>
<li>@Html.ActionLink("About",
"About", "Home")</li>
</ul>
</div>
</div>
<div id="main">
@RenderBody()
</div>
<div id="footer">
</div>
</div>
</body>
</html>
只有主要的CSS文件以及jQuery的核心文件在共享文件中加载,因为CSS是每一页都需要的,而jQuery会在绝大多数的页面中被使用。但是,我们新引用的Ajax的jQuery并不是每页都必须要求的。
现在我们使用自动完成插件有两种方法:
- 将数据发送到Javascript中进行搜索;
- 在用户输入时通过Ajax获取结果。
以我使用的感觉来说,我发现解决方案1的自动完成方法要快很多,因为它并不需要要每次都从数据库中请求数据。然而这个解决方案限制于数据量的多少,当搜索内容过于庞大时,用户页面加载以及调用Javascript方法时都会导致加载用户计算机运行变慢或等待时间过长。经过一些错误跟踪实验,我大致可以确定这个神奇的数字大约是40000条数据。如果结果数量超过这个值就需要使用2号解决方案,否则,始终坚持方案1,因为搜索时瞬间的体验,尽量不要有轻微的延迟。
在这个例子中,将要搜索的书籍没有超过40000上限,所以我们会选择使用方案1。现在必须对BooksController进行修改,创建一个存储书名列表的ViewBag变量。自动完成时需要有一个Javascript的数组,将这些书籍名称使用“|”字符进行分割。然后在视图中,这个字符串将会被Javascript的Split()方法转换为数组对象。修改了这个功能后,当用户完全的输入了想搜索的结果,将会返回一本书的匹配结果,用户点击该结果将会自动重定向到这本书的详情页面。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Dynamic;
using System.Web;
using System.Web.Mvc;
using MvcApplication4.Models;
using MvcApplication4.Utils;
using PagedList;
namespace MvcApplication4.Controllers
{
public class BooksController : Controller
{
private BookDBContext db = new BookDBContext();
//
// GET: /Books/
public ActionResult Index(string sortOrder,
string filter, string Keyword, int page = 1)
{
#region ViewBag Resources
...
#endregion
#region ViewBag Sort Params
...
#endregion
var books = from b in db.Books select b;
#region Keyword Search
if (!String.IsNullOrEmpty(Keyword))
{
books = books.Where(b => b.Title.ToUpper().
Contains(Keyword.ToUpper()) ||
b.Author.ToUpper().Contains(
Keyword.ToUpper()));
// Should we redirect because of only one result?
if (books.Count() == 1)
{
Book book = books.First();
return RedirectToAction("Details",
new { id = book.ID });
}
}
ViewBag.CurrentKeyword =
String.IsNullOrEmpty(Keyword) ? "" : Keyword;
#endregion
#region Filter switch
...
#endregion
books = books.OrderBy(sortOrder);
int maxRecords = 1;
int currentPage = page - 1;
// Get all book titles
ViewBag.BookTitles = FormatBooksForAutocomplete();
return View(books.ToPagedList(currentPage,
maxRecords));
}
private string FormatBooksForAutocomplete()
{
string bookTitles = String.Empty;
var books = from b in db.Books select b;
foreach (Book book in books)
{
if (bookTitles.Length > 0)
{
bookTitles += "|";
}
bookTitles += book.Title;
}
return bookTitles;
}
...
}
}
最后,修改Books/Index视图,添加jQuery自动完成的功能。首先,是使用@section包含必要的JavaScript和CSS文件的引用。然后,将以前创建的搜索文本框的ID修改为“wordSearch”。最后,将自动完成的代码添加到自动完成搜索框的下面。这个JavaScript是有意放置在视图的底部的,根据返回结果数量的不同,这个JavaScript的“代码块(Blocking)”可能会给用户的计算机带来很多处理负载而不至于影响视图呈现。
@model PagedList.IPagedList<MvcApplication4.Models.Book>
@if (IsAjax)
{
Layout = null;
}
@section JavascriptAndCSS {
<link rel="stylesheet" href="
@Url.Content("~/Content/jquery.autocomplete.css")"
type="text/css" />
<script src="@Url.Content(
"~/Scripts/jquery.unobtrusive-ajax.min.js")"
type="text/javascript"></script>
<script type="text/javascript" src="@Url.Content(
"~/Scripts/jquery.autocomplete.js")"></script>
}
...
@using (Html.BeginForm())
{
@:Search: @Html.TextBox("Keyword",
(string)ViewBag.CurrentKeyword,
new { id = "KeywordSearch" })
<input type="submit" value="Search" />
}
...
<script type="text/javascript">
$(document).ready(function () {
var data = "@ViewBag.BookTitles".split("|");
$("#KeywordSearch").autocomplete(data);
});
</script>
为了实现第二种方案,Ajax搜索,通过URL传递搜索参数,而不是通过自动完成方法访问数组。该URL将包含一个查询字符串变量“q”。这个变量将包含用户输入的搜索值。这将用来执行搜索与书籍的信息进行匹配,并将返回的内容使用字符分隔符进行分隔。jQuery文档中有完整的例子,以及其他相关的例子(可能包含返回书籍的封面的缩略图等)。
参考