(18)Auto Complete(自动完成)

问题

当你查找一些特殊的东西,当你输入准确的词时,找到它可能是困难的(或者很耗时)。在输入的时候展示出结果(自动完成),使查找变得更简单。

解决方案

使用JQuery自动完成插件,更新现有图书列表页面上的搜索,当用户键入的时候立即显示结果。

讨论

自动完成插件是不会象jQuery基本库一样自动包含在MVC项目中的,所以需要做的第一件事就是的是下载插件 访问http://jquery.com/。两个主要的文件是必需的:JavaScript文件和CSS文件。把新下载的javascript文件放到你MVC应用程序的script 文件夹下。CSS文件可以直接添加到您的content目录。

这个配方也将介绍在view中使用 rendering sections。在shared文件夹下layout中自动添加了2个javascript文件和1个css文件。这些是Ajax和不唐突的Ajax和网站主css文件。每次加载的内容越多,页面视图加载越慢。与其在每个页面都去包含可能不必要的javascript和css 文件,不如在layout中添加一个新的RenderSection()。这允许特别的view在<head>标签去加载特别的 javascript或css,但不是每页都添加他们。

下边是一个更新后的Views/Shared/_Layout.cshtml,他使用了一个新的RenderSection()。

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
 
<html>
<head>
    <title>_Mobile</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>
 
    <script type="text/javascript">
        $(document).ready(function () {
            if (window.innerWidth <= 480) {
                $("link[rel=stylesheet]").attr({ href: "@Url.Content("~/Content/jquery.mobile-1.0b1.min.css")" });
            }
        });
    </script>
 
    @RenderSection("JavaScriptAndCSS", required: false)
</head>
<body>
    <div class="page" data-role="page">
        <div id="header" data-role="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay" class="ui-bar">
                @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" class="ui-bar">
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home", null, new Dictionary<string, object> {{ "data-role", "button" }})</li>
                    <li>@Html.ActionLink("About", "About", "Home", null, new Dictionary<string, object> { { "data-role", "button" }})</li>
                </ul>
            </div>
        </div>
        <div id="main" data-role="content">
            @RenderBody()
        </div>
        <div id="footer" data-role="footer">
        </div>
    </div>
</body>
</html>
 

主要的CSS文件和核心的JQuery文件被留下来了,因为css在每个也都需要,并且绝大多数网页也需要JQuery。然而新的JQuery文件和不唐突的AJAX不是每个页面都需要的。

现在,有两种方式使用Autocomplete 插件:

  1. 在javascript中设置要搜索的数据。
  2. 当用户输入时通过ajax检索。

在我使用这个插件的经验看来,我发现使用解决方案1时自动完成更快。因为它并不需要每次从数据库中请求数据。然而,使用这种解决方案的限制:只有这么多字符,可传递到function中,大量的JavaScript可能会导致 用户的计算机上页面加载缓慢。经过一些试验和错误,我已经确定神奇的数字是大约40,000个结果。如果结果数量超过此,最好使用选项2;否则,始终坚持,因为搜索选项1是瞬时,而不是有轻微的延迟。

在这个例子中,将搜索书籍,我们没有超过40000,所以将使用选项1。BooksController现在必须更新,以设置ViewBag为book title。自动完成功能需要支持一个JavaScript数组的支持,所以书将管道(|)分开。然后在view中,书将被转换到一个数组,使用 JavaScript的split()函数。当用户完成键入他们的结果,他们应该有选择完全匹配标题,因此 这个函数将被更新。如果只有1本书返回并且用户执行了搜索,它会自动重定向到本书详细介绍页面。

我们要在bookcontroller 中更新Index Action 并添加一个私有方法名为:FormatBooksForAutocomplete。

代码如下:

展开查看代码
双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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 MvcApplication.Models;
using MvcApplication.Utils;
using PagedList;
 
namespace MvcApplication.Controllers
{
    public class BooksController : Controller
    {
        private BookDBContext db = new BookDBContext();
 
