Using AngularJS directive to format input field while leaving scope variable unchanged Using AngularJS directive to format input field while leaving scope variable unchanged angularjs angularjs

Using AngularJS directive to format input field while leaving scope variable unchanged


Here's a fiddle that shows how I implemented the exact same behavior in my application. I ended up using ngModelController#render instead of $formatters, and then adding a separate set of behavior that triggered on keydown and change events.

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


I've revised a little what Wade Tandy had done, and added support for several features:

  1. thousands separator is taken from $locale
  2. number of digits after decimal points is taken by default from $locale, and can be overridden by fraction attribute
  3. parser is activated only on change, and not on keydown, cut and paste, to avoid sending the cursor to the end of the input on every change
  4. Home and End keys are also allowed (to select the entire text using keyboard)
  5. set validity to false when input is not numeric, this is done in the parser:

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

You can see my revised version here - http://jsfiddle.net/KPeBD/64/


I have refactored the original directive, so that it uses $parses and $formatters instead of listening to keyboard events. There is also no need to use $browser.defer

See working demo here 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's 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 code is here [https://github.com/ST-Software/STAngular/blob/master/src/directives/SgNumberInput]