input组件支持限制字符、汉字

在平时开发的过程中,产品终会提一些另辟蹊径的建议,例如我们在做输入限制的时候,产品希望可以根据输入字符汉字不同去做不同限制;例如:限制输入10个字符或者5个汉字

开发时候踩到的坑

  1. 中文截断问题
    这个问题应该很多人都碰到过,并且网上的解决方法也有很多,其实使用onkeydown/onkeypress/onkeyup事件来监听并不能解决复制问题,在苦(cha)思(zhao)冥(gu)想(ge)的情况下,让我想(zhao)到了一种方法:compositionstart和compositionend+onInput
    compositionstart 事件在用户开始进行非直接输入的时候触发,而在非直接输入结束,也即用户点选候选词或者点击「选定」按钮之后,会触发 compositionend 事件。
// 嫌弃代码不全的队友请放心,下面会有完整的代码
// @HostListener('compositionstart')
  onCompositionstart() {
    this.isComposite = true;
  }

  // @HostListener('compositionEnd', ['$event'])
  onCompositionEnd($event) {
    this.isComposite = false;
    this.onInput($event);
  }

  // @HostListener('input', ['$event'])
  onInput($event) {
    if (!$event || this.isComposite) {
      return;
    }
    
    const target = $event.target;
    if (this.CN) {
      target.value = this.limitLength(target.value, this.maxLen);
      this.val = this.limitLength(target.value, this.maxLen);
    } else {
      let newValue = $event.target.value;
      if (newValue && this.maxLen > -1 && newValue.length > this.maxLen) {
        newValue = newValue.substring(0, this.maxLen);
        $event.target.value = newValue;
      }
    }
  }
  1. 复制问题
    这个问题解决起来相当绕,因为在输入限制的时候复制粘贴的时候需要考虑到是否有原有内容,如果有,则限制的时候需要考虑到需要加上已有的内容,因此使用了paste监控粘贴
// angular 代码;就是paste事件,其他框架通用这个方法
@HostListener('paste', ['$event'])
  onPaste(event: ClipboardEvent) {
  // 一个检测事件,监测限制值是否为负数
    if (!this.isTriggerKeyEvents()) {
      return;
    }
	// 禁止输入
    event.preventDefault();

    let currentVal = this.val;
    if (this.maxLen > -1 || this.numberOnly) {
    // 获取粘贴的值
      let clipboardData = event.clipboardData.getData('text/plain');
      if (this.numberOnly) {
        let pastedInput: string = clipboardData.replace(/\D/g, ''); // get a number-only string
        this.val = currentVal + pastedInput;
      }

      if (this.maxLen > -1) {
        if (this.CN) {
        // 限制的汉字
          this.val = this.limitPaste(currentVal, this.maxLen, clipboardData);
          this.calculateCurrentLen();
        } else {
          let pastedInput: string = clipboardData.substring(0, this.maxLen);
          this.val = currentVal + pastedInput;
        }
      }
    }
  }

  private limitPaste(value: string, byteLength: number, pastedVal: string) {
    if (!pastedVal) {
      return
    }
    if (!value) {
      let pastedValue = pastedVal.replace(/[^\x00-\xff]/g, "**");
      let limitDate = pastedValue.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, pastedVal)
    }

    let newvalue = value.replace(/[^\x00-\xff]/g, "**");
    let length = newvalue.length;//获取内容长度 
    let split_value = value + pastedVal;

    if (length * 1 >= byteLength * 1) {
      let limitDate = newvalue.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, value)
    }

    if (length * 1 < byteLength * 1) {
      let pastedValue = pastedVal.replace(/[^\x00-\xff]/g, "**");
      let val = newvalue + pastedValue;
      let limitDate = val.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, split_value)
    }
  }
  1. 如何区分中文情况下一个汉字顶两个字符
    标题很浅显,就是去判断这个是个汉字,然后如果是汉字记录一下,在超出限制的时候方便进行删除截取
