Associazione vero / falso ai pulsanti di opzione in Knockout JS


84

Nel mio modello di visualizzazione ho un valore IsMale che ha il valore vero o falso.

Nella mia interfaccia utente desidero associarlo ai seguenti pulsanti di opzione:

<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>

Il problema credo sia che si checkedaspetta una stringa "true" / "false". Quindi la mia domanda è: come posso ottenere questa associazione a 2 vie con questa interfaccia utente e modello?


10
Per le versioni Knockout> = 3.0 vedere la risposta di Natan per una soluzione più semplice di quella suggerita dalla risposta accettata.
Markus Pscheidt

Risposte:


81

Un'opzione è usare un osservabile calcolato scrivibile .

In questo caso, penso che una buona opzione sia quella di rendere l'osservabile calcolato scrivibile un "sub-osservabile" del tuo IsMaleosservabile. Il tuo modello di visualizzazione sarebbe simile a:

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

Lo legheresti nella tua interfaccia utente come:

<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>

Esempio: http://jsfiddle.net/rniemeyer/Pjdse/


Sembra un'ottima soluzione. Uso il mapping pluggin per aggiornare la mia VM. Lo sai che cancellerebbe ForEditing su IsMale?
CJ

26
Ecco un'alternativa che spinge la creazione osservabile calcolata in un'associazione personalizzata, il che la farebbe in modo da non doverti
RP Niemeyer

2
+1 all'uso di un'associazione invece di creare un'osservabile su ogni oggetto
Greg Ennis,

1
@RPNiemeyer è sicuramente la strada da percorrere.
Andrew

1
@RPNiemeyer grazie! quel violino nei commenti è quello che ha funzionato per me.
SFlagg

128

So che questo è un vecchio thread, ma stavo avendo lo stesso problema e ho trovato una soluzione molto migliore che è stata probabilmente aggiunta a knockout dopo che questa domanda è stata ufficialmente risolta, quindi lo lascerò solo per le persone con lo stesso problema.

Attualmente non sono necessari estensori, gestori di binding personalizzati o calcoli. Fornisci semplicemente un'opzione "CheckValue", la userà al posto dell'attributo html 'value', e con ciò puoi passare qualsiasi valore javascript.

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

O:

<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
Guardando la fonte, sembra prendere la decisione di utilizzare il valore selezionato qui . "UseCheckedValue" è impostato su true se l'input è una radio o una casella di controllo con il valore come array . Inoltre, sto usando Knockout 3.0. Vedi se questo aiuta.
Natan

1
sì, grazie, ho aggiornato alla 3.0.0 e ora è lì. Devo ancora racchiudere i miei valori booleani in una stringa perché il codice del server si aspettava quelli ;-)
ZiglioUK

Buone informazioni. Due punti aggiuntivi: (1) Ho scoperto che con la versione precedente alla v3.0 Knockout ero in grado di usare l' valueassociazione e poi l' checkedassociazione (in quest'ordine), ma con la 3.0 dovevo usare l' checkedValueassociazione invece dell'associazione value. (2) la pre-v3.0 era esigente nel richiedere che l' valueassociazione precedesse l' checkedassociazione per funzionare correttamente, quindi ho la sensazione che le cose potrebbero funzionare meglio anche nella v3.0 in tutti gli scenari se metti l' checkedValueassociazione prima checkeddell'associazione, come mostrano nei documenti .
Jason Frank

@ZiglioNZ un modo in cui questo può fallire è se il tuo checkedsetter (o forse qualcosa che dipende da esso) genera un errore - quindi la casella di controllo corretta non è selezionata
Simon_Weaver

Sto usando la versione 3.4 e scopro che devo ancora mettere value = "true" e quanto sopra per farlo funzionare.
wirble

11

Questo funziona per me:

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>

l'unico problema che vedo è che durante la serializzazione di viewmodel da passare al server, otterrai numeri interi osservabili (invece di booleani). Dovrai chiamare vm.IsMale(!!vm.+IsMale());prima di serializzare json per inviarlo al server (nel caso in cui il lato server non possa gestirlo correttamente)
Artem

Penso che tu abbia ragione, ma la mia conoscenza di Javascript è carente. Puoi spiegarmi come funziona !! +? Non ho familiarità con quella sintassi.
CJ

1
@CJ questo converte la stringa o il numero in booleano - controlla questo jsfiddle.net/nickolsky/6ydLZ , se la stringa è già bool lo manterrà come bool
Artem

Grazie mille. Penso che anche questa sia un'ottima soluzione.
CJ

1
Tuttavia, non funziona in entrambi i modi. I pulsanti di opzione non vengono selezionati quando vengono caricati.
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);
        }
    }
};

Usa questo raccoglitore invece di creare stupide osservabili calcolate ko.

Esempio:

<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

Una volta capito che la corrispondenza iniziale per il pulsante di opzione vuole corrispondere solo a una stringa e desidera impostare il valore su una stringa, si tratta semplicemente di convertire il valore iniziale in stringa.Ho dovuto combattere questo con i valori Int.

Dopo aver impostato i tuoi osservabili, converti il ​​valore in stringa e KO farà la sua magia da lì. Se stai mappando con singole linee, esegui la conversione in quelle linee.

Nel codice di esempio, sto usando Json per mappare l'intero modello in un singolo comando. Quindi lasciando che Razor inserisca il valore tra le virgolette per la conversione.

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>

Uso "Scarica tutto sullo schermo" nella parte inferiore della mia pagina web durante lo sviluppo.

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

Ecco i valori dei dati, Prima

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

e, dopo

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

Nel mio progetto, non avevo bisogno di convertire Bools, perché sono in grado di utilizzare una casella di controllo che non ha la stessa frustrazione.


0

Perché non semplicemente vero e falso invece di 1 e 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
se invii oggetti json fa la differenza.
Dottore

0

Puoi anche usare un extender in modo che sia facile riutilizzarli per più osservabili:

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;
};

Quindi usalo in questo modo:

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

Il parametro fornito per boolForEditingindica se il valore può essere nullo.

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


0

Dopo aver fatto molte ricerche per la versione precedente di knockout precedente alla 3.0, ci sono forse due migliori opzioni

Crea un estensore knockout come

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;
};

Per utilizzare l'extender sul tuo modello, devi fare qualcosa di simile a quanto segue:

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>

fonte: http://www.timlabonne.com/2013/02/building-a-knockout-js-extender-for-boolean-values/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.