Kejadian perubahan datepicker UI jQuery tidak tertangkap oleh KnockoutJS

135

Saya mencoba menggunakan KnockoutJS dengan jQuery UI. Saya memiliki elemen input dengan datepicker terpasang. Saat ini saya sedang berlari knockout.debug.1.2.1.jsdan sepertinya event perubahan tidak pernah tertangkap oleh Knockout. Elemennya terlihat seperti ini:

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

Saya bahkan mencoba mengubah valueUpdatejenis acara tetapi tidak berhasil. Sepertinya Chrome menyebabkan focusperistiwa tepat sebelum nilai berubah, tetapi IE tidak.

Apakah ada metode Knockout yang "memasang kembali semua ikatan"? Saya secara teknis hanya perlu nilai diubah sebelum saya mengirimkannya kembali ke server. Jadi saya bisa hidup dengan solusi seperti itu.

Saya pikir masalahnya adalah kesalahan datepicker, tetapi saya tidak tahu bagaimana cara memperbaikinya.

Ada ide?

Jose
sumber

Jawaban:

253

Saya pikir untuk datepicker UI jQuery lebih disukai menggunakan pengikatan khusus yang akan membaca / menulis dengan objek Tanggal menggunakan API yang disediakan oleh datepicker.

Pengikatannya mungkin terlihat seperti (dari jawaban saya di sini ):

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

Anda akan menggunakannya seperti:

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

Contoh di jsFiddle di sini: http://jsfiddle.net/rniemeyer/NAgNV/

RP Niemeyer
sumber
21
Yang saya suka adalah Anda tidak mengambil jalan pintas di pengikat ini, seperti dengan panggilan balik buang. Contoh suara untuk diikuti dalam perjalanan menuju penguasaan KnockoutJS!
Dav
2
Dan bagaimana dengan datepicker yang diikat ke elemen yang dibuat secara dinamis ... maksud saya, datepicker dengan handler langsung.
Phoenix_uy
6
Phoenix_uy: Agar datepicker bekerja dengan objek yang dibuat secara dinamis, pastikan untuk tidak menyetel ID atau Nama input.
James Reategui
1
Saya menggunakan ini dan bekerja dengan sempurna kecuali untuk satu hal kecil - Jika saya menetapkan minDate atau maxDate sama dengan yang dapat diamati, itu tidak diperbarui jika yang dapat diamati diubah (mis. Jika saya memiliki dua datepickers di mana tanggal maksimum pertama adalah nilai yang kedua, jika saya memperbarui yang kedua tidak memperbarui tanggal maks yang pertama) sama dengan pertanyaan ini stackoverflow.com/questions/14732204/…
PW Kad
2
sepertinya nama acara salah, ko.utils.registerEventHandler (elemen, "changeDate", function () - harus ko.utils.registerEventHandler (elemen, "change", function ()
Adam Bilinski
13

Ini adalah versi jawaban RP Niemeyer yang akan bekerja dengan skrip validasi sistem gugur yang ditemukan di sini: 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);
        }
    }
};

Perubahan pada penangan peristiwa perubahan untuk meneruskan nilai yang dimasukkan dan bukan tanggal ke skrip validasi terlebih dahulu, kemudian hanya menyetel tanggal ke yang dapat diamati jika valid. Saya juga menambahkan validationCore.init yang diperlukan untuk pengikatan khusus yang dibahas di sini:

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

Saya juga menambahkan saran rpenrose untuk blur pada perubahan untuk menghilangkan beberapa skenario datepicker yang mengganggu.

Brad M.
sumber
2
Sepertinya tidak berhasil untuk saya, saya mendapatkan TypeError: observable.isModified bukanlah fungsi pada baris 313 dari knockout.validation.js. Contoh kecil di sini: frikod.se/~capitol/fel/test.html
Alexander Kjäll
Baris penting untuk membuatnya bekerja dengan pustaka validasi adalah: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice
11

Saya telah menggunakan pendekatan yang berbeda. Karena knockout.js tampaknya tidak mengaktifkan peristiwa saat berubah, saya telah memaksa datepicker untuk memanggil change () untuk inputnya setelah ditutup.

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

Meskipun semua jawaban ini menyelamatkan saya dari banyak pekerjaan, tidak satupun dari mereka berhasil sepenuhnya untuk saya. Setelah memilih tanggal, nilai yang diikat tidak akan diperbarui. Saya hanya bisa memperbaruinya ketika mengubah nilai tanggal menggunakan keyboard lalu mengklik dari kotak input. Saya memperbaikinya dengan menambahkan kode RP Niemeyer dengan kode syb untuk mendapatkan:

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

Saya menduga meletakkan observable ($ (element) .datepicker ("getDate")); pernyataan dalam fungsinya sendiri dan mendaftarkannya dengan options.onSelect berhasil?

