Powiązanie prawda / fałsz z przyciskami opcji w Knockout JS


84

W moim modelu widoku mam wartość IsMale, która ma wartość true lub false.

W moim interfejsie użytkownika chcę powiązać go z następującymi przyciskami radiowymi:

<label>Male
   <input type="radio" name="IsMale" value="true" data-bind="checked:IsMale"/>
</label> 
<label>Female
   <input type="radio" name="IsMale" value="false" data-bind="checked:IsMale"/>
</label>

Myślę, że problem polega na tym, że checkedoczekuje łańcucha „prawda” / „fałsz”. Więc moje pytanie brzmi: jak mogę uzyskać to dwukierunkowe wiązanie w tym interfejsie użytkownika i modelu?


10
W przypadku wersji Knockout> = 3.0 zobacz odpowiedź Natana, aby uzyskać prostsze rozwiązanie niż sugeruje zaakceptowana odpowiedź.
Markus Pscheidt,

Odpowiedzi:


81

Jedną z opcji jest użycie obserwowalnych obliczanych z możliwością zapisu .

W tym przypadku myślę, że fajną opcją jest uczynienie tego, co zapisywalne, wyliczane, obserwowalne, jako „subobserwowalne” twojego IsMaleobserwowalnego. Twój model widoku wyglądałby następująco:

var ViewModel = function() {
   this.IsMale = ko.observable(true);

   this.IsMale.ForEditing = ko.computed({
        read: function() {
            return this.IsMale().toString();  
        },
        write: function(newValue) {
             this.IsMale(newValue === "true");
        },
        owner: this        
    });          
};

Powiążesz to w swoim interfejsie użytkownika w następujący sposób:

<label>Male
   <input type="radio" name="IsMale" value="true" data-bind="checked:IsMale.ForEditing"/>
</label> 
<label>Female
   <input type="radio" name="IsMale" value="false" data-bind="checked:IsMale.ForEditing"/>
</label>

Przykład: http://jsfiddle.net/rniemeyer/Pjdse/


Wydaje się, że to świetne rozwiązanie. Używam wtyczki mapowania, aby odświeżyć moją maszynę wirtualną. Czy wiesz, że to wymazałoby ForEditing na IsMale?
CJ

26
Oto alternatywa, która wypycha obliczoną obserwowalną kreację do niestandardowego powiązania, dzięki czemu nie musisz się martwić o radzenie sobie z nią we wtyczce mapowania: jsfiddle.net/rniemeyer/utsvJ
RP Niemeyer

2
+1 do używania wiązania zamiast tworzenia obserwowalnego na każdym obiekcie
Greg Ennis

1
@RPNiemeyer jest zdecydowanie właściwą drogą.
Andrew,

1
@RPNiemeyer thanks! to skrzypce w komentarzach zadziałały dla mnie.
SFlagg

128

Wiem, że to stary wątek, ale miałem ten sam problem i znalazłem znacznie lepsze rozwiązanie, które prawdopodobnie zostało dodane do nokautu po oficjalnej odpowiedzi na to pytanie, więc zostawię to dla osób z tym samym problemem.

Obecnie nie ma potrzeby stosowania rozszerzeń, niestandardowych programów obsługi powiązań ani obliczeń. Wystarczy podać opcję „CheckValue ”, która użyje jej zamiast atrybutu„ value ”html, dzięki czemu można przekazać dowolną wartość javascript.

<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: true"/>
<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: false"/>

Lub:

<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 1"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 2"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 3"/>

2
Patrząc na źródło, wydaje się, że podejmuje decyzję o użyciu tutaj sprawdzonej wartości . Właściwość „useCheckedValue” jest ustawiana na wartość true, jeśli dane wejściowe to radio lub pole wyboru z wartością jako tablicą . Ponadto używam Knockout 3.0. Zobacz, czy to pomoże.
Natan

1
tak, dzięki, zaktualizowałem do 3.0.0 i teraz jest. Nadal muszę zawijać moje wartości logiczne w łańcuch, ponieważ kod serwera ich oczekiwał ;-)
ZiglioUK

