编辑:请使用此简单指令随意添加对其他人有用的其他验证。
-
我正在尝试创建一个Angular Directive,将字符输入限制在文本框中。我已经成功地使用了几个常见的用例(alphbetical,alphanumeric和numeric),但是使用流行的方法验证电子邮件地址,日期和货币我无法使指令工作,因为我需要它否定正则表达式。至少这是我认为它需要做的事情。
非常感谢任何货币帮助(可选千位分隔符和美分),日期(mm / dd / yyyy)和电子邮件。我对正则表达式并不强烈。
这是我目前所拥有的: http://jsfiddle.net/corydorning/bs05ys69/
HTML
<div ng-app="example">
<h1>Validate Directive</h1>
<p>The Validate directive allow us to restrict the characters an input can accept.</p>
<h3><code>alphabetical</code> <span style="color: green">(works)</span></h3>
<p>Restricts input to alphabetical (A-Z, a-z) characters only.</p>
<label><input type="text" validate="alphabetical" ng-model="validate.alphabetical"/></label>
<h3><code>alphanumeric</code> <span style="color: green">(works)</span></h3>
<p>Restricts input to alphanumeric (A-Z, a-z, 0-9) characters only.</p>
<label><input type="text" validate="alphanumeric" ng-model="validate.alphanumeric" /></label>
<h3><code>currency</code> <span style="color: red">(doesn't work)</span></h3>
<p>Restricts input to US currency characters with comma for thousand separator (optional) and cents (optional).</p>
<label><input type="text" validate="currency.us" ng-model="validate.currency" /></label>
<h3><code>date</code> <span style="color: red">(doesn't work)</span></h3>
<p>Restricts input to the mm/dd/yyyy date format only.</p>
<label><input type="text" validate="date" ng-model="validate.date" /></label>
<h3><code>email</code> <span style="color: red">(doesn't work)</span></h3>
<p>Restricts input to email format only.</p>
<label><input type="text" validate="email" ng-model="validate.email" /></label>
<h3><code>numeric</code> <span style="color: green">(works)</span></h3>
<p>Restricts input to numeric (0-9) characters only.</p>
<label><input type="text" validate="numeric" ng-model="validate.numeric" /></label>
的JavaScript
angular.module('example', [])
.directive('validate', function () {
var validations = {
// works
alphabetical: /[^a-zA-Z]*$/,
// works
alphanumeric: /[^a-zA-Z0-9]*$/,
// doesn't work - need to negate?
// taken from: http://stackoverflow.com/questions/354044/what-is-the-best-u-s-currency-regex
currency: /^[+-]?[0-9]{1,3}(?:,?[0-9]{3})*(?:\.[0-9]{2})?$/,
// doesn't work - need to negate?
// taken from here: http://stackoverflow.com/questions/15196451/regular-expression-to-validate-datetime-format-mm-dd-yyyy
date: /(?:0[1-9]|1[0-2])\/(?:0[1-9]|[12][0-9]|3[01])\/(?:19|20)[0-9]{2}/,
// doesn't work - need to negate?
// taken from: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
email: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
// works
numeric: /[^0-9]*$/
};
return {
require: 'ngModel',
scope: {
validate: '@'
},
link: function (scope, element, attrs, modelCtrl) {
var pattern = validations[scope.validate] || scope.validate
;
modelCtrl.$parsers.push(function (inputValue) {
var transformedInput = inputValue.replace(pattern, '')
;
if (transformedInput != inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
};
});
答案 0 :(得分:2)
我很确定,有更好的方法,可能正则表达式也不是最好的工具,但这是我的命题。
这样您只能限制允许输入的字符并强制用户使用正确的格式,但您需要在用户完成输入后验证最终输入,但这是另一个故事。
字母,数字和字母数字非常简单,用于输入和验证输入,因为很清楚您可以键入什么,以及什么是正确的最终输入。但是对于日期,邮件,货币,您无法使用正则表达式验证输入以获得完整的有效输入,因为用户需要先输入它,同时输入需要在最终有效输入方面无效。因此,这是一件事,例如限制用户只键入数字和/
为日期格式,如:12/12/1988
,但最后你需要检查他是否键入了正确的日期或只是{ {1}}例如。当用户提交答案时,或者当文本字段失去焦点等时,需要检查这一点
要验证打字字符,您可以尝试使用:
JSFiddle DEMO
第一次改变:
12/12/126
到
var transformedInput = inputValue.replace(pattern, '')
然后使用正则表达式:
var transformedInput = inputValue.replace(pattern, '$1')
- 字母/^([a-zA-Z]*(?=[^a-zA-Z]))./
- 字母数字/^([a-zA-Z0-9]*(?=[^a-zA-Z0-9]))./
- 货币(允许字符串如:343243.34,1,123,345.34,.05,带或不带$)/(\.((?=[^\d])|\d{2}(?![^,\d.]))|,((?=[^\d])|\d{3}(?=[^,.$])|(?=\d{1,2}[^\d]))|\$(?=.)|\d{4,}(?=,)).|[^\d,.$]|^\$/
- 日期(00-12 / 00-31 / 0000-2099)^(((0[1-9]|1[012])|(\d{2}\/\d{2}))(?=[^\/])|((\d)|(\d{2}\/\d{2}\/\d{1,3})|(.+\/))(?=[^\d])|\d{2}\/\d{2}\/\d{4}(?=.)).|^(1[3-9]|[2-9]\d)|((?!^)(3[2-9]|[4-9]\d)\/)|[3-9]\d{3}|2[1-9]\d{2}|(?!^)\/\d\/|^\/|[^\d/]
- 数字/^(\d*(?=[^\d]))./
- 电子邮件通常,它使用这种模式:
/^([\w.$-]+\@[\w.]+(?=[^\w.])|[\w.$-]+\@(?=[^\w.-])|[\w.@-]+(?=[^\w.$@-])).$|\.(?=[^\w-@]).|[^\w.$@-]|^[^\w]|\.(?=@).|@(?=\.)./i
实际上它将捕获组([valid characters or structure] captured in group $1)(?= positive lookahead for not allowed characters) any character
中的所有有效字符,如果用户键入无效字符,则整个字符串将替换为已从组$1
中捕获的有效字符。它由一部分补充,该部分应排除一些明显的无效字符,例如邮件中的$1
或货币中的@@
。
了解这些正则表达式是如何工作的,尽管它看起来很复杂,但我认为通过添加额外的允许/不允许的字符来扩展它很容易。
用于验证货币,日期和邮件的正则表达式很容易找到,因此我发现将它们发布在此处是多余的。
OffTopic。您演示中的34...2
部分无法正常工作,因为:currency
代替validate="currency.us"
,或至少它修改后可以使用。
答案 1 :(得分:1)
在我看来,不可能创建正则表达式,用于匹配日期或电子邮件等内容
你使用的解析器。这主要是因为你需要非捕获组
正则表达式(可能),它们不被替换
inputValue.replace(pattern, '')
在您的解析器函数中调用。这就是
JavaScript中无法实现的部分。 JavaScript取代了您在非捕获中放置的内容
团体也是如此。
所以......你需要采取不同的方法。我建议去积极的 正则表达式,当输入有效时将产生匹配。 然后,您当然需要更改解析器的代码。你可以举个例子 决定从输入文本的末尾删除字符,直到剩下的字符为止 正则表达式测试。您可以按如下方式编写代码:
modelCtrl.$parsers.push(function (inputValue) {
var transformedInput = inputValue;
while (transformedInput && !pattern.exec(transformedInput)) {
// validation fails: chop off last character and try again
transformedInput = transformedInput.slice(0, -1);
}
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
现在生活变得容易一些。请注意你做你的常规 这些表达式不会拒绝部分输入。所以“01 /”应该是 被认为对日期有效,否则用户永远无法输入日期。上 另一方面,一旦明确添加字符将不再存在 允许有效输入,正则表达式应该拒绝它。所以“101”应该是 被拒绝作为日期,因为你永远不能在最后添加字符以使其成为有效日期。
此外,所有这些正则表达式都应该检查整个输入,因此
他们需要使用^
和$
符号。
以下是(部分)日期的正则表达式:
^([0-9]{0,2}|[0-9]{2}[\/]([0-9]{0,2}|[0-9]{2}[\/][0-9]{0,4}))$
这意味着:0到2位的输入有效,或者正好是2位数字后跟斜杠,后跟任一位:
不可否认,并不像你找到的那样聪明,但是需要进行大量编辑以允许部分输入日期。这是可能的,但是
它代表了一个很长的表达式,有很多括号和|
。
设置完所有正则表达式后,您可以考虑进一步改进 解析器。一个想法是不要让它从最后切掉字符,而是去 让它测试所有字符串,其中一个字符与原始字符相比被删除, 并看看哪一个通过了测试。如果没有办法找到删除一个字符并且有 成功,然后在输入值的任何位置删除两个连续的字符, 然后是三个,等等,直到找到一个通过测试或达到空值的值。
对于用户在输入中间插入字符的情况,这将更有效。 只是一个想法...
答案 2 :(得分:0)
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CurrencyPipe, DecimalPipe } from '@angular/common';
import { ValueChangeEvent } from '@goomTool/goom-elements/events/value-change-event.model';
const noOperation = () => {
};
@Directive({
selector: '[formattedNumber]',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: FormattedNumberDirective,
multi: true
}]
})
export class FormattedNumberDirective implements ControlValueAccessor {
@Input() public configuration;
@Output() public valueChange: EventEmitter<ValueChangeEvent> = new EventEmitter();
public locale: string = process.env.LOCALE;
private el: HTMLInputElement;
// Keeps track of the value without formatting
private innerInputValue: any;
private specialKeys: string[] =
['Backspace', 'Tab', 'End', 'Home', 'Enter', 'Shift', 'ArrowRight', 'ArrowLeft', 'Delete'];
private onTouchedCallback: () => void = noOperation;
private onChangeCallback: (a: any) => void = noOperation;
constructor(private elementRef: ElementRef,
private decimalPipe: DecimalPipe,
private currencyPipe: CurrencyPipe,
private renderer: Renderer2) {
this.el = elementRef.nativeElement;
}
public writeValue(value: any) {
if (value !== this.innerInputValue) {
if (!!value) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.getFormattedValue(value));
}
this.innerInputValue = value;
}
}
public registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
public registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
// On Focus remove all non-digit ,display actual value
@HostListener('focus', ['$event.target.value'])
public onfocus(value) {
if (!!this.innerInputValue) {
this.el.value = this.innerInputValue;
}
}
// On Blur set values to pipe format
@HostListener('blur', ['$event.target.value'])
public onBlur(value) {
this.innerInputValue = value;
if (!!value) {
this.el.value = this.getFormattedValue(value);
}
}
/**
* Allows special key, Unit Interval, value based on regular expression
*
* @param event
*/
@HostListener('keydown', ['$event'])
public onKeyDown(event) {
// Allow Backspace, tab, end, and home keys . .
if (this.specialKeys.indexOf(event.key) !== -1) {
if (event.key === 'Backspace') {
this.updateValue(this.getBackSpaceValue(this.el.value, event));
}
if (event.key === 'Delete') {
this.updateValue(this.getDeleteValue(this.el.value, event));
}
return;
}
const next: string = this.concatAtIndex(this.el.value, event);
if (this.configuration.angularPipe && this.configuration.angularPipe.length > 0) {
if (!this.el.value.includes('.')
&& (this.configuration.min == null || this.configuration.min < 1)) {
if (next.startsWith('0') || next.startsWith('0.') || next.startsWith('.')) {
if (next.length > 1) {
this.updateValue(next);
}
return;
}
}
}
/* pass your pattern in component regex e.g.
* regex = new RegExp(RegexPattern.WHOLE_NUMBER_PATTERN)
*/
if (next && !String(next).match(this.configuration.regex)) {
event.preventDefault();
return;
}
if (!!this.configuration.minFractionDigits && !!this.configuration.maxFractionDigits) {
if (!!next.split('\.')[1] && next.split('\.')[1].length > this.configuration.minFractionDigits) {
return this.validateFractionDigits(next, event);
}
}
this.innerInputValue = next;
this.updateValue(next);
}
private updateValue(newValue) {
this.onTouchedCallback();
this.onChangeCallback(newValue);
if (newValue) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', newValue);
}
}
private validateFractionDigits(next, event) {
// create real-time pattern to validate min & max fraction digits
const regex = `^[-]?\\d+([\\.,]\\d{${this.configuration.minFractionDigits},${this.configuration.maxFractionDigits}})?$`;
if (!String(next).match(regex)) {
event.preventDefault();
return;
}
this.updateValue(next);
}
private concatAtIndex(current: string, event) {
return current.slice(0, event.currentTarget.selectionStart) + event.key +
current.slice(event.currentTarget.selectionEnd);
}
private getBackSpaceValue(current: string, event) {
return current.slice(0, event.currentTarget.selectionStart - 1) +
current.slice(event.currentTarget.selectionEnd);
}
private getDeleteValue(current: string, event) {
return current.slice(0, event.currentTarget.selectionStart) +
current.slice(event.currentTarget.selectionEnd + 1);
}
private transformCurrency(value) {
return this.currencyPipe.transform(value, this.configuration.currencyCode, this.configuration.display,
this.configuration.digitsInfo, this.locale);
}
private transformDecimal(value) {
return this.decimalPipe.transform(value, this.configuration.digitsInfo, this.locale);
}
private transformPercent(value) {
return this.decimalPipe.transform(value, this.configuration.digitsInfo, this.locale) + ' %';
}
private getFormattedValue(value) {
switch (this.configuration.angularPipe) {
case ('decimal'): {
return this.transformDecimal(value);
}
case ('currency'): {
return this.transformCurrency(value);
}
case ('percent'): {
return this.transformPercent(value);
}
default: {
return value;
}
}
}
}
----------------------------------
export const RegexPattern = Object.freeze({
PERCENTAGE_PATTERN: '^([1-9]\\d*(\\.)\\d*|0?(\\.)\\d*[1-9]\\d*|[1-9]\\d*)$', // e.g. '.12% ' or 12%
DECIMAL_PATTERN: '^(([-]+)?([1-9]\\d*(\\.|\\,)\\d*|0?(\\.|\\,)\\d*[1-9]\\d*|[1-9]\\d*))$', // e.g. '123.12'
CURRENCY_PATTERN: '\\$?[-]?[0-9]{1,3}(?:,?[0-9]{3})*(?:\\.[0-9]{2})?$', // e.g. '$123.12'
KEY_PATTERN: '^[a-zA-Z\\-]+-[0-9]+', // e.g. ABC-1234
WHOLE_NUMBER_PATTERN: '^([-]?([1-9][0-9]*)|([0]+)$)$' // e.g 1234
});