利用Bootstrap Paginator插件和knockout.js完成分页功能
在最近一个项目中,需要结合一堆条件查询并对查询的结果数据完成一个简单分页功能,可是做着做着,自己的思路越来越模糊,做到心态崩溃!!! 哈哈
特此花点时间重新总结,并从最简单的分页,然后向多条件查询分页慢慢过渡,或许有人觉得这个很简单(可以绕道啦,哈哈),却是对基础知识的一次学习过程。
Demo地址:
本文地址:
一、环境介绍
分页功能很多已有的很完美的插件或是第三方应用包都能够完美实现,我在此利用了一些前端插件来完成分页功能。
前端的插件完成前端分页数字之类的切换展示;
利用插件完成分页数据的绑定;
在后端,利用asp.net core mvc 完成分页信息的接收和处理工作。
二、简单分页
完成对已有数据的分页功能,不带条件查询。
首先,一上来便是完成数据绑定工作:
1 bookList: ko.mapping.fromJS(@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model))),//展示数据 2 pageEntity: ko.mapping.fromJS(@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(new PageRequestViewModel()))),//分页信息
页面刚展示的时候得有分页数据吧,可以在当页面展示的时候,数据也带过来了,也可以页面展示后,再通过ajax去后台调用,我选择后者。
在页面启动时,调用该函数完成初始化页面数据。
1 getBookData: function () { 2 var pageEntity = ko.mapping.toJS(viewModel.pageEntity); 3 $.ajax({ 4 url: '@Url.Action("SampleGetData")', 5 type: 'POST', 6 dataType: 'json', 7 data: pageEntity, 8 success: function (result) { 9 ko.mapping.fromJS(result.data, viewModel.bookList); 10 options.totalPages = result.totalCount > 0 ? result.totalCount % options.numberOfPages == 0 ? 11 result.totalCount / options.numberOfPages : (result.totalCount / options.numberOfPages) + 1 : 1; 12 $('#pagination').bootstrapPaginator(options); 13 } 14 }); 15 }
分页信息(当前页面,页面展示数据条数)带过去,后台根据分页信息完成数据查询,并获得总的记录条数用于前端页面计算总页数。
1 [HttpPost] 2 public IActionResult SampleGetData(PageRequestViewModel pageEntity) 3 { 4 var bookList = PageDataSeed.GetPageDataList() 5 .Skip((pageEntity.PageIndex - 1) * pageEntity.PageSize) 6 .Take(pageEntity.PageSize); 7 8 var pageResultViewModel = new PageResultViewModel<Book>() 9 { 10 PageIndex = pageEntity.PageIndex, 11 PageSize = pageEntity.PageSize, 12 TotalCount = PageDataSeed.GetPageDataList().Count(), 13 Data = bookList 14 }; 15 16 return Json(pageResultViewModel); 17 }
根据分页信息查询也就搞定了,当点击底部的页面码的时候得改变当前分页信息,然后要切换当前分页信息所对应的数据出来,在Bootstrap paginator插件中,点击页面码有一个函数onPageClicked,点击具体的某一页后,根据参数page获得页面,改变当前展示的页面码数字,并对分页信息修改,然后再次调用ajax获得新数据。
1 onPageClicked: function (event, originalEvent, type, page) { 2 options.currentPage = page; 3 viewModel.pageEntity.PageIndex = page; 4 viewModel.getBookData(); 5 },
至此,简单分页功能便搞定了,在此实现中,偏重于前端实现分页逻辑,后台只是取得相应的数据。
三、单条件查询分页
复制一份简单分页后,改造下加入一个根据书名条件项,查询后完成分页功能。
首先加入书名绑定,在此用了两个书名,第二个是有目的性的留着,也只是我的理解,条件查询后,如果底部展示有多页,那么我在点击每一页的时候,我的查询条件不能动吧,基于此考虑的。
1 bookName: ko.observable(), 2 bookNameBackup: ko.observable(),
数据查询部分改动不大,主要是把查询条件加入进来,因此只改动data参数即可。
1 getBookData: function () { 2 var pageEntity = ko.mapping.toJS(viewModel.pageEntity); 3 $.ajax({ 4 url: '@Url.Action("SingleQueryGetData")', 5 type: 'POST', 6 dataType: 'json', 7 data: {"pageEntity":pageEntity, "bookName":viewModel.bookName()}, 8 success: function (result) { 9 ko.mapping.fromJS(result.data, viewModel.bookList); 10 options.totalPages = result.totalCount > 0 ? result.totalCount % options.numberOfPages == 0 ? 11 result.totalCount / options.numberOfPages : (result.totalCount / options.numberOfPages) + 1 : 1; 12 $('#pagination').bootstrapPaginator(options); 13 } 14 }); 15 },
前端Html部分加入单条件项,以书名为例,查询按钮绑定点击后的触发函数
1 <div class="form-group"> 2 <label for="bookName" class="col-sm-2 control-label">书名</label> 3 <div class="col-sm-2"> 4 <input type="text" class="form-control" id="bookName" data-bind="value:bookName"> 5 </div> 6 <button class="btn btn-primary col-sm-1" data-bind="click:queryBookList">查询</button> 7 </div>
点击查询后执行函数,将当前页面重置为1,查询数据,并记录该次查询的查询条件。
1 //查询 2 queryBookList: function () { 3 options.currentPage = 1; 4 viewModel.pageEntity.PageIndex = options.currentPage; 5 viewModel.getBookData(); 6 viewModel.bookNameBackup(viewModel.bookName());//记录查询条件,分页点击时需要用到 7 }
查询完毕,然后假设底部还存在很多页面码可以选择,当我们点击页面码的时候,因为查询条件仍然存在,我们点击后仍然会完成分页功能,但是!!!
当把查询条件清空,比如在此demo中,把书名置空,此时不点查询按钮,而是直接点击页面码,那情况会如何呢,查询条件已经没了;
或是说查询条件为空时,输入查询条件,不点查询按钮,直接点击页面码;
这都是不合理的情形,点击页面码的时候肯定得保证原查询条件的存在,至少我是这么想的,或许您有更好的建议,请告诉我,十分感谢。这也就是我在之前设置了一个监控对象bookNameBackup的原因,备份查询条件,防止点击页码切换时查询条件变更的情形。
前端已经改好了,然后进入后端来改一下,先对条件判空处理,然后执行相应的逻辑。
1 [HttpPost] 2 public IActionResult SingleQueryGetData(PageRequestViewModel pageEntity, string bookName) 3 { 4 IEnumerable<Book> bookList = PageDataSeed.GetPageDataList(); 5 PageResultViewModel<Book> pageResultViewModel = null; 6 7 if (!string.IsNullOrEmpty(bookName)) 8 bookList = bookList.Where(b => b.BookName.Contains(bookName)); 9 10 var books = bookList.Skip((pageEntity.PageIndex - 1) * pageEntity.PageSize) 11 .Take(pageEntity.PageSize); 12 13 pageResultViewModel = new PageResultViewModel<Book>() 14 { 15 PageIndex = pageEntity.PageIndex, 16 PageSize = pageEntity.PageSize, 17 TotalCount = bookList.Count(), 18 Data = books 19 }; 20 21 return Json(pageResultViewModel); 22 }
单条件查询分页功能也就搞定了,查询和点击页面码所执行的逻辑是不一样的,虽然都调用到了最终的数据查询方法,但是对于条件的处理是不一样的。
四、多条件查询分页
对单条件查询分页下多加入几个条件,进入到多条件查询,变化其实不大,只是为了方便管理如此多的查询条件,改成了以对象形式管理
首先绑定查询对象信息,该对象中包括了三个查询条件,书名,作者,出版社,仍然设置一个备份对象,原因和单条件查询下是一样的。
1 queryItemEntity: ko.mapping.fromJS(@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(new QueryItemViewModel()))),//查询条件信息 2 queryItemEntityBackup: ko.mapping.fromJS(@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(new QueryItemViewModel()))),//查询条件信息备份
页面的查询条件多了,通过queryItemEntity为前缀展示,也方便维护
1 <div id="queryItem" class="form-horizontal"> 2 <div class="form-group"> 3 <label for="bookName" class="col-sm-2 control-label">书名</label> 4 <div class="col-sm-2"> 5 <input type="text" class="form-control" id="bookName" data-bind="value:queryItemEntity.BookName"> 6 </div> 7 <label for="author" class="col-sm-2 control-label">作者</label> 8 <div class="col-sm-2"> 9 <input type="text" class="form-control" id="author" data-bind="value:queryItemEntity.Author"> 10 </div> 11 <label for="press" class="col-sm-2 control-label">出版社</label> 12 <div class="col-sm-2"> 13 <input type="text" class="form-control" id="press" data-bind="value:queryItemEntity.Press"> 14 </div> 15 </div> 16 <button class="btn btn-primary" data-bind="click:queryBookList">查询</button> 17 </div>
查询条件改动,也使得数据查询部分的条件改变了,将查询条件整体封装,而不是零散的传递,改动之处加入了第三行将ko上的查询条件信息转换为JS对象和改变了参数data的值。
1 getBookData: function () { 2 var pageEntity = ko.mapping.toJS(viewModel.pageEntity); 3 var queryItemEntity = ko.mapping.toJS(viewModel.queryItemEntity); 4 5 $.ajax({ 6 url: '@Url.Action("MultipleQueryGetData")', 7 type: 'POST', 8 dataType: 'json', 9 data: { "pageEntity": pageEntity, "queryItemEntity": queryItemEntity}, 10 success: function (result) { 11 ko.mapping.fromJS(result.data, viewModel.bookList); 12 options.totalPages = result.totalCount > 0 ? result.totalCount % options.numberOfPages == 0 ? 13 result.totalCount / options.numberOfPages : (result.totalCount / options.numberOfPages) + 1 : 1; 14 $('#pagination').bootstrapPaginator(options); 15 } 16 }); 17 },
点击按钮查询处的逻辑还是没有改变,仍然是查询记录并将查询条件备份,用于分页控件中页面码的点击函数。
1 //查询 2 queryBookList: function () { 3 options.currentPage = 1; 4 viewModel.pageEntity.PageIndex = options.currentPage; 5 viewModel.getBookData(); 6 ko.mapping.fromJS(viewModel.queryItemEntity, viewModel.queryItemEntityBackup);//记录查询条件,分页点击时需要用到 7 }
对于前端来讲改动并不是很大,无非是将查询条件封装一下,同样后端的改动只是多个查询条件的过滤,依次比对三个查询条件是否为空,并挨个去完成查询的过滤。
1 [HttpPost] 2 public IActionResult MultipleQueryGetData(PageRequestViewModel pageEntity, QueryItemViewModel queryItemEntity) 3 { 4 var bookList = PageDataSeed.GetPageDataList(); 5 6 #region 条件过滤 7 if (!string.IsNullOrEmpty(queryItemEntity.BookName)) 8 bookList = bookList.Where(b => b.BookName.Contains(queryItemEntity.BookName)).ToList(); 9 if (!string.IsNullOrEmpty(queryItemEntity.Author)) 10 bookList = bookList.Where(b => b.Author.Contains(queryItemEntity.Author)).ToList(); 11 if (!string.IsNullOrEmpty(queryItemEntity.Press)) 12 bookList = bookList.Where(b => b.Press.Contains(queryItemEntity.Press)).ToList(); 13 #endregion 14 15 var books = bookList.Skip((pageEntity.PageIndex - 1) * pageEntity.PageSize).Take(pageEntity.PageSize); 16 17 var pageResultViewModel = new PageResultViewModel<Book>() 18 { 19 PageIndex = pageEntity.PageIndex, 20 PageSize = pageEntity.PageSize, 21 TotalCount = bookList.Count(), 22 Data = books 23 }; 24 25 return Json(pageResultViewModel); 26 }
多条件查询分页的效果展示
至此,查询功能算是简单完成了,或许还有漏洞地方,没有发现,特别是逻辑漏洞,防不胜防,望多指教,感谢。
设计一个小Demo一方面是对分页功能进行一下总结,以防自己若干天或是若干年后还需要,可以回来看看,一方面也是如果有需要的朋友可以加快编码和设计的过程。
码云上存放Demo的地址:
2018-6-24,望技术有成后能回来看见自己的脚步
上一篇: 聪明的老师