JQuery UI datepicker change событие не попало в KnockoutJS

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

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

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

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

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

Любые идеи?

Ответ 1

Я думаю, что для 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 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/

Ответ 2

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

Ответ 3

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

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

Ответ 4

Хотя все эти ответы сэкономили мне много работы, никто из них не работал у меня. После выбора даты привязанное значение не будет обновляться. Я мог бы только обновить его при изменении значения даты с помощью клавиатуры, а затем щелкнуть из поля ввода. Я исправил это, добавив код 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);
            }
        }
    };

Я подозреваю, что можно установить наблюдаемый ($ (элемент).datepicker( "getDate" )); в своей собственной функции и регистрируя это с помощью options.onВыберите трюк?

Ответ 5

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

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

то есть.

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

        $(element).blur();

    });

Ответ 6

Я решил эту проблему, изменив порядок моих включенных файлов script:

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

Ответ 7

То же, что и 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/

Ответ 8

Я думаю, что это можно сделать намного проще: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

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

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

Ответ 9

В качестве альтернативы вы можете указать это в привязке:

Обновление:

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

Ответ 10

На основе решения 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"));   
    }
}

Ответ 11

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

Ответ 12

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

Ответ 13

Использование пользовательских привязок, предоставленных в предыдущих ответах, не всегда возможно. Вызов $(element).datepicker(...) занимает довольно много времени, и если у вас есть, например, несколько десятков или даже сотни элементов для вызова этого метода, вы должны сделать это "ленивым", то есть по требованию.

Например, модель представления может быть инициализирована, а input вставлена ​​в DOM, но соответствующие датыпикеры будут инициализироваться только тогда, когда пользователь нажимает на них.

Итак, вот мое решение:

Добавьте настраиваемую привязку, которая позволяет прикреплять произвольные данные к node:

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, также обновляется соответствующий наблюдаемый нокаут.