さっしーブログ

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

knockout.js チュートリアル 【Creating custom bindings】

目次

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

Step2

ポイント配布式アンケートのサンプルプログラム

サンプルコード

knockout.jsではカスタムバインディングという自作のバインディングを作成することができる。 自作バインディングを作成することにより拡張性やメンテナンス性を向上させることができる。

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>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <title></title>
  </head>
  <body>
    <h2 data-bind="text: question"></h2>
    <p>以下のオプションに<b data-bind="text: pointsBudget"></b>ポイントを配布してください。</p>
    <table>
      <thead>
        <tr>
          <th>オプション</th>
          <th>重要度</th>
        </tr>
      </thead>
      <tbody data-bind="foreach: answers">
        <tr>
          <td data-bind="text: answerText"></td>
          <td><select data-bind="options: [1, 2, 3, 4, 5], value: points"></select></td>
        </tr>
      </tbody>
    </table>
    <h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">ポイントを使いすぎです。いくつか削除してください。</h3>
    <p>あなたは<b data-bind="text: pointsBudget - pointsUsed()"></b>ポイントを残しています。</p>
    <button data-bind="enable: pointsUsed() <= pointsBudget, click: save">完了</button>
  </body>
</html>

sample.js

function Answer(text) {
  this.answerText = text;
  this.points = ko.observable(1);
}

function QuestionnaireViewModel(question, pointsBudget, answers){
  this.question = question;
  this.pointsBudget = pointsBudget;
  this.answers = $.map(answers, function(text){
    return new Answer(text)
  });
  this.save = function(){
    alert("TODO")
  };

  this.pointsUsed = ko.computed(function(){
    var total = 0;
    for(var i = 0; i < this.answers.length; i++)
      total += this.answers[i].points();
    return total;
  }, this);
};

ko.bindingHandlers.fadeVisible = {
  init: function(element, value) {
    var shouldDisp = value();
    $(element).toggle(shouldDisp);
  },
  update: function(element, value){
    var shouldDisp = value();
    shouldDisp ? $(element).fadeIn() : $(element).fadeOut();
  }
};

ko.applyBindings(new QuestionnaireViewModel(
  'selected option',
  10,
  [
    "Content - A",
    "Content - B",
    "Content - C",
    "Content - D"
  ]
));

Step3

ポイント配布式アンケートのサンプルプログラム

サンプルコード

カスタムバインディングを作成することにより、 ボタンのUIを簡単にjQueryUIを使用したデザインへ更新することができた。 また、既存のJSに手を加えることなくカスタムバインディングを定義してそのバインディングをHTML側に設定してあげるのみで修正可能。

sample.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
    <script type="text/javascript" src="sample.js" defer="defer"></script>
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/start/jquery-ui.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script>
    <title></title>
  </head>
  <body>
    <h2 data-bind="text: question"></h2>
    <p>以下のオプションに<b data-bind="text: pointsBudget"></b>ポイントを配布してください。</p>
    <table>
      <thead>
        <tr>
          <th>オプション</th>
          <th>重要度</th>
        </tr>
      </thead>
      <tbody data-bind="foreach: answers">
        <tr>
          <td data-bind="text: answerText"></td>
          <td><select data-bind="options: [1, 2, 3, 4, 5], value: points"></select></td>
        </tr>
      </tbody>
    </table>
    <h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">ポイントを使いすぎです。いくつか削除してください。</h3>
    <p>あなたは<b data-bind="text: pointsBudget - pointsUsed()"></b>ポイントを残しています。</p>
    <button data-bind="jqButton: true, enable: pointsUsed() <= pointsBudget, click: save">完了</button>
  </body>
</html>

sample.js

function Answer(text) {
  this.answerText = text;
  this.points = ko.observable(1);
}

function QuestionnaireViewModel(question, pointsBudget, answers){
  this.question = question;
  this.pointsBudget = pointsBudget;
  this.answers = $.map(answers, function(text){
    return new Answer(text)
  });
  this.save = function(){
    alert("TODO")
  };

  this.pointsUsed = ko.computed(function(){
    var total = 0;
    for(var i = 0; i < this.answers.length; i++)
      total += this.answers[i].points();
    return total;
  }, this);
};

