Vinculando verdadero / falso a botones de radio en Knockout JS

84

En mi modelo de vista, tengo un valor IsMale que tiene el valor verdadero o falso.

En mi interfaz de usuario, deseo vincularlo a los siguientes botones de opción:

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

Creo que el problema es que checkedespera una cadena "verdadero" / "falso". Entonces, mi pregunta es, ¿cómo puedo obtener este enlace bidireccional con esta interfaz de usuario y modelo?

CJ
fuente
10
Para las versiones Knockout> = 3.0, vea la respuesta de Natan para una solución más simple que la sugerida por la respuesta aceptada.
Markus Pscheidt

Respuestas:

81

Una opción es utilizar un observable calculado que se pueda escribir .

En este caso, creo que una buena opción es hacer que el observable calculado que se puede escribir sea un "subaobservable" de su IsMaleobservable. Su modelo de vista se vería así:

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 vincularía en su interfaz de usuario como:

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

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

RP Niemeyer
fuente
Parece una gran solución. Utilizo el complemento de mapeo para actualizar mi VM. ¿Sabe que acabaría con ForEditing en IsMale?
CJ
26
Aquí hay una alternativa que empuja la creación observable calculada a un enlace personalizado, lo que lo haría para que no tenga que preocuparse por tratar con él en el complemento de mapeo: jsfiddle.net/rniemeyer/utsvJ
RP Niemeyer
2
+1 para usar un enlace en lugar de crear un observable en cada objeto
Greg Ennis
1
@RPNiemeyer es definitivamente el camino a seguir.
Andrew
1
@RPNiemeyer gracias! ese violín en los comentarios es el que me funcionó.
SFlagg
128

Sé que este es un hilo antiguo, pero estaba teniendo el mismo problema y descubrí una solución mucho mejor que probablemente se agregó al nocaut después de que esta pregunta fuera respondida oficialmente, así que lo dejaré para las personas con el mismo problema.

Actualmente no hay necesidad de extensores, manejadores de enlace personalizados o computados. Simplemente proporcione una opción "checkValue", la usará en lugar del atributo html 'value', y con eso puede pasar cualquier valor 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"/>
Natan
fuente
2
Mirando la fuente, parece tomar la decisión de usar el valor verificado aquí . El 'useCheckedValue' se establece en verdadero si la entrada es una radio o una casilla de verificación con el valor como una matriz . Además, estoy usando Knockout 3.0. Vea si eso ayuda.
Natan
1
sí, gracias, he actualizado a 3.0.0 y ahora está ahí. Todavía necesito envolver mis valores booleanos en una cadena porque el código del servidor los esperaba ;-)
ZiglioUK
Buena informacion. Dos puntos adicionales: (1) Descubrí que con Knockout anterior a la versión 3.0 pude usar el valueenlace y luego el checkedenlace (en ese orden), pero con 3.0 tuve que usar el checkedValueenlace en lugar del valueenlace. (2) pre-v3.0 era exigente al requerir que el valueenlace preceda al checkedenlace para que funcione correctamente, así que tengo la sensación de que las cosas también podrían funcionar mejor en v3.0 en todos los escenarios si coloca el checkedValueenlace antes del checkedenlace, como se muestran en los documentos .
Jason Frank
@ZiglioNZ una forma en que esto puede fallar es si su checkedconfigurador (o tal vez algo que depende de él) genera un error, entonces la casilla de verificación correcta no está marcada
Simon_Weaver
Estoy usando la versión 3.4 y encuentro que todavía tengo que poner value = "true" y lo anterior para que funcione.
wirble
11

Esto funciona para mi:

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>
Artem
fuente
el único problema que veo es que al serializar el modelo de vista para pasarlo al servidor, obtendrá enteros en observables (en lugar de booleanos). Deberá llamar vm.IsMale(!!vm.+IsMale());antes de serializar json para enviarlo al servidor (en caso de que el lado del servidor no pueda manejarlo correctamente)
Artem
Creo que tienes razón, pero mi conocimiento de Javascript es deficiente. ¿Puedes explicarme cómo funciona !! +? No estoy familiarizado con esa sintaxis.
CJ
1
@CJ, esto convierte la cadena o el número en booleano: compruebe este jsfiddle.net/nickolsky/6ydLZ , si la cadena ya es bool, la mantendrá como bool
Artem
Muchas gracias. Creo que esta también es una gran solución.
CJ
1
Sin embargo, no funciona en ambos sentidos. Los botones de opción no se marcan cuando se cargan.
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);
        }
    }
};

Utilice esta carpeta en lugar de crear observables calculados ko estúpidos.

Ejemplo:

<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>
KurtJ
fuente
1

Una vez que se dé cuenta de que la coincidencia inicial para el botón de opción solo quiere hacer coincidir una cadena y desea establecer el valor en una cadena, es simplemente una cuestión de convertir su valor inicial en una cadena. Tuve que luchar contra esto con los valores de Int.

Una vez que haya configurado sus observables, convierta el valor en una cadena y KO hará su magia desde allí. Si está mapeando con líneas individuales, realice la conversión en esas líneas.

En el código de ejemplo, estoy usando Json para mapear todo el modelo en un solo comando. Luego, dejando que Razor inserte el valor entre las comillas para la conversión.

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>

Utilizo un "Volcarlo todo a la pantalla" en la parte inferior de mi página web durante el desarrollo.

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

Aquí están los valores de los datos, antes

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

y, después

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

En mi proyecto, no necesitaba convertir Bools, porque puedo usar una casilla de verificación que no tiene la misma frustración.

Greg Little
fuente
0

¿Por qué no simplemente verdadero y falso en lugar de 1 y 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>
Doctor
fuente
2
si está enviando objetos json, hace la diferencia.
Doctor
0

También puede usar un extensor para que sea fácil reutilizarlos para más observables:

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

Entonces úsalo así:

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

El parámetro proporcionado para boolForEditingindica si el valor puede ser nulo.

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

angustia
fuente
0

Después de investigar mucho para la versión anterior de knockout anterior a la 3.0, posiblemente haya dos mejores opciones

Crea un extensor de knockout como

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

Para usar el extensor en su modelo, haría algo como lo siguiente:

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>

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

Anshul Nigam
fuente