Wie kann ich Knockout JS dazu bringen, Daten per Tastendruck zu binden, anstatt den Fokus zu verlieren?

191

Dieses Beispiel für Knockout js funktioniert also, wenn Sie ein Feld bearbeiten und die Tabulatortaste drücken, werden die Ansichtsmodelldaten und damit der Text unter den Feldern aktualisiert.

Wie kann ich diesen Code so ändern, dass die Daten des Ansichtsmodells bei jedem Tastendruck aktualisiert werden?

Alt-Text

<!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
quelle
9
In KO 3.2 müssen nicht alle diese Problemumgehungen durchgeführt werden
Salvador Dali
Salvador ist richtig - es macht es automatisch in der neuesten Version.
Vapcguy

Antworten:

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>

Aus der Dokumentation

Zusätzliche Parameter

  • valueUpdate

    Wenn Ihre Bindung auch einen Parameter namens valueUpdate enthält, definiert dies, welches Browserereignis KO verwenden soll, um Änderungen zu erkennen. Die folgenden Zeichenfolgenwerte sind die am häufigsten verwendeten Optionen:

    • "Ändern" (Standard) - Aktualisiert Ihr Ansichtsmodell, wenn der Benutzer den Fokus auf ein anderes Steuerelement oder bei Elementen unmittelbar nach einer Änderung verschiebt

    • "keyup" - Aktualisiert Ihr Ansichtsmodell, wenn der Benutzer einen Schlüssel freigibt

    • "Tastendruck" - Aktualisiert Ihr Ansichtsmodell, wenn der Benutzer eine Taste eingegeben hat. Im Gegensatz zum Keyup wird dies wiederholt aktualisiert, während der Benutzer einen Schlüssel gedrückt hält

    • "afterkeydown" - Aktualisiert Ihr Ansichtsmodell, sobald der Benutzer mit der Eingabe eines Zeichens beginnt. Dies funktioniert, indem das Keydown-Ereignis des Browsers abgefangen und das Ereignis asynchron behandelt wird.

Von diesen Optionen ist "Afterkeydown" die beste Wahl, wenn Sie Ihr Ansichtsmodell in Echtzeit aktualisieren möchten.

Sergei Golos
quelle
9
aus den Dokumenten in knockout.js: // Die Syntax "nach <Ereignisname>" bedeutet "den Handler nach dem Ereignis asynchron ausführen" // Dies ist beispielsweise nützlich, um "Keydown" -Ereignisse abzufangen, nachdem der Browser das Steuerelement aktualisiert hat
John Wright
4
Gibt es überhaupt eine Möglichkeit, valueUpdate: 'afterkeydown' als Standard festzulegen ?
Aaron Shafovaloff
2
Bei einigen Eingabefeldern zeigt der Browser Werte an, die automatisch ausgefüllt werden (z. B. wenn das Eingabefeld den Namen "Vorname" trägt). Die Auswahl aus dem automatischen Ausfüllen funktioniert nicht mit "afterkeydown". Gibt es eine Möglichkeit, dies zu fangen?
Anton
2
@Anton Zusätzlich zum automatischen Ausfüllen von Werten gibt es auch Tastaturklicks auf dem Bildschirm und Mauseinfügeereignisse, die abgefangen werden sollten. Verwenden afterkeydownist eine schlechte Lösung.
John Kurlak
2
Autor dieser Antwort, bitte aktualisieren, um auch Salvador Dalis Antwort einzuschließen. TextInput ab 3.2 ist eine bessere Lösung, da es besser mit mobilen Browsern kompatibel ist (Dinge wie automatische Vervollständigung funktionieren besser)
Michael Crook
85

In Version 3.2 können Sie einfach die Texteingabebindung verwenden. ::

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

Es macht zwei wichtige Dinge:

  • Nehmen Sie sofort Aktualisierungen vor
  • behandelt Browserunterschiede für Ausschneiden, Ziehen, automatische Vervollständigung ...

Es sind also keine zusätzlichen Module, benutzerdefinierten Steuerelemente und andere Dinge erforderlich.

Salvador Dali
quelle
Das funktioniert wirklich gut und ist genau das, was ich brauchte, um den Wert zu ersetzen: Feld, WertUpdate: 'Eingabeänderung', den ich alle durch meine App hatte ... :) Danke.
Tod Thomson
Gibt es eine Möglichkeit, das Ereignis zu erfassen, wenn ein Benutzer im neuen Eingabefeld für die HTML5-Suche auf das Symbol 'x' klickt und sich so verhält, als wäre die Eingabe leer?
Codrina Valo
35

Wenn Sie möchten, dass afterkeydown"standardmäßig" Aktualisierungen durchgeführt werden, können Sie die valueUpdateBindung in den valueBindungshandler einfügen. Geben Sie allBindingsAccessordem Handler einfach ein neues Produkt an, das Folgendes enthält 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
    };
} ());

Wenn Sie die valueBindung nicht "überschreiben" möchten, können Sie der überschreibenden benutzerdefinierten Bindung einen anderen Namen geben und diesen Bindungshandler verwenden.

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

Demo

Eine solche Lösung wäre für Knockout Version 2.x geeignet. Das Knockout-Team hat eine vollständigere Bindung für textähnliche Eingaben durch die textInput- Bindung in Knockout-Version 3 und höher ausgearbeitet . Es wurde entwickelt, um alle Texteingabemethoden für Texteingaben und zu handhaben textarea. Es werden sogar Aktualisierungen in Echtzeit durchgeführt, wodurch dieser Ansatz effektiv überholt wird.

Jeff Mercado
quelle
2
Auf diese Weise wird mein Markup etwas aufgeräumter. Zusätzlich zu "Afterkeydown" können "Eingabe" - und "Einfügen" -Ereignisse hinzugefügt werden, um Einfüge- / Drag-and-Drop-Aktionen auszuführen.
Bob
In der obigen Lösung denke ich, dass "typeof valueUpdated === 'array'" durch "Object.prototype.toString.call (valueUpdate) === '[object Array]'" ersetzt werden muss. Siehe stackoverflow.com/questions/4775722/…
daveywc
20

Jeff Mercados Antwort ist fantastisch, aber leider mit Knockout 3 gebrochen.

Aber ich fand die Antwort, die von den Ko-Entwicklern vorgeschlagen wurde, als ich Knockout 3-Änderungen durcharbeitete. Siehe die unteren Kommentare unter https://github.com/knockout/knockout/pull/932 . Ihr 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 bietet jetzt eine vollständigere Lösung mit der neuen Bindung "textInput". Siehe SalvidorDalis Antwort

RockResolve
quelle
1
Vielen Dank für die Aktualisierung der Antwort auf 3.0!
Graham T
@GrahamT in KO 3.2 brauchst du es nicht mal. Es ist nur eine Zeile
Salvador Dali
Sehr schön für diejenigen von uns auf KO 3.2, die zu Beginn nichts über TextInput wussten und nicht alle Eingaben im gesamten System aktualisieren können!
cscott530