Evento de alteração de datepicker da UI do jQuery não capturado pelo KnockoutJS

134

Estou tentando usar o KnockoutJS com a interface do jQuery. Eu tenho um elemento de entrada com um datepicker anexado. Atualmente, estou em execução knockout.debug.1.2.1.jse parece que o evento de mudança nunca está sendo capturado pelo Knockout. O elemento fica assim:

<input type="text" class="date" data-bind="value: RedemptionExpiration"/>

Eu até tentei alterar o valueUpdatetipo de evento, mas sem sucesso. Parece que o Chrome causa um focusevento antes de alterar o valor, mas o IE não.

Existe algum método Knockout que "religue todas as ligações"? Tecnicamente, só preciso que o valor seja alterado antes de enviá-lo de volta ao servidor. Para que eu pudesse viver com esse tipo de solução alternativa.

Eu acho que o problema é culpa do datepicker, mas não consigo descobrir como consertar isso.

Alguma ideia?

Jose
fonte

Respostas:

253

Eu acho que, para o datepicker da interface do usuário do jQuery, é preferível usar uma ligação personalizada que leia / escreva com objetos Date usando as APIs fornecidas pelo datepicker.

A ligação pode parecer (da minha resposta aqui ):

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {},
            $el = $(element);

        $el.datepicker(options);

        //handle the field changing by registering datepicker's changeDate event
        ko.utils.registerEventHandler(element, "changeDate", function () {
            var observable = valueAccessor();
            observable($el.datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $el.datepicker("destroy");
        });

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element);

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        var current = $el.datepicker("getDate");

        if (value - current !== 0) {
            $el.datepicker("setDate", value);
        }
    }
};

Você usaria como:

<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />

Exemplo no jsFiddle aqui: http://jsfiddle.net/rniemeyer/NAgNV/

RP Niemeyer
fonte
21
O que eu amo é que você não cortou os cantos neste fichário, como no retorno de chamada descartado. Um bom exemplo a seguir no caminho para o domínio do KnockoutJS!
Dav
2
E o datepicker vinculado a um elemento criado dinamicamente ... quero dizer, o datepicker com um manipulador ativo.
Phoenix_uy
6
Phoenix_uy: Para que o datepicker funcione com objetos criados dinamicamente, certifique-se de não definir o ID ou o Nome da entrada.
James Reategui
1
Estou usando isso e ele está funcionando perfeitamente, exceto por uma pequena coisa - se eu definir minDate ou maxDate igual a um observável, ele não será atualizado se esse observável for alterado (por exemplo, se eu tiver dois seletores de data em que a data máxima do primeiro é o valor do segundo, se eu atualizar o segundo, ele não atualiza a data máxima do primeiro) mesmo que esta pergunta stackoverflow.com/questions/14732204/…
PW Kad
2
Parece que o nome do evento é errado, ko.utils.registerEventHandler (elemento, "changeDate", function () - deve ser ko.utils.registerEventHandler (elemento, "mudança", function ()
Adam Bilinski
13

Aqui está uma versão da resposta do RP Niemeyer que funcionará com os scripts de validação de knockout encontrados aqui: http://github.com/ericmbarnard/Knockout-Validation

ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).val());
            if (observable.isValid()) {
                observable($(element).datepicker("getDate"));

                $(element).blur();
            }
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datepicker("destroy");
        });

        ko.bindingHandlers.validationCore.init(element, valueAccessor, allBindingsAccessor);

    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        current = $(element).datepicker("getDate");

        if (value - current !== 0) {
            $(element).datepicker("setDate", value);
        }
    }
};

As mudanças são feitas no manipulador de eventos de mudança para passar o valor digitado e não a data para os scripts de validação primeiro, configurando apenas a data para o observável, se for válido. Também adicionei o validationCore.init necessário para ligações personalizadas discutidas aqui:

http://github.com/ericmbarnard/Knockout-Validation/issues/69

Eu também adicionei a sugestão de rpenrose para um borrão na mudança para eliminar alguns cenários desagradáveis ​​do datepicker atrapalhando as coisas.

Brad M
fonte
2
Parece não funcionar para mim, recebo TypeError: observable.isModified não é uma função na linha 313 do knockout.validation.js. Exemplo pequeno aqui: frikod.se/~capitol/fel/test.html
Alexander Kjäll
A linha importante para fazê-lo funcionar com a biblioteca de validação é: ko.bindingHandlers.validationCore.init (elemento, valueAccessor, allBindingsAccessor);
CRice
11

Eu usei uma abordagem diferente. Como o knockout.js não parece acionar o evento com alterações, forcei o datepicker a chamar change () para sua entrada depois de fechada.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});
ThiagoPXP
fonte
1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("alteração");}});
elsadek
9

