Подтвердить что ты не робот

Использование директивы AngularJS для форматирования поля ввода, оставляя переменную области без изменений

У меня возникла проблема с форматированием поля ввода, в результате чего основная переменная области неформатирована.

То, что я хочу достичь, - это текстовое поле для отображения валюты. Он должен отформатировать себя на лету, обрабатывая неправильный ввод. Я получил эту работу, но моя проблема в том, что я хочу сохранить неформатное значение в моей переменной области. Проблема со входом заключается в том, что для нее требуется модель, которая идет в обоих направлениях, поэтому изменение поля ввода обновляет модель и наоборот.

Я столкнулся с $parsers и $formatters, который, как представляется, я ищу. К сожалению, они не влияют друг на друга (что может быть полезно, чтобы избежать бесконечных циклов).

Я создал простой jsFiddle: http://jsfiddle.net/cruckie/yE8Yj/, и код выглядит следующим образом:

HTML:

<div data-ng-app="app" data-ng-controller="Ctrl">
    <input type="text" data-currency="" data-ng-model="data" />
    <div>Model: {{data}}</div>
</div>

JS:

var app = angular.module("app", []);

function Ctrl($scope) {
    $scope.data = 1234567;
}

app.directive('currency', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attr, ctrl) {

            ctrl.$formatters.push(function(modelValue) {
                return modelValue.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ',');
            });

            ctrl.$parsers.push(function(viewValue) {
                return parseFloat(viewValue.replace(new RegExp(",", "g"), ''));
            });
        }
    };
});

Опять же, это просто простой пример. Когда он загружает все выглядит так, как предполагалось. Поле ввода форматируется, а переменная - нет. Однако при изменении значения в поле ввода он больше не форматирует себя - переменная, однако, обновляется правильно.

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

С наилучшими пожеланиями

4b9b3361

Ответ 1

Вот скрипка, которая показывает, как я реализовал то же самое поведение в своем приложении. В итоге я использовал ngModelController#render вместо $formatters, а затем добавил отдельный набор действий, вызванный событиями keydown и change.

http://jsfiddle.net/KPeBD/2/

Ответ 2

Я отредактировал исходную директиву, так что вместо прослушивания событий клавиатуры он использует $parses и $formatters. Также нет необходимости использовать $browser.defer

