パート 5: Knockout.js を使用して動的 UI を作成する
作成者: Rick Anderson
Knockout.js で動的 UI を作成する
このセクションでは、Knockout.jsを使用して、Admin ビューに機能を追加します。
Knockout.js は、HTML コントロールをデータに簡単にバインドできる Javascript ライブラリです。 Knockout.js では Model-View-ViewModel (MVVM) パターンを使用します。
- Model は、ビジネス ドメインのサーバー側のデータ表現です (ここでは製品と注文)。
- View は、プレゼンテーション レイヤー (HTML) です。
- View-Model は、Model データを保持する Javascript オブジェクトです。 View-Model は、UI のコード抽象化です。 HTML 表現には関知しません。 代わりに、"アイテムの一覧" など、View の抽象的な特徴を表します。
View は、View-Model にデータ バインドされます。 View-Model に対する更新は、View に自動的に反映されます。 また、View-Model は、ボタン クリックなどのイベントを View から取得し、Model に対して操作 (注文の作成など) を実行します。
HTML データ、View-Model、json、Web API コントローラー間の相互作用を示す図。 HTML データ ボックスは、ラベル付きの View です。 データ バインディングというラベルが付いた双方向矢印は、HTML データ ボックスを View-Model ボックスにリンクします。 HTTP リクエストというラベルが付いた双方向矢印と、サーバーからの json Model は、View-Model を Web API コントローラーにリンクします。
まず、View-Model を定義します。 その後、HTML マークアップを View-Model にバインドします。
次の Razor セクションを Admin.cshtml に追加します。
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.1.0.js")"></script>
<script type="text/javascript">
// View-model will go here
</script>
}
このセクションは、ファイル内の任意の場所に追加できます。 View がレンダリングされると、そのセクションが HTML ページの下部 (終了 </body> タグの直前) に表示されます。
このページのすべてのスクリプトは、コメントで示される、スクリプト タグ内に配置されます。
<script type="text/javascript">
// View-model will go here
</script>
まず、View-Model クラスを定義します。
function ProductsViewModel() {
var self = this;
self.products = ko.observableArray();
}
ko.observableArray は、observable と呼ばれる、Knockout の特殊なタイプのオブジェクトです。 Knockout.js のドキュメントによると、observable は、"変更についてサブスクライバーに通知できる JavaScript オブジェクト" です。observable のコンテンツが変わると、それに合わせて View が自動的に更新されます。
products
配列に入力するには、Web API に対して AJAX 要求を行います。 API のベース URI をビュー バッグに格納したことを思い出してください (チュートリアルのパート 4 を参照)。
function ProductsViewModel() {
var self = this;
self.products = ko.observableArray();
// New code
var baseUri = '@ViewBag.ApiUrl';
$.getJSON(baseUri, self.products);
}
次に、View-Model に関数を追加して、製品を作成、更新、削除します。 これらの関数は、Web API に AJAX 呼び出しを送信し、その結果を使用して View-Model を更新します。
function ProductsViewModel() {
var self = this;
self.products = ko.observableArray();
var baseUri = '@ViewBag.ApiUrl';
// New code
self.create = function (formElement) {
// If the form data is valid, post the serialized form data to the web API.
$(formElement).validate();
if ($(formElement).valid()) {
$.post(baseUri, $(formElement).serialize(), null, "json")
.done(function (o) {
// Add the new product to the view-model.
self.products.push(o);
});
}
}
self.update = function (product) {
$.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
}
self.remove = function (product) {
// First remove from the server, then from the view-model.
$.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
.done(function () { self.products.remove(product); });
}
$.getJSON(baseUri, self.products);
}
最も重要な部分として、DOM がいっぱいになったら、ko.applyBindings 関数を呼び出し、ProductsViewModel
の新しいインスタンスを渡します。
$(document).ready(function () {
ko.applyBindings(new ProductsViewModel());
})
ko.applyBindings メソッドにより Knockout がアクティブになり、View-Mode を View に接続します。
これで View-Model が作成されました。次はバインディングを作成します。 Knockout.js では、HTML 要素に data-bind
属性を追加することでこれを行います。 たとえば、HTML リストを配列にバインドするには、foreach
バインディングを使用します。
<ul id="update-products" data-bind="foreach: products">
foreach
バインディングによって配列が反復処理され、配列内の各オブジェクトの子要素が作成されます。 子要素のバインディングは、配列オブジェクトのプロパティを参照できます。
"update-products" リストに次のバインディングを追加します。
<ul id="update-products" data-bind="foreach: products">
<li>
<div>
<div class="item">Product ID</div> <span data-bind="text: $data.Id"></span>
</div>
<div>
<div class="item">Name</div>
<input type="text" data-bind="value: $data.Name"/>
</div>
<div>
<div class="item">Price ($)</div>
<input type="text" data-bind="value: $data.Price"/>
</div>
<div>
<div class="item">Actual Cost ($)</div>
<input type="text" data-bind="value: $data.ActualCost"/>
</div>
<div>
<input type="button" value="Update" data-bind="click: $root.update"/>
<input type="button" value="Delete Item" data-bind="click: $root.remove"/>
</div>
</li>
</ul>
<li>
要素は、foreach バインディングのスコープ内で発生します。 つまり、Knockout は products
配列内の各製品に対してその要素を 1 回レンダリングします。 <li>
要素内のすべてのバインディングは、その製品インスタンスを参照します。 たとえば、$data.Name
は製品の Name
プロパティを参照します。
テキスト入力の値を設定するには、value
バインディングを使用します。 ボタンは、click
バインディングを使用して、Model-View の関数にバインドされます。 製品インスタンスは、各関数にパラメーターとして渡されます。 詳細については、Knockout.js のドキュメント でさまざまなバインディングについて細かく説明しています。
次に、[製品の追加] フォームで submit イベントのバインディングを追加します。
<form id="addProduct" data-bind="submit: create">
このバインディングにより、View-Model の create
関数が呼び出され、新しい製品が作成されます。
Admin ビューの完全なコードを次に示します。
@model ProductStore.Models.Product
@{
ViewBag.Title = "Admin";
}
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.0.0.js")"></script>
<script type="text/javascript">
function ProductsViewModel() {
var self = this;
self.products = ko.observableArray();
var baseUri = '@ViewBag.ApiUrl';
self.create = function (formElement) {
// If valid, post the serialized form data to the web api
$(formElement).validate();
if ($(formElement).valid()) {
$.post(baseUri, $(formElement).serialize(), null, "json")
.done(function (o) { self.products.push(o); });
}
}
self.update = function (product) {
$.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
}
self.remove = function (product) {
// First remove from the server, then from the UI
$.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
.done(function () { self.products.remove(product); });
}
$.getJSON(baseUri, self.products);
}
$(document).ready(function () {
ko.applyBindings(new ProductsViewModel());
})
</script>
}
<h2>Admin</h2>
<div class="content">
<div class="float-left">
<ul id="update-products" data-bind="foreach: products">
<li>
<div>
<div class="item">Product ID</div> <span data-bind="text: $data.Id"></span>
</div>
<div>
<div class="item">Name</div>
<input type="text" data-bind="value: $data.Name"/>
</div>
<div>
<div class="item">Price ($)</div>
<input type="text" data-bind="value: $data.Price"/>
</div>
<div>
<div class="item">Actual Cost ($)</div>
<input type="text" data-bind="value: $data.ActualCost"/>
</div>
<div>
<input type="button" value="Update" data-bind="click: $root.update"/>
<input type="button" value="Delete Item" data-bind="click: $root.remove"/>
</div>
</li>
</ul>
</div>
<div class="float-right">
<h2>Add New Product</h2>
<form id="addProduct" data-bind="submit: create">
@Html.ValidationSummary(true)
<fieldset>
<legend>Contact</legend>
@Html.EditorForModel()
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
</div>
</div>
アプリケーションを実行し、管理者アカウントでログインして、"Admin" リンクをクリックします。 製品の一覧が表示され、製品を作成、更新、削除できます。