Can't type in HTML form text/password/number input field (website with Unity WebGL build)

Viewed 1471

We are developing a site using Angular 9. We have also integrated a Unity3D WebGL build in it. When I try to type something in a text/password/number input field inside one of my forms, it doesn't write anything and the field doesn't seem to receive the input; also, the variable I bound the field to is not updated with the new value. What makes it weirder is that:

  • I can select the input field (it gets highlighted as if I can start typing)
  • I can do CTRL+C on the field and what I copied somewhere else is pasted, as expected
  • I can use the type="number" arrow selectors to set the value of the field
  • I cannot type from the keyboard in the fields
  • I can interact as expected with other form tags, such as <select>
  • If I reload the page, it usually starts working as expected and I can type into the fields

Here is the code from my login form (component.ts above, template HTML below)

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service'
import { first } from 'rxjs/operators';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {

  email: string = "";
  password: string = "";

  returnUrl: string = "home";

  constructor(private router: Router, private route: ActivatedRoute, private authService: AuthService) { }

  ngOnInit(): void {
    let tmpReturnUrl = this.route.snapshot.queryParams["returnUrl"];
    if (tmpReturnUrl != undefined)
    {
      console.log("true");
      this.returnUrl = tmpReturnUrl;
    }
    else
      console.log("false");

    setInterval(() => {
      console.log("EMAIL: " + this.email);
    }, 1000);
  }

  onSubmit(){
    this.authService.login(this.email, this.password)
    .pipe(first())
    .subscribe(
      result => {
        console.log("CAIOAOAOAOOA");
        this.router.navigate([this.returnUrl]);
      },
      err => console.log(err)
    );
  }
}
<div class="card z-depth-5 w-50">
  <div class="card-body">
    <div class="card-title">Log in</div>
    <div class="card-text">
      <form #companyLoginForm="ngForm" (ngSubmit)="onSubmit()">
        <mat-form-field>
          <mat-label>Email: </mat-label>
          <input matInput required type="text" name="email" id="email" [(ngModel)]="email">
        </mat-form-field>
        <mat-form-field>
          <mat-label>Password: </mat-label>
          <input matInput required type="password" name="password" id="password" [(ngModel)]="password">
        </mat-form-field>
        <button type="submit" [disabled]="!companyLoginForm.form.valid">Login</button>
      </form>
      <a routerLink="/company-register">
        <button mdbBtn type="button" color="primary" class="relative waves-light">Sign Up</button>
      </a>
    </div>
  </div>
</div>

And here, the code from another form where I also use type="number" and <select> (component.ts above, template HTML below)

import { Component, OnInit, Output } from '@angular/core';
import { BlockFormService } from '../block-form.service';
import { BlockData } from '../blockCardData';
import { BlockUtilsService } from '../block-utils.service';
import { ApiService } from '../../core/api.service'
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-block-form',
  templateUrl: './block-form.component.html',
  styleUrls: ['./block-form.component.less']
})
export class BlockFormComponent implements OnInit {

  updateComplete : Boolean = false;
  materials : string[];
  products : string[];
  varieties : string[];
  nations : string[];
  // companies : {name: string, id: string}[] = [];
  company : string = "";
  colors : string[] = ["White", "Grey", "Black", "Brown", "Red", "Green", "Yellow", "Blue"];
  blockData : BlockData = {_id : "", blockId: "",  company: "", material: "", product: "",
                variety: "", color: "", nation: "", modelName : "", imagePreview : "",
                price: null, blockNumber: "",
                length: null, height: null, width: null,
                weight: null
  };
  imagePreview: File = null;
  zipFile: File = null;

  invalidUpload: boolean = false;

  constructor( private blockFormService: BlockFormService, public blockUtils: BlockUtilsService, private companiesUtils: ApiService )
  { }

  ngOnInit(): void {
    this.materials = this.blockUtils.getMaterials();
    this.colors = this.blockUtils.getColors();
    this.companiesUtils.getLoggedCompany().subscribe(companiesResult => {
      this.blockData.company = companiesResult._id;
      this.company = companiesResult.name;
    });
  }

  onImageSelected(event){
    console.log(event.target.files[0]);
    if (event.target.files[0].type === "image/png")
    {
      if (this.invalidUpload)
        this.invalidUpload = false;
      this.imagePreview = event.target.files[0];
    }
    else{
      if (!this.invalidUpload)
        this.invalidUpload = true;
      event.target.value = null;
    }
  }

  onMaterialSet(newMaterial): void{
    console.log("Material set");
    this.products = this.blockUtils.getProducts(newMaterial);
    //console.log(this.products);
    // if (this.products.length > 0)
    //   this.blockData.product = this.products[0];
    // else
      this.blockData.product = "";

    this.onProductSet(this.blockData.product);
  }

  onProductSet(newProduct): void{
    console.log("Product set");
    this.varieties = this.blockUtils.getVarieties(this.blockData.material, newProduct);
    // if (this.varieties.length > 0)
    //   this.blockData.variety = this.varieties[0];
    // else
    this.blockData.variety = "";

    this.nations = this.blockUtils.getNations(this.blockData.material, this.blockData.product);
    if (this.nations.length > 0)
      this.blockData.nation = this.nations[0];
    else
      this.blockData.nation = "";

    this.onVarietySet(this.blockData.variety);
  }

  onVarietySet(newVariety): void{
    console.log("Variety set");
    // this.nations = this.blockUtils.getNations(this.blockData.material, this.blockData.product);
    // if (this.nations.length > 0)
    //   this.blockData.nation = this.nations[0];
    // else
    //   this.blockData.nation = "";
  }

