KnockoutJs Tutorial|Creating custom bindings
knockout.js tutorial4こめhttp://learn.knockoutjs.com/#/?tutorial=collections
bindingを自作します。日本語で軽くコメントアウト入れました。(まずチュートリアルをみて、よくわからなかったら見てみてください)。
つくりたいバインディング
もともとのソースが多いのでわかりづらいですが、やりたいバインディングは、
- starRating
- fadeVisible
- jqButton
の3つです。
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"
]));