Dobra informacja. Dwa dodatkowe punkty: (1) Zauważyłem, że w przypadku Knockout sprzed wersji 3.0 mogłem użyć valuewiązania, a następnie checkedwiązania (w tej kolejności), ale w wersji 3.0 musiałem użyć checkedValuewiązania zamiast valuewiązania. (2) wersja przed wersją 3.0 była wybredna, jeśli chodzi o wymaganie, aby valuewiązanie poprzedzało checkedpowiązanie, aby działało poprawnie, więc mam wrażenie, że wszystko może działać lepiej w wersji 3.0 we wszystkich scenariuszach, jeśli umieścisz checkedValuewiązanie przed checkedwiązaniem, na przykład pokazują w dokumentach .
Jason Frank

@ZiglioNZ jednym ze sposobów może się nie powieść, jeśli twój checkedustawiacz (lub może coś od niego zależnego)
zgłosi

Używam wersji 3.4 i stwierdzam, że nadal muszę wpisać value = "true" i powyższe, aby działało.
wirble

11

To działa dla mnie:

http://jsfiddle.net/zrBuL/291/

<label>Male
   <input type="radio" name="IsMale" value="1" data-bind="checked:IsMale"/>
</label> 
<label>Female
   <input type="radio" name="IsMale" value="0" data-bind="checked:IsMale"/>
</label>

jedyny problem, jaki widzę, polega na tym, że podczas serializacji modelu widoku w celu przekazania do serwera, otrzymasz liczby całkowite w postaci obserwowalnej (zamiast wartości logicznych). Będziesz musiał zadzwonić vm.IsMale(!!vm.+IsMale());przed serializacją json, aby wysłać go na serwer (na wypadek, gdyby strona serwera nie mogła tego poprawnie obsłużyć)
Artem

Myślę, że masz rację, ale brakuje mojej wiedzy o Javascript. Czy możesz mi wyjaśnić, jak działa !! +? Nie jestem zaznajomiony z tą składnią.
CJ

1
@CJ to konwertuje ciąg lub liczbę na boolean - sprawdź to jsfiddle.net/nickolsky/6ydLZ , jeśli ciąg jest już bool, zachowa go jako bool
Artem

Dziękuję bardzo. Myślę, że to też świetne rozwiązanie.
CJ

1
Nie działa to jednak w obie strony. Przyciski opcji nie są sprawdzane po załadowaniu.
Mikael Östberg

1
ko.bindingHandlers['radiobuttonyesno'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) {
        var stateHandler = function (property, allBindingsAccessor, key, value, checkIfDifferent) {
            if (!property || !ko.isObservable(property)) {
                var propWriters = allBindingsAccessor()['_ko_property_writers'];
                if (propWriters && propWriters[key])
                    propWriters[key](value);
            } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
                property(value);
            }
        };

        var updateHandler = function () {
            var valueToWrite;

            if ((element.type == "radio") && (element.checked)) {
                valueToWrite = element.value;
            } else {
                return; // "radiobuttonyesno" binding only responds to selected radio buttons
            }

            valueToWrite = (valueToWrite === "True") ? true : false;

            var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue); //can be true of false

            stateHandler(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
        };
        ko.utils.registerEventHandler(element, "click", updateHandler);

        // IE 6 won't allow radio buttons to be selected unless they have a name
        if ((element.type == "radio") && !element.name)
            ko.bindingHandlers['uniqueName']['init'](element, function () { return true });
    },
    'update': function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());

        value = value ? "True" : "False";

        if (element.type == "radio") {
            element.checked = (element.value == value);
        }
    }
};

Użyj tego segregatora zamiast tworzyć głupie obserwable obliczone ko.

Przykład:

<label>Male
        <input type="radio" name="IsMale" value="True" data-bind="radiobuttonyesno:IsMale"/>
     </label> 
     <label>Female
        <input type="radio" name="IsMale" value="False" data-bind="radiobuttonyesno:IsMale"/>
     </label>

1

Gdy już zorientujesz się, że początkowe dopasowanie przycisku radiowego chce dopasować tylko ciąg i chce ustawić wartość na ciąg, wystarczy przekonwertować wartość początkową na łańcuch. Musiałem z tym walczyć wartościami Int.

