Zdarzenie zmiany datepicker interfejsu użytkownika jQuery nie zostało przechwycone przez KnockoutJS

134

Próbuję używać KnockoutJS z interfejsem użytkownika jQuery. Mam element wejściowy z dołączonym datownikiem. Obecnie biegnę knockout.debug.1.2.1.jsi wygląda na to, że wydarzenie zmiany nigdy nie jest przechwytywane przez Knockout. Element wygląda następująco:

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

Próbowałem nawet zmienić valueUpdatetyp wydarzenia, ale bezskutecznie. Wygląda na to, że Chrome powoduje focuszdarzenie tuż przed zmianą wartości, ale IE tego nie robi.

Czy jest jakaś metoda Knockout, która „rebinduje wszystkie powiązania”? Technicznie potrzebuję tylko zmiany wartości, zanim wyślę ją z powrotem na serwer. Więc mógłbym żyć z tego rodzaju obejściem.

Myślę, że problem leży po stronie datepickera, ale nie wiem, jak to naprawić.

Jakieś pomysły?

Jose
źródło

Odpowiedzi:

253

Myślę, że w przypadku Datepicker interfejsu użytkownika jQuery lepiej jest użyć niestandardowego powiązania, które będzie czytać / pisać z obiektami Date za pomocą interfejsów API dostarczonych przez datepicker.

Wiązanie może wyglądać (na podstawie mojej odpowiedzi tutaj ):

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

Używałbyś go tak:

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

Przykład w jsFiddle tutaj: http://jsfiddle.net/rniemeyer/NAgNV/

RP Niemeyer
źródło
21
Uwielbiam to, że nie poszedłeś na skróty w tym segregatorze, jak w przypadku wywołania zwrotnego dispose. Dobry przykład do naśladowania na drodze do mistrzostwa KnockoutJS!
Dav
2
A co z datepicker powiązanym z elementem, który jest tworzony dynamicznie ... mam na myśli datepicker z live handlerem.
Phoenix_uy,
6
Phoenix_uy: Aby datapicker działał z dynamicznie tworzonymi obiektami, nie należy ustawiać identyfikatora ani nazwy danych wejściowych.
James Reategui
1
Używam tego i działa idealnie, z wyjątkiem jednej małej rzeczy - jeśli ustawię minDate lub maxDate na wartość obserwowalną, nie zostanie zaktualizowana, jeśli ta obserwowalna zostanie zmieniona (np. Jeśli mam dwa datepickery, w których maksymalna data pierwsza to wartość drugiej, jeśli zaktualizuję drugą, nie aktualizuje maksymalnej daty pierwszej) tak samo jak to pytanie stackoverflow.com/questions/14732204/ ...
PW Kad
2
wygląda na to, że nazwa zdarzenia jest nieprawidłowa, ko.utils.registerEventHandler (element, "changeDate", function () - powinno być ko.utils.registerEventHandler (element, "change", function ()
Adam Bilinski
13

Oto wersja odpowiedzi RP Niemeyera, która będzie działać ze skryptami walidacji knockout, które można znaleźć tutaj: 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);
        }
    }
};

Zmiany dotyczą obsługi zdarzenia zmiany, aby najpierw przekazać wprowadzoną wartość, a nie datę, do skryptów walidacyjnych, a następnie ustawić datę na obserwowalną, jeśli jest poprawna. Dodałem również validationCore.init, który jest potrzebny do omówionych tutaj niestandardowych powiązań:

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

Dodałem również sugestię rpenrose'a dotyczącą rozmycia po zmianie, aby wyeliminować niektóre nieznośne scenariusze wyboru daty, które przeszkadzają.

Brad M.
źródło
2
Wydaje się, że nie działa dla mnie, otrzymuję TypeError: observable.isModified nie jest funkcją w linii 313 knockout.validation.js. Mały przykład tutaj: frikod.se/~capitol/fel/test.html
Alexander Kjäll
Ważnym wierszem umożliwiającym jego współpracę z biblioteką walidacji jest: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice
11

Użyłem innego podejścia. Ponieważ knockout.js nie wydaje się uruchamiać zdarzenia przy zmianie, wymusiłem na module datepicker wywołanie zmiany () dla swojego wejścia po zamknięciu.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});
ThiagoPXP
źródło
1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("zmiana");}});
elsadek
9

Chociaż wszystkie te odpowiedzi zaoszczędziły mi dużo pracy, żadna z nich w pełni nie zadziałała. Po wybraniu daty powiązana wartość nie zostanie zaktualizowana. Mogłem go zaktualizować tylko po zmianie wartości daty za pomocą klawiatury, a następnie kliknięciu pola wprowadzania. Naprawiłem to, rozszerzając kod RP Niemeyera o kod syb, aby uzyskać:

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

Podejrzewam umieszczenie obserwowalnego ($ (element) .datepicker ("getDate")); instrukcja w swojej własnej funkcji i zarejestrowanie tego za pomocą options.onSelect załatwiło sprawę?

