¿Cómo puedo hacer que Knockout JS enlace los datos al presionar las teclas en lugar de perder el foco?

191

Este ejemplo de knockout js funciona así que cuando edita un campo y presiona TAB, se actualizan los datos del modelo de vista y, por lo tanto, el texto debajo de los campos.

¿Cómo puedo cambiar este código para que los datos del modelo de vista se actualicen cada vez que se presione una tecla?

texto alternativo

<!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
fuente
9
En KO 3.2 no es necesario hacer todas estas soluciones: stackoverflow.com/a/25493265/1090562
Salvador Dali
Salvador tiene razón: lo hace automáticamente en la última versión.
vapcguy

Respuestas:

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 documentación

Parámetros adicionales

  • valueUpdate

    Si su enlace también incluye un parámetro llamado valueUpdate, esto define qué evento del navegador KO debe usar para detectar cambios. Los siguientes valores de cadena son las opciones más útiles:

    • "cambiar" (predeterminado): actualiza su modelo de vista cuando el usuario mueve el foco a un control diferente, o en el caso de elementos, inmediatamente después de cualquier cambio

    • "keyup": actualiza su modelo de vista cuando el usuario suelta una clave

    • "presionar tecla": actualiza su modelo de vista cuando el usuario ha escrito una tecla. A diferencia del keyup, esto se actualiza repetidamente mientras el usuario mantiene presionada una tecla

    • "afterkeydown": actualiza su modelo de vista tan pronto como el usuario comienza a escribir un carácter. Esto funciona capturando el evento keydown del navegador y manejando el evento de forma asincrónica.

De estas opciones, "afterkeydown" es la mejor opción si desea mantener actualizado su modelo de vista en tiempo real.

Sergei Golos
fuente
9
de los documentos en knockout.js: // La sintaxis "después de <nombre del evento>" significa "ejecutar el controlador de forma asíncrona después del evento" // Esto es útil, por ejemplo, para capturar eventos de "keydown" después de que el navegador haya actualizado el control
John Wright
44
¿Hay alguna forma de obtener valueUpdate: 'afterkeydown' como predeterminado ?
Aaron Shafovaloff
2
para algunos cuadros de entrada, el navegador muestra valores de "autocompletar" (como si el campo de entrada se llama "nombre"). seleccionar desde el autocompletado no funciona con "afterkeydown". ¿hay alguna manera de atrapar esto?
Anton
2
@Anton Además de los valores de autocompletado, también hay clics en el teclado y eventos de pegado del mouse que deben capturarse. Usar afterkeydownes una mala solución.
John Kurlak
2
Autor de esta respuesta, actualice para incluir también la respuesta de Salvador Dalí, textInput agregado a partir de 3.2 es una mejor solución ya que tiene una mejor compatibilidad con los navegadores móviles (cosas como autocompletar funcionan mejor)
Michael Crook
85

En la versión 3.2 , simplemente puede usar el enlace textinput. :

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

Hace dos cosas importantes:

  • hacer actualizaciones inmediatas
  • maneja las diferencias del navegador para cortar, arrastrar, autocompletar ...

Por lo tanto, no necesita módulos adicionales, controles personalizados y otras cosas.

Salvador Dalí
fuente
Esto funciona muy bien y es exactamente lo que necesitaba para reemplazar el valor: field, valueUpdate: 'input change' que tuve en toda mi aplicación ... :) gracias.
Tod Thomson el
¿Hay alguna forma de capturar el evento cuando un usuario hace clic en el icono 'x' en el nuevo campo de entrada de búsqueda HTML5 y actúa como en el caso de que la entrada esté vacía?
Codrina Valo
35

Si desea que realice actualizaciones afterkeydown"por defecto", puede inyectar el valueUpdateenlace en el valuecontrolador de enlace. Solo proporcione una nueva allBindingsAccessorpara que la use el controlador que incluya 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 no se siente cómodo "anulando" el valueenlace, puede darle un nombre diferente al enlace personalizado y usar ese controlador de enlace.

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

manifestación

Una solución como esta sería adecuada para Knockout versión 2.x. El equipo de Knockout ha desarrollado un enlace más completo para entradas de texto a través del enlace textInput en Knockout versión 3 y superior. Fue diseñado para manejar todos los métodos de entrada de texto para entradas de texto y textarea. Incluso manejará la actualización en tiempo real, lo que hace que este enfoque quede obsoleto.

Jeff Mercado
fuente
2
De esta manera, mi marcado es un poco más ordenado. Además, además de 'afterkeydown', se pueden agregar eventos 'input' y 'pegar' para manejar las acciones de pegar / arrastrar y soltar.
bob
En la solución anterior, creo que "typeof valueUpdated === 'array'" necesita ser reemplazado por "Object.prototype.toString.call (valueUpdate) === '[object Array]'". Ver stackoverflow.com/questions/4775722/…
daveywc
20

La respuesta de Jeff Mercado es fantástica, pero desafortunadamente se rompió con Knockout 3.

Pero encontré la respuesta sugerida por los desarrolladores de ko mientras trabajaba a través de los cambios de Knockout 3. Vea los comentarios inferiores en https://github.com/knockout/knockout/pull/932 . Su código:

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

Editar Ko 3.2.0 ahora tiene una solución más completa con el nuevo enlace "textInput". Ver la respuesta de SalvidorDali

RockResolve
fuente
1
¡Gracias por actualizar la respuesta a 3.0!
Graham T
@GrahamT en KO 3.2 ni siquiera lo necesita. Es solo una línea
Salvador Dali
¡Muy agradable para aquellos de nosotros en KO 3.2 que no conocíamos TextInput cuando comenzamos, y no pueden actualizar cada entrada en todo el sistema!
cscott530