jQuery UI Datepicker-Änderungsereignis, das von KnockoutJS nicht abgefangen wurde

134

Ich versuche, KnockoutJS mit der jQuery-Benutzeroberfläche zu verwenden. Ich habe ein Eingabeelement mit einem Datepicker angehängt. Ich laufe gerade knockout.debug.1.2.1.jsund es scheint, dass das Änderungsereignis niemals von Knockout erfasst wird. Das Element sieht folgendermaßen aus:

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

Ich habe sogar versucht, den valueUpdateEreignistyp zu ändern, aber ohne Erfolg. Es scheint, dass Chrome ein focusEreignis verursacht , kurz bevor es den Wert ändert, der IE jedoch nicht.

Gibt es eine Knockout-Methode, die "alle Bindungen neu bindet"? Ich muss den Wert technisch nur ändern, bevor ich ihn an den Server zurücksende. Ich könnte also mit dieser Art von Problemumgehung leben.

Ich denke, das Problem ist die Schuld des Datepickers, aber ich kann nicht herausfinden, wie ich das beheben kann.

Irgendwelche Ideen?

Jose
quelle

Antworten:

253

Ich denke, dass es für den jQuery UI-Datepicker vorzuziehen ist, eine benutzerdefinierte Bindung zu verwenden, die mit Date-Objekten unter Verwendung der vom Datepicker bereitgestellten APIs liest / schreibt.

Die Bindung könnte aussehen (aus meiner Antwort hier ):

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

Sie würden es wie folgt verwenden:

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

Beispiel in jsFiddle hier: http://jsfiddle.net/rniemeyer/NAgNV/

RP Niemeyer
quelle
21
Was ich liebe, ist, dass Sie in diesem Ordner keine Ecken geschnitten haben, wie beim Rückruf zum Entsorgen. Ein gutes Beispiel für die KnockoutJS-Meisterschaft!
Dav
2
Und was ist mit dem Datepicker, der an ein Element gebunden ist, das dinamisch erstellt wurde? Ich meine, der Datepicker mit einem Live-Handler.
Phoenix_uy
6
Phoenix_uy: Damit der Datepicker mit dynamisch erstellten Objekten arbeiten kann, müssen Sie weder die ID noch den Namen der Eingabe festlegen.
James Reategui
1
Ich benutze dies und es funktioniert perfekt, bis auf eine kleine Sache - Wenn ich das minDate oder maxDate gleich einem Observable setze, wird es nicht aktualisiert, wenn dieses Observable geändert wird (z. B. wenn ich zwei Datepicker habe, bei denen das maximale Datum des Der erste ist der Wert des zweiten. Wenn ich den zweiten aktualisiere, wird das maximale Datum des ersten nicht aktualisiert. Dies entspricht der Frage stackoverflow.com/questions/14732204/…
PW Kad
2
Es sieht so aus, als ob der Ereignisname falsch ist. ko.utils.registerEventHandler (Element, "changeDate", function () - sollte ko.utils.registerEventHandler (Element, "change", function () sein
Adam Bilinski
13

Hier ist eine Version der Antwort von RP Niemeyer, die mit den hier gefundenen Knockout-Validierungsskripten funktioniert: 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);
        }
    }
};

Änderungen betreffen den Änderungsereignishandler, um den eingegebenen Wert und nicht das Datum zuerst an die Validierungsskripte zu übergeben und dann das Datum nur dann auf das beobachtbare Wert zu setzen, wenn es gültig ist. Ich habe auch die validationCore.init hinzugefügt, die für die hier beschriebenen benutzerdefinierten Bindungen benötigt wird:

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

Ich habe auch den Vorschlag von rpenrose für eine Unschärfe bei Änderungen hinzugefügt, um einige lästige Datepicker-Szenarien zu beseitigen, die den Dingen im Weg stehen.

Brad M.
quelle
2
Scheint bei mir nicht zu funktionieren, ich erhalte TypeError: Observable.isModified ist keine Funktion in Zeile 313 von knockout.validation.js. Kleines Beispiel hier: frikod.se/~capitol/fel/test.html
Alexander Kjäll
Die wichtige Zeile, damit es mit der Validierungsbibliothek funktioniert, lautet: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice
11

Ich habe einen anderen Ansatz gewählt. Da knockout.js das Ereignis bei Änderung nicht auszulösen scheint, habe ich den Datepicker gezwungen, change () für seine Eingabe aufzurufen, sobald es geschlossen ist.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});
ThiagoPXP
quelle
1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("change");}});
Elsadek
9

Obwohl all diese Antworten mir viel Arbeit erspart haben, hat keine von ihnen für mich voll funktioniert. Nach Auswahl eines Datums wird der gebundene Wert nicht aktualisiert. Ich konnte es nur aktualisieren, wenn ich den Datumswert über die Tastatur änderte und dann aus dem Eingabefeld klickte. Ich habe dies behoben, indem ich den Code von RP Niemeyer mit dem Code von syb erweitert habe, um Folgendes zu erhalten:

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

Ich vermute, das beobachtbare ($ (Element) .datepicker ("getDate")) zu setzen; Anweisung in seiner eigenen Funktion und Registrierung mit options.onSelect hat den Trick getan?

