Como posso obter o Knockout JS para vincular dados ao pressionar teclas em vez de perder o foco?

191

Este exemplo de knockout js funciona assim, quando você edita um campo e pressiona TAB, os dados do modelo de exibição e, portanto, o texto abaixo dos campos são atualizados.

Como posso alterar esse código para que os dados do modelo de exibição sejam atualizados a cada pressionamento de 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
fonte
9
Em KO 3.2 não há necessidade de fazer todas estas soluções alternativas: stackoverflow.com/a/25493265/1090562
Salvador Dali
Salvador está correto - ele faz isso automaticamente na versão mais recente.
vapcguy

Respostas:

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>

A partir da documentação

Parâmetros adicionais

  • valueUpdate

    Se sua ligação também incluir um parâmetro chamado valueUpdate, isso definirá qual evento de navegador o KO deve usar para detectar alterações. Os seguintes valores de string são as opções mais úteis:

    • "change" (padrão) - atualiza seu modelo de visualização quando o usuário move o foco para um controle diferente, ou no caso de elementos, imediatamente após qualquer alteração

    • "keyup" - atualiza seu modelo de exibição quando o usuário libera uma chave

    • "keypress" - atualiza seu modelo de exibição quando o usuário digita uma tecla. Ao contrário do keyup, isso é atualizado repetidamente enquanto o usuário mantém pressionada uma tecla

    • "afterkeydown" - atualiza seu modelo de exibição assim que o usuário começa a digitar um caractere. Isso funciona capturando o evento de pressionamento de tecla do navegador e manipulando o evento de forma assíncrona.

Dessas opções, "afterkeydown" é a melhor opção se você deseja manter seu modelo de visualização atualizado em tempo real.

Sergei Golos
fonte
9
dos documentos em knockout.js: // A sintaxe "após <eventname>" significa "executar o manipulador de forma assíncrona após o evento" // Isso é útil, por exemplo, para capturar eventos "keydown" depois que o navegador atualizou o controle
John Wright
4
Existe alguma maneira de obter valueUpdate: 'afterkeydown' para ser o padrão ?
Aaron Shafovaloff 8/12
2
para algumas caixas de entrada, o navegador mostra valores de "preenchimento automático" (como se o campo de entrada tivesse o nome "nome"). a seleção no preenchimento automático não funciona com "afterkeydown". existe uma maneira de pegar isso?
Anton
2
@Anton Além de preencher automaticamente os valores, também há cliques no teclado na tela e eventos de colagem do mouse que devem ser capturados. Usar afterkeydowné uma má solução.
precisa saber é o seguinte
2
Autor desta resposta, por favor atualização para incluir também a resposta de Salvador Dali, textInput adicionado como de 3.2 é uma solução melhor, pois tem melhor compatibilidade com os navegadores móveis (coisas como auto obra completa melhor)
Michael Crook
85

Na versão 3.2, você pode simplesmente usar a ligação de entrada de texto. :

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

Faz duas coisas importantes:

  • faça atualizações imediatas
  • lida com as diferenças do navegador para cortar, arrastar, preencher automaticamente ...

Portanto, não há necessidade de módulos adicionais, controles personalizados e outras coisas.

Salvador Dalí
fonte
Isso funciona muito bem e é exatamente o que eu precisava para substituir o valor: campo, valorUpdata: 'alteração de entrada' que eu tive durante todo o meu aplicativo ... :) obrigado.
Tod Thomson
Existe uma maneira de capturar o evento quando um usuário clica no ícone 'x' no novo campo de entrada de pesquisa HTML5 e age como se a entrada estivesse vazia?
Codrina Valo 6/08/16
35

Se você quiser fazer atualizações em afterkeydown"por padrão", poderá injetar a valueUpdateligação no valuemanipulador de ligações. Basta fornecer um novo allBindingsAccessorpara o manipulador usar, que inclui 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
    };
} ());

Se você não estiver confortável em "substituir" a valueligação, poderá atribuir um nome diferente à ligação personalizada de substituição e usar esse manipulador de ligação.

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

demonstração

Uma solução como essa seria adequada para o Knockout versão 2.x. A equipe do Knockout aprimorou uma ligação mais completa para entradas do tipo texto através da ligação textInput no Knockout versão 3 e posterior. Foi projetado para lidar com todos os métodos de entrada de texto para entradas e textarea. Ele ainda manipula a atualização em tempo real, que efetivamente torna essa abordagem obsoleta.

Jeff Mercado
fonte
2
Dessa forma, minha marcação fica um pouco mais organizada. Além disso, além dos eventos 'afterkeydown', 'input' e 'paste' podem ser adicionados para lidar com as ações de colar / arrastar e soltar.
22413 Bob
Na solução acima, acho que "typeof valueUpdated === 'array'" precisa ser substituído por "Object.prototype.toString.call (valueUpdate) === '[object Array]'". Veja stackoverflow.com/questions/4775722/…
daveywc
20

A resposta de Jeff Mercado é fantástica, mas infelizmente quebrou com o Knockout 3.

Mas eu encontrei a resposta sugerida pelos ko devs enquanto trabalhava nas mudanças do Knockout 3. Veja os comentários inferiores em https://github.com/knockout/knockout/pull/932 . O código deles:

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

O Edit Ko 3.2.0 agora possui uma solução mais completa com a nova ligação "textInput". Veja a resposta de SalvidorDali

RockResolve
fonte
1
Obrigado por atualizar a resposta para 3.0!
Graham T
@GrahamT no KO 3.2, você nem precisa dele. É apenas uma linha
Salvador Dali
Muito bom para aqueles de nós no KO 3.2 que não sabiam sobre o TextInput quando começamos e não podem atualizar todas as entradas em todo o sistema!
cscott530