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

133

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

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

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

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

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

Любые идеи?

Теги:
knockout.js

13 ответов

263
Лучший ответ

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

  • 21
    Что мне нравится, так это то, что вы не срезали углы в этом переплете, как с обратным вызовом dispose. Яркий пример для подражания на пути к мастерству KnockoutJS!
  • 2
    А как насчет DatePicker, привязанного к элементу, который динамически создается ... Я имею в виду, DatePicker с живым обработчиком.
Показать ещё 12 комментариев
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 о размытии изменений, чтобы устранить некоторые досадные сценарии датпикера, мешающие вещам.

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

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

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});
  • 1
    $ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("change");}});
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);
            }
        }
    };

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

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

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

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

то есть.

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

        $(element).blur();

    });
  • 0
    Этот ответ не выглядит полным? Это комментарий к ответу @ RPNiemeyer или кто-то еще?
3

Я решил эту проблему, изменив порядок моих включенных файлов 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>
  • 0
    Подобные проблемы с моделью не обновлялись, несмотря на то, что входные данные отображали правильно выбранную дату из средства выбора даты. Начал со списка предложений .. но .. это определенно была моя проблема. Хм ... мой MVC-проект долгое время опережал сценарий KO по сравнению со сценариями пользовательского интерфейса jquery и jquery - придется тщательно тестировать.
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

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

Обновление:

 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);
    }               
}
  • 0
    В качестве альтернативы чему?
  • 2
    Это устраняет проблему, когда возвращаемое значение даты находится в строковом формате, т.е. «2013-01-20T05: 00: 00» вместо объекта даты. Я столкнулся с этим при загрузке данных из веб-API.
1

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

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

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

0

Использование пользовательских привязок, предоставленных в предыдущих ответах, не всегда возможно. Вызов $(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, также обновляется соответствующий наблюдаемый нокаут.

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню