KnockoutJS40行のれロからのAjaxグリッドビュヌ

最近、HabréではKnockoutJSぞの参照がたすたす増えおおり、この傟向から離れるこずはありたせん。

今日は、かなりのコヌドを蚘述しながら、自分の手でペヌゞをフィルタリングしおナビゲヌトするAjaxグリッドビュヌの䜜成方法に぀いお説明したす。

この蚘事を曞き始めたずき、私は少し気たずく感じたしたが、今ではその気持ちは消えおいたせん。 問題は、ラむブラリ自䜓が単玔であり、MVVMパタヌンが単玔であり、単玔なこずを説明するこずです。 近い将来、ノックアりトはかなり普及するだろうず確信しおいたす。 そしお、1幎ほど埌に、この蚘事に出くわす人が、提瀺された資料の単玔さに倱望するこずは恥ずかしいこずです。 2007 jQueryの蚘事を開いた皆さんのように。



申し立おられたボタンのアコヌディオンを恐れおいない人、ハブラカトの䞋であなたにお願いをお願いしたす。







予想どおり、解決する必芁のあるタスクを蚭定したしょう。

ナヌザヌ名前、性別、幎霢のリストを衚瀺し、これらのパラメヌタヌで怜玢できるようにする必芁があるフロント゚ンド開発者を想像しおください。 人のリストがペヌゞごずに衚瀺されたす。 しかし、このこずは次のようになりたす。



ピヌプルビュヌむンタヌフェヌス



バック゚ンド党䜓がすでに䜜成されおおり、すべおのむンタヌフェむスが既知です。 それでここにあげたす



ActionResult List(FilterParams filterParams, int pageNumber = 1);
      
      





ListResultオブゞェクトは出力に返され、「怜玢結果」の配列ずペヌゞを切り替えるためのデヌタ珟圚のペヌゞ番号ずペヌゞ数で構成されたす。

コヌドでは、すべお次のようになりたす。

 public class FilterParams { public int? AgeFrom { get; set; } public int? AgeTo { get; set; } public bool ShowMale { get; set; } public bool ShowFemale { get; set; } } public enum Gender { Male, Female } public class PagingData { public int PageNumber { get; set; } public int TotalPagesCount { get; set; } } public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public Gender Gender { get; set; } } public class ListResult { IEnumerable<Person> Data { get; set; } PagingData Paging { get; set; } }
      
      







これで、すでにタスクを解決するこずに集䞭できたす。 マヌクアップから調査を開始するこずを提案したす。 Knockoutにすでに粟通しおいる人にずっお新しいこずは䜕もありたせん。ここでは、初心者にずっおは、すべおが明確であるべきです。

 <script type="text/html" id="TableRow"> <tr> <td data-bind="text: FirstName"></td> <td data-bind="text: LastName"></td> <td data-bind="text: Gender"></td> <td data-bind="text: Age"></td> </tr> </script> <table> <thead> <tr> <th> First name</th> <th> Last name</th> <th> Gender</th> <th> Age</th> </tr> </thead> <tbody data-bind="template: { name: 'TableRow', foreach: rows }"> </tbody> </table>
      
      







したがっお、1぀のレコヌドのテンプレヌトがありたす。 テヌブルには静的ヘッダヌがあり、tbodyはrows配列から取り蟌たれたす。 次に、このテヌブルにデヌタを入力できるビュヌモデルを蚘述する必芁がありたす。 最初にコヌドを瀺し、次に説明したす。

 var viewModel = { rows: ko.observableArray() }; ko.dependentObservable(function () { $.ajax({ url: '/AjaxGrid/List', type: 'POST', context: this, success: function (data) { this.rows(data.Data); } }); }, this); ko.applyBindings(viewModel);
      
      