Embora todas essas respostas tenham me poupado muito trabalho, nenhuma delas funcionou totalmente para mim. Após selecionar uma data, o valor vinculado não será atualizado. Só consegui atualizá-lo ao alterar o valor da data usando o teclado e clicando fora da caixa de entrada. Corrigi isso aumentando o código do RP Niemeyer com o código do syb para obter:

ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            //initialize datepicker with some optional options
            var options = allBindingsAccessor().datepickerOptions || {};

            var funcOnSelectdate = function () {
                var observable = valueAccessor();
                observable($(element).datepicker("getDate"));
            }

            options.onSelect = funcOnSelectdate;

            $(element).datepicker(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, "change", funcOnSelectdate);

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).datepicker("destroy");
            });

        },
        update: function (element, valueAccessor) {

            var value = ko.utils.unwrapObservable(valueAccessor());
            if (typeof(value) === "string") { // JSON string from server
                value = value.split("T")[0]; // Removes time
            }

            var current = $(element).datepicker("getDate");

            if (value - current !== 0) {
                var parsedDate = $.datepicker.parseDate('yy-mm-dd', value);
                $(element).datepicker("setDate", parsedDate);
            }
        }
    };

Eu suspeito de colocar o observável ($ (elemento) .datepicker ("getDate")); em sua própria função e registrando-a com options.onSelect fez o truque?

brudert
fonte
2
Graças um milhão! Eu tentei todos os exemplos e, em seguida, encontrei este na parte inferior da página e finalmente funciona. Eu só tenho um pequeno ajuste no meu para que o valor limite permaneça no mesmo formato "amigável ao servidor" em que foi encontrado. Na função funcOnSelectdate, use isto: observable ($. Datepicker.formatDate ('aa-mm-dd' , $ (elemento). datapicker ('getDate')));
BrutalDev
Eu acho que se você substituir a onSelectfunção, ela não aumentará o changeevento ...
NickL 5/14
6

Obrigado por este artigo, achei muito útil.

Se você deseja que o DatePicker se comporte exatamente como o comportamento padrão da UI do JQuery, recomendo adicionar um desfoque no elemento no manipulador de eventos de alteração:

ie

    //handle the field changing
    ko.utils.registerEventHandler(element, "change", function () {
        var observable = valueAccessor();
        observable($(element).datepicker("getDate"));

        $(element).blur();

    });
rpenrose
fonte
Esta resposta não parece completa? Isso é um comentário na resposta do @ RPNiemeyer, ou de outra pessoa?
Rjmunro
3

Resolvi esse problema alterando a ordem dos meus arquivos de script incluídos:

<script src="@Url.Content("~/Scripts/jquery-ui-1.10.2.custom.js")"></script>
<script src="@Url.Content("~/Scripts/knockout-2.2.1.js")"></script>
Susanna
fonte
Teve problemas semelhantes com o modelo não sendo atualizado, embora a entrada tenha renderizado a data escolhida corretamente no datepicker. Começou a lista de sugestões .. mas .. este foi definitivamente o meu problema. Hmmm .. meu projeto MVC teve o script KO à frente dos scripts jquery e jquery UI por um longo tempo - terá que testar completamente.
bkwdesign
2

O mesmo que RP Niemeyer, mas melhor suporte para WCF DateTime, Timezones e Using the DatePicker onSelect JQuery propriedade.

        ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            //initialize datepicker with some optional options
            var options = allBindingsAccessor().datepickerOptions || {};

            var funcOnSelectdate = function () {
                var observable = valueAccessor();
                var d = $(element).datepicker("getDate");
                var timeInTicks = d.getTime() + (-1 * (d.getTimezoneOffset() * 60 * 1000));

                observable("/Date(" + timeInTicks + ")/");
            }
            options.onSelect = funcOnSelectdate;

            $(element).datepicker(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, "change", funcOnSelectdate);

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).datepicker("destroy");
            });

        },
        update: function (element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            //handle date data coming via json from Microsoft
            if (String(value).indexOf('/Date(') == 0) {
                value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
            }

            current = $(element).datepicker("getDate");

            if (value - current !== 0) {
                $(element).datepicker("setDate", value);
            }
        }
    };

Aproveitar :)

http://jsfiddle.net/yechezkelbr/nUdYH/

syb
fonte
1

Eu acho que isso pode ser feito muito mais fácil: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Portanto, você não precisa de tratamento manual de alterações na função init.

Mas, nesse caso, sua variável 'myDate' receberá apenas valor visível, não o objeto Date.

mot
fonte
1

Como alternativa, você pode especificar isso na ligação:

