Je rencontre un problème de formatage d'un champ de saisie tout en laissant la variable de portée sous-jacente non formatée.
Ce que je veux réaliser est un champ de texte pour afficher la devise. Il devrait se formater à la volée, tout en gérant les erreurs de saisie. Cela fonctionne, mais mon problème est que je veux stocker la valeur non formatée dans ma variable de portée. Le problème avec l'entrée est que cela nécessite un modèle qui va dans les deux sens, donc changer le champ d'entrée met à jour le modèle, et inversement.
Je suis tombé sur $parsers
et $formatters
qui semble être ce que je recherche. Malheureusement, ils ne s’affectent pas (ce qui peut être utile pour éviter des boucles sans fin).
J'ai créé un simple jsFiddle: http://jsfiddle.net/cruckie/yE8Yj/ et le code est le suivant:
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"), ''));
});
}
};
});
Encore une fois, ceci est juste un exemple simple. Quand il charge tout ressemble à ce qu'il est supposé. Le champ de saisie est formaté et la variable ne l’est pas. Cependant, lorsque vous modifiez la valeur dans le champ de saisie, il ne se formate plus lui-même - la variable est toutefois mise à jour correctement.
Existe-t-il un moyen de s'assurer que le champ de texte est formaté alors que la variable ne l'est pas? Je suppose que ce que je recherche, c’est un filtre pour les champs de texte, mais je ne vois rien trouver à ce sujet.
Meilleures salutations
Voici un violon qui montre comment j'ai implémenté exactement le même comportement dans mon application. J'ai fini par utiliser ngModelController#render
au lieu de $formatters
, puis en ajoutant un ensemble de comportements distinct qui s'est déclenché lors des événements keydown
et change
.
J'ai refactoré la directive d'origine, de sorte qu'elle utilise $ parses et $ formateurs au lieu d'écouter les événements du clavier. Il n’est pas non plus nécessaire d’utiliser $ browser.defer
Voir la démo de travail ici 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
}]);
Le code Github est ici [ https://github.com/ST-Software/STAngular/blob/master/src/directives/SgNumberInput]
J'ai révisé un peu ce que Wade Tandy avait fait et ajouté un support pour plusieurs fonctionnalités:
définir la validité à false lorsque l'entrée n'est pas numérique, cela se fait dans l'analyseur:
// 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;
});
Vous pouvez voir ma version révisée ici - http://jsfiddle.net/KPeBD/64/
En effet, le $parsers
et le $formatters
sont "indépendants" comme vous dites (probablement pour des boucles, encore une fois comme vous le dites). Dans notre application, nous formatons explicitement avec l'événement onchange
(dans la fonction link
), à peu près comme:
element.bind("change", function() {
...
var formattedModel = format(ctrl.$modelValue);
...
element.val(formattedModel);
});
Voir votre violon mis à jour pour l'exemple détaillé et fonctionnel: http://jsfiddle.net/yE8Yj/1/
J'aime relier l'événement onchange
, car je trouve ennuyeux de modifier l'entrée pendant que l'utilisateur tape.
Basé sur la réponse de Wade Tandy, voici un nouveau jsfiddle avec les améliorations suivantes:
J'ai également remplacé tout String.replace(regex)
par split().join()
, car cela me permet d'utiliser des variables dans l'expression.
Le violon utilise une ancienne version de angular (1.0.7).
Lors de la mise à jour vers une version récente, 1.2.6, la fonction $ render de ngModelCtrl n'est jamais appelée, ce qui signifie que si la valeur du modèle est modifiée dans le contrôleur,
le numéro n'est jamais formaté comme requis dans la vue.
//check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)){
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', false);
}
Voici le violon mis à jour http://jsfiddle.net/KPeBD/78/