// typeScript,js时不需要参数后面的类型
limitLength(value: string, byteLength: number) {
    let newvalue = value.replace(/[^\x00-\xff]/g, "**"); //输入内容                   
    let length = newvalue.length;//获取内容长度               
    //当填写的字节数小于设置的字节数 
    if (length * 1 <= byteLength * 1) {
      return value;
    }
    
    let limitDate = newvalue.substr(0, byteLength);
    let count = 0;
    let limitvalue = "";
    for (let i = 0; i < limitDate.length; i++) {
      let flat = limitDate.substr(i, 1);
      if (flat == "*") {
        count++;
      }
    }
    let size = 0;
    //if 基点是×; 判断在基点内有×为偶数还是奇数  
    if (count % 2 == 0) {
      //当为偶数时 
      size = count / 2 + (byteLength * 1 - count);
      limitvalue = value.substr(0, size);
    } else {
      //当为奇数时 
      size = (count - 1) / 2 + (byteLength * 1 - count);
      limitvalue = value.substr(0, size);
    }
    return limitvalue;
  }

完整代码

由于我的是angular的代码,因此使用了ts而非js,但是看起来各位大佬一定觉得很 easy,毕竟我这个只是总结前人经验应用到我这里而已
我这边将input封装为组件,支持限制字符、汉字、粘贴、仅输入数字功能
html:

<div class="cil-input-wrapper flex-wrap row-flex">
  <div class="flex-wrap col-flex" *ngIf="label">
    <label for="cil-input" class="cil-input-lbl">
      <span class="form-required" *ngIf="isRequired">*</span>
      {{ label }}
    </label>
  </div>
  <div class="flex-wrap col-flex input-container">
    <input id="cil-input" class="cil-input" type="text" [(ngModel)]="val" [placeholder]="placeholder" (input)="onInput($event)"
      (compositionstart)="onCompositionstart()" (compositionend)="onCompositionEnd($event)" (change)="onChange($event)"
      (keyup)="onChange($event)" (blur)="onBlur($event)" [attr.disabled]="isDisabled ? '' : null"
      [class.disabled]="isDisabled" [style.padding-right.px]="(maxLen > -1 && maxLen < 1000) ? '65' : '10'"
      autocomplete="off">
    <span class="error-message" *ngIf="errorMessage">{{ errorMessage }}</span>
    <div class="cil-len" *ngIf="maxLen > -1 && maxLen < 1000">
      <span class="cur-length">{{ currentLen }}</span> / <span>{{ maxLen }}</span>
    </div>
  </div>
</div>
import { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, OnInit, forwardRef, ChangeDetectorRef, OnChanges, SimpleChanges, HostListener } from "@angular/core";
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor, Validator, ValidationErrors, AbstractControl } from "@angular/forms";

@Component({
  selector: `cil-input`,
  templateUrl: 'input.html',
  styleUrls: ['input.scss'],
  encapsulation: ViewEncapsulation.None,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CilInput),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => CilInput),
      multi: true,
    }]
})

export class CilInput implements OnInit, OnChanges, ControlValueAccessor, Validator {
  val: string;

  private navigationKeys = [
    'Backspace',
    'Delete',
    'Tab',
    'Escape',
    'Enter',
    'Home',
    'End',
    'ArrowLeft',
    'ArrowRight',
    'Clear',
    'Copy',
    'Paste'
  ];
  currentLen: number = 0;
  chineseReg = /[^\x00-\xff]/g;
  // 输入完成的标记
  isComposite: boolean = false;

  @Input() label: string;
  @Input() isRequired: boolean;
  @Input() placeholder: string = '';
  @Input() errorMessage: string;
  @Input() isDisabled: boolean;
  @Input() CN: boolean = false;

  // Default -1 indicate that the length of input is not limited
  // The max of maxLen is 999
  @Input() maxLen: number = -1;

  @Input() numberOnly: boolean = false;
  private emitChange = (_: any) => { };

  constructor() { }