Atualizar:

 function (element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).datepicker("getDate");

    if (typeof value === "string") {            
       var dateValue = new Date(value);
       if (dateValue - current !== 0)
           $(element).datepicker("setDate", dateValue);
    }               
}
Martin
fonte
2
Isso corrige um problema quando o valor da data retornada está no formato de sequência, ou seja. "2013-01-20T05: 00: 00" em vez do objeto de data. Eu me deparei com isso ao carregar dados da API da Web.
James Reategui
0

Com base na solução de Ryan, myDate retorna a sequência de datas padrão, que não é a ideal no meu caso. Eu usei o date.js para analisar o valor, para que ele sempre retorne o formato de data desejado. Dê uma olhada neste exemplo de violino .

update: function(element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).datepicker("getDate");
    var d = Date.parse(value);
    if (value - current !== 0) {
        $(element).datepicker("setDate", d.toString("MM/dd/yyyy"));   
    }
}
Princa
fonte
0

Eu precisava atualizar repetidamente meus dados do servidor, mas não concluí o trabalho para minhas necessidades de compartilhamento abaixo (meu formato de data / data (1224043200000) /):

//Object Model
function Document(data) {
        if (String(data.RedemptionExpiration).indexOf('/Date(') == 0) {
            var newDate = new Date(parseInt(data.BDate.replace(/\/Date\((.*?)\)\//gi, "$1")));
            data.RedemptionExpiration = (newDate.getMonth()+1) +
                "/" + newDate.getDate() +
                "/" + newDate.getFullYear();
        }
        this.RedemptionExpiration = ko.observable(data.RedemptionExpiration);
}
//View Model
function DocumentViewModel(){
    ///additional code removed
    self.afterRenderLogic = function (elements) {
        $("#documentsContainer .datepicker").each(function () {
            $(this).datepicker();                   
        });
    };
}

Depois que o modelo foi formatado corretamente para a saída, adicionei um modelo com a documentação knockoutjs :

<div id="documentsContainer">
    <div data-bind="template: { name: 'document-template', foreach: documents, afterRender: afterRenderLogic }, visible: documents().length > 0"></div>
</div>

//Inline template
<script type="text/html" id="document-template">
  <input data-bind="value: RedemptionExpiration" class="datepicker" />
</script>
Ken
fonte
0

Poucas pessoas pediram opções dinâmicas de datapicker. No meu caso, eu precisava de um período dinâmico - portanto, a primeira entrada define o valor mínimo do segundo e o segundo define o valor máximo para o primeiro. Eu o resolvi estendendo o manipulador do RP Niemeyer. Então, para o original:

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {},
            $el = $(element);

        $el.datepicker(options);

        //handle the field changing by registering datepicker's changeDate event
        ko.utils.registerEventHandler(element, "change", function() {
            var observable = valueAccessor();
            observable($el.datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $el.datepicker("destroy");
        });

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element);

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        var current = $el.datepicker("getDate");

        if (value - current !== 0) {
            $el.datepicker("setDate", value);
        }
    }
};

Adicionei mais dois manipuladores correspondentes às opções que eu queria modificar:

ko.bindingHandlers.minDate = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("option", "minDate", value);
    }
};

ko.bindingHandlers.maxDate = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("option", "maxDate", value);
    }
};

E os usei assim no meu modelo:

<input data-bind="datepicker: selectedLowerValue, datepickerOptions: { minDate: minValue()}, maxDate: selectedUpperValue" />       
<input data-bind="datepicker: selectedUpperValue, datepickerOptions: { maxDate: maxValue()}, minDate: selectedLowerValue" />
Adam Bilinski
fonte
0

O uso de ligações personalizadas fornecidas nas respostas anteriores nem sempre é possível. Chamando$(element).datepicker(...) leva um tempo considerável e, se você tiver, por exemplo, algumas dezenas ou mesmo centenas de elementos para chamar esse método, precisará fazê-lo "preguiçosamente", ou seja, sob demanda.

Por exemplo, o modelo de vista pode ser inicializado, o input s sendo inseridos no DOM, mas os seletores de datas correspondentes serão inicializados somente quando um usuário clicar neles.

Então, aqui está a minha solução:

Adicione uma ligação personalizada que permita anexar dados arbitrários a um nó:

KO.bindingHandlers.boundData = {
  init: function(element, __, allBindings) {
    element.boundData = allBindings.get('boundData');
  }
};

Use a ligação para anexar o observável usado para o inputvalor de:

<input type='text' class='my-date-input'
       data-bind='textInput: myObservable, boundData: myObservable' />

E, finalmente, ao inicializar o datepicker, use sua onSelectopção:

$('.my-date-input').datepicker({
  onSelect: function(dateText) {
    this.myObservable(dateText);
  }
  //Other options
});

Dessa forma, toda vez que um usuário altera a data com o seletor de datas, o correspondente Knockout observável também é atualizado.

ololoepepe
fonte