ko.bindingHandlers.fadeVisible = {
  init: function(element, value) {
    var shouldDisp = value();
    $(element).toggle(shouldDisp);
  },
  update: function(element, value){
    var shouldDisp = value();
    shouldDisp ? $(element).fadeIn() : $(element).fadeOut();
  }
};

ko.bindingHandlers.jqButton = {
  init: function(element) {
    $(element).button();
  }
};

ko.applyBindings(new QuestionnaireViewModel(
  'selected option',
  10,
  [
    "Content - A",
    "Content - B",
    "Content - C",
    "Content - D"
  ]
));

Step4

ポイント配布式(スターレーティング版)アンケートのサンプルプログラム

サンプルコード

カスタムウィジェットの実装もカスタムバインディングを使用すれば、既存のJSに修正を加えることなくHTMLのdata-bind属性の値だけ修正することにより実装が可能。 このようにknockout.jsを使用することによる保守性の向上も可能であるということが分かった。

sample.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script type="text/javascript" src="../../lib/knockout-3.2.0.js"></script>
    <script type="text/javascript" src="sample.js" defer="defer"></script>
    <link rel="Stylesheet" href="sample.css">
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/start/jquery-ui.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script>
    <title></title>
  </head>
  <body>
    <h2 data-bind="text: question"></h2>
    <p>以下のオプションに<b data-bind="text: pointsBudget"></b>ポイントを配布してください。</p>
    <table>
      <thead>
        <tr>
          <th>オプション</th>
          <th>重要度</th>
        </tr>
      </thead>
      <tbody data-bind="foreach: answers">
        <tr>
          <td data-bind="text: answerText"></td>
          <td data-bind="starRating: points"></td>
        </tr>
      </tbody>
    </table>
    <h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">ポイントを使いすぎです。いくつか削除してください。</h3>
    <p>あなたは<b data-bind="text: pointsBudget - pointsUsed()"></b>ポイントを残しています。</p>
    <button data-bind="jqButton: true, enable: pointsUsed() <= pointsBudget, click: save">完了</button>
  </body>
</html>

sample.js

function Answer(text) {
  this.answerText = text;
  this.points = ko.observable(1);
}

function QuestionnaireViewModel(question, pointsBudget, answers){
  this.question = question;
  this.pointsBudget = pointsBudget;
  this.answers = $.map(answers, function(text){
    return new Answer(text)
  });
  this.save = function(){
    alert("TODO")
  };

  this.pointsUsed = ko.computed(function(){
    var total = 0;
    for(var i = 0; i < this.answers.length; i++)
      total += this.answers[i].points();
    return total;
  }, this);
};

ko.bindingHandlers.fadeVisible = {
  init: function(element, value) {
    var shouldDisp = value();
    $(element).toggle(shouldDisp);
  },
  update: function(element, value){
    var shouldDisp = value();
    shouldDisp ? $(element).fadeIn() : $(element).fadeOut();
  }
};

ko.bindingHandlers.jqButton = {
  init: function(element) {
    $(element).button();
  }
};

ko.bindingHandlers.starRating = {
  init: function(element, value){
    $(element).addClass("starRating");
    for(var i = 0; i < 5; i++)
      $("<span>").appendTo(element);
      $("span", element).each(function(index){
        $(this).hover(
          function(){$(this).prevAll().add(this).addClass("hoverChosen")},
          function(){$(this).prevAll().add(this).removeClass("hoverChosen")}
        ).click(function(){
          var observable = value();
          observable(index+1);
        });
      });
  },
  update: function(element, value) {
    var observable = value();
    $("span", element).each(function(index){
      $(this).toggleClass("chosen", index < observable());
    });
  }
}

ko.applyBindings(new QuestionnaireViewModel(
  'selected option',
  10,
  [
    "Content - A",
    "Content - B",
    "Content - C",
    "Content - D"
  ]
));

sample.css

.starRating span.chosen {
  background-position: 0 0;
}

.starRating span {
  width: 24px;
  height: 24px;
  background-image: url(stars.png);
  display: inline-block;
  cursor: pointer;
  background-position: -24px 0;
}

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

www.sassy-blog.com