KnockoutJS는 JavaScript로 구현된 MVVM 프레임워크입니다. 아주 멋지다. 예를 들어 목록 데이터 항목을 추가하거나 삭제한 후 전체 제어 조각을 새로 고치거나 JS를 작성하여 노드를 직접 추가하거나 삭제할 필요가 없으며 해당 구문에 맞는 템플릿과 속성만 미리 정의하면 됩니다. 정의. 간단히 말해서, 우리는 데이터 액세스에만 집중하면 됩니다.
1. 소개
최근 회사의 시스템을 개정할 필요가 있어서 KnockoutJ를 사용하여 새로운 시스템에 대한 웹 프런트엔드를 만들 계획입니다. 그 과정에서 KnockoutJ를 사용하여 페이징 기능을 완료하는 방법에 문제가 발생했습니다. 이전 기사에서는 KnockoutJs를 사용하여 페이징을 구현하는 방법을 소개하지 않았으므로 이번 기사에서는 KnockoutJs+Bootstrap을 사용하여 데이터의 페이징 표시를 구현하는 방법을 보완하겠습니다.
2. KnockoutJ를 사용하여 페이징 구현
페이징을 구현하는 방법에는 두 가지가 있습니다. 첫 번째는 모든 데이터를 로드한 다음 모든 데이터를 페이지에 표시하는 것입니다. 두 번째는 매번 데이터의 일부만 로드하고 각 요청에 대해 다음을 다시 로드하는 것입니다. 데이터.
이 두 가지 방법에 대해 Razor 방법을 사용하여 구현한 페이징은 일반적으로 두 번째 방법을 사용하여 페이징을 구현합니다. 그러나 단일 페이지 프로그램의 경우 첫 번째 구현 방법도 데이터 양이 많지 않다는 장점이 있습니다. 첫 번째 구현 방법을 사용할 수 있습니다. 이 경우 후속 데이터 로드에 대한 사용자 경험이 매우 원활해지기 때문입니다. 따라서 여기서는 이 두 가지 구현 방법을 소개하겠습니다.
2.1 부분 데이터 매번 로딩 구현
여기 백엔드 코드는 이전 기사의 코드를 사용하고 몇 가지 추가 샘플 데이터만 추가했습니다. 구체적인 백엔드 구현 코드는 다음과 같습니다.
/// <summary> /// Web API 服务,为Web前端提供数据服务 /// </summary> public class TaskController : ApiController { private readonly TaskRepository _taskRepository = TaskRepository.Current; public IEnumerable<Task> GetAll() { return _taskRepository.GetAll().OrderBy(a => a.Id); } [Route("api/task/GetByPaged")] public PagedModel GetAll([FromUri]int pageIndex) { const int pageSize = 3; int totalCount; var tasks = _taskRepository.GetAll(pageIndex, pageSize, out totalCount).OrderBy(a => a.Id); var pageData = new PagedModel() { PageIndex = pageIndex, PagedData = tasks.ToList(), TotalCount = totalCount, PageCount = (totalCount+ pageSize -1) / pageSize }; //返回数据 return pageData; } } /// <summary> /// 任务仓储,封装了所有关于数据库的操作 /// </summary> public class TaskRepository { #region Static Filed private static Lazy<TaskRepository> _taskRepository = new Lazy<TaskRepository>(() => new TaskRepository()); public static TaskRepository Current { get { return _taskRepository.Value; } } #endregion #region Fields private readonly List<Task> _tasks = new List<Task>() { new Task { Id =1, Name = "创建一个SPA程序", Description = "SPA(single page web application),SPA的优势就是少量带宽,平滑体验", Owner = "Learning hard", FinishTime = DateTime.Parse(DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =2, Name = "学习KnockoutJs", Description = "KnockoutJs是一个MVVM类库,支持双向绑定", Owner = "Tommy Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(2).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =3, Name = "学习AngularJS", Description = "AngularJs是MVVM框架,集MVVM和MVC与一体。", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(3).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =4, Name = "学习ASP.NET MVC网站", Description = "Glimpse是一款.NET下的性能测试工具,支持asp.net 、asp.net mvc, EF等等,优势在于,不需要修改原项目任何代码,且能输出代码执行各个环节的执行时间", Owner = "Tonny Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(4).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =5, Name = "测试任务1", Description = "测试任务1", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(5).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =6, Name = "测试任务2", Description = "测试任务2", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(6).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =7, Name = "测试任务3", Description = "测试任务3", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(7).ToString(CultureInfo.InvariantCulture)) }, }; #endregion #region Public Methods public IEnumerable<Task> GetAll() { return _tasks; } public IEnumerable<Task> GetAll(int pageNumber, int pageSize, out int totalCount) { var skip = (pageNumber - 1) * pageSize; var take = pageSize; totalCount = _tasks.Count; return _tasks.Skip(skip).Take(take); } public Task Get(int id) { return _tasks.Find(p => p.Id == id); } public Task Add(Task item) { if (item == null) { throw new ArgumentNullException("item"); } item.Id = _tasks.Count + 1; _tasks.Add(item); return item; } public void Remove(int id) { _tasks.RemoveAll(p => p.Id == id); } public bool Update(Task item) { if (item == null) { throw new ArgumentNullException("item"); } var taskItem = Get(item.Id); if (taskItem == null) { return false; } _tasks.Remove(taskItem); _tasks.Add(item); return true; } #endregion }
웹 프런트엔드 구현 코드:
@{ ViewBag.Title = "Index2"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="list2"> <h2>分页第二种实现方式——任务列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>编号</th> <th>名称</th> <th>描述</th> <th>负责人</th> <th>创建时间</th> <th>完成时间</th> <th>状态</th> </tr> </thead> <tbody data-bind="foreach:pagedList"> <tr> <td data-bind="text: id"></td> <td><a data-bind="text: name"></a></td> <td data-bind="text: description"></td> <td data-bind="text: owner"></td> <td data-bind="text: creationTime"></td> <td data-bind="text: finishTime"></td> <td data-bind="text: state"></td> </tr> </tbody> <tbody data-bind="if: loadingState"> <tr> <td colspan="8" class="text-center"> <img width="60" src="/images/loading.gif" /> </td> </tr> </tbody> <tfoot data-bind="ifnot:loadingState"> <tr> <td colspan="8"> <div class="pull-right"> <div>总共有<span data-bind="text: totalCount"></span>条记录, 每页显示:<span data-bind="text: pageSize"></span>条</div> <div> <ul class="pagination"> <li data-bind="css: { disabled: pageIndex() === 1 }"><a href="#" data-bind="click: previous">«</a></li> </ul> <ul data-bind="foreach: allPages" class="pagination"> <li data-bind="css: { active: $data.pageNumber === ($root.pageIndex()) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.gotoPage($data.pageNumber); }"></a></li> </ul> <ul class="pagination"><li data-bind="css: { disabled: pageIndex() === pageCount }"><a href="#" data-bind="click: next">»</a></li></ul> </div> </div> </td> </tr> </tfoot> </table> </div> </div>
해당 Js 구현은 다음과 같습니다.
// 实现分页的第二种方式 var ListViewModel2 = function() { //viewModel本身。用来防止直接使用this的时候作用域混乱 var self = this; self.loadingState = ko.observable(true); self.pageSize = ko.observable(3); //数据 this.pagedList = ko.observableArray(); //要访问的页码 this.pageIndex = ko.observable(1); //总页数 this.pageCount = ko.observable(1); //页码数 this.allPages = ko.observableArray(); //当前页 this.currengePage = ko.observable(1); self.totalCount = ko.observable(1); this.refresh = function() { //限制请求页码在该数据页码范围内 if (self.pageIndex() < 1) self.pageIndex(1); if (self.pageIndex() > self.pageCount()) { self.pageIndex(self.pageCount()); } //post异步加载数据 sendAjaxRequest("GET", function (data) { // 加载新的数据前,先移除原先的数据 self.pagedList.removeAll(); self.allPages.removeAll(); self.totalCount(data.totalCount); self.pageCount(data.pageCount); self.loadingState(false); for (var i = 1; i <= data.pageCount; i++) { //装填页码 self.allPages.push({ pageNumber: i }); } //for...in 语句用于对数组或者对象的属性进行循环操作。 //for ... in 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。 for (var i in data.pagedData) { //装填数据 self.pagedList.push(data.pagedData[i]); } }, 'GetByPaged', { 'pageIndex': self.pageIndex() }); }; //请求第一页数据 this.first = function() { self.pageIndex(1); self.refresh(); }; //请求下一页数据 this.next = function() { self.pageIndex(this.pageIndex() + 1); self.refresh(); }; //请求先前一页数据 this.previous = function() { self.pageIndex(this.pageIndex() - 1); self.refresh(); }; //请求最后一页数据 this.last = function() { self.pageIndex(this.pageCount() - 1); self.refresh(); }; //跳转到某页 this.gotoPage = function (data, event) { self.pageIndex(data); self.refresh(); }; }; function sendAjaxRequest(httpMethod, callback, url, reqData) { $.ajax("/api/task" + (url ? "/" + url : ""), { type: httpMethod, success: callback, data: reqData }); } $(document).ready(function () { var viewModel = new ListViewModel2(); viewModel.refresh(); if ($('#list2').length) ko.applyBindings(viewModel, $('#list2').get(0)); });
다음은 KnockoutJ를 사용하여 페이징 기능을 구현하는 구현 아이디어를 소개합니다.
1. 페이지가 로드된 후 REST 서비스를 비동기적으로 호출하여 일부 데이터를 요청하는 Ajax 요청을 시작합니다.
2. 그런 다음 KnockoutJs 바인딩을 통해 요청한 데이터를 표시합니다.
3. 해당 페이징 정보를 부트스트랩 페이징에 바인딩합니다
4. 사용자가 클릭하여 페이지를 넘기면 Ajax 요청을 시작하여 Rest 서비스를 비동기적으로 호출하여 데이터를 요청한 다음 요청된 데이터를 표시합니다.
위 설명은 위에서 설명한 코드의 호출 논리 관계입니다. 해당 JS 코드를 참조하면 위 설명을 이해할 수 있습니다. 이 시점에서 두 번째 구현 방법이 완료되었습니다.
2.2 처음으로 모든 데이터를 로드한 후 모든 데이터를 페이지에 표시합니다
다음으로 첫 번째 구현 방법을 소개합니다. 이 구현 방법을 사용하면 사용자는 처음으로 데이터가 로드되는 느낌만 받게 되며, 이런 식으로 페이지를 넘기는 과정에서는 페이지가 로드되는 느낌을 받지 않게 됩니다. , 일부에게는 데이터 자체가 너무 많지 않으면 사용자 경험이 더 원활합니다.
구체적인 구현 아이디어는 요청한 데이터를 페이지에 모두 표시하지 않는 것입니다. 왜냐하면 데이터가 너무 많아 페이지에 한꺼번에 표시되면 사용자가 어지러울 수 있기 때문입니다. 페이지에 데이터를 표시하면 사용자가 더 명확하게 알 수 있습니다.
웹 프론트엔드 Js의 구체적인 구현 코드는 다음과 같습니다.
var ListViewModel = function () { var self = this; window.viewModel = self; self.list = ko.observableArray(); self.pageSize = ko.observable(3); self.pageIndex = ko.observable(0); //要访问的页码 self.totalCount = ko.observable(1); //总记录数 self.loadingState = ko.observable(true); self.pagedList = ko.dependentObservable(function () { var size = self.pageSize(); var start = self.pageIndex() * size; return self.list.slice(start, start + size); }); self.maxPageIndex = ko.dependentObservable(function () { return Math.ceil(self.list().length / self.pageSize()) - 1; }); self.previousPage = function () { if (self.pageIndex() > 0) { self.pageIndex(self.pageIndex() - 1); } }; self.nextPage = function () { if (self.pageIndex() < self.maxPageIndex()) { self.pageIndex(self.pageIndex() + 1); } }; self.allPages = ko.dependentObservable(function () { var pages = []; for (var i = 0; i <= self.maxPageIndex() ; i++) { pages.push({ pageNumber: (i + 1) }); } return pages; }); self.moveToPage = function (index) { self.pageIndex(index); }; }; var listViewModel = new ListViewModel(); function bindViewModel() { sendAjaxRequest("GET", function (data) { listViewModel.loadingState(false); listViewModel.list(data); listViewModel.totalCount(data.length); if ($('#list').length) ko.applyBindings(listViewModel, $('#list').get(0)); }, null, null); } $(document).ready(function () { bindViewModel(); });
프런트엔드 페이지 구현은 이전 구현과 유사합니다. 구체적인 페이지 코드는 다음과 같습니다.
@{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="list"> <h2>任务列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>编号</th> <th>名称</th> <th>描述</th> <th>负责人</th> <th>创建时间</th> <th>完成时间</th> <th>状态</th> </tr> </thead> <tbody data-bind="foreach:pagedList"> <tr> <td data-bind="text: id"></td> <td><a data-bind="text: name"></a></td> <td data-bind="text: description"></td> <td data-bind="text: owner"></td> <td data-bind="text: creationTime"></td> <td data-bind="text: finishTime"></td> <td data-bind="text: state"></td> </tr> </tbody> <tbody data-bind="if:loadingState"> <tr> <td colspan="8" class="text-center"> <img width="60" src="/images/loading.gif" /> </td> </tr> </tbody> <tfoot data-bind="ifnot:loadingState"> <tr> <td colspan="8"> <div class="pull-right"> <div>总共有<span data-bind="text: totalCount"></span>条记录, 每页显示:<span data-bind="text: pageSize"></span>条</div> <div> <ul class="pagination"> <li data-bind="css: { disabled: pageIndex() === 0 }"><a href="#" data-bind="click: previousPage">«</a></li> </ul> <ul data-bind="foreach: allPages" class="pagination"> <li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.moveToPage($data.pageNumber-1); }"></a></li> </ul> <ul class="pagination"><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">»</a></li></ul> </div> </div> </td> </tr> </tfoot> </table> </div> </div>
3. 작전효과
다음으로 KnockoutJ를 사용하여 얻은 페이징 효과를 살펴보겠습니다.
4. 요약
이로써 이번 글에서 소개할 내용은 마무리됩니다. 비록 이번 글에서 구현한 내용은 비교적 간단하지만, KnockoutJs를 처음 접하는 몇몇 친구들에게는 이 글의 구현이 많은 지침이 될 것이라 믿습니다. . 다음으로 AngularJs 관련 내용을 알려드리겠습니다.
위는 편집자가 소개한 페이징 효과를 구현하기 위해 Bootstrap과 KnockoutJ를 결합한 예에 대한 자세한 설명입니다. 모든 분들께 도움이 되길 바랍니다!