        //
        // GET: /Books/
        [OutputCache(Duration = Int32.MaxValue, SqlDependency = "MvcApplication.Models.BookDBContext:books", VaryByParam = "sortOrder;filter;page")]
        public ActionResult Index(string sortOrder, string filter, string Keyword, int page = 1)
        {
            #region ViewBag Resources
            ViewBag.Title = Resources.Resource1.BookIndexTitle;
            ViewBag.CreateLink = Resources.Resource1.CreateLink;
            ViewBag.TitleDisplay = Resources.Resource1.TitleDisplay;
            ViewBag.IsbnDisplay = Resources.Resource1.IsbnDisplay;
            ViewBag.SummaryDisplay = Resources.Resource1.SummaryDisplay;
            ViewBag.AuthorDisplay = Resources.Resource1.AuthorDisplay;
            ViewBag.ThumbnailDisplay = Resources.Resource1.ThumbnailDisplay;
            ViewBag.PriceDisplay = Resources.Resource1.PriceDisplay;
            ViewBag.PublishedDisplay = Resources.Resource1.PublishedDisplay;
            ViewBag.EditLink = Resources.Resource1.EditLink;
            ViewBag.DetailsLink = Resources.Resource1.DetailsLink;
            ViewBag.DeleteLink = Resources.Resource1.DeleteLink;
            #endregion
 
            #region ViewBag Sort Params
            ViewBag.TitleSortParam = (sortOrder == "Title") ? "Title desc" : "Title";
            ViewBag.IsbnSortParam = (sortOrder == "Isbn") ? "Isbn desc" : "Isbn";
            ViewBag.AuthorSortParam = (sortOrder == "Author") ? "Author desc" : "Author";
            ViewBag.PriceSortParam = (sortOrder == "Price") ? "Price desc" : "Price";
            ViewBag.PublishedSortParam = (String.IsNullOrEmpty(sortOrder)) ? "Published desc" : "";
 
            // Default the sort order
            if (String.IsNullOrEmpty(sortOrder))
            {
                sortOrder = "Published desc";
            }
 
            ViewBag.CurrentSortOrder = sortOrder;
            #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
            switch (filter)
            {
                case "NewReleases":
                    var startDate = DateTime.Today.AddDays(-14);
                    books = books.Where(b => b.Published <= DateTime.Today.Date
                        && b.Published >= startDate
                    );
                    break;
 
                case "ComingSoon":
                    books = books.Where(b => b.Published > DateTime.Today.Date);
                    break;
 
                default:
                    // No filter needed
                    break;
            }
            ViewBag.CurrentFilter = String.IsNullOrEmpty(filter) ? "" : filter;
            #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;
        }
 
        //
        // GET: /Books/Details/5
 
        public ActionResult Details(int id = 0, string bookTitle = "")
        {
            Book book = db.Books.Find(id);
            return View(book);
        }
 
        //
        // GET: /Books/Create
 
        public ActionResult Create()
        {
            return View();
        }
 
        //
        // POST: /Books/Create
 
        [HttpPost]
        public ActionResult Create(Book book, HttpPostedFileBase file)
        {
            if (ModelState.IsValid)
            {
                // Upload our file
                book.Thumbnail = FileUpload.UploadFile(file);
 
                db.Books.Add(book);
                db.SaveChanges();
                return RedirectToAction("Index"); 
            }
 
            return View(book);
        }
         
        //
        // GET: /Books/Edit/5
  
        public ActionResult Edit(int id)
        {
            Book book = db.Books.Find(id);
            return View(book);
        }
 
        //
        // POST: /Books/Edit/5
 
