利用Checkbox模擬Radio清單的互斥選項是我常用的UI風格,之前曾用jQuery實作過,現在網頁都搬到Knockout的場子,少不了也要重現相同功能,順便考驗KO的能耐。(為何不直接用下拉選單就好? 這裡有一個好理由)
廢話不多說,直覺用以下Demo定義規則! (PS: 寫得太順手,不小心連多選的版本都寫進去 XD)
我最開始的想法是開發一個自訂繫結,將ko.observableArray轉成選項,並將勾選結果反應給ko.observable,而選項繫結時還需要像下拉選單一樣指定Text及Value… 想來想去,幾乎就是重寫一組跟<select>的options、optionsText、optionsValue、value一樣的繫組,那那那,何不直接寄生在<select>上? 把<option>轉成<input type="checkbox">,再把<select>本體藏起來,出乎順利地便完成了上述展示的效果。
使用方法很簡單,在一般的<select> data-bind後方再多加一個xorChkValue參數指向繫結對象就OK了:
(被選取項目的文字預設會變成藍色,如需修改可透過xorChkColor指定)
<select data-bind="options: categories, optionsText: 't', optionsValue: 'v', value: category, xorChkValue: category, xorChkColor: 'brown'"></select>
多選時一樣是加xorChkValue,但繫結對象要是ko.observableArray,記得<select>要加上multiple並改用selectedOptions取代value:
<select data-bind="options: categories, optionsText: 't', optionsValue: 'v', selectedOptions: selCatgs, xorChkValue: selCatgs" multiple>
</select>
完整程式碼如下,線上展示這回我放在JS Bin,有興趣的朋友可以試玩看看,發現問題請再回饋給我。
<!DOCTYPEhtml>
<html>
<head>
<scriptsrc="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<script>
//互斥點選的checkbox
ko.bindingHandlers.xorChkValue = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var $elem = $(element); //應為select
var val = valueAccessor();
var settings = allBindingsAccessor();
//檢查是否為多選
var multiple = "push"in val;
//支援自訂顏色
var color = settings.xorChkColor || "blue";
//取得<select>後方的元素
var $next = $elem.next();
var $container;
//取後方已有容器元素,清空即可
if ($next.hasClass("xor-checks")) {
$container = $next;
$container.empty();
}
else { //否則建立容器
$container = $("<span class='xor-checks'></span>");
//加入對label及checkbox的點擊行為
$container.on("click", "input,label", function () {
//點擊label時透過prev()找到checkbox
var inp = this;
if (this.tagName.toLowerCase() === "label") {
inp = $(this).prev()[0];
inp.checked = !inp.checked; //切換選取
}
console.log(inp.checked);
if (multiple) { //多選時, 視狀態決定新增或移除
if (inp.checked) {
if ($.inArray(inp.value, val()) == -1)
val.push(inp.value);
}
else {
val.remove(inp.value);
}
}
else { //單選
inp.checked = true;
val(inp.value);
}
});
}
$elem.find("option").each(function () {
var $cbx = $("<span><input type='checkbox' /><label /></span>");
varchecked =
multiple ? $.inArray(this.value, val()) != -1 :
val() == this.value;
$cbx.find("input").val(this.value).prop("checked", checked);
$cbx.find("label").text(this.text).css("color", checked ? color : "");
$container.append($cbx);
});
$elem.after($container);
$elem.hide();
},
}
var c = 1;
function myViewModel() {
var self = this;
self.categories = ko.observableArray();
self.category = ko.observable("D");
self.selCatgs = ko.observableArray(["D", "T"]);
self.selCatgsText = ko.computed(function () {
return JSON.stringify(self.selCatgs());
});
self.addOption = function () {
self.categories.push({ t: "Extra-" + c, v: c });
c++;
};
}
var vm = new myViewModel();
vm.categories.push({ t: "Desktop", v: "D" });
vm.categories.push({ t: "Phone", v: "P" });
vm.categories.push({ t: "Tablet", v: "T" });
vm.categories.push({ t: "TV", v: "V" });
$(function () {
ko.applyBindings(vm);
});
</script>
<metacharset="utf-8"/>
<title>KO範例23 – 單選或多選兩用Checkbox清單</title>
</head>
<bodystyle="padding: 24px">
<inputtype='button'data-bind="click: addOption"value="Add Option"/>
<br/>
單選:
<selectdata-bind="options: categories, optionsText: 't', optionsValue: 'v', value: category, xorChkValue: category, xorChkColor: 'brown'"></select>
<br/>
<spandata-bind="text: category"></span>
<br/>
多選:
<selectdata-bind="options: categories, optionsText: 't', optionsValue: 'v', selectedOptions: selCatgs, xorChkValue: selCatgs"multiple>
</select>
<br/>
<spandata-bind="text: selCatgsText"></span>
</body>
</html>
[KO系列]