Knockout.JS Tutorials

Point alloting

by Marventus

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-debug.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://jquery-star-rating-plugin.googlecode.com/svn/trunk/jquery.rating.js"></script>
<link rel="stylesheet" href="https://jquery-star-rating-plugin.googlecode.com/svn/trunk/jquery.rating.css">
<link rel="stylesheet" href="http://learn.knockoutjs.com/Content/TutorialSpecific/custombindings.css">
<h3 data-bind="text: question"></h3>

<p>Please distribute <b data-bind="text: pointsBudget" class="blue status"></b> points between the following options.</p>

<table class="table">
    <thead>
        <tr>
            <th>Factors</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>

<p><strong>You've got <span data-bind="text: pointsBudget - pointsUsed(), css: updateClass" class="status"></span> points left to use.</strong></p>

<h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">You have alloted <span class="red" data-bind="text: pointsUsed() - pointsBudget"></span> extra point<span data-bind="visible: (pointsUsed() - pointsBudget) > 1">s</span>! Please remove some.</h3>

<button data-bind="jqButton: {enable: pointsUsed() <= pointsBudget}, click: save">Finished</button>

CSS

body {
    padding:10px 5px;
}
.table {
    background-color: #cfdfff;
    margin: 15px 0!important;
}
.table,
.table > thead > tr > th {
    border:2px solid #abe!important;
    border-width: 1px 0!important;
}
.table > tbody > tr > td,
.table > tbody > tr > th {
    border:1px solid rgba(255,255,255, 0.2)!important;
    border-width: 1px 0!important;
}
.blue {
    color: #080;
}
.red {
    color: #c33;
}
.status {
    background-color: #ddd;
    padding: 0 5px;
}
.ui-state-disabled {
    opacity:0.5!important;
}
.hoverChosen:not(.chosen) {
    opacity:0.6;
}

JavaScript

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

ko.bindingHandlers.fadeVisible = {
    init: function(element, valueAccessor) {
        var shouldDisplay = valueAccessor();
        $(element).toggle(shouldDisplay);
    },
    update: function(element, valueAccessor) {
        var shouldDisplay = valueAccessor();
        shouldDisplay ? $(element).fadeIn() : $(element).fadeOut();
    } 
};
ko.bindingHandlers.jqButton = {
    init: function(element) {
       $(element).button();
    },
    update: function(element, valueAccessor) {
        var currentValue = valueAccessor();
        $(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);
        $("span", element).each(function(index) {
            $(this)
            .hover(function() {
                $(this).prevAll()
                .add(this)
                .toggleClass("hoverChosen")
            })
            .click(function() { 
                var observable = valueAccessor();
                observable(index+1);
            });
        });            
    },
    update: function(element, valueAccessor) {
        var observable = valueAccessor();
        $("span", element).each(function(index) {
            $(this).toggleClass("chosen", index < observable());
        });
    }    
};

// --------------------------------------------------------------
// Page viewmodel

function Answer(text) {
    this.answerText = text; this.points = ko.observable(1);
}
function SurveyViewModel(question, pointsBudget, answers) {
    this.question = question;
    this.pointsBudget = pointsBudget;
    this.answers = $.map(answers, function(text) {
        return new Answer(text)
    });
    this.save = function() { alert('You are...