web-dev-qa-db-fra.com

Radio Knockout + Bootstrap 3 boutons

En rapport avec: Groupe de boutons radio Bootstrap

HTML:

<div class="btn-group" data-toggle="buttons">
    <label class="btn btn-primary">
        <input type="radio" name="options" id="option1" value="1" data-bind="checked: optionsValue"> Option 1
    </label>
    <label class="btn btn-primary">
        <input type="radio" name="options" id="option2" value="2" data-bind="checked: optionsValue"> Option 2
    </label>
    <label class="btn btn-primary">
        <input type="radio" name="options" id="option3" value="3" data-bind="checked: optionsValue"> Option 3
    </label>
</div>
<br />
<span data-bind="text: optionsValue"></span>

Javascript:

var ViewModel = function() {
    this.optionsValue = ko.observable()
};

ko.applyBindings(new ViewModel());

JsFiddle:


J'ai le code ci-dessus que j'essaye de faire fonctionner comme je l'attends. Le problème est que lorsque data-toggle="buttons" est ajouté au div de btn-group (comme dans l'exemple Bootstrap 3 ), la liaison par knock-out cesse de fonctionner. Si je laisse la bascule de données hors du groupe de boutons, la liaison fonctionne comme prévu, mais le groupe de boutons est affreux. Je sais que cela ne fonctionnait pas dans Bootstrap 2 car ils n'utilisaient pas réellement l'entrée radio pour leur style. Comment se fait-il qu'il refuse de travailler maintenant même s'ils le font?

31
Kittoes0124

Les boutons bootstrap et le knockout checked binding ne lisent toujours pas Nice:

  • knockout utilise l'événement click à l'intérieur de la liaison checked pour activer le changement d'observable sous-jacent
  • bootstrap s'abonne à l'événement click pour effectuer le basculement, mais appelle e.preventDefault() pour que KO ne soit pas averti du clic.

Une solution possible consiste à créer un gestionnaire de liaison personnalisé dans lequel vous vous abonnez à l'événement change (déclenché par bootstrap sur toogle) et définissez la valeur de vos observables à cet emplacement:

ko.bindingHandlers.bsChecked = {
    init: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {
        var value = valueAccessor();
        var newValueAccessor = function () {
            return {
                change: function () {
                    value(element.value);
                }
            }
        };
        ko.bindingHandlers.event.init(element, newValueAccessor,
        allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {
        if ($(element).val() == ko.unwrap(valueAccessor())) {
             setTimeout(function () {
                $(element).closest('.btn').button('toggle');
             }, 1); 
        }
    }
}

Et utilisez-le à vos yeux avec:

<label class="btn btn-primary">
    <input type="radio" name="options" id="option1" value="1" 
           data-bind="bsChecked: optionsValue"> Option 1
</label>

Démo originale utilisant Bootstrap 3.0.2 JSFiddle .
Mise à jour de la démo avec Bootstrap 3.2.0 JSFiddle .

49
nemesv

Je ne peux pas en croire le mérite, car une fois sur mes collègues l'a proposé mais cela fonctionne vraiment bien.

<div class="btn-group" data-toggle="buttons">
    <label data-bind="css: { active: !HideInLeaderboards() }, 
                      click: function () { HideInLeaderboards(false); },
                      clickBubble: false" 
                      class="btn btn-default">
        Show Name
    </label>
    <label data-bind="css: { active: HideInLeaderboards() },
                      click: function () { HideInLeaderboards(true); },
                      clickBubble: false" class="btn btn-default">
        Hide Name
    </label>
</div>
15
Matthew Will

changer le gestionnaire @nemesv a proposé d'être ceci: et cela a bien fonctionné dans mon application.

ko.bindingHandlers.bsChecked = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = valueAccessor();
        var newValueAccessor = function () {
            return {
                change: function () {
                    value(element.value);
                }
            }
        };
        if ($(element).val() == ko.unwrap(valueAccessor())) {
            $(element).closest('.btn').button('toggle');
        }
        ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, viewModel, bindingContext);
    }
}
1
danatcofo

Knockstrap semble être une liaison entre Bootstrap et Knockout, et il semble être assez bien tenu à jour. En ce qui concerne les boutons radio en particulier, ils utilisent ce code:

// Knockout checked binding doesn't work with Bootstrap radio-buttons
ko.bindingHandlers.radio = {
    init: function (element, valueAccessor) {

        if (!ko.isObservable(valueAccessor())) {
            throw new Error('radio binding should be used only with observable values');
        }

        $(element).on('change', 'input:radio', function (e) {
            // we need to handle change event after bootsrap will handle its event
            // to prevent incorrect changing of radio button styles
            setTimeout(function() {
                var radio = $(e.target),
                    value = valueAccessor(),
                    newValue = radio.val();

                value(newValue);
            }, 0);
        });
    },

    update: function (element, valueAccessor) {
        var $radioButton = $(element).find('input[value="' + ko.unwrap(valueAccessor()) + '"]'),
            $radioButtonWrapper;

        if ($radioButton.length) {
            $radioButtonWrapper = $radioButton.parent();

            $radioButtonWrapper.siblings().removeClass('active');
            $radioButtonWrapper.addClass('active');

            $radioButton.prop('checked', true);
        } else {
            $radioButtonWrapper = $(element).find('.active');
            $radioButtonWrapper.removeClass('active');
            $radioButtonWrapper.find('input').prop('checked', false);
        }
    }
};
1
CC Inc

Il existe une approche beaucoup plus facile trouvée ici

Nous pouvons simplement ne pas utiliser l'attribut data-toggle et essayer d'obtenir le même comportement en utilisant la liaison de données de knockout.

C'est presque aussi simple que les radios html natives.

var ViewModel = function() {
  this.optionsValue = ko.observable("1");
};

ko.applyBindings(new ViewModel());
input[type="radio"] {
  /* Make native radio act the same as bootstrap's radio. */
  display: none;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="btn-group">
  <label class="btn btn-primary" data-bind="css: {active: optionsValue() == '1'}">
    <input type="radio" name="options" id="option1" value="1" data-bind="checked: optionsValue">Option 1
  </label>
  <label class="btn btn-primary" data-bind="css: {active: optionsValue() == '2'}">
    <input type="radio" name="options" id="option2" value="2" data-bind="checked: optionsValue">Option 2
  </label>
  <label class="btn btn-primary" data-bind="css: {active: optionsValue() == '3'}">
    <input type="radio" name="options" id="option3" value="3" data-bind="checked: optionsValue">Option 3
  </label>
</div>
<br/>
<span data-bind="text: optionsValue"></span>

1
J3soon

Il y a une solution simple ici qui, je suis surpris, n'a pas encore été mentionnée.

1) Supprimez le data-toggle="buttons" du btn-group

Maintenant, KO devrait fonctionner

2) Bootstrap applique des CSS personnalisés pour ces entrées afin de les masquer, ce que nous venons de perdre en supprimant le basculement des données. Appliquez donc le CSS suivant à la radio input :

position: absolute;
clip: rect(0,0,0,0);
pointer-events: none;

3) La dernière chose dont nous avons besoin est que le bouton de l'option sélectionnée ait la classe active. Ajoutez les éléments suivants aux boutons label :

data-bind="css: {active: optionsValue() == valueOfThisInput}"
0
izzy