AngularTool: onkey down event handler

In C# oder VB gibt ein Eventhandler um Keyboard Eingaben zu filtern und direkt zu unterdrücken:

'#### EINGABEFILTER
    Private Sub EingabeFilter(absender As Object, keyArgs As KeyPressEventArgs)
        Dim asc As Integer = Convert.ToInt32(keyArgs.KeyChar)  ' Asci-Code ermitteln
        Select Case asc
            Case 8, 44, 46, 48 To 57
            Case Else
                keyArgs.Handled = True
        End Select
    End Sub


--- bzw ---
 If (e.KeyCode = Keys.Enter) And (sender.Text <> String.Empty) Then    ' Enter-Taste
            EventDone = True 'Vermeiden der Ausführung

In JavaScript könnte so etwas implementiert werden, aber beim Tastenprellen funktioniert es nicht, auch bei schnellen Eingaben kann etwas "unter dem Tisch" fallen.

window.addEventListener('keyup', (event:KeyboardEvent )=>
{this.keyValue = event.key;
  if (parseInt( this.keyValue) >=  0 && parseInt( this.keyValue) <= 9 )
      { /*Eingabe OK*/ }
  else 
      {this.A = this.A.replace( event.key,"");}
}

Welche Lösung könnte es geben um nur Zahlen und Komma zuzulasen oben erst "ENTER Taste" zu drücken.

  1. Hallo AngularTool,

    was für ein Gerät und was für ein Betriebssystem verwendest Du, dass da die Tasten prellen und das nicht vom Betriebssystem abgefangen wird? Vermutlich meinst Du eher Autorepeat.

    in HTML - nicht JavaScript - gibt es <input type="number">

    Wenn Dir das zu viel zulässt, kannst Du noch das pattern-Attribut an den Start bringen (wobei ich grad erstmal ausprobieren müsste, ob das bei type="number" greift)

    Wenn Dir das auch nicht passt (weil das Pattern keine Eingabe unterdrückt), dann nimm JavaScript, aber registriere Dich nicht auf keyup, sondern auf keydown (nicht keypress, das soll aussterben). Zum einen wird das ausgelöst, bevor das input das Zeichen sieht, zum anderen wird es bei Autorepeat nicht nur ein einziges Mal, sondern einmal pro Repeat ausgelöst.

    Im Standard gibt's auch noch das beforeinit-Event, aber das ist noch zu rot.

    Im keydown-Event kannst Du das Event unterdrücken, indem Du auf dem Event-Objekt, dass der Eventhandler übergeben bekommt, die Methode preventDefault aufrufst. Denke auch daran, shiftKey, ctrlKey, metaKey und altKey auf false zu prüfen.

    Weitere Lektüre

    Rolf

    --
    sumpsi - posui - clusi
    1. Hallo Rolf B, danke für die schnelle Antwort. (Windows 10, Firefox 70.0.1 (64-Bit) )

      Ja, Du hast recht: Ich meine Autorepeat bei ständig gedrückter Taste.

      <input type="number">, hier wird das Input Element mit "Enter" zur Validierung abgeschickt.

      Ich habe "keydown, Keypress, und Keyup versucht, leider war über preventDefault nicht die Eingabe zu "stoppen". Da ist VB und C# absolut zuverlässig, es wird nichts übernommen, egal wie lang der4 String wird.

      Leider habe das Ganze mit Angular am Start, und hier ist eine Doppel-Bindung zwischen dem Input und der Varíablen möglich und vorhanden.

      Übrigen dieser Code klappt, bis auf das Autorepeat:

      window.addEventListener('keyup', (event:KeyboardEvent )=>
      {this.keyValue = event.key;
        if (parseInt( this.keyValue) >=  0 && parseInt( this.keyValue) <= 9 )
            { /*Eingabe OK*/ }
        else 
            {this.A = this.A.replace( event.key,"");} // Löscht das letzte Eingeabezeichen.
      }
      

      DANKE für die Links, da scheint eine Lösung dabei zu sein, über Funktionen ersteinmal alles abfangen.

      1. Hallo AngularTool,

        ja ok, an Angular habe ich nicht gedacht. Damit habe ich keine Erfahrung.

        Meine Gehversuche mit MVVM habe ich mit knockout.js gemacht.

        Hast Du es in Angular nicht in der Hand, wie das Input an das Modell gebunden wird? Normalerweise gehen MVVM Tools doch her und übertragen den Feldinhalt erst ins Modell, wenn das Eingabefeld verlassen wird. Das wäre eine Überlegung wert.

        Wenn das nicht hilft, und Angular sich auf keydown oder keypress registriert, bevor dein Code anläuft, bist Du ausgezählt. Es wird sicherlich nicht klappen, vor Angular zum Zug zu kommen.

        Es bleibt dann wohl nur ein Hack, oder eine gute Idee der Kollegen hier ;).

        Der Hack: Du müsstest dich auf einen Container des input registrieren, und zwar mit true als 3. Parameter von addEventListener (capturing handler). Damit kannst Du erreichen, dass das Event gar nicht erst beim Input ankommt.

        Hier bietet sich das Label an, das ein Input sowieso haben sollte. Man kann das for-Attribut von <label> vermeiden, indem man das input als Kindelement des Label setzt:

        <label class="digitOnly">Zahlenfolge<br>
        <input type="text">
        </label>
        

        Wenn Du auf alle Elemente mit class="digitOnly" einen capturing-Handler für keydown legst, könnte der Coup gelingen.

        Rolf

        --
        sumpsi - posui - clusi
      2. document.getElementById('name').addEventListener('click', handler, true);

        Der Dritte Parameter true ist offensichtlich vergleichbar mit dem event.done im C# oder VB. Leider nirgendswo in den Büchern gesehen.

        Ja, mit dem Input INNERHALB des Labels komme ich weiter... Werde über das Ergebinis berichten.

        1. Hallo AngularTool,

          Der Dritte Parameter true ist offensichtlich vergleichbar mit dem event.done im C# oder VB.

          Nein, wenn ich das eventdone richtig verstehe, entspricht das in etwa dem preventDefault Aufruf.

          Ein Capturing-Eventhandler (addEventListener("...", handler, true)) ist etwas Spezielles im DOM: Das Event sucht sich zunächst seinen Weg vom Root des DOM (also das Dokument) nach unten zum Event-Auslöser (capturing-Phase), wird dort geworfen (trigger) und blubbert dann zurück nach oben (bubbling-Phase). Das gibt's meines Wissens in .net so nicht, zumindest nicht die Capturing-Phase.

          Wenn der Eventhandler auf dem Element registriert ist, auf dem das Event ausgelöst wird, ändert der 3. Parameter nichts. Trigger ist Trigger. Der Unterschied entsteht bei Registrierung auf Elternelementen im DOM.

          Ist der 3. Parameter false, greift der Handler beim Bubbling. Das ist das vertraute Verhalten: Elternelemente können Events ihrer Kinder auch behandeln, aber die Kinder sind zuerst dran.

          Ist der 3. Parameter true, greift der Handler vor dem Trigger. Das ist das Verhalten von überfürsorglichen Eltern: Alles erstmal begutachten bevor das Kind dran kommt.

          Dabei haben die Eltern zwei Optionen: preventDefault - das sorgt dafür, dass der Browser das Event nicht mehr anfasst wenn es durch's JavaScript durchgereicht wurde. Und stopPropagation - das ist etwas anderes, aber genauso gemein: Das Weiterreichen des Events durch's JavaScript wird gestoppt. Das hindert den Browser aber nicht an seiner Default-Aktion.

          D.h. ein preventDefault könnte - wenn Angular auf dem Element selbst lauscht, ggf. wirkungslos sein. Und zwar dann, wenn Angular die Tastendrücke aus irgendeinem schrägen Grund selbst verarbeitet. In dem Fall hilft dann nur noch das Fernhalten der leckeren Keydown-Kekse vom plärrenden Angular-Kleinkind, durch stopPropagation in der Capturing-Phase.

          Rolf

          --
          sumpsi - posui - clusi
      3. Tach!

        Leider habe das Ganze mit Angular am Start, und hier ist eine Doppel-Bindung zwischen dem Input und der Varíablen möglich und vorhanden.

        Übrigen dieser Code klappt, bis auf das Autorepeat:

        window.addEventListener('keyup', (event:KeyboardEvent )=>
        {this.keyValue = event.key;
          if (parseInt( this.keyValue) >=  0 && parseInt( this.keyValue) <= 9 )
              { /*Eingabe OK*/ }
          else 
              {this.A = this.A.replace( event.key,"");} // Löscht das letzte Eingeabezeichen.
        }
        

        Sich an das window-Objekt zu hängen ist eigentlich nicht der Angular-Weg. Sollen wirklich nach Aufruf dieses Codes in der gesamten Anwendung Tastendrucke gefiltert werden? Falls das nur auf bestimmte Input-Elemente angewendet werden soll, ist der Einsatz einer Directive besser.

        @Directive({
            selector: '[input-restriction]'
        })
        export class InputRestriction {
            constructor(private element: ElementRef) {}
        
            @HostListener('keydown', ['$event'])
            onKeyDown(event: KeyboardEvent) {
                if (event.key == 'a') {
                    event.preventDefault();
                }
            }
        }
        

        Anwendungsbeispiel:

        @Component({
            selector: 'test',
            templateUrl: './test.component.html'
        })
        export class TestComponent {
            formControl: FormControl;
        
            constructor() {
                this.formControl = new FormControl('');
                this.formControl.valueChanges.subscribe(console.log);
            }
        }
        

        Ausschnitt aus test.component.html:

            <label>No-a's Arc
                <input [formControl]="formControl" input-restriction>
            </label>
        

        Die InputDirective filtert derzeit alle a aus. Sie ist ausbaufähig, zum Beispiel, dass man Parameter übergeben kann, was gefiltert werden soll.

        dedlfix.

        1. Hallo Rolf,

          erst einmal vielen, vielen Dank für Deine Antwort!

          Leider geht das Ganze ja noch etwas weiter, Angular wird in TypeScript programmiert. Das ist ja erst einmal nicht schlimm gesehen werden, da es in JS später umgesetzt wird. Dennoch ist im TypeScript die Klasse "noch mehr" Klasse als im JS und die "this." ist der Funktionsauftrufe ist durch JS vom Aufrufungsort(-objekt) (und nicht von der Definition). D.h. mit meinem Klassen .this habe ich nun ein Problem. Das Klassen .this benötige ich zur Dopplelbindung zwischen dem HTML Code <input ng{(A)} und dem JS (Typescript) A.this … Da habe ich mit gestern erst einmal die Karten gelegt. Hier werde ich mit etwas mit Delegaten ausdenken müssen.

          Was als Resultat dabei herauskommen soll kann auf der Seite angulartool...de gesehen werden. Bei der Eingabe der Zahlen werden sofort die Ergebnisse geliefert, auch später einmal, "kreuzweise". D.h. es ist egal, welcher Parameter geändert wird von A+B=C, es wird entsprechend (nach meiner Vorgabe) geändert. Hier bei C-Änderung wird das A oder selektiert das B geändert.

          Grüße Jürgen

          1. Hallo dedlfix,

            ja, das ist genau die Baustelle die habe!

            @HostListener('keydown', ['$event'])...

            Denn nach der Lösung möchte ich ja nicht für jedes <input> Element die konkrete this.A auf B.This ect. erweitern.

            Leider bin ich auf der Arbeit, und kann den Code nicht laufen lassen... Melde mich aber wieder.

            DANKE! Jürgen

            1. Tach!

              ja, das ist genau die Baustelle die habe!

              @HostListener('keydown', ['$event'])...

              Denn nach der Lösung möchte ich ja nicht für jedes <input> Element die konkrete this.A auf B.This ect. erweitern.

              Beschreib doch mal konkret, was du für ein Problem hast. Vermutlich löst sich das mit den this in Luft auf, wenn du es Angular-like löst.

              Meine Beispiel-Direktive kommt in ihrer jetzigen Form ohne this aus, weil sie die notwendigen Dinge (hier das Event-Objekt) als benannten Parameter reingereicht bekommt. Ein this würde sie nur für ihre eigenen Zwecke benötigen, beispielsweise wenn sie mit Parametern initialisiert werden soll, die in einer Eigenschaft abgelegt werden müssen, um zum Event darauf zugreifen zu können.

              Das Input-Element muss sie ja auch gar nicht befummeln, und wenn doch, steht es über event.target zur Verfügung. Da spielt also auch kein this eine Rolle, das für das aktuelle Element steht.

              dedlfix.