เหตุการณ์การเปลี่ยนแปลง jQuery UI datepicker ไม่จับโดย KnockoutJS


135

ฉันพยายามใช้ KnockoutJS กับ jQuery UI ฉันมีองค์ประกอบอินพุตที่แนบ datepicker ฉันกำลังดำเนินการอยู่knockout.debug.1.2.1.jsและดูเหมือนว่าเหตุการณ์การเปลี่ยนแปลงจะไม่ถูกจับโดย Knockout องค์ประกอบมีลักษณะดังนี้:

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

ฉันได้ลองเปลี่ยนvalueUpdateประเภทเหตุการณ์ แต่ก็ไม่เกิดประโยชน์ ดูเหมือนว่า Chrome จะทำให้เกิดfocusเหตุการณ์ก่อนที่จะเปลี่ยนค่า แต่ IE ไม่ทำ

มีวิธีการ Knockout บางวิธีที่ "rebinds all the bindings" หรือไม่? ในทางเทคนิคฉันต้องการเพียงค่าที่เปลี่ยนแปลงก่อนที่จะส่งกลับไปที่เซิร์ฟเวอร์ ดังนั้นฉันจึงสามารถใช้วิธีแก้ปัญหาแบบนั้นได้

ฉันคิดว่าปัญหาดังกล่าวเป็นความผิดของผู้ระบุวันที่ แต่ฉันไม่สามารถหาวิธีแก้ไขได้

ความคิดใด ๆ ?

คำตอบ:


253

ฉันคิดว่าสำหรับ jQuery UI datepicker ควรใช้การผูกแบบกำหนดเองที่จะอ่าน / เขียนด้วยอ็อบเจ็กต์ Date โดยใช้ API ที่ให้โดย datepicker

การผูกอาจมีลักษณะ (จากคำตอบของฉันที่นี่ ):

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

คุณจะใช้มันเช่น:

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

ตัวอย่างใน jsFiddle ที่นี่: http://jsfiddle.net/rniemeyer/NAgNV/


21
สิ่งที่ฉันชอบคือคุณไม่ได้ตัดมุมในเครื่องผูกนี้เช่นเดียวกับการเรียกกลับทิ้ง ตัวอย่างเสียงที่จะติดตามบนเส้นทางสู่ KnockoutJS mastery!
Dav

2
แล้ว datepicker ที่ผูกกับองค์ประกอบที่สร้างขึ้นแบบดินามิก ... ฉันหมายถึง datepicker ที่มีตัวจัดการแบบสด
Phoenix_uy

6
Phoenix_uy: เพื่อให้ datepicker ทำงานกับออบเจ็กต์ที่สร้างแบบไดนามิกอย่าตั้งค่า ID หรือชื่อของอินพุต
James Reategui

1
ฉันใช้สิ่งนี้และมันทำงานได้อย่างสมบูรณ์ยกเว้นสิ่งเล็ก ๆ สิ่งหนึ่ง - หากฉันตั้งค่า minDate หรือ maxDate ให้เท่ากับค่าที่สังเกตได้มันจะไม่ได้รับการอัปเดตหากมีการเปลี่ยนแปลงที่สังเกตได้ (เช่นถ้าฉันมี datepickers สองตัวโดยที่วันที่สูงสุดของ อันดับแรกคือค่าของวินาทีถ้าฉันอัปเดตวินาทีมันไม่ได้อัปเดตวันที่สูงสุดของครั้งแรก) เช่นเดียวกับคำถามนี้stackoverflow.com/questions/14732204/…
PW กาด

2
ดูเหมือนว่าชื่อเหตุการณ์ไม่ถูกต้อง ko.utils.registerEventHandler (องค์ประกอบ "changeDate" ฟังก์ชัน () - ควรเป็น ko.utils.registerEventHandler (องค์ประกอบ "เปลี่ยน" ฟังก์ชัน ()
Adam Bilinski

13

นี่คือคำตอบของ RP Niemeyer เวอร์ชันที่จะทำงานร่วมกับสคริปต์การตรวจสอบความน่าพิศวงที่พบได้ที่นี่: 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);
        }
    }
};

การเปลี่ยนแปลงเป็นตัวจัดการเหตุการณ์การเปลี่ยนแปลงเพื่อส่งผ่านค่าที่ป้อนไม่ใช่วันที่ไปยังสคริปต์ตรวจสอบความถูกต้องก่อนจากนั้นตั้งค่าวันที่เป็นค่าที่สังเกตได้เท่านั้นหากถูกต้อง ฉันยังเพิ่ม validationCore.init ที่จำเป็นสำหรับการผูกแบบกำหนดเองที่กล่าวถึงที่นี่:

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

