Событие изменения DatePicker пользовательского интерфейса jQuery не обнаружено KnockoutJS

134

Я пытаюсь использовать KnockoutJS с jQuery UI. У меня есть элемент ввода с прикрепленным DatePicker. В данный момент я работаю, knockout.debug.1.2.1.jsи кажется, что событие change никогда не перехватывается Knockout. Элемент выглядит так:

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

Я даже пытался изменить valueUpdateтип события, но безрезультатно. Похоже, что Chrome вызывает focusсобытие непосредственно перед тем, как он меняет значение, а IE - нет.

Есть ли какой-нибудь метод нокаута, который "связывает все привязки"? Технически мне нужно только изменить значение, прежде чем я отправлю его обратно на сервер. Так что я мог бы жить с таким обходным решением.

Я думаю, что проблема в том, кто виноват, но я не могу понять, как это исправить.

Любые идеи?

Хосе
источник

Ответы:

253

Я думаю, что для средства выбора даты в jQuery UI предпочтительно использовать настраиваемую привязку, которая будет читать / записывать объекты Date с использованием API, предоставляемых средством выбора даты.

Привязка может выглядеть так (из моего ответа здесь ):

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
Что мне нравится, так это то, что вы не срезали углы в этом переплете, как с обратным вызовом dispose. Яркий пример для подражания на пути к мастерству KnockoutJS!
Дав
2
А как насчет DatePicker, привязанного к элементу, который динамически создается ... Я имею в виду DatePicker с живым обработчиком.
Phoenix_uy
6
Phoenix_uy: чтобы средство выбора даты работало с динамически создаваемыми объектами, обязательно не устанавливайте идентификатор или имя входа.
Джеймс Реатеги
1
Я использую это, и он работает отлично, за исключением одной маленькой вещи - если я устанавливаю minDate или maxDate равным наблюдаемой, она не обновляется, если эта наблюдаемая изменена (например, если у меня есть два указателя даты, где максимальная дата первый - значение второго, если я обновляю второй, он не обновляет максимальную дату первого) так же, как этот вопрос stackoverflow.com/questions/14732204/…
PW Kad
2
выглядит как неправильное имя события, ko.utils.registerEventHandler (element, "changeDate", function () - должно быть ko.utils.registerEventHandler (element, "change", function ()
Адам Билински,
13

Вот версия ответа Р. П. Нимейера, которая будет работать со скриптами проверки нокаута, найденными здесь: 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 для размытия изменений, чтобы избежать некоторых неприятных сценариев выбора даты, мешающих вещам.

Брэд М
источник
2
Кажется, не работает для меня, я получаю TypeError: observable.isModified не является функцией в строке 313 knockout.validation.js. Небольшой пример здесь: frikod.se/~capitol/fel/test.html
Александр Кьялл
Важная строка, чтобы заставить это работать с библиотекой проверки: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice
11

Я использовал другой подход. Поскольку похоже, что knockout.js не запускает событие при изменении, я заставил указатель даты вызывать change () для своего ввода после закрытия.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});
ThiagoPXP
источник
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")); заявление в своей собственной функции и регистрация, что с options.onSelect сделали свое дело?

brudert
источник
2
Бесконечно благодарен! Я попробовал каждый пример, а затем нашел его внизу страницы, и он наконец-то работает. У меня просто есть небольшая настройка, чтобы связанное значение оставалось в том же «дружественном к серверу» формате, в котором оно было записано. В вашей функции funcOnSelectdate используйте это: observable ($. Datepicker.formatDate ('yy-mm-dd' , $ (element) .datepicker ('getDate')));
BrutalDev
Я думаю, что если вы переопределите onSelectфункцию, она не вызовет changeсобытие ...
NickL
6

Спасибо за эту статью, я нашел это очень полезным.

Если вы хотите, чтобы DatePicker вел себя точно так же, как поведение по умолчанию в пользовательском интерфейсе JQuery, я рекомендую добавить размытие на элемент в обработчике события изменения:

т.е.

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

        $(element).blur();

    });
rpenrose
источник
Этот ответ не выглядит полным? Это комментарий к ответу @ 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>
Сюзанна
источник
Подобные проблемы с моделью не обновлялись, несмотря на то, что входные данные отображали правильно выбранную дату из средства выбора даты. Начал со списка предложений .. но .. это определенно была моя проблема. Хм ... мой 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: {}" />

Таким образом, вам не нужна ручная обработка изменений в функции инициализации.

Но в этом случае ваша переменная 'myDate' получит только видимое значение, а не объект Date.

словцо
источник
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» вместо объекта даты. Я столкнулся с этим при загрузке данных из веб-API.
Джеймс Реатеги
0

Основываясь на решении Райана, 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"));   
    }
}
Princa
источник
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();                   
        });
    };
}

После того, как модель правильно отформатирована для вывода, я добавил шаблон с документацией 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

Мало кто спрашивал о динамических опциях выбора даты. В моем случае мне был нужен динамический диапазон дат - поэтому первый вход определяет минимальное значение второго, а второй устанавливает максимальное значение для первого. Я решил это, расширив обработчик 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' />

И наконец, при инициализации средства onSelectвыбора даты, используйте его опцию:

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

Таким образом, каждый раз, когда пользователь изменяет дату с помощью средства выбора даты, соответствующая наблюдаемая нокаут также обновляется.

ololoepepe
источник