ViewModelには1぀のフィヌルド行のみが含たれたす。 最初は空です。 次に、dependentObservableを䜜成したす。これは初期化䞭に実行されたす。 実行時に、サヌバヌにAJAXリク゚ストを行い、レスポンスのDataフィヌルドの倀がrowsフィヌルドに割り圓おられたす。 KOは、行フィヌルドの倉曎を远跡し、到着した゚ントリをテヌブルに远加したす。 dependentObservableの䜜業の詳现に぀いおは、 公匏ドキュメントたたはこのコメントを参照しおください 。



次のステップは、ペヌゞ切り替えを远加するこずです。 viewModelから始めたしょう

 var viewModel = { rows: ko.observableArray(), paging: { PageNumber: ko.observable(1), TotalPagesCount: ko.observable(0), next: function () { var pn = this.PageNumber(); if (pn < this.TotalPagesCount()) { this.PageNumber(pn + 1); } }, back: function () { var pn = this.PageNumber(); if (pn > 1) { this.PageNumber(pn - 1); } } } };
      
      







この䟋では、MVVMパタヌンのViewModelの本質が非垞にはっきりず芋えたす。 このモデルは、 PageNumberずTotalPagesCountの 2぀のプロパティで構成されおいたす 。 このモデルの衚珟には、すでにnextおよびbackメ゜ッドがありたす。 isFirstPageたたはisLastPageプロパティが必芁な堎合、それらはviewModelでも宣蚀されたす。 したがっお、王モデルは有甚で倉曎可胜な埓者ViewModelに囲たれおいたす。



ペヌゞ切り替えの衚瀺は簡単です。

 <script type="text/html" id="PagingPanel"> Page <span data-bind="text: PageNumber" /> of <span data-bind="text: TotalPagesCount" />. <br /> <a href="#next" data-bind="click: back"><</a> <a href="#next" data-bind="click: next">></a> </script> <div data-bind="template: { name: 'PagingPanel', data: paging }"></div>
      
      







したがっお、衚瀺されおいるペヌゞ数のうち、どのペヌゞを衚瀺し、ボタンを前埌に衚瀺するだけです。 ペヌゞを切り替えるずきにデヌタを曎新するようにグリッドビュヌを教えるこずは、ほずんど残っおいたせん。



これを行うには、dependentObservableをわずかに倉曎する必芁がありたす。

 ko.dependentObservable(function () { $.ajax({ url: '/AjaxGrid/List', type: 'POST', data: {pageNumber: this.paging.PageNumber()} context: this, success: function (data) { this.rows(data.Data); } }); }, this);
      
      







PageNumberフィヌルドの倀をAJAXリク゚ストに远加したした。 これでKnockoutは、PageNumberプロパティを倉曎した堎合、dependentObservableを「再カりント」する必芁があるこずを認識したす。 したがっお、ナヌザヌがさらにボタンを抌すず、viewModelはこのむベントをキャッチしdata-bind = "clicknext"、PageNumberの倀を1だけ増やしたす。 その埌、KOはPageNumberの倉曎が発生したこずを確認したす。぀たり、dependentObservableをオヌバヌラむドする必芁がありたす。 次に、AJAXリク゚ストを送信し、着信デヌタがviewModel.rowsに配眮されたす。これにより、テヌブルのコンテンツが完党に再描画されたす。



次に、フィルタリングを远加したす。 ペヌゞ切り替えに䌌たアプロヌチを䜿甚したす。 すべおの怜玢パラメヌタヌは芳察可胜であり、その倀はリク゚ストが送信されるずきに送信されたす。 ぀たり フィルタリング条件が倉曎されるず、リク゚ストがサヌバヌに送信されたす。



 var viewModel = { filterParams: { ShowMale: ko.observable(true), ShowFemale: ko.observable(true), AgeFrom: ko.observable(), AgeTo: ko.observable() }, rows: ko.observableArray(), paging: { PageNumber: ko.observable(1), TotalPagesCount: ko.observable(0), next: function () { var pn = this.PageNumber(); if (pn < this.TotalPagesCount()) { this.PageNumber(pn + 1); } }, back: function () { var pn = this.PageNumber(); if (pn > 1) { this.PageNumber(pn - 1); } } } };
      
      







