Подія зміни виборця інтерфейсу jQuery, яку не зловив KnockoutJS


134

Я намагаюся використовувати KnockoutJS з інтерфейсом jQuery. У мене є елемент введення із доданим датчиком дати. Зараз я балотуюсь, knockout.debug.1.2.1.jsі здається, що подія зміни ніколи не застане нокаутом. Елемент виглядає приблизно так:

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

Я навіть намагався змінити valueUpdateтип події, але безрезультатно. Схоже, Chrome спричиняє focusподію безпосередньо перед зміною значення, але IE цього не робить.

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

Я думаю, що в цьому проблема вини датника, але я не можу зрозуміти, як це виправити.

Будь-які ідеї?

Відповіді:


253

Я думаю, що для інтерфейсу дат інтерфейсу jQuery бажано використовувати спеціальне прив'язування, яке буде читати / записувати з об'єктами 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
Мені подобається, що ви не вирізали кути в цій палітурці, як, наприклад, у зворотній зв'язок. Чудовий приклад для наслідування на шляху до майстерності KnockoutJS!
Дав

2
А як щодо датчика, прив'язаного до елемента, який динамічно створений ... я маю на увазі, датчик з живим обробником.
Phoenix_uy

6
Phoenix_uy: Щоб підбирач дат працювати з динамічно створеними об'єктами, не забудьте встановити ідентифікатор або ім'я вводу.
James Reategui

1
Я використовую це, і він працює чудово, за винятком однієї невеликої речі - Якщо я встановив minDate або maxDate рівним спостережуваному, він не оновлюється, якщо це спостерігається змінено (наприклад, якщо у мене є два вибирачі дат, де максимальна дата перше - це значення другого, якщо я оновлюю другий, він не оновлює максимальну дату першого) так само, як це питання stackoverflow.com/questions/14732204/…
PW Kad

2
виглядає так, що назва події неправильна, ko.utils.registerEventHandler (елемент, "changeDate", функція () - має бути ko.utils.registerEventHandler (елемент, "змінити", функція ()
Адам Білінський

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/isissue/69

Я також додав пропозицію rpenrose про розмиття змін, щоб усунути деякі примхливі сценарії вибору дати, що перешкоджають речам.


2
Здається, це не працює для мене, я отримую TypeError: observable.isModified - це не функція в рядку 313 knockout.validation.js. Невеликий приклад тут: frikod.se/~capitol/fel/test.html
Alexander Kjäll

Важливим рядком для його роботи з бібліотекою перевірки є: ko.bindingHandlers.validationCore.init (елемент, valueAccessor, allBindingsAccessor);
Кріс

11

Я використовував інший підхід. Оскільки knockout.js, здається, не запускає подію на зміну, я змусив виборника дати викликати change () для його введення після закриття.

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

1
$ ('. datepicker'). datepicker ({onSelect: функція (dateText) {$ ("# date_in"). тригер ("зміни");}});
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);
            }
        }
    };

Я підозрюю, що поставив спостережуване ($ (елемент) .datepicker ("getDate")); оператор у власній функції та зареєструвавши це за допомогою options.onSelect зробив трюк?


2
Завдяки мільйонів! Я спробував кожен приклад, а потім знайшов цей внизу сторінки, і він, нарешті, працює. У мене просто невеликий тюнінг в моєму, щоб зв'язане значення залишалося у тому самому "зручному для сервера" форматі, в якому воно потрапило. У функції funcOnSelectdate використовуйте це: спостерігаемо ($. Datepicker.formatDate ('yy-mm-dd') , $ (елемент) .datepicker ('getDate')));
BrutalDev

Я думаю, що якщо ви onSelectchange
скасуєте

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>

Якщо подібні проблеми з моделлю не оновлювались, навіть якщо введення відображало правильно обрану дату з вибору дати. Розпочав список пропозицій .. але .. це, безумовно, була моєю проблемою. Гммм .. мій проект MVC вже давно мав сценарій KO перед сценаріями інтерфейсу jquery та jquery - доведеться ретельно перевірити.
bkwdesign

2

Так само, як RP Niemeyer, але краща підтримка WCF DateTime, часових поясів та використання властивості 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" отримає лише видиме значення, а не об'єкт 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"));   
    }
}

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

Після правильного форматування моделі для виводу я додав шаблон із нокаутом документації :

<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(...)потребує значного часу, і якщо у вас є, наприклад, кілька десятків, а то й сотень елементів, щоб викликати цей метод, вам потрібно зробити це "ледачим", тобто на вимогу.

Наприклад, модель перегляду може бути ініціалізована, inputs вставляється в DOM, але відповідні вибирачі дат ініціалізуються лише тоді, коли користувач натисне на них.

Отже, ось моє рішення:

Додайте власну прив'язку, яка дозволяє приєднувати до вузла довільні дані:

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

Використовуйте прив'язку, щоб присвоїти спостережуване, що використовується для значення input's:

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.