Lier vrai / faux aux boutons radio dans Knockout JS

84

Dans mon modèle de vue, j'ai une valeur IsMale qui a la valeur true ou false.

Dans mon interface utilisateur, je souhaite le lier aux boutons radio suivants:

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

Le problème que je pense est checkedattend une chaîne "vrai" / "faux". Ma question est donc la suivante: comment puis-je obtenir cette liaison bidirectionnelle avec cette interface utilisateur et ce modèle?

CJ
la source
10
Pour les versions Knockout> = 3.0, voir la réponse de Natan pour une soution plus simple que celle suggérée par la réponse acceptée.
Markus Pscheidt

Réponses:

81

Une option consiste à utiliser une observable calculée inscriptible .

Dans ce cas, je pense qu'une bonne option est de faire de l'observable calculée inscriptible une "sous-observable" de votre IsMaleobservable. Votre modèle de vue ressemblerait à ceci:

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

Vous le lieriez dans votre interface utilisateur comme:

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

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

RP Niemeyer
la source
Cela semble être une excellente solution. J'utilise le pluggin de mappage pour actualiser ma VM. Le savez-vous qui effacerait ForEditing sur IsMale?
CJ
26
Voici une alternative qui pousse la création observable calculée dans une liaison personnalisée, ce qui ferait en sorte que vous n'ayez pas à vous soucier de la gérer dans le plugin de mappage: jsfiddle.net/rniemeyer/utsvJ
RP Niemeyer
2
+1 à utiliser une liaison au lieu de créer une observable sur chaque objet
Greg Ennis
1
@RPNiemeyer est définitivement la voie à suivre.
Andrew
1
@RPNiemeyer merci! ce violon dans les commentaires est celui qui a fonctionné pour moi.
SFlagg
128

Je sais que c'est un vieux fil de discussion, mais j'avais le même problème et j'ai découvert une bien meilleure solution qui a probablement été ajoutée à KO après que cette question ait été officiellement répondue, alors je vais laisser cela aux personnes ayant le même problème.

Actuellement, il n'y a pas besoin d'extendeurs, de gestionnaires de liaison personnalisés ou de calculés. Fournissez simplement une option "checkedValue", il l'utilisera à la place de l'attribut html 'value', et avec cela, vous pouvez passer n'importe quelle valeur javascript.

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

Ou:

<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
la source
2
En regardant la source, il semble prendre la décision d'utiliser la valeur cochée ici . Le 'useCheckedValue' est défini sur true si l'entrée est une radio ou une case à cocher avec la valeur sous forme de tableau . De plus, j'utilise Knockout 3.0. Voyez si cela aide.
Natan
1
oui, merci, j'ai mis à niveau vers 3.0.0 et maintenant c'est là. Encore faut-il envelopper mes valeurs booléennes dans une chaîne car le code du serveur les attendait ;-)
ZiglioUK
Bonne info. Deux points supplémentaires: (1) J'ai trouvé qu'avec Knockout pré-v3.0, je pouvais utiliser la valueliaison puis la checkedliaison (dans cet ordre), mais avec 3.0, je devais utiliser la checkedValueliaison au lieu de la valueliaison. (2) avant la v3.0 était difficile d'exiger que la valueliaison précède la checkedliaison pour fonctionner correctement, j'ai donc le sentiment que les choses pourraient également mieux fonctionner dans la v3.0 dans tous les scénarios si vous mettez la checkedValueliaison avant la checkedliaison, comme ils apparaissent dans les documents .
Jason Frank
@ZiglioNZ une façon dont cela peut échouer est si votre checkedpasseur (ou peut-être quelque chose qui en dépend) lève une erreur - alors la case à cocher correcte n'est pas cochée
Simon_Weaver
J'utilise la version 3.4 et constate que je dois encore mettre value = "true" et ce qui précède pour que cela fonctionne.
wirble
11

Cela fonctionne pour moi:

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
la source
le seul problème que je vois est que lors de la sérialisation de viewmodel à passer au serveur, vous obtiendrez des entiers dans observable (au lieu de booléens). Vous devrez appeler vm.IsMale(!!vm.+IsMale());avant de sérialiser json pour l'envoyer au serveur (au cas où le côté serveur ne pourrait pas le gérer correctement)
Artem
Je pense que vous avez raison, mais mes connaissances en Javascript font défaut. Pouvez-vous m'expliquer comment fonctionne !! +? Je ne connais pas cette syntaxe.
CJ
1
@CJ cela convertit une chaîne ou un nombre en booléen - vérifiez ce jsfiddle.net/nickolsky/6ydLZ , si la chaîne est déjà booléenne , elle la conservera sous forme de booléen
Artem
Merci beaucoup. Je pense que c'est aussi une excellente solution.
CJ
1
Cela ne fonctionne pas dans les deux sens. Les boutons radio ne sont pas cochés lors du chargement.
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);
        }
    }
};

Utilisez ce classeur au lieu de créer des observables calculés par ko stupides.

Exemple:

<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
la source
1

Une fois que vous avez compris que la correspondance initiale pour le bouton radio ne veut correspondre qu'à une chaîne et que vous souhaitez définir la valeur sur une chaîne, il s'agit simplement de convertir votre valeur initiale en chaîne. J'ai dû lutter contre cela avec les valeurs d'Int.

Après avoir configuré vos observables, convertissez la valeur en chaîne et KO fera sa magie à partir de là. Si vous mappez avec des lignes individuelles, effectuez la conversion dans ces lignes.

Dans l'exemple de code, j'utilise Json pour mapper l'ensemble du modèle en une seule commande. Laissez ensuite Razor insérer la valeur entre les guillemets pour la conversion.

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>

J'utilise un "Dump it all to the screen" en bas de ma page Web pendant le développement.

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

Voici les valeurs des données, avant

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

et, après

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

Dans mon projet, je n'ai pas eu besoin de convertir des Bools, car je suis capable d'utiliser une case à cocher qui n'a pas la même frustration.

Greg Little
la source
0

Pourquoi pas simplement vrai et faux au lieu de 1 et 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>
Docteur
la source
2
si vous envoyez des objets json, cela fait la différence.
Docteur
0

Vous pouvez également utiliser un extenseur afin qu'il soit facile de les réutiliser pour plus d'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;
};

Ensuite, utilisez-le comme ceci:

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

Le paramètre fourni à boolForEditing indique si la valeur peut être nulle.

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

sroes
la source
0

Après avoir effectué de nombreuses recherches sur l'ancienne version de KO avant la version 3.0, il existe probablement deux meilleures options.

Créez un prolongateur knockout comme

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

Pour utiliser l'extension sur votre modèle, procédez comme suit:

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>

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

Anshul Nigam
la source