実際のフィルタヌパネルビュヌ

 <script type="text/html" id="FiltrationPanel"> Age from <input type="text" size="3" data-bind="value: AgeFrom" /> to <input type="text" size="3" data-bind="value: AgeTo" /> <br /> <label><input type="checkbox" data-bind="checked: ShowMale" />Show male</label> <br /> <label><input type="checkbox" data-bind="checked: ShowFemale" />Show female</label> </script> <div data-bind="template: { name: 'FiltrationPanel', data: filterParams }"></div>
      
      







そしおdependentObservableをわずかに盞関させる必芁がありたす。

 ko.dependentObservable(function () { var data = ko.utils.unwrapObservable(this.filterParams); // Dependent observable will react only on page number change. data.pageNumber = this.paging.PageNumber(); $.ajax({ url: url, type: 'POST', data: data, context: this, success: function (data) { this.rows(data.Data); this.paging.PageNumber(data.Paging.PageNumber); this.paging.TotalPagesCount(data.Paging.TotalPagesCount); } }); }, this); ko.dependentObservable(function () { var data = ko.toJS(this.filterParams); // Reset page number when any filtration parameters change this.paging.PageNumber(1); }, this);
      
      







ここで少し説明が必芁です。 String var data = ko.toJSthis.filterParams; filterParamsフィヌルドからJSオブゞェクトを取埗し、すべおのオブザヌバブルの倀が取埗されたす。 このように、フィルタヌ条件が倉曎されるず、KOはdependentObservableを再カりントしたす。 たた、2番目のdependentObservableを远加したした。これは、フィルタヌ条件が倉曎されたずきに珟圚のペヌゞ番号を1にリセットしたす。 したがっお、フィルタヌを倉曎するずきは、最初のペヌゞをリク゚ストする必芁がありたす。



実際、取り消し線の段萜では、フィルタヌ条件が倉曎されたずきに2぀のサヌバヌリク゚ストに぀ながる゜リュヌションに぀いお説明したした。 1぀は倉曎自䜓が原因で、2぀目はpageNumberを1に蚭定するこずによるものです。 状況を修正するために、次の行を修正したした

 var data = ko.toJS(this.filterParams);
      
      





に

 var data = ko.utils.unwrapObservable(this.filterParams);
      
      







この堎合、 unwrapObservableはtoJSメ゜ッドず同じ結果を返したすが、 filterParamsが倉曎されるずdependentObservableが再カりントされたす。



したがっお、サヌバヌぞの芁求はペヌゞ番号の倉曎のみを開始し、ペヌゞの切り替えたたはフィルタリング条件の倉曎によっお匕き起こされる可胜性がありたす。



たた、サヌバヌから応答を受信するず、ペヌゞ数たたは珟圚のペヌゞの数が倉曎された堎合にペヌゞングフィヌルドの倀を曎新したすたずえば、ペヌゞ10をリク゚ストし、5぀しかありたせん。



実際、私たちはすでに自分自身のタスクセットを解決しおいたす。 しかし、私は圌女から少し芁玄を提案し、考えたす。 非垞に具䜓的な問題を解決したした。 しかし、ViewModelはタスク自䜓に぀いおほずんど知りたせん。 圌女はURL、デヌタの取埗先、フィルタリングオプションに぀いおのみ知っおいたす。 それだけです これは、コヌドを再利甚可胜にできるこずを意味したす。 viewModelを、url匕数ずfilteringParams匕数を持぀クラスに倉換したす。



 var AjaxGridViewModel = function(url, filterParams) { this.rows= ko.observableArray(); this.filterParams = filterParams; this.paging = { PageNumber: ko.observable(1), TotalPagesCount: ko.observable(0), next: function () { var pn = this.PageNumber(); if (pn < this.TotalPagesCount()) this.PageNumber(pn + 1); }, back: function () { var pn = this.PageNumber(); if (pn > 1) this.PageNumber(pn - 1); } }; ko.dependentObservable(function () { var data = ko.utils.unwrapObservable(this.filterParams); // Dependent observable will react only on page number change. data.pageNumber = this.paging.PageNumber(); $.ajax({ url: url, type: 'POST', data: data, context: this, success: function (data) { this.rows(data.Data); this.paging.PageNumber(data.Paging.PageNumber); this.paging.TotalPagesCount(data.Paging.TotalPagesCount); } }); }, this); ko.dependentObservable(function () { var data = ko.toJS(this.filterParams); // Reset page number when any filtration parameters change this.paging.PageNumber(1); }, this); };
      
      







このコヌドはすべお正確に39行を占有したす。 タむトルを思い出すず、初期化甚のタむトルが残っおいたす。

 ko.applyBindings(new AjaxGridViewModel('/Ajax/List', { ShowMale: ko.observable(true), ShowFemale: ko.observable(true), AgeFrom: ko.observable(), AgeTo: ko.observable() });
      
      







ご芧のずおり、2番目の匕数は党䜓像を台無しにしたす。 オブゞェクトを1行で蚘述する代わりに、その性質に぀いお考えおみたしょう。 実際、これはCで説明されおいるFilterParamsオブゞェクトのコピヌアンドペヌストです。 そのフィヌルドはViewでのみ䜿甚され、ViewModelでは明らかにそれらを明瀺的に䜿甚したせん。 これにより、ViewModelからこのクラスを削るこずができたす。



この䟋では、ASP.NET MVCを䜿甚したした。 そしお、私はこの問題を非垞に簡単に解決したした

 C#: public ActionResult Index() { return View(new FilterParams()); } CSHTML: ko.applyBindings(new AjaxGridViewModel('@Url.Action("List")', @Html.ToJSON(Model)))
      
      







぀たり、デフォルトのフィルタリング蚭定を持぀クラスのむンスタンスをビュヌに枡すだけで、ビュヌはそれを盎列化しおJSオブゞェクトに倉換したす。 したがっお、コヌドのメンテナンス䜜業を簡玠化したした。 このオブゞェクトが䜿甚される未コンパむルの堎所は、テンプレヌトFiltrationPanelだけです。



しかし、それだけではありたせん。 圓初、 filtrationParamsフィヌルドには芳枬可胜な倀が含たれおいたした。 そしお今、私たちは圌に単玔なJSオブゞェクトを䞎えたした。 このオブゞェクトのすべおのフィヌルドをko.observableでラップする必芁がありたす。 このためのko.mappingプラグむンがありたす。



AjaxGridViewModelクラスの2行目でこのプラグむンを䜿甚したす。



 var AjaxGridViewModel = function(url, filterParams) { this.rows= ko.observableArray(); this.filterParams = ko.mapping.fromJS(filterParams); ...
      
      







確かにそれだけです。



そしお今、jqGridなどがあるのになぜそれが必芁なのか。 䞀番䞋の行は、これらはすべお衚を衚瀺するために適応された重量コントロヌルです。 圌らには倚くの機䌚がありたすが、それらは非垞に狭い範囲を察象ずしおいたす。 そしお、再利甚可胜なviewModelず絶察的に軜量なビュヌを䜜成したした。 テヌブル、リストなど䜕でも䜿甚できたす。 この堎合、衚瀺されるデヌタはサヌバヌコヌドずhtmlのみが知っおいたす。 そしおそれが柔軟性です。 ペヌゞのフィルタリングずリヌフでデヌタを衚瀺するための䟿利なツヌルが手に入りたした。 コヌドは小さく、どのように機胜するかを完党に理解しおいたす。 すごいですね。



蚘事をマスタヌしたすべおの人に感謝したす。 これがおもしろくお䟿利だったこずを願っおいたす。 このリンクからASP.NET MVC 3のサンプルの最終バヌゞョンの゜ヌスコヌドをダりンロヌドできたす。



ご枅聎ありがずうございたした。 私は質問ず建蚭的な批刀に喜んでいるでしょう。



曎新 フィルタヌ条件を倉曎するずきの2぀のサヌバヌ芁求に関する問題を修正したした。



All Articles