Evento di modifica del datepicker dell'interfaccia utente jQuery non intercettato da KnockoutJS


134

Sto cercando di usare KnockoutJS con l'interfaccia utente di jQuery. Ho un elemento di input con un datepicker allegato. Attualmente sto correndo knockout.debug.1.2.1.jse sembra che l'evento di cambiamento non sia mai stato catturato da Knockout. L'elemento è simile al seguente:

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

Ho anche provato a cambiare il valueUpdatetipo di evento, ma senza risultati. Sembra che Chrome causi un focusevento poco prima che cambi il valore, ma IE no.

Esiste un metodo Knockout che "abbina tutti gli attacchi"? Tecnicamente ho solo bisogno di cambiare il valore prima di rispedirlo al server. Quindi ho potuto vivere con quel tipo di soluzione alternativa.

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

Qualche idea?

Risposte:


253

Penso che per il datepicker dell'interfaccia utente jQuery sia preferibile utilizzare un'associazione personalizzata che leggerà / scriverà con gli oggetti Date utilizzando le API fornite dal datepicker.

L'associazione potrebbe apparire come (dalla mia risposta qui ):

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

Lo useresti come:

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

Esempio in jsFiddle qui: http://jsfiddle.net/rniemeyer/NAgNV/


21
Quello che amo è che non hai tagliato gli angoli in questo raccoglitore, come nel caso del callback dispose. Un valido esempio da seguire sulla strada della maestria di KnockoutJS!
Dav

2
E che dire del datepicker associato a un elemento creato dinamicamente ... Voglio dire, il datepicker con un gestore live.
Phoenix_uy,

6
Phoenix_uy: affinché il datepicker funzioni con oggetti creati dinamicamente, assicurarsi di non impostare l'ID o il nome dell'input.
James Reategui,

1
Sto usando questo e funziona perfettamente tranne una piccola cosa: se imposto la data minima o la data massima uguale a un osservabile, non verrà aggiornato se tale osservabile viene modificato (es. Se ho due datepicker in cui la data massima del il primo è il valore del secondo, se aggiorno il secondo non aggiorna la data massima del primo) uguale a questa domanda stackoverflow.com/questions/14732204/…
PW Kad

2
sembra che il nome dell'evento sia sbagliato, ko.utils.registerEventHandler (element, "changeDate", function () - dovrebbe essere ko.utils.registerEventHandler (element, "change", function ()
Adam Bilinski

13

Ecco una versione della risposta di RP Niemeyer che funzionerà con gli script di convalida knockout trovati 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 apportano al gestore eventi change per passare prima il valore immesso e non la data agli script di convalida, quindi impostando la data sull'osservabile solo se è valida. Ho anche aggiunto validationCore.init necessario 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 si frappongono alle cose.


2
Non sembra funzionare per me, ottengo TypeError: observable.isModified non è una funzione sulla linea 313 di knockout.validation.js. Piccolo esempio qui: frikod.se/~capitol/fel/test.html
Alexander Kjäll,

La linea importante per farlo funzionare con la libreria di validazione è: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice

11

Ho usato un approccio diverso. Poiché knockout.js non sembra generare l'evento al momento del cambiamento, ho costretto il datepicker a chiamare change () per il suo input una volta chiuso.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});

1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("change");}});
elsadek,

9

Sebbene tutte queste risposte mi abbiano risparmiato molto lavoro, nessuna di esse ha funzionato completamente per me. Dopo aver selezionato una data, il valore associato non verrà aggiornato. Ho potuto farlo aggiornare solo cambiando il valore della data usando la tastiera e 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 ($ (element) .datepicker ("getDate")); dichiarazione nella propria funzione e registrandola con options.onSelect ha fatto il trucco?


2
Grazie mille! Ho provato tutti gli esempi e poi ho trovato questo in fondo alla pagina e finalmente funziona. Ho solo una piccola modifica in modo che il valore associato rimanga nello stesso formato "server friendly" in cui è arrivato. Nella tua funzione funcOnSelectdate usa questo: osservabile ($. Datepicker.formatDate ('yy-mm-dd' , $ (element) .datepicker ('getDate')));
BrutalDev,

Penso che se si sovrascrive la onSelectfunzione non genererà l' changeevento ...
NickL

6

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 di eventi change:

vale a dire

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

        $(element).blur();

    });

Questa risposta non sembra completa? È un commento sulla risposta di @ RPNiemeyer o di qualcun altro?
rjmunro,

3

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>

Aveva problemi simili con il modello che non veniva aggiornato anche se l'input rendeva la data scelta correttamente dal datepicker. Ho iniziato l'elenco dei suggerimenti .. ma .. questo è stato sicuramente il mio problema. Hmmm .. il mio progetto MVC ha avuto lo script KO davanti agli script dell'interfaccia utente jquery e jquery per lungo tempo - dovrà testare a fondo.
bkwdesign,

2

Lo stesso di RP Niemeyer, ma un supporto migliore di WCF DateTime, Timezones e Uso della proprietà DatePicker onSelect 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/


1

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 un valore visibile, non un oggetto Date.


1

In alternativa, è possibile specificare questo in associazione:

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

2
Questo risolve un problema quando il valore della data restituita è in formato stringa, ad es. "2013-01-20T05: 00: 00" invece dell'oggetto data. Mi sono imbattuto in questo durante il caricamento dei dati dall'API Web.
James Reategui,

0

Basato sulla soluzione di Ryan, myDate restituisce la stringa di date standard, che non è quella ideale nel mio caso. Ho usato date.js per analizzare il valore in modo che restituisca sempre il formato data desiderato. Dai un'occhiata a questo esempio violino 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"));   
    }
}

0

Avevo bisogno di aggiornare ripetutamente i miei dati dal server, ma non ho ancora finito il lavoro per le mie esigenze condividendo di seguito (il mio formato 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();                   
        });
    };
}

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>

0

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 l'handler 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" />

0

L'uso di associazioni personalizzate fornite nelle risposte precedenti non è sempre possibile. chiamata$(element).datepicker(...) richiede un tempo considerevole e, ad esempio, se hai alcune dozzine o addirittura centinaia di elementi per chiamare questo metodo, devi farlo "pigro", cioè su richiesta.

Ad esempio, il modello di vista può essere inizializzato, il input i messaggi vengono inseriti nel DOM, ma i corrispondenti datepicker verranno inizializzati solo quando un utente fa clic su di essi.

Quindi, ecco la mia soluzione:

Aggiungi un'associazione personalizzata che consente di collegare dati arbitrari a un nodo:

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

Usa la rilegatura per attcah l'osservabile usato per il inputvalore di:

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

E infine, quando si inizializza il datepicker, utilizzare l' onSelectopzione è:

$('.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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.