11
votes

I want to move focus from an input field to another when a user has entered the maxLength amount of characters into the first input field. So in my example below, when a user has entered 2 characters into the day input, the focus would move to the month input.

This is my code so far:

<input formControlName="day" maxlength="2" placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />

And in my TS file:

 keytab(event){
    let nextInput = event.srcElement.nextElementSibling; // get the sibling element

    var target = event.target || event.srcElement;
    var id = target.id
    console.log(id.maxlength); // prints undefined

    if(nextInput == null)  // check the maxLength from here
        return;
    else
        nextInput.focus();   // focus if not null
}

I know the code in my TS file is wrong, but I was trying to find a way of getting the maxLength property and then shifting the focus. Right now the focus will move as soon as there is a keyup in the input field.

Can anyone tell me how I can access the inputs maxLength property from the keytab function? Thanks.

I'm using Angular 4.

Edit - I'm trying to get the maxLength value and then compare to the input value length. If the input value is more, then move focus to the input field.

5
No its not - I said that I have the shifting focus part working - I don't know how to access the maxLength. But thanks for the link. Interesting information there. - Jose the hose
Just use <input type="date"> - Endless
why are you using input type text instead of number? - Naga

5 Answers

7
votes

here is a generic (Directive) solution to move to next similar control type when reaches the maximum length

1- Create the Directive

import { Directive, HostListener } from '@angular/core';

@Directive({
  selector: 'input[moveNextByMaxLength], textarea[moveNextByMaxLength]',
})
export class MoveNextByMaxLengthDirective {
  @HostListener('keyup', ['$event']) onKeyDown(keyboardEvent: KeyboardEvent) {
    const target = keyboardEvent.target as
      | HTMLInputElement
      | HTMLTextAreaElement
      | null;

    if (!target || target.maxLength !== target.value.length) return;

    keyboardEvent.preventDefault();

    const { type } = target;
    let { nextElementSibling } = target;

    while (nextElementSibling) {
      if (
        (nextElementSibling as HTMLInputElement | HTMLTextAreaElement).type ===
        type
      ) {
        (nextElementSibling as HTMLInputElement | HTMLTextAreaElement).focus();
        return;
      }

      nextElementSibling = nextElementSibling.nextElementSibling;
    }
  }
}

2- Declare the Directive in the module

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    AppComponent,
    MoveNextByMaxLengthDirective 
  ],
  bootstrap: [ AppComponent ]
})

3- Use the Directive in the component

<input formControlName="day" maxlength="2" moveNextByMaxLength placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" moveNextByMaxLength placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />
4
votes

Use a different approach. Angular does not select elements and read attributes from the existing DOM, as jQuery does, because Angular generates the DOM from data. So it's difficult, if possible at all, to read the input's maxlength attribute, and anyway it would be clumsy an "non-Angulary".

Instead, use a different approach and pass the maxLength in the keyup function :

<input type="text" (keyup)="keytab($event, 2)" />
<input type="text" (keyup)="keytab($event, 4)" />


keytab(event, maxLength){
   console.log(maxlength); // 2 or 4
   // .......
}
2
votes

Just an idea but if you're using reactive forms you could do something like this:

import { FormGroup, FormBuilder, Validators } from "@angular/forms";
import { Component, OnInit, ViewChild, ElementRef } from "@angular/core";
import { filter } "rxjs/operators";

@Component({
  selector: "app-form",
  template: `
    <form [formGroup]="form">
        <input formControlName="day" placeholder="DD" type="text" #day />
        <input formControlName="month" placeholder="MM" type="text" #month />
        <input formControlName="year" placeholder="YYYY" type="text" #year />
    </form>
`
})
export class FormComponent implements OnInit {
  form: FormGroup;

  @ViewChild("day") dayElement: ElementRef;

  @ViewChild("month") monthElement: ElementRef;

  @ViewChild("year") yearElement: ElementRef;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    const dayMaxLength = 2;
    const monthMaxLength = 2;
    const yearMaxLength = 4;

    this.form = this.fb.group({
      day: ["", [Validators.required, Validators.maxLength(dayMaxLength)]],
      month: ["", [Validators.required, Validators.maxLength(monthMaxLength)]],
      year: ["", [Validators.required, Validators.maxLength(yearMaxLength)]]
    });

    this.form.get("day").valueChanges
      .pipe(filter((value: string) => value.length === dayMaxLength))
      .subscribe(() => this.monthElement.nativeElement.focus());

    this.form.get("month").valueChanges
      .pipe(filter((value: string) => value.length === monthMaxLength))
      .subscribe(() => this.yearElement.nativeElement.focus());
}

Basically subscribe to the value changes of both day and month form controls, filter each stream so that it only continues when the value is equal to the max length, then set the focus to the next element. Probably worth noting these will need to be unsubscribed from too.

1
votes

Pure js approach

let allInputs = document.getElementsByTagName('input');
let index = 0;
for(i=0;i<allInputs.length;i++) {
allInputs[i].onkeydown =trackInputandChangeFocus;
}

function trackInputandChangeFocus() {
let allInputsArr = Array.from(allInputs); 
let presentInput = allInputsArr.indexOf(this)
if(this.value.length == parseInt(this.getAttribute('maxlength'))) {
      let next;
      if(presentInput != 2) next = allInputsArr[presentInput+1]
      else next = allInputsArr[0]
      next.focus();
    }
}
<input formControlName="day" maxlength="2" placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />
1
votes

Another simple answer for Angular 9+

<input #input1 (keyup)="(input1.value.length == 2) ? input2.focus() : ''" type="text" maxlength="2">
<input #input2 (keyup)="(input2.value.length == 2) ? input3.focus() : ''" type="text" maxlength="2">
<input #input3 (keyup)="(input2.value.length == 2) ? submit.focus() : ''"  type="text" maxlength="2">

<button #submit type="submit">Submit</button>