brudert
sumber
2
Terima kasih banyak! Saya mencoba setiap contoh dan kemudian menemukan yang ini di bagian bawah halaman dan akhirnya berhasil. Saya hanya memiliki sedikit tweak di milik saya sehingga nilai terikat tetap dalam format "ramah server" yang sama yang diturunkan masuk Dalam fungsi funcOnSelectdate Anda gunakan ini: observable ($. Datepicker.formatDate ('yy-mm-dd' , $ (element) .datepicker ('getDate')));
BrutalDev
Saya pikir jika Anda mengesampingkan onSelectfungsi itu tidak akan menaikkan changeacara ...
NickL
6

Terima kasih untuk artikel ini, saya merasa sangat berguna.

Jika Anda ingin DatePicker berperilaku persis seperti perilaku default JQuery UI, saya sarankan untuk menambahkan blur pada elemen di penangan peristiwa perubahan:

yaitu

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

        $(element).blur();

    });
rpenrose.dll
sumber
Jawaban ini tidak lengkap? Apakah ini komentar atas jawaban @ RPNiemeyer, atau komentar orang lain?
rjmunro
3

Saya memecahkan masalah ini dengan mengubah urutan file skrip yang saya sertakan:

<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
sumber
Memiliki masalah yang sama dengan model yang tidak diperbarui meskipun input memberikan tanggal yang dipilih dengan benar dari datepicker. Mulai dari daftar saran .. tapi .. ini pasti masalah saya. Hmmm .. proyek MVC saya telah memiliki skrip KO di depan skrip jquery dan jquery UI untuk waktu yang lama - harus diuji secara menyeluruh.
bkwdesign
2

Sama seperti RP Niemeyer, tetapi lebih baik mendukung WCF DateTime, Timezones dan Menggunakan properti JQuery onSelect DatePicker.

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

Nikmati :)

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

syb
sumber
1

Saya pikir ini bisa dilakukan dengan lebih mudah: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Jadi Anda tidak perlu penanganan perubahan manual dalam fungsi init.

Tetapi dalam kasus ini, variabel 'myDate' Anda hanya akan mendapatkan nilai yang terlihat, bukan objek Tanggal.

mot
sumber
1

Atau, Anda dapat menentukan ini dalam penjilidan:

Memperbarui:

 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
sumber
2
Ini memperbaiki masalah ketika nilai tanggal yang dikembalikan dalam format string yaitu. "2013-01-20T05: 00: 00" bukan objek tanggal. Saya mengalami ini saat memuat data dari Web API.
James Reategui
0

Berdasarkan solusi Ryan, myDate mengembalikan string tanggal standar, yang bukan yang ideal dalam kasus saya. Saya menggunakan date.js untuk mengurai nilainya sehingga itu akan selalu mengembalikan format tanggal yang Anda inginkan. Lihatlah Contoh biola contoh ini .

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

Saya perlu berulang kali memperbarui data saya dari server menemukan ini tetapi tidak cukup menyelesaikan pekerjaan untuk kebutuhan saya berbagi di bawah ini (format tanggal saya / Tanggal (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();                   
        });
    };
}

Setelah model diformat dengan benar untuk output, saya menambahkan template dengan dokumentasi 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
sumber
0

Beberapa orang meminta opsi pemilih data dinamis. Dalam kasus saya, saya memerlukan rentang tanggal dinamis - jadi masukan pertama menentukan nilai min dari yang kedua dan yang kedua menetapkan nilai maksimal untuk yang pertama. Saya menyelesaikannya dengan memperpanjang handler RP Niemeyer. Jadi aslinya:

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

Saya telah menambahkan dua penangan lagi yang sesuai dengan opsi yang ingin saya ubah:

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

Dan menggunakannya seperti itu di template saya:

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

Menggunakan binding kustom yang disediakan di jawaban sebelumnya tidak selalu memungkinkan. Memanggil $(element).datepicker(...)membutuhkan waktu yang cukup lama, dan jika Anda memiliki, misalnya, beberapa lusinan, atau bahkan ratusan elemen untuk memanggil metode ini, Anda harus melakukannya "malas", yaitu sesuai permintaan.

Misalnya, model tampilan dapat diinisialisasi, inputs dimasukkan ke DOM, tetapi datepickers terkait hanya akan diinisialisasi ketika pengguna mengkliknya.

Jadi, inilah solusi saya:

Tambahkan pengikatan khusus yang memungkinkan untuk melampirkan data arbitrer ke sebuah node:

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

Gunakan pengikatan ke attcah yang dapat diamati yang digunakan untuk inputnilai:

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

Dan terakhir, saat menginisialisasi datepicker, gunakan onSelectopsinya:

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

Dengan cara ini, setiap kali pengguna mengubah tanggal dengan datepicker, observasi Knockout yang sesuai juga diperbarui.

ololoepepe.dll
sumber