Comment puis-je faire en sorte que Knockout JS se lie aux données en appuyant sur une touche au lieu de perdre la mise au point?

191

Cet exemple de knockout js fonctionne donc lorsque vous éditez un champ et appuyez sur TAB, les données du modèle de vue et donc le texte sous les champs sont mis à jour.

Comment puis-je modifier ce code afin que les données de viewmodel soient mises à jour à chaque pression de touche?

texte alternatif

<!doctype html>
<html>
    <title>knockout js</title>
    <head>
        <script type="text/javascript" src="js/knockout-1.1.1.debug.js"></script>
        <script type="text/javascript">
        window.onload= function() {

            var viewModel = {
                firstName : ko.observable("Jim"),
                lastName : ko.observable("Smith")
            };
            viewModel.fullName = ko.dependentObservable(function () {
                return viewModel.firstName() + " " + viewModel.lastName();
            });

            ko.applyBindings(viewModel);
       }
        </script>
    </head>
    <body>
        <p>First name: <input data-bind="value: firstName" /></p>
        <p>Last name: <input data-bind="value: lastName" /></p>
        <h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
    </body>
</html>
Edward Tanguay
la source
9
Dans KO 3.2 pas besoin de faire toutes ces solutions de contournement: stackoverflow.com/a/25493265/1090562
Salvador Dali
Salvador a raison - il le fait automatiquement dans la dernière version.
vapcguy

Réponses:

299
<body>
        <p>First name: <input data-bind="value: firstName, valueUpdate: 'afterkeydown'" /></p>
        <p>Last name: <input data-bind="value: lastName, valueUpdate: 'afterkeydown'" /></p>
        <h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
</body>

De la documentation

Paramètres supplémentaires

  • valueUpdate

    Si votre liaison inclut également un paramètre appelé valueUpdate, cela définit l'événement de navigateur que KO doit utiliser pour détecter les modifications. Les valeurs de chaîne suivantes sont les choix les plus utiles:

    • "change" (par défaut) - met à jour votre modèle de vue lorsque l'utilisateur déplace le focus vers un contrôle différent, ou dans le cas d'éléments, immédiatement après toute modification

    • "keyup" - met à jour votre modèle de vue lorsque l'utilisateur libère une clé

    • "keypress" - met à jour votre modèle de vue lorsque l'utilisateur a tapé une touche. Contrairement à keyup, cela se met à jour à plusieurs reprises pendant que l'utilisateur maintient une touche enfoncée

    • "afterkeydown" - met à jour votre modèle de vue dès que l'utilisateur commence à taper un caractère. Cela fonctionne en capturant l'événement keydown du navigateur et en gérant l'événement de manière asynchrone.

Parmi ces options, "afterkeydown" est le meilleur choix si vous souhaitez garder votre modèle de vue mis à jour en temps réel.

