我在格式化输入字段时遇到问题,同时使基础范围变量保持非格式化。
我想要实现的是显示货币的文本字段。它应该在运行时自行格式化,同时处理错误的输入。我有这个工作,但我的问题是我想将非格式化的值存储在我的范围变量中。输入的问题是它需要一个双向的模型,因此更改输入字段会更新模型,反之亦然。
我遇到了$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"), ''));
});
}
};
});
同样,这只是一个简单的例子。当它加载一切看起来像它应该。格式化输入字段,而不是变量。但是,在更改输入字段中的值时,它不再自行格式化 - 但变量会正确更新。
有没有办法确保文件字段被格式化而变量不是?我想我正在寻找的是文本字段的过滤器,但我无法在其上找到任何内容。
祝你好运
答案 0 :(得分:15)
这是一个小提琴,展示了我如何在我的应用程序中实现完全相同的行为。我最终使用ngModelController#render
而不是$formatters
,然后添加了一组在keydown
和change
事件上触发的行为。
答案 1 :(得分:5)
我已经修改了Wade Tandy所做的一些事情,并增加了对几个功能的支持:
当输入不是数字时,将有效性设置为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/
答案 2 :(得分:5)
我重构了原始指令,因此它使用$ 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'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代码在这里[https://github.com/ST-Software/STAngular/blob/master/src/directives/SgNumberInput]
答案 3 :(得分:3)
确实$parsers
和$formatters
是“独立的”,正如你所说的那样(可能是循环,再次如你所说)。在我们的应用程序中,我们使用onchange
事件(在link
函数内)显式格式化,大致如下:
element.bind("change", function() {
...
var formattedModel = format(ctrl.$modelValue);
...
element.val(formattedModel);
});
有关详细且有效的示例,请参阅您的更新小提琴:http://jsfiddle.net/yE8Yj/1/
我喜欢绑定到onchange
事件,因为我觉得在用户输入时更改输入很烦人。
答案 4 :(得分:0)
小提琴使用旧版角度(1.0.7)。
在更新到最新版本1.2.6时,永远不会调用ngModelCtrl的$ render函数,这意味着如果在控制器中更改了模型值,
该视图中的数字永远不会按要求格式化。
//check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)){
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', false);
}
这是更新的小提琴http://jsfiddle.net/KPeBD/78/
答案 5 :(得分:0)
根据Wade Tandy的回答,这里有一个新的jsfiddle有以下改进:
我还用String.replace(regex)
替换了所有split().join()
,因为这允许我在表达式中使用变量。