1 前言

最近在项目中涉及表单的情况下,需要对用户输入进行过滤,比如填写用户名的时候不可以使用空格或者特殊符号,这里有几个解决方法:

  1. 使用 Angular 的正则同步验证器
  2. 使用 RxJS对输入的值进行替换或者删除
  3. 使用 Event对象 ,阻止事件的默认表现(非禁止传播)

2 各种方案的分析

2.1 正则同步验证器

使用验证器配合输入提示是最直观最容易了解的,但是背后有几个痛点难以解决

  1. 即使知道特定的非法字符,用户也可能在不经意的情况下输入这些字符,在看到错误之后需要用户主动查看自己输入了哪些非法字符并手动删除
  2. 即使将实现的验证器抽取出来成为一个公共方法,在判断表单合法性的时候还要多加一层判断,举个栗子:usernameControl.hasError('invalidChar'),并且显示一个错误

该方法需要实现的 process:

  1. 一个正则同步验证器;
  2. 一个维护非法字符的 Array;
  3. 一层显示输入非法字符的判断与用户提示语句;

由于解决过于繁琐,这里不给出实例。

2.2 RxJS 同步删除值

在习惯了响应式编程后,对于这个场景我们会顺手写下下边的语句

 1   @ViewChild('usernameInput', {static: true})
 2   usernameInputRel: ElementRef;
 3   invalidChars: string[] = [',', '.', ';', '\[', '\]', '`'];
 4 
 5   ngAfterViewInit(): void {
 6     this.initI18NMessages();
 7     const usernameE = this.usernameInputRel.nativeElement as HTMLInputElement;
 8     fromEvent(usernameE, 'input')
 9       .pipe(
10         debounceTime(1000),
11       )
12       .subscribe((e: KeyboardEvent) => {
13         const invalid = asSequence(this.invalidChars).find((c) => usernameE.value.indexOf(c) !== -1);
14         if (invalid !== null) {
15           this.messageService.error(`非法输入: ${invalid}`);
16         }
17         usernameE.value = usernameE.value.replace(/[,.\/;'\[\]`]/, '');
18       });
19   }

让我们审视上边的语句,该方法需要实现的 process:

  1. 在Component 传入一个 Input的 reference;
  2. 根据非法值 Array 维护对应的 RegExp 对象;
  3. 在 Component 的视图渲染完成后订阅这个 reference 的 'input' 事件,查找是否输入中是否含有非法字符;
  4. 给用户非法输入提示;
  5. 同步删除非法字符的值;

 2.3 使用 Event对象

2.3.1 使用原生的方法

我们将的 Javascript 代码中的监听模式应用到元素上,当输入的值在禁止范围内,则阻止默认行为,我们不会使用 stopPropagation() 方法,因为它阻止了事件冒泡,如果之后需要在它的扩展父元素上监听事件,代码将会需要修改;同时应该注意到,由于我们没有删除值的步骤,所以需要禁用粘贴功能;

 1   @ViewChild('passwordInput', {static: true})
 2   passwordInput: ElementRef;
 3 
 4   ngAfterViewInit(): void {
 5     const input = this.passwordInput.nativeElement as HTMLInputElement;
 6     input.addEventListener('paste', ev => ev.preventDefault());
 7     input.addEventListener('keydown', ev => {
 8         const regExp: RegExp = new RegExp(['\\', ',', '/', '\"'].join('|'), 'gi');
 9         if (regExp.exec(ev.key) !== null) {
10           ev.preventDefault();
11         }
12       });
13   }

让我们审视这次的实现并与上边对比

  1. 在Component 传入一个 Input的 reference;
  2. 根据非法值 Array 维护对应的 RegExp 对象;
  3.  在 Component 的视图渲染完成后订阅这个 reference 的 'input' 事件,查找是否输入中是否含有非法字符; 在 Component 的视图渲染完成后订阅这个 reference 的 'keydown'与 ‘paste’ 事件,如果事件的 key 在禁止值数组中,调用 preventDefault ,并禁止粘贴动作
  4. 给用户非法输入提示;
  5. 同步删除非法字符的值;

2.3.1 使用 Directive

如果需要在多个表单元素上应用上边的代码,代码将变得非常臃肿,因此我们将借助 Angular 的 Directive ,先构想一些组件中需要的实现:

  1. 组件需要知道哪些值是被禁止的,并且禁止相应的输入
  2. 组件需要禁止粘贴动作

那么,当应用到 Input 元素上,它应该是这样子的: 

 <input  [crxBlockKeys]="blockNameChars" type="text"    />

blockNameChars 是从 component 获得的,它的值是:

const blockNameChars: string[] = [' ', '/', '\''];

或者这样子应用:

 <input  [crxBlockKeys]="[' ', '\\', '\'']" type="text"    />

现在让我们手动实现:

 1 @Directive({selector: '[crxBlockKeys]'})
 2 export class BlockKeysDirective {
 3   @Input('crxBlockKeys')
 4   blockKeys: string[];
 5 
 6   /**
 7    * @see DocumentAndElementEventHandlersEventMap.paste
 8    * @param e ClipboardEvent
 9    */
10   @HostListener('paste', ['$event'])
11   preventPaste(e: Event): void {
12     e.preventDefault();
13   }
14 
15   /**
16    * @see WindowEventMap.keydown
17    * @param e  KeyboardEvent
18    */
19   @HostListener('keydown', ['$event'])
20   preventEmptyInput(e: KeyboardEvent): void {
21     const regExp: RegExp = new RegExp(this.blockKeys.join('|'), 'gi');
22     if (regExp.exec(e.key) !== null) {
23       e.preventDefault();
24     }
25   }
26 }

现在我们的代码得到了复用,当需要使用的时候,只需要在 input 内加入这个属性,并传入禁止的数值组;

如果你抗拒写转义字符,可以使用下边的 Directive,我们更改传入数值组为 Code  (eg: ['Semicolon', 'NumpadDivide', 'Space']):

 1 @Directive({selector: '[crxBlockCodes]'})
 2 export class BlockCodesDirective {
 3   @Input('crxBlockCodes')
 4   blockCodes: string[];
 5 
 6   /**
 7    * @see DocumentAndElementEventHandlersEventMap.paste
 8    * @param e ClipboardEvent
 9    */
10   @HostListener('paste', ['$event'])
11   preventPaste(e: Event): void {
12     e.preventDefault();
13   }
14 
15   /**
16    * @see WindowEventMap.keydown
17    * @param e  KeyboardEvent
18    */
19   @HostListener('keydown', ['$event'])
20   preventEmptyInput(e: KeyboardEvent): void {
21     const regExp: RegExp = new RegExp(this.blockCodes.join('|'), 'gi');
22     if (e.code.match(regExp).length !== 0) {
23       e.preventDefault();
24     }
25   }
26 }

让我们审视这次的实现并与上边对比

  1. 在Component 传入一个 Input的 reference;  在输入标签中加入 属性 crxBlockKeys 并传入禁止的数值组;
  2. 根据非法值 Array 维护对应的 RegExp 对象;
  3. 在 Component 的视图渲染完成后订阅这个 reference 的 'keydown'与 ‘paste’ 事件,如果事件的 key 在禁止值数组中,调用 preventDefault ,并禁止粘贴动作 

3 总结

我们的实现需要的步骤变化为: 5  >  3 > 1 ,这个改变是巨大的,同时,代码的复用性与稳定性也有了很大改善

4 额外的内容

你可以从这里查找 key 值或者这里查找 code  值

你也可以使用这个在线工具快速获取 key 与 code 值

posted on 2019-12-22 13:07  四维胖次  阅读(880)  评论(0编辑  收藏  举报