brudert
źródło
2
Stukrotne dzięki! Wypróbowałem każdy przykład, a potem znalazłem ten na dole strony i wreszcie działa. Mam tylko małą poprawkę w moim, aby wartość związana pozostała w tym samym "przyjaznym dla serwera" formacie, w jakim została zapisana. W funkcji funcOnSelectdate użyj tego: obserwa walne ($. Datepicker.formatDate ('rr-mm-dd' , $ (element) .datepicker ('getDate')));
BrutalDev
Myślę, że jeśli zmienisz onSelectfunkcję, to nie podniesie changezdarzenia ...
NickL
6

Dzięki za ten artykuł uważam, że jest bardzo przydatny.

Jeśli chcesz, aby DatePicker zachowywał się dokładnie tak, jak domyślne zachowanie interfejsu użytkownika JQuery, polecam dodanie rozmycia na elemencie w module obsługi zdarzenia zmiany:

to znaczy

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

        $(element).blur();

    });
rpenrose
źródło
Ta odpowiedź nie wygląda na kompletną? Czy to komentarz do odpowiedzi @ RPNiemeyer, czy kogoś innego?
rjmunro
3

Rozwiązałem ten problem, zmieniając kolejność dołączonych plików skryptów:

<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
źródło
Wystąpiły podobne problemy z modelem, który nie był aktualizowany, mimo że dane wejściowe renderowały poprawnie wybraną datę z selektora dat. Zacząłem od listy sugestii… ale… to był zdecydowanie mój problem. Hmmm ... mój projekt MVC miał skrypt KO wyprzedzający jquery i jquery UI przez długi czas - będzie musiał dokładnie przetestować.
bkwdesign
2

To samo co RP Niemeyer, ale lepsza obsługa WCF DateTime, stref czasowych i przy użyciu właściwości 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);
            }
        }
    };

Cieszyć się :)

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

syb
źródło
1

Myślę, że można to zrobić znacznie łatwiej: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Nie potrzebujesz więc ręcznej obsługi zmian w funkcji init.

Ale w tym przypadku zmienna „myDate” otrzyma tylko widoczną wartość, a nie obiekt Date.

mot
źródło
1

Alternatywnie możesz określić to w wiążącej:

Aktualizacja:

 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);
    }               
}
Jaskółka oknówka
źródło
2
Rozwiązuje to problem, gdy zwracana wartość daty jest w formacie ciągu, tj. „2013-01-20T05: 00: 00” zamiast obiektu daty. Wpadłem na to podczas ładowania danych z internetowego interfejsu API.
James Reategui,
0

Opierając się na rozwiązaniu Ryana, myDate zwraca standardowy ciąg daty, który nie jest idealny w moim przypadku. Użyłem date.js do przeanalizowania wartości, aby zawsze zwracała żądany format daty. Spójrz na ten przykład Przykład skrzypiec .

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
źródło
0

Musiałem wielokrotnie aktualizować moje dane z serwera, ale nie do końca ukończyłem zadanie, aby udostępnić moje potrzeby poniżej (mój format daty / 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();                   
        });
    };
}

Po poprawnym sformatowaniu modelu do wyjścia dodałem szablon z dokumentacją 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>
Rozpoznać
źródło
0

Niewiele osób prosiło o opcje dynamicznego wyboru daty. W moim przypadku potrzebowałem dynamicznego zakresu dat - więc pierwsze wejście określa minimalną wartość drugiego, a drugie ustawia maksymalną wartość dla pierwszego. Rozwiązałem to rozszerzając handler RP Niemeyera. A więc do jego oryginału:

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

Dodałem jeszcze dwa moduły obsługi odpowiadające opcjom, które chciałem zmodyfikować:

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

I użyłem ich w ten sposób w moim szablonie:

<input data-bind="datepicker: selectedLowerValue, datepickerOptions: { minDate: minValue()}, maxDate: selectedUpperValue" />       
<input data-bind="datepicker: selectedUpperValue, datepickerOptions: { maxDate: maxValue()}, minDate: selectedLowerValue" />
Adam Biliński
źródło
0

Korzystanie z niestandardowych powiązań podanych w poprzednich odpowiedziach nie zawsze jest możliwe. Wywołanie $(element).datepicker(...)zajmuje sporo czasu, a jeśli masz np. Kilkadziesiąt, a nawet setki elementów, z którymi możesz wywołać tę metodę, musisz to zrobić „leniwie”, czyli na żądanie.

Na przykład model widoku może zostać zainicjowany, plik input a elementy zostaną wstawione do DOM, ale odpowiadające im datepickery zostaną zainicjowane tylko wtedy, gdy użytkownik je kliknie.

Oto moje rozwiązanie:

Dodaj niestandardowe powiązanie, które umożliwia dołączenie dowolnych danych do węzła:

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

Użyj wiązania, aby dołączyć obserwowalne używane dla inputwartości:

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

I na koniec, inicjując datepicker, użyj jego onSelectopcji:

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

W ten sposób za każdym razem, gdy użytkownik zmieni datę za pomocą selektora dat, odpowiednia obserwowalna obserwacja Knockout jest również aktualizowana.

ololoepepe
źródło