  ngOnInit() { }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes) { }
  }

  onChange(event: any) {
    this.onBlur(event);
  }

  onBlur(event: any) {
    // this.onInput(event);
    this.emitChange(this.val);
  }

  writeValue(obj: any): void {
    if (obj || obj === '') {
      this.val = obj;
      if (this.maxLen > -1) {
        this.calculateCurrentLen();
      }
    }
  }

  registerOnChange(fn: any): void {
    this.emitChange = fn;
  }

  registerOnTouched(fn: any): void { }

  validate(control: AbstractControl): ValidationErrors {
    // TODO: customize validation
    return null;
  }

  @HostListener('keyup', ['$event'])
  onKeyup(e: KeyboardEvent) {
    // console.log('>>>>>>')
    if (this.maxLen === -1) {
      return;
    }

    if (this.maxLen > -1) {
      this.calculateCurrentLen();
      // Hack: when typing chinese character, input control will not prevent the character typing
      // let chineseCharLen = this.val ? (this.val.match(this.chineseReg) || []) : [];
      // if (chineseCharLen.length > 0) {
      //   let len = this.maxLen - chineseCharLen.length + 1;
      //   this.val = this.val.substring(0, len);
      // }
    }

    if (this.numberOnly) {
      this.val = this.val ? this.val.replace(this.chineseReg, '') : '';
    }
  }

  @HostListener('keydown', ['$event'])
  onKeyDown(e: KeyboardEvent) {
    if (!this.isTriggerKeyEvents()) {
      return;
    }

    if (
      this.navigationKeys.indexOf(e.key) > -1 ||  // Allow: navigation keys: backspace, delete, arrows etc.
      (e.key === 'a' && e.ctrlKey === true) ||    // Allow: Ctrl+A
      (e.key === 'c' && e.ctrlKey === true) ||    // Allow: Ctrl+C
      (e.key === 'v' && e.ctrlKey === true) ||    // Allow: Ctrl+V
      (e.key === 'x' && e.ctrlKey === true) ||    // Allow: Ctrl+X
      (e.key === 'a' && e.metaKey === true) ||    // Allow: Cmd+A (Mac)
      (e.key === 'c' && e.metaKey === true) ||    // Allow: Cmd+C (Mac)
      (e.key === 'v' && e.metaKey === true) ||    // Allow: Cmd+V (Mac)
      (e.key === 'x' && e.metaKey === true)       // Allow: Cmd+X (Mac)
    ) {
      return;
    }

    if (this.maxLen > -1) {
      let hightlightText = window.getSelection() + '';
      let currentLen = 0;
      if (this.CN) {
        let chineseCharLen = this.val ? (this.val.match(this.chineseReg) || []) : [];
        currentLen = this.val ? this.val.length + chineseCharLen.length : 0;
      } else {
        currentLen = this.val ? this.val.length : 0;
      }

      if (this.val && currentLen >= this.maxLen && hightlightText === '') {
        e.preventDefault();
      }
    }

    if (this.numberOnly) {
      if (isNaN(Number(e.key))) {
        e.preventDefault();
      }
    }
  }

  // @HostListener('compositionstart')
  onCompositionstart() {
    this.isComposite = true;
  }

  // @HostListener('compositionEnd', ['$event'])
  onCompositionEnd($event) {
    this.isComposite = false;
    this.onInput($event);
  }

  // @HostListener('input', ['$event'])
  onInput($event) {
    if (!$event || this.isComposite) {
      return;
    }
    
    const target = $event.target;
    if (this.CN) {
      target.value = this.limitLength(target.value, this.maxLen);
      this.val = this.limitLength(target.value, this.maxLen);
    } else {
      let newValue = $event.target.value;
      if (newValue && this.maxLen > -1 && newValue.length > this.maxLen) {
        newValue = newValue.substring(0, this.maxLen);
        $event.target.value = newValue;
      }
    }
  }

  limitLength(value: string, byteLength: number) {
    let newvalue = value.replace(/[^\x00-\xff]/g, "**"); //输入内容                   
    let length = newvalue.length;//获取内容长度               
    //当填写的字节数小于设置的字节数 
    if (length * 1 <= byteLength * 1) {
      return value;
    }
    
    let limitDate = newvalue.substr(0, byteLength);
    let count = 0;
    let limitvalue = "";
    for (let i = 0; i < limitDate.length; i++) {
      let flat = limitDate.substr(i, 1);
      if (flat == "*") {
        count++;
      }
    }
    let size = 0;
    //if 基点是×; 判断在基点内有×为偶数还是奇数  
    if (count % 2 == 0) {
      //当为偶数时 
      size = count / 2 + (byteLength * 1 - count);
      limitvalue = value.substr(0, size);
    } else {
      //当为奇数时 
      size = (count - 1) / 2 + (byteLength * 1 - count);
      limitvalue = value.substr(0, size);
    }
    return limitvalue;
  }

  @HostListener('paste', ['$event'])
  onPaste(event: ClipboardEvent) {
    if (!this.isTriggerKeyEvents()) {
      return;
    }

    event.preventDefault();

    let currentVal = this.val;
    if (this.maxLen > -1 || this.numberOnly) {
      let clipboardData = event.clipboardData.getData('text/plain');
      if (this.numberOnly) {
        let pastedInput: string = clipboardData.replace(/\D/g, ''); // get a number-only string
        this.val = currentVal + pastedInput;
      }

      if (this.maxLen > -1) {
        if (this.CN) {
          this.val = this.limitPaste(currentVal, this.maxLen, clipboardData);
          this.calculateCurrentLen();
        } else {
          let pastedInput: string = clipboardData.substring(0, this.maxLen);
          this.val = currentVal + pastedInput;
        }
      }
    }
  }

  private limitPaste(value: string, byteLength: number, pastedVal: string) {
    if (!pastedVal) {
      return
    }
    if (!value) {
      let pastedValue = pastedVal.replace(/[^\x00-\xff]/g, "**");
      let limitDate = pastedValue.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, pastedVal)
    }

    let newvalue = value.replace(/[^\x00-\xff]/g, "**");
    let length = newvalue.length;//获取内容长度 
    let split_value = value + pastedVal;

    if (length * 1 >= byteLength * 1) {
      let limitDate = newvalue.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, value)
    }

    if (length * 1 < byteLength * 1) {
      let pastedValue = pastedVal.replace(/[^\x00-\xff]/g, "**");
      let val = newvalue + pastedValue;
      let limitDate = val.substr(0, byteLength);
      return this.limtFunc(limitDate, byteLength, split_value)
    }
  }

  private limtFunc(limitVal: string, byteLength: number, split_value: string) {
    let count = 0;
    let limitvalue = "";
    for (let i = 0; i < limitVal.length; i++) {
      let flat = limitVal.substr(i, 1);
      if (flat == "*") {
        count++;
      }
    }
    let size = 0;
    //if 基点是×; 判断在基点内有×为偶数还是奇数  
    if (count % 2 == 0) {
      //当为偶数时 
      size = count / 2 + (byteLength * 1 - count);
      limitvalue = split_value.substr(0, size);
    } else {
      //当为奇数时 
      size = (count - 1) / 2 + (byteLength * 1 - count);
      limitvalue = split_value.substr(0, size);
    }
    return limitvalue
  }

  private calculateCurrentLen() {
    if (this.CN) {
      let chineseCharLen = this.val ? (this.val.match(this.chineseReg) || []) : [];
      this.currentLen = this.val ? this.val.length + chineseCharLen.length : 0;
    } else {
      this.currentLen = this.val ? this.val.length : 0;
    }
    this.currentLen = this.currentLen > this.maxLen ? this.maxLen : this.currentLen;
  }

  private isTriggerKeyEvents() {
    return this.maxLen !== -1 || this.numberOnly;
  }
}

应用

此时在项目中应用就很简单

<cil-input formControlName="testName" [isRequired]="true" [errorMessage]="requiredMessage"
        [placeholder]="'请输入您的品牌名称'" [maxLen]="10" [CN]="true"></cil-input>
<span style="padding: 10px 0 10px 100px;">输入内容: {{f.testName.value}}</span>
<br>
<cil-input [label]="'汉语字符'" [(ngModel)]="inputValCN" [isRequired]="true" [placeholder]="'请输入您的商户名称'" [maxLen]="10" [CN]="true"></cil-input>

当有属性maxLen时候进行限制,默认对中文不做任何校验,当传递CN为true的时候则对中文进行限制;

总结

学海无涯,回头是岸

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值