Open insinfo opened 5 years ago
my mask implementation is working correctly but the angular does not detect the input change
import 'dart:html';
import 'dart:async';
import 'dart:math';
import 'package:angular/angular.dart';
// <!-- <input maskMoney [maxlength]="11" type="text" > -->
@Directive(selector: '[maskMoney]')
class MaskMoneyDirective {
/* @Input()
Map<String, String> maskMoney;*/
InputElement inputElement;
MaskMoneyOptions options = new MaskMoneyOptions();
StreamSubscription strSubKeyUp;
StreamSubscription strSubKeyDown;
final Element _el;
var onFocusValue;
var browser = {};
MaskMoneyDirective(this._el) {
browser['mozilla'] = RegExp('mozilla').hasMatch(window.navigator.userAgent.toLowerCase()) &&
!RegExp('webkit').hasMatch(window.navigator.userAgent.toLowerCase());
browser['webkit'] = RegExp('webkit').hasMatch(window.navigator.userAgent.toLowerCase());
browser['opera'] = RegExp('opera').hasMatch(window.navigator.userAgent.toLowerCase());
browser['msie'] = RegExp('msie').hasMatch(window.navigator.userAgent.toLowerCase());
browser['device'] = RegExp('android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini', caseSensitive: false)
.hasMatch(window.navigator.userAgent.toLowerCase());
inputElement = _el;
inputElement.onKeyPress.listen(keypressEvent);
inputElement.onKeyDown.listen(keydownEvent);
inputElement.onClick.listen(clickEvent);
inputElement.onBlur.listen(blurEvent);
inputElement.onFocus.listen(focusEvent);
inputElement.onDoubleClick.listen(doubleClickEvent);
inputElement.onCut.listen(cutPasteEvent);
inputElement.onPaste.listen(cutPasteEvent);
}
unmasked() {
var value = inputElement.value != "" ? inputElement.value : "0";
var isNegative = value.indexOf("-") != -1;
String decimalPart;
// get the last position of the array that is a number(coercion makes "" to be evaluated as false)
var reversed = value.split(r'\D').reversed.toList();
for (var element in reversed) {
if (element != null) {
decimalPart = element;
break;
}
}
value = value.replaceAll(new RegExp('\D'), "");
value = value.replaceAll(new RegExp(decimalPart + "\$"), "." + decimalPart);
if (isNegative) {
value = "-" + value;
}
return double.tryParse(value);
}
Selection getInputSelection() {
var el = inputElement, start = 0, end = 0;
start = el.selectionStart;
end = el.selectionEnd;
return new Selection(start: start, end: end);
} //getInputSelection
//pode introduzir mais números
canInputMoreNumbers() {
//Comprimento Máximo Atingido
var haventReachedMaxLength = false;
var maxlength = inputElement.getAttribute("maxlength");
if (maxlength != null) {
int maxl = int.tryParse(maxlength);
if (maxl != null && maxl >= 0) {
haventReachedMaxLength = true;
}
}
var selection = getInputSelection();
var start = selection.start;
var end = selection.end;
//tem número selecionado
var haveNumberSelected = false;
if (selection.start != selection.end) {
var re = inputElement.value.substring(start, end);
if (re != null) {
if (re.allMatches(r'\d').length > 0) {
haveNumberSelected = true;
}
}
}
var startWithZero = false;
if (inputElement.value.length > 0) {
startWithZero = inputElement.value.startsWith("0");
}
var result = haventReachedMaxLength || haveNumberSelected || startWithZero;
print(result);
return result;
}
setCursorPosition(pos) {
// Do not set the position if
// the we're formatting on blur.
// This is because we do not want
// to refocus on the control after
// the blur.
if (!!options.formatOnBlur) {
return;
}
inputElement.focus();
inputElement.setSelectionRange(pos, pos);
}
maskAndPosition(startPos) {
var originalLen = inputElement.value.length, newLen;
inputElement.value = maskValue(inputElement.value);
newLen = inputElement.value.length;
// If the we're using the reverse option,
// do not put the cursor at the end of
// the input. The reverse option allows
// the user to input text from left to right.
if (!options.reverse) {
startPos = startPos - (originalLen - newLen);
}
setCursorPosition(startPos);
}
mask() {
String value = inputElement.value;
if (options.allowEmpty && value == "") {
return;
}
print("mask value " + value);
var isNumber = !isNotNumeric(value);
print("mask isNumber " + isNumber.toString());
var decimalPointIndex = isNumber ? value.indexOf(".") : value.indexOf(options.decimal);
if (options.precision > 0) {
if (decimalPointIndex < 0) {
print("decimalPointIndex $decimalPointIndex");
value += options.decimal + List<String>.generate(options.precision, (i) => "0").join();
} else {
// If the following decimal part dosen't have enough length
//against the precision, it needs to be filled with zeros.
//Se a parte decimal a seguir não tiver comprimento suficiente
//em relação à precisão, ela precisará ser preenchida com zeros.
var integerPart = value.substring(0, decimalPointIndex);
var decimalPart = value.substring(decimalPointIndex + 1);
String numberZerosRequired = (options.precision - decimalPart.length) < 1
? ""
: List<String>.generate(options.precision - decimalPart.length, (i) => "0").join();
value = integerPart + options.decimal + decimalPart + numberZerosRequired;
}
} else if (decimalPointIndex > 0) {
// if the precision is 0, discard the decimal part
value = value.substring(0, decimalPointIndex);
}
print("mask value " + value);
var result = maskValue(value);
print("mask result " + result);
inputElement.value = result;
}
changeSign() {
var inputValue = inputElement.value;
if (options.allowNegative) {
if (inputValue != "" && inputValue.substring(0, 1) == "-") {
return inputValue.replaceAll("-", "");
} else {
return "-" + inputValue;
}
} else {
return inputValue;
}
}
preventDefault(e) {
if (e.preventDefault != null) {
//standard browsers
e.preventDefault();
} else {
// old internet explorer
e.returnValue = false;
}
}
keypressEvent(KeyboardEvent e) {
var key = e.keyCode;
var decimalKeyCode = options.decimal.codeUnitAt(0);
//added to handle an IE "special" event
if (key == null) {
return false;
}
// any key except the numbers 0-9. if we're using settings.reverse,
// allow the user to input the decimal key
//Qualquer tecla, exceto os números 0-9. se estivermos usando settings.reverse,
//permitir que o usuário insira a tecla decimal
if ((key < 48 || key > 57) && (key != decimalKeyCode || !options.reverse)) {
print("keypressEvent !options.reverse");
return handleAllKeysExceptNumericalDigits(key, e);
} else if (!canInputMoreNumbers()) {
print("keypressEvent !canInputMoreNumbers()");
return false;
} else {
if (key == decimalKeyCode && shouldPreventDecimalKey()) {
return false;
}
if (options.formatOnBlur) {
return true;
}
preventDefault(e);
print("keypressEvent else");
applyMask(e);
return false;
}
}
shouldPreventDecimalKey() {
// If all text is selected, we can accept the decimal
// key because it will replace everything.
if (isAllTextSelected()) {
return false;
}
return alreadyContainsDecimal();
}
isAllTextSelected() {
var length = inputElement.value.length;
var selection = getInputSelection();
// This should if all text is selected or if the
// input is empty.
return selection.start == 0 && selection.end == length;
}
applyMask(e) {
var key = e.keyCode, keyPressedChar = "", selection, startPos, endPos, value;
if (key >= 48 && key <= 57) {
keyPressedChar = String.fromCharCode(key);
}
selection = getInputSelection();
startPos = selection.start;
endPos = selection.end;
value = inputElement.value;
print("applyMask $value");
var result = value.substring(0, startPos) + keyPressedChar + value.substring(endPos, value.length);
inputElement.value = result;
print("applyMask result");
maskAndPosition(startPos + 1);
}
handleAllKeysExceptNumericalDigits(key, e) {
// -(minus) key
if (key == 45) {
inputElement.value = changeSign();
return false;
// +(plus) key
} else if (key == 43) {
inputElement.value = inputElement.value.replaceAll("-", "");
return false;
// enter key or tab key
} else if (key == 13 || key == 9) {
return true;
} else if (browser['mozilla'] && (key == 37 || key == 39) && e.charCode == 0) {
// needed for left arrow key or right arrow key with firefox
// the charCode part is to avoid allowing "%"(e.charCode 0, e.keyCode 37)
return true;
} else {
// any other key with keycode less than 48 and greater than 57
preventDefault(e);
return true;
}
}
alreadyContainsDecimal() {
return inputElement.value.indexOf(options.decimal) > -1;
}
keydownEvent(e) {
var key = e.keyCode, selection, startPos, endPos, value, lastNumber;
//needed to handle an IE "special" event
if (key == null) {
return false;
}
print("keydownEvent key $key");
selection = getInputSelection();
startPos = selection.start;
endPos = selection.end;
print("keydownEvent startPos $startPos");
print("keydownEvent endPos $endPos");
if (key == 8 || key == 46 || key == 63272) {
// backspace or delete key (with special case for safari)
preventDefault(e);
value = inputElement.value;
// not a selection
if (startPos == endPos) {
// backspace
if (key == 8) {
if (options.suffix == "") {
startPos -= 1;
} else {
// needed to find the position of the last number to be erased
lastNumber = value.split("").reverse().join("").search(r'\d');
startPos = value.length - lastNumber - 1;
endPos = startPos + 1;
}
//delete
} else {
endPos += 1;
}
}
inputElement.value = (value.substring(0, startPos) + value.substring(endPos, value.length));
maskAndPosition(startPos);
return false;
} else if (key == 9) {
// tab key
return true;
} else {
// any other key
return true;
}
}
focusEvent(e) {
onFocusValue = inputElement.value;
mask();
var input = inputElement, textRange;
if (!!options.selectAllOnFocus) {
input.select();
}
}
cutPasteEvent(e) {
var future = new Future.delayed(const Duration(milliseconds: 0), () {
mask();
});
}
getDefaultMask() {
double n = double.tryParse("0") / pow(10, options.precision);
return (n.toStringAsFixed(options.precision)).replaceAll(new RegExp("\\."), options.decimal);
}
blurEvent(e) {
if (browser['msie']) {
keypressEvent(e);
}
if (!!options.formatOnBlur && inputElement.value != onFocusValue) {
applyMask(e);
}
if (inputElement.value == "" && options.allowEmpty) {
inputElement.value = "";
} else if (inputElement.value == "" || inputElement.value == setSymbol(getDefaultMask())) {
if (!options.allowZero) {
inputElement.value = "";
} else if (!options.affixesStay) {
inputElement.value = getDefaultMask();
} else {
inputElement.value = setSymbol(getDefaultMask());
}
} else {
if (!options.affixesStay) {
var newValue = inputElement.value.replaceAll(options.prefix, "").replaceAll(options.suffix, "");
inputElement.value = newValue;
}
}
if (inputElement.value != onFocusValue) {
inputElement.onChange.listen((e) {});
}
}
clickEvent(e) {
var input = inputElement, length;
if (!!options.selectAllOnFocus) {
// selectAllOnFocus will be handled by
// the focus event. The focus event is
// also fired when the input is clicked.
return;
} else if (input.setSelectionRange != null && options.bringCaretAtEndOnFocus) {
length = inputElement.value.length;
input.setSelectionRange(length, length);
} else {
inputElement.value = inputElement.value;
}
}
doubleClickEvent(e) {
var input = inputElement, start, length;
if (input.setSelectionRange != null && options.bringCaretAtEndOnFocus) {
length = inputElement.value.length;
start = options.doubleClickSelection ? 0 : length;
input.setSelectionRange(start, length);
} else {
inputElement.value = inputElement.value;
}
}
setSymbol(String value) {
var oper = "";
if (value.indexOf("-") > -1) {
value = value.replaceAll("-", "");
oper = "-";
}
if (value.indexOf(options.prefix) > -1) {
value = value.replaceAll(options.prefix, "");
}
if (value.indexOf(options.suffix) > -1) {
value = value.replaceAll(options.suffix, "");
}
var r = oper + options.prefix + value + options.suffix;
print("setSymbol $r");
return r;
}
maskValue(String value) {
if (options.allowEmpty && value == "") {
return "";
}
if (!!options.reverse) {
var r = maskValueReverse(value);
print("maskValueReverse $r");
return r;
}
var result = maskValueStandard(value);
print("maskValueStandard $result");
return result;
}
maskValueStandard(String value) {
String negative = (value.indexOf("-") > -1 && options.allowNegative) ? "-" : "";
print("negative " + negative);
String onlyNumbers = value.replaceAll(RegExp('[^0-9]'), "");
print("onlyNumbers " + onlyNumbers);
String integerPart = "0";
if (onlyNumbers.length > 1) {
integerPart = onlyNumbers.substring(0, onlyNumbers.length - options.precision);
}
print("integerPart " + integerPart);
String newValue;
String decimalPart;
String leadingZeros;
newValue = buildIntegerPart(integerPart, negative);
if (options.precision > 0) {
if (!isNotNumeric(value) && value.indexOf(".") > -1) {
var precision = value.substring(value.indexOf(".") + 1);
onlyNumbers += new List<String>.generate((options.precision) - precision.length, (e) => "0").join();
integerPart = onlyNumbers.substring(0, onlyNumbers.length - options.precision);
newValue = buildIntegerPart(integerPart, negative);
}
if (onlyNumbers.length > options.precision) {
decimalPart = onlyNumbers.substring(onlyNumbers.length - options.precision);
}else{
decimalPart = onlyNumbers;
}
leadingZeros = new List<String>.generate((options.precision) - decimalPart.length, (e) => "0").join();
newValue += options.decimal + leadingZeros + decimalPart;
}
print("newValue " + newValue);
print("decimalPart " + decimalPart);
print("leadingZeros " + leadingZeros);
return setSymbol(newValue);
}
maskValueReverse(String value) {
var negative = value.indexOf("-") > -1 && options.allowNegative ? "-" : "";
var valueWithoutSymbol = value.replaceAll(options.prefix, "").replaceAll(options.suffix, "");
var integerPart = valueWithoutSymbol.split(options.decimal)[0];
var newValue;
var decimalPart = "";
if (integerPart == "") {
integerPart = "0";
}
newValue = buildIntegerPart(integerPart, negative);
if (options.precision > 0) {
var arr = valueWithoutSymbol.split(options.decimal);
if (arr.length > 1) {
decimalPart = arr[1];
}
newValue += options.decimal + decimalPart;
var rounded = double.tryParse((integerPart + "." + decimalPart)).toStringAsFixed(options.precision);
var roundedDecimalPart = rounded.toString().split(options.decimal)[1];
newValue = newValue.split(options.decimal)[0] + "." + roundedDecimalPart;
}
return setSymbol(newValue);
}
buildIntegerPart(integerPart, negative) {
// remove initial zeros
integerPart = integerPart.replaceAll(RegExp('^0*'), "");
// put settings.thousands every 3 chars
integerPart = integerPart.replaceAll(RegExp('\B(?=(\d{3})+(?!\d))'), options.thousands);
if (integerPart == "") {
integerPart = "0";
}
return negative + integerPart;
}
bool isNumeric(String s) {
if (s == null) {
return false;
}
return double.parse(s, (e) => null) != null;
}
bool isNotNumeric(String s) {
if (s == null) {
return true;
} else if (s.trim() == "") {
return false;
}
return double.parse(s, (e) => null) == null;
}
//print(dynamic item) {}
}
class Selection {
int start, end;
Selection({this.start, this.end});
}
class MaskMoneyOptions {
String prefix = "R\$ ";
String suffix = "";
bool affixesStay = false;
String thousands = ".";
String decimal = ",";
int precision = 2;
bool allowZero = false;
bool allowNegative = true;
bool doubleClickSelection = true;
bool allowEmpty = false;
bool bringCaretAtEndOnFocus = true;
bool formatOnBlur = false;
bool reverse = false;
bool selectAllOnFocus = false;
}
I was creating a money mask directive, in my app I came across this problem, because in my mascara implementation I use the "event.preventDefault ();" in the onKeyDown event to prevent nonnumeric characters from entering an input. This disrupts the function of [(ngModel)], ie the value of the input stops being passed to the model.
Hypothetical example for demonstration