利用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,有興趣的朋友可以試玩看看,發現問題請再回饋給我。

<!DOCTYPE html>
<html>
<head>
    <script src="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>");
                    var checked =
                        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>
    <meta charset="utf-8" />
    <title>KO範例23 – 單選或多選兩用Checkbox清單</title>
</head>
<body style="padding: 24px">
    <input type='button' data-bind="click: addOption" value="Add Option" />
    <br />
    單選: 
    <select data-bind="options: categories, optionsText: 't', optionsValue: 'v', value: category, xorChkValue: category, xorChkColor: 'brown'"></select>
    <br />
    <span data-bind="text: category"></span>
    <br />
    多選:
  <select data-bind="options: categories, optionsText: 't', optionsValue: 'v', selectedOptions: selCatgs, xorChkValue: selCatgs" multiple>
  </select>
    <br />
    <span data-bind="text: selCatgsText"></span>
</body>
</html>

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

Comments

Be the first to post a comment

Post a comment


24 + 20 =