brudert
quelle
2
Tausend Dank! Ich habe jedes Beispiel ausprobiert und dann dieses am Ende der Seite gefunden und es funktioniert schließlich. Ich habe nur eine kleine Änderung in meinem, damit der gebundene Wert im gleichen "serverfreundlichen" Format bleibt, in dem er gespeichert wurde. Verwenden Sie in Ihrer Funktion funcOnSelectdate Folgendes: Observable ($. Datepicker.formatDate ('yy-mm-dd') , $ (Element) .datepicker ('getDate')));
BrutalDev
Ich denke, wenn Sie die onSelectFunktion überschreiben , wird das changeEreignis nicht
ausgelöst
6

Vielen Dank für diesen Artikel fand ich es sehr nützlich.

Wenn Sie möchten, dass sich der DatePicker genau wie das Standardverhalten der JQuery-Benutzeroberfläche verhält, empfehle ich, dem Element im Handler für Änderungsereignisse eine Unschärfe hinzuzufügen:

dh

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

        $(element).blur();

    });
Rpenrose
quelle
Diese Antwort sieht nicht vollständig aus? Ist dies ein Kommentar zur Antwort von @ RPNiemeyer oder der eines anderen?
rjmunro
3

Ich habe dieses Problem gelöst, indem ich die Reihenfolge meiner enthaltenen Skriptdateien geändert habe:

<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
quelle
Hatte ähnliche Probleme mit dem Modell, das nicht aktualisiert wurde, obwohl die Eingabe das korrekt ausgewählte Datum aus der Datumsauswahl wiedergab. Begann die Liste der Vorschläge .. aber .. das war definitiv mein Problem. Hmmm .. mein MVC-Projekt hat das KO-Skript schon lange vor den Skripten jquery und jquery UI - muss gründlich getestet werden.
bkwdesign
2

Wie RP Niemeyer, jedoch bessere Unterstützung von WCF DateTime, Timezones und der Verwendung der DatePicker onSelect JQuery-Eigenschaft.

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

Genießen :)

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

syb
quelle
1

Ich denke, es kann viel einfacher gemacht werden: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Sie benötigen also keine manuelle Änderungsbehandlung in der Init-Funktion.

In diesem Fall erhält Ihre Variable 'myDate' jedoch nur einen sichtbaren Wert, kein Date-Objekt.

mot
quelle
1

Alternativ können Sie dies in der Bindung angeben:

Aktualisieren:

 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
quelle
2
Dies behebt ein Problem, wenn der zurückgegebene Datumswert im Zeichenfolgenformat vorliegt, d. H. "2013-01-20T05: 00: 00" anstelle des Datumsobjekts. Ich bin darauf gestoßen, als ich Daten von der Web-API geladen habe.
James Reategui
0

Basierend auf Ryans Lösung gibt myDate die Standard-Datumszeichenfolge zurück, die in meinem Fall nicht die ideale ist. Ich habe date.js verwendet, um den Wert zu analysieren, damit immer das gewünschte Datumsformat zurückgegeben wird. Schauen Sie sich dieses Beispiel an .

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
quelle
0

Ich musste meine Daten vom Server wiederholt aktualisieren, stieß jedoch darauf, konnte den Auftrag für die unten stehende Bedarfsfreigabe jedoch nicht vollständig abschließen (mein Datumsformat / Datum (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();                   
        });
    };
}

Nachdem das Modell für die Ausgabe korrekt formatiert wurde, habe ich eine Vorlage mit der Dokumentation knockoutjs hinzugefügt :

<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
quelle
0

Nur wenige Leute fragten nach dynamischen Datepicker-Optionen. In meinem Fall brauchte ich einen dynamischen Datumsbereich - daher definiert die erste Eingabe den Min-Wert der zweiten und die zweite den Max-Wert für die erste. Ich habe es gelöst, indem ich den Handler des RP Niemeyer erweitert habe. Also zu seinem 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);
        }
    }
};

Ich habe zwei weitere Handler hinzugefügt, die den Optionen entsprechen, die ich ändern wollte:

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

Und habe sie so in meiner Vorlage verwendet:

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

Die Verwendung von benutzerdefinierten Bindungen in früheren Antworten ist nicht immer möglich. Das Aufrufen $(element).datepicker(...)dauert einige Zeit, und wenn Sie beispielsweise einige Dutzend oder sogar Hunderte von Elementen haben, mit denen Sie diese Methode aufrufen können, müssen Sie dies "faul" tun, dh auf Anfrage.

Beispielsweise kann das Ansichtsmodell initialisiert werden, wobei das inputs in das DOM eingefügt wird, aber die entsprechenden Datumsauswahl wird nur initialisiert, wenn ein Benutzer darauf klickt.

Also, hier ist meine Lösung:

Fügen Sie eine benutzerdefinierte Bindung hinzu, mit der beliebige Daten an einen Knoten angehängt werden können:

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

Verwenden Sie die Bindung, um das für den inputWert von ' verwendete Observable zu ermitteln :

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

Verwenden Sie zum Initialisieren des Datepickers die folgende onSelectOption:

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

Auf diese Weise wird jedes Mal, wenn ein Benutzer das Datum mit der Datumsauswahl ändert, auch das entsprechende beobachtbare Knockout aktualisiert.

ololoepepe
quelle