KnockoutJs Tutorial｜カスタムバインディング



# **KnockoutJs Tutorial｜Creating custom bindings**

knockout.js tutorial４こめhttp://learn.knockoutjs.com/#/?tutorial=collections 

bindingを自作します。日本語で軽くコメントアウト入れました。（まずチュートリアルをみて、よくわからなかったら見てみてください）。


# **つくりたいバインディング**

もともとのソースが多いのでわかりづらいですが、やりたいバインディングは、

* starRating
* fadeVisible
* jqButton

の３つです。


# **HTML**

```
<h3 data-bind="text: question"></h3>
<p>Please distribute <b data-bind="text: pointsBudget"></b> points between the following options.</p>

<table>
    <thead><tr><th>Option</th><th>Importance</th></tr></thead>
    <tbody data-bind="foreach: answers">
        <tr>
            <td data-bind="text: answerText"></td>

            <!-- ↓starRatingのバインディング。pointsの部分がJS側のvalueAccessorになる -->
            <td data-bind="starRating: points"></td>

            <td><select data-bind="options: [1,2,3,4,5], value: points"></select></td>
        </tr>    
    </tbody>
</table>

<!-- ↓fadeVisibleのバインディング。「pointsUsed() > pointsBudget」のboolean値がJS側のvalueAccessorになる -->
<h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">You've used too many points! Please remove some.</h3>

<p>You've got <b data-bind="text: pointsBudget - pointsUsed()"></b> points left to use.</p>

<!-- ↓jqButtonのバインディング。「{ enable: pointsUsed() <= pointsBudget }」のboolean値がJS側のvalueAccessorに。jQuery UIをつかっているので通常の書き方と異なる。 -->
<button data-bind="jqButton: { enable: pointsUsed() <= pointsBudget }, click: save">Finished</button>
```



# **JS**

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

function SurveyViewModel(question, pointsBudget, answers) {

    // ===== starRating =====
    ko.bindingHandlers.starRating = {
        init: function(element, valueAccessor) {
            $(element).addClass("starRating");
            for (var i = 0; i < 5; i++)
               $("<span>").appendTo(element);

            // Handle mouse events on the stars
            $("span", element).each(function(index) {
                $(this).hover(

                    // hoverしたらクラスを追加
                    function() { $(this).prevAll().add(this).addClass("hoverChosen") }, 
                    function() { $(this).prevAll().add(this).removeClass("hoverChosen") }                

                ).click(function() { 

                   // clickしたら、valueAccessorを更新。eachをつかってindexを取得している
                   var observable = valueAccessor();  // Get the associated observable
                   observable(index+1);               // Write the new rating to it

                 }); 
            });
        },
        update: function(element, valueAccessor) {
            // Give the first x stars the "chosen" class, where x <= rating
            var observable = valueAccessor();
            $("span", element).each(function(index) {
                $(this).toggleClass("chosen", index < observable());
            });
        }
    };
    // ===== starRating end =====

    // ===== jqButton =====
    ko.bindingHandlers.jqButton = {
        init: function(element) {
           $(element).button(); // jQuery UIのbuttonに変える
        },
        update: function(element, valueAccessor) {
            var currentValue = valueAccessor();
            // "disabled"プロバティを更新
            $(element).button("option", "disabled", currentValue.enable === false);
        }
    };
    // ===== jqButton end =====

    // ===== fadeVisible =====
    ko.bindingHandlers.fadeVisible = {
        init: function(element, valueAccessor) {
            // visible/invisibleかを初期値によって判別
            var shouldDisplay = valueAccessor();
            $(element).toggle(shouldDisplay);
        },
        update: function(element, valueAccessor) {
            // visible/invisibleかを更新
            var shouldDisplay = valueAccessor();
            shouldDisplay ? $(element).fadeIn() : $(element).fadeOut();
        } 
    };
    // ===== fadeVisible end =====

    this.question = question;
    this.pointsBudget = pointsBudget;
    this.answers = $.map(answers, function(text) { return new Answer(text) });
    this.save = function() { alert('To do') };
                       
    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.applyBindings(new SurveyViewModel("Which factors affect your technology choices?", 10, [
   "Functionality, compatibility, pricing - all that boring stuff",
   "How often it is mentioned on Hacker News",    
   "Number of gradients/dropshadows on project homepage",        
   "Totally believable testimonials on project homepage"
]));
```