jquery-ui - jQuery UI DatePicker modifica l'evento non rilevato da KnockoutJS





knockout.js (12)


Poche persone hanno chiesto opzioni di Datepicker dinamiche. Nel mio caso avevo bisogno di un intervallo di date dinamico, quindi il primo input definisce il valore minimo del secondo e il secondo imposta il valore massimo per il primo. L'ho risolto estendendo il gestore di RP Niemeyer. Quindi al suo originale:

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);
        }
    }
};

Ho aggiunto altri due gestori corrispondenti alle opzioni che volevo modificare:

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 li ho usati così nel mio modello:

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

Sto cercando di usare KnockoutJS con l'interfaccia utente di jQuery. Ho un elemento di input con un datepicker allegato. Al momento sto eseguendo knockout.debug.1.2.1.js e sembra che l'evento change non venga mai catturato da Knockout. L'elemento si presenta così:

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

Ho anche provato a cambiare il tipo di evento valueUpdate ma inutilmente. Sembra che Chrome causi un evento di focus a focus poco prima che cambi il valore, ma IE no.

Esiste un metodo Knockout che "rebinds tutti i binding"? Tecnicamente ho solo bisogno di modificare il valore prima di inviarlo al server. Quindi potrei vivere con quel tipo di soluzione alternativa.

Penso che il problema sia colpa del datepicker, ma non riesco a capire come risolvere questo problema.

Qualche idea?




Uguale a RP Niemeyer, ma migliore supporto di WCF DateTime, Timezones e Using the DatePicker onSelect Proprietà JQuery.

        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);
            }
        }
    };

Godere :)

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




Ho risolto questo problema modificando l'ordine dei miei file di script inclusi:

<script src="@Url.Content("~/Scripts/jquery-ui-1.10.2.custom.js")"></script>
<script src="@Url.Content("~/Scripts/knockout-2.2.1.js")"></script>



Grazie per questo articolo l'ho trovato molto utile.

Se vuoi che DatePicker si comporti esattamente come il comportamento predefinito dell'interfaccia utente di JQuery, ti consiglio di aggiungere una sfocatura sull'elemento nel gestore dell'evento di modifica:

vale a dire

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

        $(element).blur();

    });



Penso che possa essere fatto molto più facilmente: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Quindi non è necessaria la gestione manuale delle modifiche nella funzione init.

Ma in questo caso, la variabile "myDate" otterrà solo il valore visibile, non l'oggetto Date.




L'utilizzo di associazioni personalizzate fornite nelle risposte precedenti non è sempre possibile. Chiamare $(element).datepicker(...) richiede un tempo considerevole, e se si dispone, ad esempio, di qualche dozzina o anche di centinaia di elementi con cui chiamare questo metodo, è necessario farlo "pigro", cioè su richiesta.

Ad esempio, il modello di visualizzazione può essere inizializzato, l' input s viene inserito nel DOM, ma i datepicker corrispondenti verranno inizializzati solo quando un utente fa clic su di essi.

Quindi, ecco la mia soluzione:

Aggiungi un binding personalizzato che consente di allegare dati arbitrari a un nodo:

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

Usa il legame per attacare l'osservabile utilizzato per il valore input :

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

Infine, durante l'inizializzazione di DatePicker, usa l'opzione onSelect :

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

In questo modo, ogni volta che un utente cambia la data con il datepicker, viene aggiornato anche il corrispondente Knockout osservabile.




Basato sulla soluzione di Ryan, myDate restituisce la stringa di data standard, che non è quella ideale nel mio caso. Ho usato date.js per analizzare il valore in modo che restituisca sempre il formato della data desiderato. Dai un'occhiata a questo esempio di esempio .

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"));   
    }
}



Ecco una versione della risposta di RP Niemeyer che funzionerà con gli script di convalida a eliminazione diretta qui: 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);
        }
    }
};

Le modifiche riguardano il gestore dell'evento change per passare prima il valore immesso e non la data agli script di convalida, quindi solo impostando la data sull'osservabile se è valida. Ho anche aggiunto la validationCore.init che è necessaria per i collegamenti personalizzati discussi qui:

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

Ho anche aggiunto il suggerimento di rpenrose per una sfocatura sul cambiamento per eliminare alcuni fastidiosi scenari di datepicker che intralciano le cose.




In alternativa, puoi specificarlo in binding:

Aggiornare:

 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);
    }               
}



Sebbene tutte queste risposte mi abbiano salvato un sacco di lavoro, nessuno di questi ha funzionato pienamente per me. Dopo aver selezionato una data, il valore associato non si aggiornava. Ho potuto farlo aggiornare solo cambiando il valore della data usando la tastiera, quindi facendo clic fuori dalla casella di input. Ho risolto questo problema aumentando il codice di RP Niemeyer con il codice di syb per ottenere:

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);
            }
        }
    };

Sospetto di mettere l'osservabile ($ (elemento) .datepicker ("getDate")); affermazione nella propria funzione e registrando ciò con options.onSelect ha fatto il trucco?




Avevo bisogno di aggiornare ripetutamente i miei dati dal server, ma non ho terminato il lavoro per la mia condivisione delle esigenze di seguito (my date format / Date (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();                   
        });
    };
}

Dopo che il modello è stato formattato correttamente per l'output, ho aggiunto un modello con la documentazione 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>



Solo un pensiero veloce. I tuoi designer sembrano attivare il clic e modificare l'evento in questa riga:

self.$input.click().trigger('change');

Non potrebbe essere qualcosa da provare? Forse è legato all'evento click piuttosto che all'evento change? Prova questo in un'altra istruzione:

$(element).prop('checked', value).click().trigger('change'); // <--- this should work!

Quello che ho capito è che .click () fa: imita un clic del mouse e allo stesso tempo attiva l'evento click (Duh?).

Lo so che a volte (come quando si è in una radio o in una casella di controllo) alcuni programmatori legano l'evento al clic piuttosto che al cambiamento poiché è più probabile che si attivi al momento giusto (in IE7 l'evento di modifica viene attivato in un modo davvero strano).

Solo un pensiero, potrebbe essere qualcosa da provare. Spero che sia d'aiuto!

EDIT: ho visto una risposta sui namespace nei tuoi eventi e eventi personalizzati. Questo potrebbe anche aiutarti, spero che non sia un cambiamento troppo grande.

/ J.





jquery-ui knockout.js