  onSubmit(blockForm : NgForm, imageField, zipField): void{
    this.blockFormService.sendBlock(this.blockData, this.imagePreview, this.zipFile)
    .subscribe(res => {
      console.log("Sent!");
      this.updateComplete = true;
    });

    this.blockData = {
      _id: "", blockId: "", company: "", material: "", product: "",
      variety: "", color: "", nation: "", modelName: "", imagePreview: "",
      price: null, blockNumber: "",
      length: null, height: null, width: null,
      weight: null
    };
    blockForm.resetForm();
    imageField.value = null;
    zipField.value = null;
    this.imagePreview = null;
    this.zipFile = null;
  }
}
<div class="form-group">


  <div class="text-center" *ngIf='updateComplete'>
    Block added successfuly
  </div>

  <form #blockForm="ngForm" (ngSubmit)="onSubmit(blockForm, imageField, zipField)">
    <div class="container">
      <div class="row">
        <div class="col-3">
          <mat-form-field>
            <mat-label>Company: </mat-label>
            <!-- <mat-select required [(ngModel)]="blockData.company" name="company-field"
              id="company-field">
              <mat-option selected [value]="company.id">{{company.name}}</mat-option>
            </mat-select> -->
            <input matInput disabled [value]="company" type="text" name="company-field" id="company-field">
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Material: </mat-label>
            <mat-select #matField required [(ngModel)]="blockData.material" name="kind-field"
              id="kind-field" (selectionChange)="onMaterialSet(blockData.material)">
              <mat-option *ngFor="let mat of materials" [value]="mat">{{mat}}</mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Product: </mat-label>
            <mat-select required [(ngModel)]="blockData.product" name="product-field"
              id="product-field" (selectionChange)="onProductSet(blockData.product)">
              <mat-option *ngFor="let prod of products" [value]="prod">{{prod}}</mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Block Number: </mat-label>
            <input matInput required [(ngModel)]="blockData.blockNumber" type="text" name="blockNumber-field" id="blockNumber-field">
          </mat-form-field>
        </div>
      </div>

      <div class="row">
        <div class="col-3">
          <mat-form-field>
            <mat-label>Variety: </mat-label>
            <mat-select required [(ngModel)]="blockData.variety" name="variety-field" id="variety-field"
              placeholder="Variety" (selectionChange)="onVarietySet(blockData.variety)">
              <mat-option *ngFor="let variety of varieties" [value]="variety">{{variety}}</mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="col-3">
          <!-- <label for="color-field">Color: </label> -->
          <mat-form-field>
            <mat-label>Color: </mat-label>
            <mat-select required [(ngModel)]="blockData.color" name="color-field" id="color-field" placeholder="Color">
              <mat-option *ngFor="let col of colors" [value]="col">{{col}}</mat-option>
            </mat-select>
          </mat-form-field>
          <!-- <input #colField required [(ngModel)]="blockData.color" type="text" name="color-field" id="color-field" placeholder="Color"> -->
          <!-- <color-circle #colorField [colors]='["#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#607d8b"]' name="color-field" id="color-field" (onChange)="blockData.color = $event.color.hex"></color-circle> -->
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Nation: </mat-label>
            <!-- <mat-select required [(ngModel)]="blockData.nation" name="nation-field"
              id="nation-field">
              <mat-option *ngFor="let nat of nations" [value]="nat">{{nat}}</mat-option>
            </mat-select> -->
            <input matInput disabled [(ngModel)]="blockData.nation" type="text" name="nation-field" id="nation-field">
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Price: </mat-label>
            <input matInput required [(ngModel)]="blockData.price" type="number" name="price-field" id="price-field">
          </mat-form-field>
        </div>
      </div>

      <div class="row">
        <div class="col-3">
          <mat-form-field>
            <mat-label>Length: </mat-label>
            <input matInput required [(ngModel)]="blockData.length" type="number" name="length-field" id="length-field">
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Width: </mat-label>
            <input matInput required [(ngModel)]="blockData.width" type="number" name="width-field" id="width-field">
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Height: </mat-label>
            <input matInput required [(ngModel)]="blockData.height" type="number" name="height-field" id="height-field">
          </mat-form-field>
        </div>
        <div class="col-3">
          <mat-form-field>
            <mat-label>Weight: </mat-label>
            <input matInput required [(ngModel)]="blockData.weight" type="number" name="weight-field" id="weight-field">
          </mat-form-field>
        </div>
      </div>

      <div class="row">
        <div class="col-3">
          <div class="file-field">
            <div class="btn btn-primary btn-sm float-left">
              <label for="image-field">Upload preview image: </label>
              <input #imageField (change)="onImageSelected($event)" name="image-field" id="image-field" type="file" accept=".png, image/png" placeholder="Upload your file">
            </div>
          </div>
        </div>
        <div class="col-3">
          <div class="file-field">
            <div class="btn btn-primary btn-sm float-left">
              <label for="zip-field">Upload models' zip: </label>
              <input #zipField (change)="zipFile = $event.target.files[0];" name="zip-field" id="zip-field" type="file" placeholder="Upload your file">
            </div>
          </div>
        </div>
      </div>
      <button type="submit" [disabled]="!blockForm.form.valid || imagePreview == null || zipFile == null || blockData.company === ''">Submit</button>
    </div>
  </form>
</div>

I hope I was clear enough, any help is appreciated :)

1 Answers

Finally I found the source of the problem. In our website we have integrated a Unity3D WebGL build and, if I moved from the web page with Unity to the login page, the Unity process was still running. Unity had the focus of every input of the keyboard, so it was catching all the inputs.

We resolved it by quitting the Unity application when we change page. This way, input fields can receive inputs from the keyboard again.

Another solution, maybe (I have not tested it), could be to not make Unity get the inputs, as discussed in this Unity forum's thread or by setting WebGLInput.captureAllKeyboardInput to false.

Related