さっしーブログ

埼玉県在住のシステムエンジニアです。基本的には技術的な内容を中心に発信していきます。

knockout.js チュートリアル 【Working with Lists and Collections】

目次

本記事はknockout.jsの公式サイトにある以下のチュートリアルを実施した備忘録です。 learn.knockoutjs.com

Step1

foreachバインディングを使用して、プログラム内で保持しているデータを1個1個取り出して画面上に表形式で表示するサンプルプログラム

サンプルコード

ko.observableArray()で保持しているデータをhtml上で「foreach: [プロパティ名]」を指定することにより個々に取り出せることが分かった。

sample.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
<script type="text/javascript" src="sample.js" defer="defer"></script>
<title></title>
</head>
<body>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Meal</th>
<th>Surcharge</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: meal().mealName"></td>
<td data-bind="text: meal().price"></td>
</tr>
</tbody>
</table>
</body>
</html>

sample.js

function SeatReservation(name, initialMeal) {
  var self = this;
  self.name = name;
  self.meal = ko.observable(initialMeal);
}

function ReservationsViewModel(){
  var self = this;

  self.availableMeals = [
    { mealName: "Standard (sandwich)", price: 0 },
    { mealName: "Premium (lobster)", price: 34.95 },
    { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  self.seats = ko.observableArray(
    [
      new SeatReservation("Steve", self.availableMeals[0]),
      new SeatReservation("Bert", self.availableMeals[0])
    ]
  );
}

ko.applyBindings(new ReservationsViewModel());

Step2

ボタンクリックで座席テーブルに対してデータを1行追加するサンプルプログラム

サンプルコード

ko.observableArray()を設定したプロパティからpush()が呼び出せ、 データを追加できることが分かった。 追加されると画面にも自動的に反映される。 画面が全体が再生成されず、テーブルの1行分のデータのみが部分的に反映されていることが分かった。

sample.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
<script type="text/javascript" src="sample.js" defer="defer"></script>
<title></title>
</head>
<body>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Meal</th>
<th>Surcharge</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: meal().mealName"></td>
<td data-bind="text: meal().price"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addSeat">座席を予約する</button>
</body>
</html>

sample.js

function SeatReservation(name, initialMeal) {
  var self = this;
  self.name = name;
  self.meal = ko.observable(initialMeal);
}

function ReservationsViewModel(){
  var self = this;

  self.availableMeals = [
    { mealName: "Standard (sandwich)", price: 0 },
    { mealName: "Premium (lobster)", price: 34.95 },
    { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  self.seats = ko.observableArray(
    [
      new SeatReservation("Steve", self.availableMeals[0]),
      new SeatReservation("Bert", self.availableMeals[0])
    ]
  );

  self.addSeat = function(){
    self.seats.push(new SeatReservation("", self.availableMeals[0]))
  }
}

ko.applyBindings(new ReservationsViewModel());

Step3

前回までテキスト表示していた部分をテキストボックスとセレクトボックスに変更してデータを更新できるようにしたサンプルプログラム

サンプルコード

前回までの内容を組み合わせた応用のサンプルプログラム。 そして簡単にテキスト表示から入力ボックスへの変更など行えることが分かった。

sample.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
<script type="text/javascript" src="sample.js" defer="defer"></script>
<title></title>
</head>
<body>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Meal</th>
<th>Surcharge</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name"></td>
<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
<td data-bind="text: formattedPrice"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addSeat">座席を予約する</button>
</body>
</html>

sample.js

function SeatReservation(name, initialMeal) {
  var self = this;
  self.name = name;
  self.meal = ko.observable(initialMeal);

  self.formattedPrice = ko.computed(function(){
    var price = self.meal().price;
    return price ? "$" + price.toFixed(2) : "None";
  });
}

function ReservationsViewModel(){
  var self = this;

  self.availableMeals = [
    { mealName: "Standard (sandwich)", price: 0 },
    { mealName: "Premium (lobster)", price: 34.95 },
    { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  self.seats = ko.observableArray([
    new SeatReservation("Steve", self.availableMeals[0]),
    new SeatReservation("Bert", self.availableMeals[0])
  ]);

  self.addSeat = function(){
    self.seats.push(new SeatReservation("", self.availableMeals[0]))
  }
}

ko.applyBindings(new ReservationsViewModel());

Step4

シートの削除と合計料金表示を実装したサンプルプログラム

サンプルコード

ViewModel内のデータ追加削除などが自由に行えることが分かった。 今回のサンプルプログラムにてforeach内のスコープはSeatReservationを指しており、 ReservationsViewModel内のプロパティを使用するには$rootを使用しなくてはいけないことが分かった。 $rootはトップレベルのViewModelであるReservationsViewModelを指している。 つまりはko.applyBindings()の引数に渡しているViewModelがトップレベルとなっていることが分かった。

sample.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
<script type="text/javascript" src="sample.js" defer="defer"></script>
<title></title>
</head>
<body>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Meal</th>
<th>Surcharge</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name"></td>
<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
<td data-bind="text: formattedPrice"></td>
<td><a href="#" data-bind="click: $root.removeSeat">削除</a></td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: totalSurcharge() > 0">
Total surcharge: $<span data-bind="text: totalSurcharge().toFixed(2)"></span>
</h3>
<button data-bind="click: addSeat">座席を予約する</button>
</body>
</html>

sample.js

function SeatReservation(name, initialMeal) {
  var self = this;
  self.name = name;
  self.meal = ko.observable(initialMeal);

  self.formattedPrice = ko.computed(function(){
    var price = self.meal().price;
    return price ? "$" + price.toFixed(2) : "None";
  });
}

function ReservationsViewModel(){
  var self = this;

  self.availableMeals = [
    { mealName: "Standard (sandwich)", price: 0 },
    { mealName: "Premium (lobster)", price: 34.95 },
    { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  self.seats = ko.observableArray([
    new SeatReservation("Steve", self.availableMeals[0]),
    new SeatReservation("Bert", self.availableMeals[0])
  ]);

  self.addSeat = function(){
    self.seats.push(new SeatReservation("", self.availableMeals[0]))
  }

  self.removeSeat = function(seat){
    self.seats.remove(seat)
  }

  self.totalSurcharge = ko.computed(function(){
    var total = 0;
    for(var i = 0; i < self.seats().length; i++)
      total += self.seats()[i].meal().price;
    return total;
  });
}

ko.applyBindings(new ReservationsViewModel());

Step5

総座席数と予約可能座席数を満たした場合にボタンを非活性にする処理を実装したサンプルプログラム

サンプルコード

一度作成した要素のdata-bind属性にプロパティを追加していけば、細かい制御を追加できることが分かった。 jQueryなどで行うとDOMを書き換える必要があるので、それに比べると細かい処理が簡潔に実装できるなと感じた。

sample.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
<script type="text/javascript" src="sample.js" defer="defer"></script>
<title></title>
</head>
<body>
<h2>Your seat reservations (<span data-bind="text: seats().length"></span>)</h2>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Meal</th>
<th>Surcharge</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name"></td>
<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
<td data-bind="text: formattedPrice"></td>
<td><a href="#" data-bind="click: $root.removeSeat">削除</a></td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: totalSurcharge() > 0">
Total surcharge: $<span data-bind="text: totalSurcharge().toFixed(2)"></span>
</h3>
<button data-bind="click: addSeat, enable: seats().length < 5">座席を予約する</button>
</body>
</html>

sample.js

function SeatReservation(name, initialMeal) {
  var self = this;
  self.name = name;
  self.meal = ko.observable(initialMeal);

  self.formattedPrice = ko.computed(function(){
    var price = self.meal().price;
    return price ? "$" + price.toFixed(2) : "None";
  });
}

function ReservationsViewModel(){
  var self = this;

  self.availableMeals = [
    { mealName: "Standard (sandwich)", price: 0 },
    { mealName: "Premium (lobster)", price: 34.95 },
    { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  self.seats = ko.observableArray(
    [
      new SeatReservation("Steve", self.availableMeals[0]),
      new SeatReservation("Bert", self.availableMeals[0])
    ]
  );

  self.addSeat = function(){
    self.seats.push(new SeatReservation("", self.availableMeals[0]))
  }

  self.removeSeat = function(seat){
    self.seats.remove(seat)
  }

  self.totalSurcharge = ko.computed(function(){
    var total = 0;
    for(var i = 0; i < self.seats().length; i++)
      total += self.seats()[i].meal().price;
    return total;
  });
}

ko.applyBindings(new ReservationsViewModel());

記事トップは以下になります。

www.sassy-blog.com