Po skonfigurowaniu swoich obserwabli, zamień wartość na łańcuch, a KO wykona swoją magię z tego miejsca. Jeśli mapujesz z pojedynczymi liniami, wykonaj konwersję w tych liniach.

W przykładowym kodzie używam Json do mapowania całego modelu w jednym poleceniu. Następnie pozwalając Razorowi wstawić wartość między cudzysłowami do konwersji.

script type="text/javascript">
    KoSetup.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));
    KoSetup.ViewModel.ManifestEntered("@Model.ManifestEntered");       //Bool
    KoSetup.ViewModel.OrderStatusID("@Model.OrderStatusID");           //Int
</script>

Podczas programowania używam opcji „Zrzuć to wszystko na ekran” na dole mojej strony internetowej.

<h4>Debug</h4>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>

Oto wartości danych, przed

"OrderStatusID": 6,
"ManifestEntered": true,

i po

"OrderStatusID": "6",
"ManifestEntered": "True",

W moim projekcie nie musiałem konwertować Bools, ponieważ mogę użyć pola wyboru, które nie powoduje takiej samej frustracji.


0

Dlaczego nie po prostu prawda i fałsz zamiast 1 i 0?

 <label>Male
    <input type="radio" name="IsMale" value="true" data-bind="checked:IsMale"/>
 </label> 
 <label>Female
    <input type="radio" name="IsMale" value="false" data-bind="checked:IsMale"/>
 </label>

2
jeśli wysyłasz obiekty json, ma to znaczenie.
Doktor

0

Możesz także użyć przedłużacza, aby łatwo było ich ponownie użyć w celu uzyskania większej liczby obserwacji:

ko.extenders.boolForEditing = function (target, allowNull) {
    var result = ko.computed({
        read: function () {
            var current = target();
            var newValue = null;
            if (current === undefined || current === null || current === '') {
                if (!allowNull) {
                    newValue = 'false';
                }
            } else {
                newValue = current ? 'true' : 'false';
            }
            return newValue;
        },
        write: function (newValue) {
            var current = target();
            var valueToWrite = null;
            if (newValue === undefined || newValue === null || newValue === '') {
                if (!allowNull) {
                    valueToWrite = false;
                }
            } else {
                valueToWrite = newValue === 'true';
            }
            // only write if it changed
            if (valueToWrite !== current) {
                target(valueToWrite);
            } else {
                if (newValue !== current) {
                    target.notifySubscribers(valueToWrite);
                }
            }
        }
    }).extend({
        notify: 'always'
    });

    result(target());

    return result;
};

Następnie użyj tego w ten sposób:

this.IsMale.forEditing = this.IsMale.extend({boolForEditing: true});

Parametr podany w celu boolForEditingwskazania, czy wartość może być null.

Zobacz http://jsfiddle.net/G8qs9/1/


0

Po przeprowadzeniu wielu badań dla starszej wersji knockout przed 3.0 są prawdopodobnie dwie najlepsze opcje

Utwórz przedłużacz do wybijania, taki jak

ko.extenders["booleanValue"] = function (target) {
    target.formattedValue = ko.computed({
        read: function () {
            if (target() === true) return "True";
            else if (target() === false) return "False";
        },
        write: function (newValue) {
            if (newValue) {
                if (newValue === "False") target(false);
                else if (newValue === "True") target(true);
            }
        }
    });

    target.formattedValue(target());
    return target;
};

Aby użyć przedłużacza w swoim modelu, wykonaj coś takiego:

function Order() {
  this.wantsFries= ko.observable(false).extend({ booleanValue: null });
}

<span>Do you want fries with that?</span>
<label>
  <input type="radio" name="question" value="True"
             data-bind="value: wantsFries.formattedValue" /> Yes
</label>
<label>
  <input type="radio" name="question" value="False"
             data-bind="value: wantsFries.formattedValue" /> No
</label>

źródło: http://www.timlabonne.com/2013/02/building-a-knockout-js-extender-for-boolean-values/

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.