ฉันยังเพิ่มข้อเสนอแนะของ rpenrose สำหรับการเปลี่ยนแปลงที่ไม่ชัดเจนเพื่อกำจัดสถานการณ์ที่น่ารำคาญบางอย่างของ datepicker ที่ขวางทางสิ่งต่างๆ


2
ดูเหมือนจะไม่ได้ผลสำหรับฉันฉันได้รับ TypeError: observable.isModified ไม่ใช่ฟังก์ชันในบรรทัด 313 ของ knockout.validation.js ตัวอย่างเล็ก ๆ ที่นี่: frikod.se/~capitol/fel/test.html
Alexander Kjäll

บรรทัดสำคัญในการทำให้มันทำงานร่วมกับไลบรารีการตรวจสอบความถูกต้องคือ: ko.bindingHandlers.validationCore.init (องค์ประกอบ, valueAccessor, allBindingsAccessor);
CRice

11

ฉันใช้วิธีการอื่น เนื่องจากสิ่งที่น่าพิศวง js ดูเหมือนจะไม่ทำให้เหตุการณ์เกิดการเปลี่ยนแปลงฉันได้บังคับให้ datepicker เรียกเปลี่ยน () สำหรับอินพุตเมื่อปิดแล้ว

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

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

9

แม้ว่าคำตอบทั้งหมดนี้จะช่วยให้ฉันทำงานได้มาก แต่ก็ไม่มีคำตอบใดที่เหมาะกับฉัน หลังจากเลือกวันที่แล้วค่าที่ผูกไว้จะไม่อัปเดต ฉันสามารถอัปเดตได้ก็ต่อเมื่อเปลี่ยนค่าวันที่โดยใช้แป้นพิมพ์จากนั้นคลิกออกจากช่องป้อนข้อมูล ฉันแก้ไขสิ่งนี้โดยการเพิ่มรหัส RP Niemeyer ด้วยรหัสของ syb เพื่อรับ:

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

ฉันสงสัยว่าใส่ค่าที่สังเกตได้ ($ (element) .datepicker ("getDate")); คำสั่งในฟังก์ชั่นของตัวเองและลงทะเบียนด้วยตัวเลือก onSelect กลลวงหรือไม่?


2
ขอบคุณล้าน! ฉันลองทุกตัวอย่างแล้วพบสิ่งนี้ที่ด้านล่างของหน้าและในที่สุดก็ใช้งานได้ ฉันมีการปรับแต่งเล็กน้อยในของฉันเพื่อให้ค่าที่ถูกผูกไว้อยู่ในรูปแบบ "เป็นมิตรกับเซิร์ฟเวอร์" เดียวกันกับที่ลงมาในฟังก์ชัน funcOnSelectdate ของคุณให้ใช้สิ่งนี้: observable ($. datepicker.formatDate ('yy-mm-dd' , $ (องค์ประกอบ) .datepicker ('getDate')));
BrutalDev

ฉันคิดว่าถ้าคุณลบล้างonSelectฟังก์ชันมันจะไม่ทำให้changeเกิดเหตุการณ์ขึ้น ...
NickL

6

ขอบคุณสำหรับบทความนี้ฉันพบว่ามีประโยชน์มาก

หากคุณต้องการให้ DatePicker ทำงานเหมือนกับลักษณะการทำงานเริ่มต้นของ JQuery UI ฉันขอแนะนำให้เพิ่มการเบลอองค์ประกอบในตัวจัดการเหตุการณ์การเปลี่ยนแปลง:

กล่าวคือ

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

        $(element).blur();

    });

คำตอบนี้ดูไม่สมบูรณ์? นี่คือความคิดเห็นเกี่ยวกับคำตอบของ @ RPNiemeyer หรือของคนอื่น?
rjmunro

3

ฉันแก้ไขปัญหานี้โดยเปลี่ยนลำดับของไฟล์สคริปต์ที่รวมไว้:

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

มีปัญหาที่คล้ายกันกับโมเดลที่ไม่ได้รับการอัพเดตแม้ว่าอินพุตจะแสดงวันที่ที่เลือกอย่างถูกต้องจาก datepicker เริ่มลงรายการคำแนะนำ .. แต่ .. นี่เป็นปัญหาของฉันแน่นอน อืม .. โครงการ MVC ของฉันมีสคริปต์ KO นำหน้าสคริปต์ jquery และ jquery UI มานานแล้ว - จะต้องทดสอบอย่างละเอียด
bkwdesign

