learn.knockoutjs.com - Creating custom bindings

http://learn.knockoutjs.com/#/?tutorial=custombindings

by Mark

HTML

<link rel="stylesheet" href="http://learn.knockoutjs.com/Content/App/coderunner.css">
<link rel="stylesheet" href="http://learn.knockoutjs.com/Content/TutorialSpecific/custombindings.css">
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/start/jquery-ui.css">
<script src="http://knockoutjs.com/downloads/knockout-3.2.0.js"></script>
<body class="codeRunner"> 

    <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>
                <td data-bind="starRating: points"></td>
            </tr>    
        </tbody>
    </table>
    
    <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>
    <button data-bind="jqButton: { enable: pointsUsed() <= pointsBudget }, click: save">Finished</button>
    
</body>

CSS

/* 
  from RP Niemeyer
*/

JavaScript

// ----------------------------------------------------------------------------
// Reusable bindings - ideally kept in a separate file

ko.bindingHandlers.fadeVisible = {
    init: function(element, valueAccessor) {
        // Start visible/invisible according to initial value
        var shouldDisplay = valueAccessor();
        $(element).toggle(shouldDisplay);
    },
    update: function(element, valueAccessor) {
        // On update, fade in/out
        var shouldDisplay = valueAccessor();
        shouldDisplay ? $(element).fadeIn() : $(element).fadeOut();
    } 
};

ko.bindingHandlers.jqButton = {
    init: function(element) {
       $(element).button(); // Turns the element into a jQuery UI button
    },
    update: function(element, valueAccessor) {
        var currentValue = valueAccessor();
        // Here we just update the "disabled" state, but you could update other properties too
        $(element).button("option", "disabled", currentValue.enable === false);
    }    
};

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(
                function() { $(this).prevAll().add(this).addClass("hoverChosen") }, 
                function() { $(this).prevAll().add(this).removeClass("hoverChosen") }                
            ).click(function() { 
                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) {
           ...