        [HttpPost]
        public ActionResult Edit(Book book, HttpPostedFileBase file)
        {
            if (ModelState.IsValid)
            {
                // Delete old file
                FileUpload.DeleteFile(book.Thumbnail);
 
                // Upload our file
                book.Thumbnail = FileUpload.UploadFile(file);
 
                db.Entry(book).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(book);
        }
 
        //
        // GET: /Books/Delete/5
  
        public ActionResult Delete(int id)
        {
            Book book = db.Books.Find(id);
            return View(book);
        }
 
        //
        // POST: /Books/Delete/5
 
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {           
            Book book = db.Books.Find(id);
             
            // Delete old file
            FileUpload.DeleteFile(book.Thumbnail);
 
            db.Books.Remove(book);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}
 

最后book/index view需要更新去初始化jQuery的自动完成。要做的第一件事是使用@节标记,包括必要的JavaScript和CSS文件。接下来,以前创建的搜索文本框更新设置一个键的IDwordSearch。最后,JavaScript代码添加在视图的底部去在搜索文本框上建立自动完成功能。此 JavaScript是有意添加在view的底部,以确保完全呈现给用户,因为在用户的电脑上建立数据是一项工作,Javascript处理可能会“堵塞”页面加载。(译者:先呈现数据再执行javascript,js不是像传统那样放在head标签里),这取决于结果的数量。代码如下:

展开查看代码
双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@model PagedList.IPagedList<MvcApplication.Models.Book>
 
@if (IsAjax)
{
    Layout = null;
}
 
@section JavascriptAndCSS {
<link rel="stylesheet" href="@Url.Content("~/Content/jquery.autocomplete.css")" type="text/css" />
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.autocomplete.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
}
 
<h2>@MvcApplication4.Resources.Resource1.BookIndexTitle</h2>
 
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<p>
    Show:
    @if (ViewBag.CurrentFilter != "")
    {
        @Ajax.ActionLink("All", "Index", new { sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:All
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "NewReleases")
    {
        @Ajax.ActionLink("New Releases", "Index", new { filter = "NewReleases", sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:New Releases
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "ComingSoon")
    {
        @Ajax.ActionLink("Coming Soon", "Index", new { filter = "ComingSoon", sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:Coming Soon
    }
</p>
@using (Html.BeginForm())
{
    @:Search: @Html.TextBox("Keyword", (string)ViewBag.CurrentKeyword, new { id = "KeywordSearch" }) <input type="submit" value="Search" />
}
@Html.Partial("_Paging")
<table>
    <tr>
        <th>
            @Ajax.ActionLink("Title", "Index", new { sortOrder = ViewBag.TitleSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            @Ajax.ActionLink("Isbn", "Index", new { sortOrder = ViewBag.IsbnSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            Summary
        </th>
        <th>
            @Ajax.ActionLink("Author", "Index", new { sortOrder = ViewBag.AuthorSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            Thumbnail
        </th>
        <th>
            @Ajax.ActionLink("Price", "Index", new { sortOrder = ViewBag.PriceSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            @Ajax.ActionLink("Published", "Index", new { sortOrder = ViewBag.PublishedSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th></th>
    </tr>
 
@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Isbn)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Summary)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Author)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Thumbnail)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Price)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Published)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
            @Html.ActionLink("Details", "Details", new { id = item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.ID })
        </td>
    </tr>
}
 
</table>
 
@Html.Partial("_Paging")
 
<script type="text/javascript">
    $(document).ready(function () {
        var data = "@ViewBag.BookTitles".split("|");
        $("#KeywordSearch").autocomplete(data);
    });
</script>
 

为了实施选项2,一个Ajax搜索,而不是传递数据数组到自动完成函数,您可以传递一个URL。URL将需要接受查询字符串变量:q。这包含用户输入的搜索值。这将用于执行书本上包含部分匹配的搜索,并返回以分隔符分隔的字符串。JQuery文档中含有较多的这样的成品例子,也有其他的例子,去更新的输出结果(可能包括书的封面的缩略图)。

年轻不是你玩的理由,而是你奋斗的资本
原文地址:https://www.cnblogs.com/lyaxx1314/p/3603525.html