Пользовательское числовое связывание нокаутом

Я хотел использовать эту технику: сделать ввод только числовым типом при нокауте

чтобы пользователь мог вводить только числа.

Однако этот метод не обновляет наблюдаемое значение в пользовательском интерфейсе.

HTML:

 <span data-bind="text: Interval" ></span>
 <input data-bind="numeric: Interval" />

Переплет:

ko.bindingHandlers.numeric = {
    init: function (element, valueAccessor) {
        $(element).on("keydown", function (event) {
            // Allow: backspace, delete, tab, escape, and enter
            if (event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 9 || event.keyCode == 27 || event.keyCode == 13 ||
                // Allow: Ctrl+A
                (event.keyCode == 65 && event.ctrlKey === true) ||
                // Allow: . ,
                (event.keyCode == 188 || event.keyCode == 190 || event.keyCode == 110) ||
                // Allow: home, end, left, right
                (event.keyCode >= 35 && event.keyCode <= 39)) {
                // let it happen, don't do anything
                return;
            }
            else {
                // Ensure that it is a number and stop the keypress
                if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) {
                    event.preventDefault();
                }
            }
        });
    }    
};

Таким образом, привязка не позволяет вводить символы, отличные от цифр, но когда фокус теряется на input, соответствующий наблюдаемый не обновляется (поэтому элементы span не изменяются).

Примечание:

Мне не нужно позволять пользователю вводить нечетные символы на вход. Я знаю, что есть другое решение, такое как числовое расширение ko, которое преобразует все в числовые числа, но мне это не нужно. Мне нужно решение, которое позволяет вводить только цифры (включая что-то вроде backspace и т.д.).

Ответ 1

Это сделает то, что вы хотите:

<span data-bind="text: Interval" ></span>
<input data-bind="numeric, value: Interval" />

http://jsfiddle.net/mbest/n4z8Q/

Ответ 2

Твердый маршрут для числовых номеров будет для пользователя расширителем.

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

FIDDLE:

HTML

<input type="text" data-bind="value: myNum, valueUpdate: 'afterkeyup'" />

JS

(function(ko) {

    ko.observable.fn.numeric = function () {
        // the observable we are extending
        var target = this;

        // subscribe to the observable so we can
        // intercept the value and do our custom
        // processing. 
        this.subscribe(function() {
           var value = target();
           // this will strip out any non numeric characters
           target(value.replace(/[^0-9]+/g,'')); //[^0-9\.]/g - allows decimals
        }, this);

        return target;
    };

    function ViewModel() {
        this.myNum = ko.observable().numeric();
    };

    ko.applyBindings(new ViewModel());

})(ko);

Ответ 3

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

Однако в вопросе, который вы связали, на самом деле есть лучший подход в комментариях. Это использовать расширитель нокаута. См. Живой пример 1 на http://knockoutjs.com/documentation/extenders.html

Вот несколько причин:  1. Более надежный. Например, вы все равно можете вставить строку текста из буфера обмена в своем решении.  2. Более удобный. Ваше решение явно отключает кучу ключей. Это совсем не удобно. Решение, предложенное Knockout, просто гарантирует, что конечная ценность является правильной.  3. Лучшее разделение кода и ремонтопригодность: ваш HTML может содержать простое связывание значений. Как только возникает требование о том, что значение должно быть числовым, вы просто расширяете наблюдаемое в своей модели viewmodel. Единственное изменение, которое вы делаете, - это JavaScript, как и должно быть, поскольку это функциональность, а не презентация. Это изменение также само по себе, и он очень четко показывает, что расширитель делает для любого, кто мог бы использовать наблюдаемые в вычислениях или w/e.

Ответ 4

Я предложил бы сделать обертку вокруг http://numeraljs.com/. Вы просто подключаете настройки и при обновлении вы будете вызывать формат на входе.

Ответ 5

Мне нужно решение, которое позволяет вводить только цифры (в том числе что-то вроде backspace и т.д.).

Проверьте этот плагин jquery: http://www.texotela.co.uk/code/jquery/numeric/

он позволяет десятичный разделитель, вы можете его разветкить, чтобы разрешить другие символы, такие как backspace

Ответ 6

Это моя фиксированная версия, рассматривающая все выше, но работающая как привязка реального значения и поддержка не наблюдаемых объектов как источник/цель.

РЕД.. Минимальная версия нокаута не предоставляет функцию writeValueToProperty и twoWayBindings. Поэтому мы должны клонировать writeValueToProperty и использовать _twoWayBindings. Я обновил код для поддержки сокращенной версии нокаута.

ko.expressionRewriting._twoWayBindings.numericValue = true;
ko.expressionRewriting.writeValueToProperty = function (property, allBindings, key, value, checkIfDifferent) {
    if (!property || !ko.isObservable(property)) {
        var propWriters = allBindings.get('_ko_property_writers');
        if (propWriters && propWriters[key])
            propWriters[key](value);
    } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
        property(value);
    }
};
ko.bindingHandlers.numericValue = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        $(element).on("keydown", function (event) {
            // Allow: backspace, delete, tab, escape, and enter.
            if (event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 9 || event.keyCode == 27 || event.keyCode == 13 ||
                // Allow: Ctrl+A
                (event.keyCode == 65 && event.ctrlKey === true) ||
                // Allow: . ,
                (event.keyCode == 188 || event.keyCode == 190 || event.keyCode == 110) ||
                // Allow: home, end, left, right.
                (event.keyCode >= 35 && event.keyCode <= 39)) {
                // Let it happen, don't do anything.
                return;
            }
            else {
                if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) {
                    event.preventDefault();
                }
            }
        });

        var underlying = valueAccessor();
        var interceptor = ko.dependentObservable({
            read: function () {
                if (ko.isObservable(underlying) == false) {
                    return underlying;
                } else {
                    return underlying();
                }
            },
            write: function (value) {
                if (ko.isObservable(underlying) == false) {
                    if (!isNaN(value)) {
                        var parsed = parseFloat(value);
                        ko.expressionRewriting.writeValueToProperty(underlying, allBindingsAccessor, 'numericValue', !isNaN(parsed) ? parsed : null);
                    }
                } else {
                    if (!isNaN(value)) {
                        var parsed = parseFloat(value);
                        underlying(!isNaN(parsed) ? parsed : null);
                    }
                }
            }
        });
        ko.bindingHandlers.value.init(element, function () { return interceptor; }, allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    }
}

Ответ 7

Вы можете улучшить обработчик привязки для поддержки модификации valueAccessor

Переплет:

ko.bindingHandlers.numeric = {
    init: function (element, valueAccessor) {
        var value = valueAccessor();
        $(element).on("keydown", function (event) {
            // Allow: backspace, delete, tab, escape, and enter
            if (event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 9 || event.keyCode == 27 || event.keyCode == 13 ||
                // Allow: Ctrl+A
                (event.keyCode == 65 && event.ctrlKey === true) ||
                // Allow: . ,
                (event.keyCode == 188 || event.keyCode == 190 || event.keyCode == 110) ||
                // Allow: home, end, left, right
                (event.keyCode >= 35 && event.keyCode <= 39)) {
                // let it happen, don't do anything
                return;
            }
            else {
                // Ensure that it is a number and stop the keypress
                if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) {
                    event.preventDefault();
                }
            }
        });

        $(element).change(function () {
            value($(element).val());
        });
    }    
};

В этом случае HTML будет

<span data-bind="text: Interval" ></span>
<input data-bind="numeric: Interval" />

FIDDLE