UNPKG

angular-numeric-directive

Version:
293 lines (259 loc) 11.4 kB
(function () { 'use strict'; if(typeof module !== 'undefined') { module.exports = 'purplefox.numeric'; } /* global angular */ angular .module('purplefox.numeric', []) .directive('numeric', numeric); numeric.$inject = ['$locale']; function numeric($locale) { // Usage: // <input type="text" decimals="3" min="-20" max="40" formatting="false" ></input> // Creates: // var directive = { link: link, require: 'ngModel', restrict: 'A' }; return directive; function link(scope, el, attrs, ngModelCtrl) { var decimalSeparator = $locale.NUMBER_FORMATS.DECIMAL_SEP; var groupSeparator = $locale.NUMBER_FORMATS.GROUP_SEP; // Create new regular expression with current decimal separator. var NUMBER_REGEXP = "^\\s*(\\-|\\+)?(\\d+|(\\d*(\\.\\d*)))\\s*$"; var regex = new RegExp(NUMBER_REGEXP); var formatting = true; var maxInputLength = 16; // Maximum input length. Default max ECMA script. var max; // Maximum value. Default undefined. var min; // Minimum value. Default undefined. var limitMax = true; // Limit input to max value (value is capped). Default true. var limitMin = true; // Limit input to min value (value is capped). Default true. var decimals = 2; // Number of decimals. Default 2. var lastValidValue; // Last valid value. // Create parsers and formatters. ngModelCtrl.$parsers.push(parseViewValue); ngModelCtrl.$parsers.push(minValidator); ngModelCtrl.$parsers.push(maxValidator); ngModelCtrl.$formatters.push(formatViewValue); el.bind('blur', onBlur); // Event handler for the leave event. el.bind('focus', onFocus); // Event handler for the focus event. // Put a watch on the min, max and decimal value changes in the attribute. scope.$watch(attrs.min, onMinChanged); scope.$watch(attrs.max, onMaxChanged); scope.$watch(attrs.limitMax, onLimitMaxChanged); scope.$watch(attrs.limitMin, onLimitMinChanged); scope.$watch(attrs.decimals, onDecimalsChanged); scope.$watch(attrs.formatting, onFormattingChanged); // Setup decimal formatting. if (decimals > -1) { ngModelCtrl.$parsers.push(function (value) { return (value) ? round(value) : value; }); ngModelCtrl.$formatters.push(function (value) { return (value || value === 0) ? formatPrecision(value) : value; }); } function onMinChanged(value) { if (!angular.isUndefined(value)) { min = parseFloat(value); lastValidValue = minValidator(ngModelCtrl.$modelValue); ngModelCtrl.$setViewValue(formatPrecision(lastValidValue)); ngModelCtrl.$render(); } } function onMaxChanged(value) { if (!angular.isUndefined(value)) { max = parseFloat(value); maxInputLength = calculateMaxLength(max); lastValidValue = maxValidator(ngModelCtrl.$modelValue); ngModelCtrl.$setViewValue(formatPrecision(lastValidValue)); ngModelCtrl.$render(); } } function onDecimalsChanged(value) { if (!angular.isUndefined(value)) { decimals = parseFloat(value); maxInputLength = calculateMaxLength(max); if (lastValidValue !== undefined) { ngModelCtrl.$setViewValue(formatPrecision(lastValidValue)); ngModelCtrl.$render(); } } } function onFormattingChanged(value) { if (!angular.isUndefined(value)) { formatting = (value !== false); ngModelCtrl.$setViewValue(formatPrecision(lastValidValue)); ngModelCtrl.$render(); } } function onLimitMinChanged(value) { if (!angular.isUndefined(value)) { limitMin = (value == "true"); } } function onLimitMaxChanged(value) { if (!angular.isUndefined(value)) { limitMax = (value == "true"); } } /** * Round the value to the closest decimal. */ function round(value) { var d = Math.pow(10, decimals); return Math.round(value * d) / d; } /** * Format a number with the thousand group separator. */ function numberWithCommas(value) { if (formatting) { var parts = (""+value).split(decimalSeparator); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator); return parts.join(decimalSeparator); } else { // No formatting applies. return value; } } /** * Format a value with thousand group separator and correct decimal char. */ function formatPrecision(value) { if (!(value || value === 0)) { return ''; } var formattedValue = parseFloat(value).toFixed(decimals); formattedValue = formattedValue.replace('.', decimalSeparator); return numberWithCommas(formattedValue); } function formatViewValue(value) { return ngModelCtrl.$isEmpty(value) ? '' : '' + value; } /** * Parse the view value. */ function parseViewValue(value) { if (angular.isUndefined(value)) { value = ''; } value = (""+value).replace(decimalSeparator, '.'); // Handle leading decimal point, like ".5" if (value.indexOf('.') === 0) { value = '0' + value; } // Allow "-" inputs only when min < 0 if (value.indexOf('-') === 0) { if (min >= 0) { value = null; ngModelCtrl.$setViewValue(formatViewValue(lastValidValue)); ngModelCtrl.$render(); } else if (value === '-') { value = ''; } } var empty = ngModelCtrl.$isEmpty(value); if (empty) { lastValidValue = ''; //ngModelCtrl.$modelValue = undefined; } else { if (regex.test(value) && (value.length <= maxInputLength)) { if ((value > max) && limitMax) { lastValidValue = max; } else if ((value < min) && limitMin) { lastValidValue = min; } else { lastValidValue = (value === '') ? null : parseFloat(value); } } else { // Render the last valid input in the field ngModelCtrl.$setViewValue(formatViewValue(lastValidValue)); ngModelCtrl.$render(); } } return lastValidValue; } /** * Calculate the maximum input length in characters. * If no maximum the input will be limited to 16; the maximum ECMA script int. */ function calculateMaxLength(value) { var length = 16; if (!angular.isUndefined(value)) { length = Math.floor(value).toString().length; } if (decimals > 0) { // Add extra length for the decimals plus one for the decimal separator. length += decimals + 1; } if (min < 0) { // Add extra length for the - sign. length++; } return length; } /** * Value validator for min and max. */ function minmaxValidator(value, testValue, validityName, limit, compareFunction) { if (!angular.isUndefined(testValue) && limit) { if (!ngModelCtrl.$isEmpty(value) && compareFunction) { return testValue; } else { return value; } } else { if (!limit) { ngModelCtrl.$setValidity(validityName, !compareFunction); } return value; } } /** * Minimum value validator. */ function minValidator(value) { return minmaxValidator(value, min, 'min', limitMin, (value < min)); } /** * Maximum value validator. */ function maxValidator(value) { return minmaxValidator(value, max, 'max', limitMax, (value > max)); } /** * Function for handeling the blur (leave) event on the control. */ function onBlur() { var value = ngModelCtrl.$modelValue; if (!angular.isUndefined(value)) { // Format the model value. ngModelCtrl.$viewValue = formatPrecision(value); ngModelCtrl.$render(); } } /** * Function for handeling the focus (enter) event on the control. * On focus show the value without the group separators. */ function onFocus() { var value = ngModelCtrl.$modelValue; if (!angular.isUndefined(value)) { ngModelCtrl.$viewValue = (""+value).replace(".", decimalSeparator); ngModelCtrl.$render(); } } } } })();