最近公司网站首页搜索框改进,需要在智能提示列表上加上支持键盘上下键移动的效果。 搞了一晚上,下面呈上纯javascript代码,没有用到jquery等其他类库。 以下仅供自己收藏,贴上来希望能起到抛砖引玉的作用。 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <style type="text/css"> ul,li{list-style-type:none;} </style> <script type="text/javascript" language="javascript"> var currentSelIndex = -1; var oldSelIndex = -1; function selectItem(keyword, event) { if (keyword == "") { document.getElementById("ulItems").style.display = "none"; return; } else { var liLength = document.getElementById("ulItems").getElementsByTagName("li").length; //获取列表数量 if ((event.keyCode == 38 || event.keyCode == 40) && document.getElementById("ulItems").style.display != "none") { if (liLength > 0) { oldSelIndex = currentSelIndex; //上移 if (event.keyCode == 38) { if (currentSelIndex == -1) { currentSelIndex = liLength - 1; } else { currentSelIndex = currentSelIndex - 1; if (currentSelIndex < 0) { currentSelIndex = liLength - 1; } } if (currentSelIndex != -1) { document.getElementById("li_" + currentSelIndex).style.backgroundColor = "#cbf3fd"; document.getElementById("txtKeyword").value = document.getElementById("li_" + currentSelIndex).innerText; } if (oldSelIndex != -1) { document.getElementById("li_" + oldSelIndex).style.backgroundColor = "#ffffff"; } } //下移 if (event.keyCode == 40) { if (currentSelIndex == liLength - 1) { currentSelIndex = 0; } else { currentSelIndex = currentSelIndex + 1; if (currentSelIndex > liLength - 1) { currentSelIndex = 0; } } if (currentSelIndex != -1) { document.getElementById("li_" + currentSelIndex).style.backgroundColor = "#cbf3fd"; document.getElementById("txtKeyword").value = document.getElementById("li_" + currentSelIndex).innerText; } if (oldSelIndex != -1) { document.getElementById("li_" + oldSelIndex).style.backgroundColor = "#ffffff"; } } } } else if (event.keyCode == 13 && document.getElementById("ulItems").style.display != "none") { if (liLength > 0) { document.getElementById("txtKeyword").value = document.getElementById("li_" + currentSelIndex).innerText; document.getElementById("ulItems").style.display = "none"; currentSelIndex = -1; oldSelIndex = -1; } } else { autoComplete(keyword); document.getElementById("ulItems").style.display = ""; currentSelIndex = -1; oldSelIndex = -1; } } } function autoComplete(keyword) { var liHtml0 = "<li id=\"li_0\">1</li><li id=\"li_1\">12</li><li id=\"li_2\">123</li><li id=\"li_3\">1234</li>"; var liHtml1 = "<li id=\"li_0\">12</li><li id=\"li_1\">123</li><li id=\"li_2\">1234</li>"; var liHtml2 = "<li id=\"li_0\">123</li><li id=\"li_1\">1234</li>"; var liHtml3 = "<li id=\"li_0\">1234</li>"; if (keyword == "1234") { document.getElementById("ulItems").innerHTML = liHtml3; } else if (keyword == "123") { document.getElementById("ulItems").innerHTML = liHtml2; } else if (keyword == "12") { document.getElementById("ulItems").innerHTML = liHtml1; } else if (keyword == "1") { document.getElementById("ulItems").innerHTML = liHtml0; } else { document.getElementById("ulItems").innerHTML = ""; } } </script> </head> <body> <input id="txtKeyword" type="text" onkeyup="selectItem(this.value, event)" style="200px;" /> <ul id="ulItems" style="display: none; border:1px solid #cecece; border-top:none; 200px; padding:2px; margin:0px;"> </ul> </body> </html>
数据库中有如下两张表,一个文章分类表(ArtCategory),一个文章表(Articles),它们之间的关系是一对多。其中,ArtCategory中的主键AC_Id是自动增长字段。
操作一:添加文章到数据库
1 /// <summary>
2 /// 添加文章
3 /// </summary>
4 /// <param name="form">提交的Form</param>
5 [AcceptVerbs(HttpVerbs.Post)]
6 [ValidateInput(false)]
7 [ValidateUserLogin(Order = 1)]
8 [MyAuthorityValidation(Order = 2)]
9 public ActionResult Add(FormCollection form)
10 {
11 Blog.Models.Articles article = null;
12 if (form != null)
13 {
14 if (Blog.Utility.Article.Token == 0)
15 {
16 #region 验证用户输入
17 if (form["txtTitle"].IsNullOrEmpty())
18 {
19 ViewData["Message"] = JSUtility.GetScript("$.MsgBox(\"请输入文章标题!\");");
20 return View();
21 }
22 if (form["ddlCat"].IsNullOrEmpty())
23 {
24 ViewData["Message"] = JSUtility.GetScript("$.MsgBox(\"文章类别有误!\");");
25 return View();
26 }
27 if (Suxibo.Convert.ToInt32(form["ddlCat"]) == -1)
28 {
29 ViewData["Message"] = JSUtility.GetScript("$.MsgBox(\"请选择文章类别!\");");
30 return View();
31 }
32 if (form["editor1"].IsNullOrEmpty())
33 {
34 ViewData["Message"] = JSUtility.GetScript("$.MsgBox(\"请输入文章内容!\");");
35 return View();
36 }
37 #endregion
38 int userId = ((Blog.Models.UserInfo)Session["UserInfo"]).Usr_Id;
39 try
40 {
41 #region ==发布文章==
42 using (Blog.Models.BlogDb db = new Models.BlogDb())
43 {
44 DateTime datetime = DateTime.Now;
45 //初始化一个文章实体对象
46 article = new Models.Articles();
47 //将当前时间转换成指定的格式并作为文章编号
48 article.Art_Id = datetime.ToString("yyyyMMddHHmmss");
49 #region 设置类别
50 //获取用户选择的文章类别编号
51 int AC_Id = Suxibo.Convert.ToInt32(form["ddlCat"]).GetValueOrDefault();
52 //根据用户选择的文章类别编号获取对应的文章类别实体
53 var selectedCat = db.ArtCategory.FirstOrDefault(x => x.AC_Id == AC_Id);
54 //将当前文章实体的类别指向上面选定的文章类别实体
55 article.ArtCategoryReference.Value = selectedCat;
56 #endregion
57 //设置文章标题
58 article.Art_Title = form["txtTitle"].HtmlEncode();
59 //设置文章发布时间
60 article.Art_PostTime = datetime;
61 //设置文章是否置顶
62 article.Art_IsTopest = form["chkTopest"] == "on" ? true : false;
63 //设置文章内容
64 article.Art_Content = form["editor1"].HtmlDecode().HtmlEncode();
65 #region 设置用户
66 //根据登录用户的编号获取用户实体
67 var selectedUser = db.UserInfo.FirstOrDefault(x => x.Usr_Id == userId);
68 //将当前文章实体对应的用户实体指向上面选定的用户实体
69 article.UserInfoReference.Value = selectedUser;
70 #endregion
71 //设置文章的标签
72 article.Art_Tag = form["txtTag"].HtmlEncode();
73
74 if (article.Art_IsTopest.Value)
75 {
76 article.Art_TopTime = datetime;
77 }
78 //添加当前文章实体到数据库
79 db.AddToArticles(article);
80 bool rs = db.SaveChanges() > 0;
81 if (rs)
82 {
83 string artUrl = "/Archives/Details/".ToAbsoluteUrl() + article.Art_Id;
84 string dirUrl = ("~/Archives/" + datetime.Year + "/" + datetime.Month + "/");
85 string fileName = article.Art_Id + ".html";
86 UriBuilder ub = new UriBuilder(artUrl);
87 Utility.Article.MakeStaticHtmlPage(ub.Uri, dirUrl, fileName);
88 return RedirectToAction("SkipPrompt", new { success = true, returnMessage = "发布成功!", returnUrl = dirUrl.ToUrl() + fileName });
89 }
90 else
91 {
92 return RedirectToAction("SkipPrompt", new { success = false, returnMessage = "发布失败!" });
93 }
94 }
95 #endregion
96 }
97 catch (Exception ex)
98 {
99 return RedirectToAction("SkipPrompt", new { success = false, returnMessage = "发布失败!原因:" + ex.Message });
100 }
101 finally
102 {
103 Blog.Utility.Article.Token++;
104 }
105 }
106 else
107 {
108 ViewData["Message"] = JSUtility.GetScript("$.MsgBox(\"不可重复提交!\");");
109 }
110 }
111 return View();
112 }
说明:在指定与文章实体关联的文章类别实体时,不可用这种方式去设置:article.ArtCategory.AC_Id = AC_Id; 即不可通过给文章实体的关联实体ArtCategory的AC_Id赋值去实现。原因:其一,由于未调用文章实体的关联属性的Load方法,即 article.ArtCategoryReference.Load(); ,所以 article.ArtCategory 值为null,此时引用它必定报错。其二,即使调用了前面的Load()方法,则程序依然报错,因为article的关联实体ArtCategory的主键值是AC_Id,它是自动增长字段,是不允许被修改的!所以必须通过更改文章实体对文章类别实体的引用才可以执行通过。
操作二:更新文章
说明:更新操作与添加操作的思路是一模一样的,对于文章实体的关联实体的修改同样是通过更改对关联实体的引用实现的。而对于文章实体本身的属性,则直接赋予修改后的值即可。
操作三:删除文章
/// <summary>
2 /// 删除文章
3 /// </summary>
4 /// <param name="Id">文章编号</param>
5 [ValidateUserLogin(Order = 1)]
6 [MyAuthorityValidation(Order = 2)]
7 public ContentResult Delete(string Id)
8 {
9 if (Request.IsAjaxRequestx())
10 {
11 string contentType = "text/plain";
12 if (Permission.IsTimeOut())
13 return Content("您已登录超时,请重新登录后再操作!", contentType);
14 if (Id.IsNullOrEmpty())
15 {
16 return Content("不存在指定编号的文章!", contentType);
17 }
18 if (!Id.IsNumber())
19 {
20 return Content("请求的文章编号错误!", contentType);
21 }
22 using (System.Transactions.TransactionScope trans = new TransactionScope())
23 {
24 using (Blog.Models.BlogDb db = new Models.BlogDb())
25 {
26 var sla = db.Articles.FirstOrDefault(x => x.Art_Id == Id);
27 if (sla.Comment != null && !sla.Comment.IsLoaded)
28 sla.Comment.Load();
29 while (sla.Comment.Count > 0)
30 {
31 var co = sla.Comment.FirstOrDefault();
32 if (co != null)
33 {
34 if (co.AdminReply != null && !co.AdminReply.IsLoaded)
35 co.AdminReply.Load();
36 while (co.AdminReply.Count > 0)
37 {
38 db.DeleteObject(co.AdminReply.FirstOrDefault());
39 }
40 db.DeleteObject(co);
41 }
42 }
43 db.DeleteObject(sla);
44 int rs = db.SaveChanges();
45 trans.Complete();
46 if (rs > 0)
47 {
48 Article.DeleteStaticHtmlPageFile(Id);
49 return Content("0", contentType);
50 }
51 }
52 }
53 return Content("删除失败!", contentType);
54 }
55 return Content("服务器拒绝请求!");
56 }
注意:对于删除实体操作,不可用foreach来进行循环遍历,因为foreach是只读的,对集合里面的元素进行删除会造成foreach操作抛出异常,切记!
操作四:查询文章列表
/// <summary>
2 /// 获取指定范围的文章
3 /// </summary>
4 /// <param name="pageIndex">起始索引</param>
5 /// <param name="pageSize">记录的个数</param>
6 /// <param name="url">地址</param>
7 /// <param name="catId">文章类别编号</param>
8 public PageOfList<Articles> GetArticlesList(int pageIndex, int pageSize, string url, string catId)
9 {
10 using(BlogDb Db = new BlogDb())
11 {
12 var src = Db.Articles
13 .OrderByDescending(x => x.Art_IsTopest)
14 .ThenByDescending(x => x.Art_PostTime)
15 .ThenByDescending(x => x.Art_TopTime)
16 .ToList();
17
18 if (src != null)
19 {
20 foreach (var s in src)
21 {
22 if (s != null)
23 {
24 if (s.UserInfoReference != null && !s.UserInfoReference.IsLoaded)
25 s.UserInfoReference.Load();
26 if (s.ArtCategoryReference != null && !s.ArtCategoryReference.IsLoaded)
27 s.ArtCategoryReference.Load();
28 if (s.ArtCategoryReference != null && !s.Comment.IsLoaded)
29 s.Comment.Load();
30 }
31 }
32 }
33
34 /**
35 * 由于这里出现了对ArtCategory实体的引用,所以上面必须先加载与当前Article关联的
36 * ArtCategory实体
37 * */
38 var rs = src.Where(x => x.ArtCategory.AC_Id.ToString() == catId);
39
40 if (rs != null)
41 {
42 return new PageOfList<Articles>(rs, pageIndex, pageSize, url);
43 }
44 return null;
45 }
46 }
说明:在上述查询中,我需要获取文章实体的关联实体ArtCategory的属性AC_Id的值,这时需要注意的是,如果在这之前没加载关联实体ArtCategory,那么这个操作将不会被编译通过,原因在操作一中已经解释了。其次,还要注意一点很重要的,就是在写Linq to sql 或者 Entity Sql语句时,一定要搞清哪些方法是可以在linq to sql中执行的,
哪些方法是只能在linq to object中执行的。比方说上面代码中的这一句:var rs = src.Where(x => x.ArtCategory.AC_Id.ToString() == catId);我为什么可以用.ToString()方法?因为我在前面已经把src给ToList()了,如果没有把src给ToList(),那么.ToString()方法将不能用于这里,因为.ToString()是linq to object里面的方法,而不是linq to sql里面的方法!切记!