1

Ich versuche eine benutzerdefinierte Bindung in Knockout zu erstellen, die wie der options Standardbindungshandler funktioniert, aber Radiobuttons anstelle eines Dropdowns verwendet.Knockoutjs Optionsfeldgruppe Benutzerdefinierte Bindung wird bei Auswahl nicht aktualisiert

Das Hinzufügen eines Elements zum Array meldet die update, aber das Auswählen eines anderen Optionsfelds funktioniert nicht.

Hinweis: Ich habe den Custom-Binding-Handler auf das Nötigste reduziert.

Binding Handler

// 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); 
    }, 
}; 

Wie es zu benutzen ...

// 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); 

So, das Hinzufügen eines neuen Elements zur vm.letters({ text: "C", value: 2 }) die update benachrichtigen. Wenn Sie jedoch auf eine andere Optionsschaltfläche klicken, wird die update nicht benachrichtigt.

Was muss ich tun, damit das Klicken auf den Radiobutton die update benachrichtigt?

DEMO HERE

+0

habe ich einen Hack, aber jetzt fühle ich mich schmutzig ... '$ (Eingang) .auf ("Klick", function() {var value = allBindings.get ("value"); value (this.value);}); ' – christo8989

+0

Ich habe auch bemerkt, dass ich mit' vm.value' keine bidirektionale Bindung bekomme . – christo8989

Antwort

1

Es sieht nicht wie Ihre Bindung hat einen Weg, um die beobachtbaren, wenn die Auswahl Änderungen zu aktualisieren. Die bidirektionale Bindung erfolgt nicht automatisch mit benutzerdefinierten Bindungen, es sei denn, Sie übergeben Parameter nur an eine andere vorhandene Bindung. Event-Handler wie Ihr "Hack" sind genau das, wofür die "init" -Funktion einer benutzerdefinierten Bindung normalerweise verwendet wird.

Die „init“ Rückruf

Knockout Ihre init-Funktion einmal für jedes DOM-Element nennen, dass Sie verwenden> auf die Bindung. Es gibt zwei Hauptverwendungszwecke für init:

  • für das DOM-Element jeden Anfangszustand einzustellen
  • alle Event-Handler zu registrieren, so dass zum Beispiel, wenn der Benutzer klickt auf oder ändert das DOM-Element, Sie ändern kann, um den Zustand des zugehörigen beobachtbaren

Versuchen Zugabe so etwas wie die folgenden init Funktion, um Ihre Auswahlknöpfe Bindung:

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) { 
     ... 
    } 

Es gibt wahrscheinlich sicherere Möglichkeiten, das Klickereignis zu behandeln, als zu überprüfen, ob das Ziel ein "Eingabe" -Element ist. Das ist nur ein schnelles Beispiel. Sie müssen das ändern, wenn Sie Radiobuttons oder andere Arten von Eingabeelementen als untergeordnete Elemente in Ihrem Radiobutton-Element verschachtelt haben. Im Zweifelsfall können Sie immer aus dem Knockout "checked" Bindung source code kopieren.

EDIT: Einige Dinge zu beheben und wie ich sie behoben.

  1. Aktualisieren Sie die Werteigenschaft des Viewmodels.
  2. Aktualisieren Sie die Ansicht, wenn die Werteigenschaft programmgesteuert geändert wird (bidirektionale Bindung).
  3. Wenn das ausgewählte Optionsfeld entfernt wird, muss ich die Eigenschaft value auf undefined setzen.

ist es wahrscheinlich ein besserer Weg, um all dies zu erreichen ...

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); 
     } 
    } 
} 
Verwandte Themen