ホームページ > 記事 > ウェブフロントエンド > Bootstrap と KnockoutJs を組み合わせてページング効果を実現する詳細な例_JavaScript スキル
KnockoutJS は、JavaScript で実装された MVVM フレームワークです。とてもクールです。たとえば、リスト データ項目を追加または削除した後、コントロール フラグメント全体を更新したり、ノードを追加または削除するための JS を自分で作成したりする必要はなく、その構文に準拠するテンプレートと属性を事前に定義するだけで済みます。意味。簡単に言うと、データ アクセスだけに注目する必要があります。
1. はじめに
最近会社のシステムを見直す必要があるので、新しいシステムのWebフロントエンドをKnockoutJsを使って作る予定です。その過程で、KnockoutJs を使用してページング機能を完了する方法という問題に遭遇しました。前回の記事ではKnockoutJsを使ったページングの実装について紹介していませんでしたので、今回はKnockoutJs+Bootstrapを使ったデータのページング表示の実装について補足していきます。
2. KnockoutJs を使用してページングを実装します
ページングを実装するには 2 つの方法があります。1 つ目は、すべてのデータをロードしてページにすべてのデータを表示する方法で、2 つ目は、毎回データの一部のみをロードし、リクエストごとに次のデータを再ロードする方法です。データ。
これら 2 つの方法の場合、Razor メソッドを使用して実装されるページングは、通常、2 番目のメソッドを使用してページングを実装します。ただし、単一ページのプログラムの場合、最初の実装方法は、それほど大量ではない場合には完全に適しています。この場合、その後のデータ読み込みのユーザー エクスペリエンスが非常にスムーズになるため、最初の実装方法を使用できます。そこで、ここではこれら 2 つの実装方法を紹介します。
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 }
Web フロントエンド実装コード:
@{ 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)); });
ここでは、KnockoutJs を使用してページング機能を実装する実装アイデアを紹介します。
1. ページがロードされた後、Ajax リクエストを開始して REST サービスを非同期的に呼び出し、データをリクエストします。
2. 次に、KnockoutJs バインディングを通じて要求されたデータを表示します。
3. 対応するページング情報をブートストラップ ページングにバインドします
4. ユーザーがクリックしてページをめくると、Ajax リクエストを開始して Rest サービスを非同期に呼び出してデータを要求し、要求されたデータを表示します。
上記は、上記のコードの呼び出しロジック関係です。上記の説明を理解するには、対応する JS コードを参照してください。この時点で、2 番目の実装方法が完了しました。
2.2 初めてすべてのデータをロードし、すべてのデータをページに表示します
次に、最初の実装方法を紹介します。この実装方法では、ユーザーは最初にデータが読み込まれていると感じるだけで、ページめくりのプロセス中にはページが読み込まれているとは感じません。データ自体がそれほど多くない場合、ユーザー エクスペリエンスはよりスムーズになります。
具体的な実装のアイデアは、要求されたすべてのデータをページ上に表示しないことです。データが多すぎるため、ページ上に一度にすべてを表示するとユーザーが目がくらんでしまう可能性があります。ページ内にデータを表示すると、ユーザーにとってわかりやすくなります。
Web フロントエンド 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. 操作効果
次に、KnockoutJs を使用して達成されるページング効果を見てみましょう:
4. 概要
この記事で紹介する内容はここまでです。この記事で実装する内容は比較的単純ですが、KnockoutJs を初めて使用する友人にとって、この記事の実装は多くの指針になると思います。 。次に、AngularJs の関連コンテンツを共有します。
以上、編集者が紹介したBootstrapとKnockoutJsを組み合わせてページング効果を実現する例を詳しく解説しましたので、皆様のお役に立てれば幸いです。