2017-04-06 77 views
1

我試圖創建一個自定義的淘汰賽是像options默認功能結合的處理程序,但使用單選按鈕,而不是一個下拉的結合。Knockoutjs單選按鈕組自定義綁定不更新的選擇

添加到數組中的項將通知update但選擇一個不同的單選按鈕不會。

注:我扯下了自定義綁定處理到最低限度。

裝訂處理器

// LOOK FOR MY COMMENT //<------------------------------ LOOK FOR IT 
ko.bindingHandlers.radioButtons = { 
    update: function (element, valueAccessor, allBindings) { 
     var unwrappedArray = ko.utils.unwrapObservable(valueAccessor()); 
     var previousSelectedValue = ko.utils.unwrapObservable(allBindings().value); 


     // Helper function 
     var applyToObject = function (object, predicate, defaultValue) { 
      var predicateType = typeof predicate; 
      if (predicateType == "function") // Given a function; run it against the data value 
       return predicate(object); 
      else if (predicateType == "string") // Given a string; treat it as a property name on the data value 
       return object[predicate]; 
      else        // Given no optionsText arg; use the data value itself 
       return defaultValue; 
     }; 

     // Map the array to a newly created label and input. 
     var isFirstPass = true; 
     var radioButtonForArrayItem = function (arrayEntry, index, oldOptions) { 
      if (oldOptions.length) { 
       var item = oldOptions[0]; 
       if ($(item).is(":checked")) 
        previousSelectedValue = item.value; 

       isFirstPass = false; 
      } 

      var input = element.ownerDocument.createElement("input"); 
      input.type = "radio"; 

      var radioButtonGroupName = allBindings.get("groupName"); 
      input.name = radioButtonGroupName; 

      if (isFirstPass) { 
       var selectedValue = ko.utils.unwrapObservable(allBindings.get("value")); 
       var itemValue = ko.utils.unwrapObservable(arrayEntry.value); 
       if (selectedValue === itemValue) { 
        input.checked = true; 
       } 
      } else if ($(oldOptions[0].firstElementChild).is(":checked")) { 
       input.checked = true; 
      } 

      var radioButtonValue = applyToObject(arrayEntry, allBindings.get("radioButtonValue"), arrayEntry); 
      ko.selectExtensions.writeValue(input, ko.utils.unwrapObservable(radioButtonValue)); 

      var label = element.ownerDocument.createElement("label"); 
      label.appendChild(input); 
      var radioButtonText = applyToObject(arrayEntry, allBindings.get("radioButtonText"), arrayEntry); 
      label.append(" " + radioButtonText); 

      return [label]; 
     }; 

     var setSelectionCallback = function (arrayEntry, newOptions) { 
      var inputElement = newOptions[0].firstElementChild; 
      if ($(inputElement).is(":checked")) { 
       var newValue = inputElement.value; 
       if (previousSelectedValue !== newValue) { 
        var value = allBindings.get("value"); 
        value(newValue); //<------------------------- Get observable value and set here. Shouldn't this notify the update? 
       } 
      } 
     }; 

     ko.utils.setDomNodeChildrenFromArrayMapping(element, unwrappedArray, radioButtonForArrayItem, {}, setSelectionCallback); 
    }, 
}; 

如何使用它...

// Html 
<div data-bind=" 
    radioButtons: letters, 
    groupName: 'letters', 
    radioButtonText: 'text', 
    radioButtonValue: 'value', 
    value: value,  
"></div> 

// Javascript 
var vm = { 
    letters: ko.observableArray([ 
     { 
      text: "A", 
      value: 1, 
     }, 
     { 
      text: "B", 
      value: 2, 
     }, 
    ]), 
    value: ko.observable(2), 
}; 

ko.applyBindings(vm); 

所以,加入新的項目,以vm.letters({ text: "C", value: 2 })將通知update。但是,單擊不同的單選按鈕將不會通知update

什麼我必須這樣做的單選按鈕點擊後會通知update

DEMO HERE

+0

我添加了一個技巧,但現在我覺得髒...'$(輸入)。在(「點擊」,函數(){var value = allBindings.get(「value」); value(this.value);});' – christo8989

+0

我也注意到我沒有得到'vm.value'的雙向綁定。 – christo8989

回答

1

它看起來並不像你的綁定有一個方法來更新觀察到當選擇的變化。除非您只是將參數傳遞給另一個現有綁定,否則雙向綁定不會自動使用自定義綁定。像「黑客」這樣的事件處理程序正是通常使用自定義綁定的「init」函數的原因。

的「初始化」回調

淘汰賽將再次調用你的初始化函數每個DOM元素 使用>的結合上。主要有兩種用途的init:

  • 要設置任何初始狀態的DOM元素
  • 要註冊任何事件處理程序,使得,例如,當用戶點擊或修改的DOM元素,則可以更改相關的可觀察的

嘗試添加類似下面的初始化函數,以您的單選按鈕的狀態結合:

ko.bindingHandlers.radioButtons = { 
    init: function(element, valueAccessor, allBindings){ 
    var unwrappedArray = ko.utils.unwrapObservable(valueAccessor()); 
    var value = allBindings().value; 

    ko.utils.registerEventHandler(element, "click", function(event){ 
     var target = event.target; 
     if(target.nodeName.toLowerCase() == "input"){ 
      value($(target).attr("value")); 
     } 
    }); 
    }, 
    update: function (element, valueAccessor, allBindings) { 
     ... 
    } 

可能有更安全的方式來處理click事件,不是檢查,看是否該目標是一個「輸入」元素;這只是一個簡單的例子。如果您在單選按鈕元素中有嵌套的單選按鈕或其他類型的輸入元素作爲子元素,則必須修改該元素。如果有疑問,你總是可以從敲落的「檢查」裝訂source code複製。

編輯:有些事情要解決,以及如何我固定他們。

  1. 更新viewmodel的value屬性。
  2. 以值編程方式更改value屬性時更新視圖(雙向綁定)。
  3. 如果所選的單選按鈕被刪除,我需要將value屬性設置爲undefined。

有可能是一個更好的方式來實現這一切......

ko.bindingHandlers.radioButtons = { 
    init: function(element, valueAccessor, allBindings){ 
    //... error checks ... Remove all child elements to "element ... 

    var value = allBindings.get("value"); 

    // 1. Update viewmodel 
    ko.utils.registerEventHandler(element, "click", function(event){ 
     var target = event.target; 
     if(target.nodeName.toLowerCase() == "input"){ 
      value(target.value); 
     } 
    }); 

    // 2. Update view 
    value.subscribe(function (newValue) { 
     var inputs = element.getElementsByTagName("input") 
     $.each(inputs, function (i, input) { 
      input.checked = input.value === newValue; 
     }); 
    }; 
    }, 
    update: function (element, valueAccessor, allBindings) { 
     ... 

     var value = allBindings.get("value"); 

     // 3. Edge case: remove radio button of the value selected. 
     var selectedRadioButton = unwrappedArray.find(function (item) { 
      return item.value === value(); 
     }); 
     if (selectedRadioButton == null) { 
      value(undefined); 
     } 
    } 
}