2

เหมือนกับ RP Niemeyer แต่รองรับ WCF DateTime, Timezones และการใช้คุณสมบัติ 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);
            }
        }
    };

สนุก :)

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


1

ฉันคิดว่ามันสามารถทำได้ง่ายกว่ามาก: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

ดังนั้นคุณไม่จำเป็นต้องจัดการการเปลี่ยนแปลงด้วยตนเองในฟังก์ชัน init

แต่ในกรณีนี้ตัวแปร 'myDate' ของคุณจะได้รับเฉพาะค่าที่มองเห็นเท่านั้นไม่ใช่วัตถุวันที่


1

หรือคุณสามารถระบุสิ่งนี้ในการผูก:

อัปเดต:

 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
สิ่งนี้แก้ไขปัญหาเมื่อค่าวันที่ส่งคืนอยู่ในรูปแบบสตริงเช่น "2013-01-20T05: 00: 00" แทนวัตถุวันที่ ฉันพบปัญหานี้เมื่อโหลดข้อมูลจาก Web API
James Reategui

0

จากวิธีแก้ปัญหาของ Ryan myDate จะส่งคืนสตริงวันที่มาตรฐานซึ่งไม่ใช่สิ่งที่ดีที่สุดในกรณีของฉัน ฉันใช้ date.js เพื่อแยกวิเคราะห์ค่าดังนั้นมันจะส่งคืนรูปแบบวันที่ที่คุณต้องการเสมอ ลองดูตัวอย่างซอนี้

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

ฉันต้องการอัปเดตข้อมูลซ้ำ ๆ จากเซิร์ฟเวอร์เจอสิ่งนี้ แต่งานยังไม่เสร็จสมบูรณ์สำหรับความต้องการของฉันที่แบ่งปันด้านล่าง (รูปแบบวันที่ของฉัน / วันที่ (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();                   
        });
    };
}

หลังจากที่โมเดลถูกจัดรูปแบบอย่างถูกต้องสำหรับเอาต์พุตฉันได้เพิ่มเทมเพลตพร้อมกับเอกสารประกอบที่น่าพิศวง js :

<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

มีไม่กี่คนที่ขอตัวเลือกวันที่แบบไดนามิก ในกรณีของฉันฉันต้องการช่วงวันที่แบบไดนามิกดังนั้นอินพุตแรกจึงกำหนดค่าต่ำสุดของวินาทีและค่าที่สองจะกำหนดค่าสูงสุดสำหรับช่วงแรก ฉันแก้ไขได้โดยการขยายตัวจัดการ RP Niemeyer ดังนั้นสำหรับต้นฉบับของเขา:

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

ฉันได้เพิ่มตัวจัดการอีกสองตัวที่ตรงกับตัวเลือกที่ฉันต้องการแก้ไข:

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

และใช้แบบนั้นในเทมเพลตของฉัน:

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

0

การใช้การผูกแบบกำหนดเองที่ให้ไว้ในคำตอบก่อนหน้าไม่สามารถทำได้เสมอไป การโทร$(element).datepicker(...)ต้องใช้เวลาพอสมควรและถ้าคุณมีเช่นไม่กี่สิบหรือหลายร้อยองค์ประกอบที่จะเรียกวิธีนี้ด้วยคุณต้องทำแบบนี้ว่า "ขี้เกียจ" คือตามความต้องการ

ตัวอย่างเช่นโมเดลมุมมองอาจถูกเตรียมใช้งาน inputจะถูกแทรกลงใน DOM แต่ตัวระบุวันที่ที่เกี่ยวข้องจะเริ่มต้นเมื่อผู้ใช้คลิกเท่านั้น

ดังนั้นนี่คือทางออกของฉัน:

เพิ่มการเชื่อมโยงแบบกำหนดเองที่อนุญาตให้แนบข้อมูลที่กำหนดเองกับโหนด:

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

ใช้การผูกกับ attcah ที่สังเกตได้ที่ใช้สำหรับinputค่าของ:

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

และในที่สุดเมื่อเริ่มต้น datepicker ให้ใช้onSelectตัวเลือกนี้:

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

ด้วยวิธีนี้ทุกครั้งที่ผู้ใช้เปลี่ยนวันที่ด้วย datepicker การสังเกตสิ่งที่น่าพิศวงที่เกี่ยวข้องจะได้รับการอัปเดตด้วย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.