См. рабочую демонстрацию здесь http://jsfiddle.net/davidvotrubec/ebuqo6Lm/

    var myApp = angular.module('myApp', []);

    myApp.controller('MyCtrl', function($scope) {
      $scope.numericValue = 12345678;
    });

    //Written by David Votrubec from ST-Software.com
    //Inspired by http://jsfiddle.net/KPeBD/2/
    myApp.directive('sgNumberInput', ['$filter', '$locale', function ($filter, $locale) {
            return {
                require: 'ngModel',
                restrict: "A",
                link: function ($scope, element, attrs, ctrl) {
                    var fractionSize = parseInt(attrs['fractionSize']) || 0;
                    var numberFilter = $filter('number');
                    //format the view value
                    ctrl.$formatters.push(function (modelValue) {
                        var retVal = numberFilter(modelValue, fractionSize);
                        var isValid = isNaN(modelValue) == false;
                        ctrl.$setValidity(attrs.name, isValid);
                        return retVal;
                    });
                    //parse user input
                    ctrl.$parsers.push(function (viewValue) {
                        var caretPosition = getCaretPosition(element[0]), nonNumericCount = countNonNumericChars(viewValue);
                        viewValue = viewValue || '';
                        //Replace all possible group separators
                        var trimmedValue = viewValue.trim().replace(/,/g, '').replace(/`/g, '').replace(/'/g, '').replace(/\u00a0/g, '').replace(/ /g, '');
                        //If numericValue contains more decimal places than is allowed by fractionSize, then numberFilter would round the value up
                        //Thus 123.109 would become 123.11
                        //We do not want that, therefore I strip the extra decimal numbers
                        var separator = $locale.NUMBER_FORMATS.DECIMAL_SEP;
                        var arr = trimmedValue.split(separator);
                        var decimalPlaces = arr[1];
                        if (decimalPlaces != null && decimalPlaces.length > fractionSize) {
                            //Trim extra decimal places
                            decimalPlaces = decimalPlaces.substring(0, fractionSize);
                            trimmedValue = arr[0] + separator + decimalPlaces;
                        }
                        var numericValue = parseFloat(trimmedValue);
                        var isEmpty = numericValue == null || viewValue.trim() === "";
                        var isRequired = attrs.required || false;
                        var isValid = true;
                        if (isEmpty && isRequired) {
                            isValid = false;
                        }
                        if (isEmpty == false && isNaN(numericValue)) {
                            isValid = false;
                        }
                        ctrl.$setValidity(attrs.name, isValid);
                        if (isNaN(numericValue) == false && isValid) {
                            var newViewValue = numberFilter(numericValue, fractionSize);
                            element.val(newViewValue);
                            var newNonNumbericCount = countNonNumericChars(newViewValue);
                            var diff = newNonNumbericCount - nonNumericCount;
                            var newCaretPosition = caretPosition + diff;
                            if (nonNumericCount == 0 && newCaretPosition > 0) {
                                newCaretPosition--;
                            }
                            setCaretPosition(element[0], newCaretPosition);
                        }
                        return isNaN(numericValue) == false ? numericValue : null;
                    });
                } //end of link function
            };
            //#region helper methods
            function getCaretPosition(inputField) {
                // Initialize
                var position = 0;
                // IE Support
                if (document.selection) {
                    inputField.focus();
                    // To get cursor position, get empty selection range
                    var emptySelection = document.selection.createRange();
                    // Move selection start to 0 position
                    emptySelection.moveStart('character', -inputField.value.length);
                    // The caret position is selection length
                    position = emptySelection.text.length;
                }
                else if (inputField.selectionStart || inputField.selectionStart == 0) {
                    position = inputField.selectionStart;
                }
                return position;
            }
            function setCaretPosition(inputElement, position) {
                if (inputElement.createTextRange) {
                    var range = inputElement.createTextRange();
                    range.move('character', position);
                    range.select();
                }
                else {
                    if (inputElement.selectionStart) {
                        inputElement.focus();
                        inputElement.setSelectionRange(position, position);
                    }
                    else {
                        inputElement.focus();
                    }
                }
            }
            function countNonNumericChars(value) {
                return (value.match(/[^a-z0-9]/gi) || []).length;
            }
            //#endregion helper methods
        }]);

Здесь приведен код Github [https://github.com/ST-Software/STAngular/blob/master/src/directives/SgNumberInput]

Ответ 3

Я немного пересмотрел то, что сделал Уэйд Тэнди, и добавил поддержку нескольких функций:

  • разделитель тысяч берется из $locale
  • количество цифр после десятичных точек берется по умолчанию из $locale и может быть переопределено атрибутом fraction
  • парсер активируется только при изменении, а не при нажатии, вырезании и вставке, чтобы не отправлять курсор в конец ввода при каждом изменении
  • Также доступны домашние и конечные ключи (для выбора всего текста с клавиатуры)
  • установить значение false, если ввод не является числовым, это делается в синтаксическом анализаторе:

            // This runs when we update the text field
        ngModelCtrl.$parsers.push(function(viewValue) {
            var newVal = viewValue.replace(replaceRegex, '');
            var newValAsNumber = newVal * 1;
    
            // check if new value is numeric, and set control validity
            if (isNaN(newValAsNumber)){
                ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', false);
            }
            else{
                newVal = newValAsNumber.toFixed(fraction);
                ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
            }
            return newVal;
    
        });
    

Вы можете увидеть мою пересмотренную версию здесь - http://jsfiddle.net/KPeBD/64/

Ответ 4

Действительно, $parsers и $formatters являются "независимыми", как вы говорите (возможно, для циклов, опять же, как вы говорите). В нашем приложении мы явно форматируем событие onchange (внутри функции link) примерно так:

element.bind("change", function() {
    ...
    var formattedModel = format(ctrl.$modelValue);
    ...
    element.val(formattedModel);
});

См. обновленную скрипту для подробного и рабочего примера: http://jsfiddle.net/yE8Yj/1/

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

Ответ 5

Сценарий использует старую версию angular (1.0.7).

При обновлении до последней версии версии 1.2.6 функция рендеринга ngModelCtrl никогда не вызывается, то есть если значение модели изменяется в контроллере,

номер никогда не отформатируется, как требуется в представлении.

//check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)){
  ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', false);
}

Вот обновленная скрипка http://jsfiddle.net/KPeBD/78/

Ответ 6

На основе ответа от Wade Tandy здесь представлен новый jsfiddle со следующими улучшениями:

  • возможно десятичное число
  • разделитель тысяч и разделитель десятичных знаков на основе локальных языков
  • и некоторые другие настройки...

Я также заменяю все String.replace(regex) на split().join(), потому что это позволяет мне использовать переменные в выражении.

http://jsfiddle.net/KPeBD/283/