Sergei Golos
la source
9
from the docs in knockout.js: // La syntaxe "after <eventname>" signifie "exécuter le gestionnaire de manière asynchrone après l'événement" // Ceci est utile, par exemple, pour intercepter les événements "keydown" après que le navigateur a mis à jour le contrôle
John Wright
4
Est-il possible d'obtenir valueUpdate: 'afterkeydown' comme valeur par défaut ?
Aaron Shafovaloff
2
pour certaines boîtes de saisie, le navigateur affiche des valeurs "auto-populate" (comme si le champ de saisie est nommé "firstname"). la sélection à partir du remplissage automatique ne fonctionne pas avec "afterkeydown". y a-t-il un moyen d'attraper ça?
Anton
2
@Anton En plus des valeurs de remplissage automatique, il existe également des clics de clavier à l'écran et des événements de collage de souris qui doivent être capturés. L'utilisation afterkeydownest une mauvaise solution.
John Kurlak
2
Auteur de cette réponse, veuillez mettre à jour pour inclure également la réponse de Salvador Dali, textInput ajouté à partir de 3.2 est une meilleure solution car il a une meilleure compatibilité avec les navigateurs mobiles (des choses comme l'auto-complétion fonctionnent mieux)
Michael Crook
85

Dans la version 3.2, vous pouvez simplement utiliser la liaison d'entrée de texte. :

<input data-bind="textInput: userName" />

Il fait deux choses importantes:

  • faire des mises à jour immédiates
  • gère les différences du navigateur pour couper, glisser, compléter automatiquement ...

Donc pas besoin de modules supplémentaires, de contrôles personnalisés et d'autres choses.

Salvador Dali
la source
Cela fonctionne vraiment bien et c'est exactement ce dont j'avais besoin pour remplacer la valeur: field, valueUpdate: 'input change' que j'avais tout au long de mon application ... :) merci.
Tod Thomson
Existe-t-il un moyen de capturer l'événement lorsqu'un utilisateur clique sur l'icône «x» du nouveau champ d'entrée de recherche HTML5 et agit comme si l'entrée était vide?
Codrina Valo
35

Si vous souhaitez qu'il effectue des mises à jour sur afterkeydown«par défaut», vous pouvez injecter la valueUpdateliaison dans le valuegestionnaire de liaison. Fournissez simplement un nouveau allBindingsAccessorpour le gestionnaire à utiliser qui inclut afterkeydown.

(function () {
    var valueHandler = ko.bindingHandlers.value;
    var getInjectValueUpdate = function (allBindingsAccessor) {
        var AFTERKEYDOWN = 'afterkeydown';
        return function () {
            var allBindings = ko.utils.extend({}, allBindingsAccessor()),
                valueUpdate = allBindings.valueUpdate;

            if (valueUpdate === undefined) {
                return ko.utils.extend(allBindings, { valueUpdate: AFTERKEYDOWN });
            } else if (typeof valueUpdate === 'string' && valueUpdate !== AFTERKEYDOWN) {
                return ko.utils.extend(allBindings, { valueUpdate: [valueUpdate, AFTERKEYDOWN] });
            } else if (typeof valueUpdate === 'array' && ko.utils.arrayIndexOf(valueUpdate, AFTERKEYDOWN) === -1) {
                valueUpdate = ko.utils.arrayPushAll([AFTERKEYDOWN], valueUpdate);
                return ko.utils.extend(allBindings, {valueUpdate: valueUpdate});
            }
            return allBindings;
        };
    };
    ko.bindingHandlers.value = {
        // only needed for init
        'init': function (element, valueAccessor, allBindingsAccessor) {
            allBindingsAccessor = getInjectValueUpdate(allBindingsAccessor);
            return valueHandler.init(element, valueAccessor, allBindingsAccessor);
        },
        'update': valueHandler.update
    };
} ());

Si vous n'êtes pas à l'aise pour «remplacer» la valueliaison, vous pouvez attribuer un nom différent à la liaison personnalisée de substitution et utiliser ce gestionnaire de liaison.

ko.bindingHandlers.realtimeValue = { 'init':..., 'update':... };

démo

Une solution comme celle-ci conviendrait à la version 2.x de Knockout. L'équipe Knockout a développé une liaison plus complète pour les entrées de type texte via la liaison textInput dans Knockout version 3 et plus. Il a été conçu pour gérer toutes les méthodes de saisie de texte pour les entrées de texte et textarea. Il gérera même la mise à jour en temps réel, ce qui rendra cette approche obsolète.

Jeff Mercado
la source
2
De cette façon, mon balisage est un peu plus net. De plus, en plus des événements «afterkeydown», des événements «input» et «paste» peuvent être ajoutés pour gérer les actions coller / glisser-déposer.
bob
Dans la solution ci-dessus, je pense que "typeof valueUpdated === 'array'" doit être remplacé par "Object.prototype.toString.call (valueUpdate) === '[object Array]'". Voir stackoverflow.com/questions/4775722/…
daveywc
20

La réponse de Jeff Mercado est fantastique, mais malheureusement rompue avec Knockout 3.

Mais j'ai trouvé la réponse suggérée par les développeurs de ko en travaillant sur les changements de Knockout 3. Voir les commentaires du bas sur https://github.com/knockout/knockout/pull/932 . Leur code:

//automatically add valueUpdate="afterkeydown" on every value binding
(function () {
    var getInjectValueUpdate = function (allBindings) {
        return {
            has: function (bindingKey) {
                return (bindingKey == 'valueUpdate') || allBindings.has(bindingKey);
            },
            get: function (bindingKey) {
                var binding = allBindings.get(bindingKey);
                if (bindingKey == 'valueUpdate') {
                    binding = binding ? [].concat(binding, 'afterkeydown') : 'afterkeydown';
                }
                return binding;
            }
        };
    };

    var valueInitHandler = ko.bindingHandlers.value.init;
    ko.bindingHandlers.value.init = function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        return valueInitHandler(element, valueAccessor, getInjectValueUpdate(allBindings), viewModel, bindingContext);
    };
}());

http://jsfiddle.net/mbest/GKJnt/

Edit Ko 3.2.0 a maintenant une solution plus complète avec la nouvelle liaison "textInput". Voir la réponse de SalvidorDali

RockResolve
la source
1
Merci d'avoir mis à jour la réponse à 3.0!
Graham T
@GrahamT dans KO 3.2 vous n'en avez même pas besoin. C'est juste une ligne
Salvador Dali
Très bien pour ceux d'entre nous sur KO 3.2 qui ne connaissions pas TextInput quand nous avons commencé, et ne peuvent pas mettre à jour toutes les entrées dans tout le système!
cscott530