Programmatically trigger click event of button on Android

I posted the same question on Stackoverflow but got no answer. Maybe it is better suited in this forum. I’m currectly working on Android TV controls in Ionic 3.3.0 with Cordova 7.0.1. Since the device has no touchscreen I have to use the remote control. I’m using

@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
  // Check the key code and determine the next element to focus
  // ...

  // Focus the element
  this.renderer.invokeElementMethod(nextElement._elementRef.nativeElement,'focus');
}

to check for remote control pushes to change focus. This works fine. When the enter key is pressed I want that the click action of the button is activated. Now i tried using something like

@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
  if(event.key == 'Enter') {
    var focusedButton = Helper.getCurrentlyFocusedButton();
    focusedButton._elementRef.nativeElement.click();
    // Using the debugger I know that these lines are triggered, but nothing happens
  }
}

to programmatically press the button. This works fine when using ionic serve with Chrome but doesn’t work on the Android device (Sony KD-49XD7005 with Android TV 6.0.1). As far as i could gather from googling, this is because click() is not supported. But there must be another way to trigger this event? I also tried to create touch events manually

var e1 = document.createEvent('TouchEvent'); // Also tried 'UIEvent'
e1.initEvent('touchstart', true, true);

var e2 = document.createEvent('TouchEvent');
e2.initEvent('touchend', true, true);

this.renderer.invokeElementMethod(focusedButton._elementRef.nativeElement, 'dispatchEvent', [e1]);
this.renderer.invokeElementMethod(focusedButton._elementRef.nativeElement, 'dispatchEvent', [e2]);

// Also tried
// focusedButton._elementRef.nativeElement.dispatchEvent(e1);
// focusedButton._elementRef.nativeElement.dispatchEvent(e2);

but ultimately nothing worked. Is there any way to do this?

I figured it out myself by looking at the source code of ionic-angular. In the file node_modules/ionic-angular/tap-click/tap-click.js is a method that handles the click event. It calls another method called shouldCancelClick(ev) which returns true when this.dispatchClick is undefined or false. this.dispatchClick is set by pointerStart(ev) which is bound to the mousedown event.

So all that has to be done is to trigger a mousedown event before triggering the click event.

I solved it like this:

var e1 = document.createEvent('MouseEvents');
e1.initEvent('mousedown', true, true);
focusedButton.dispatchEvent(e1);

var e2 = document.createEvent('MouseEvents');
e2.initEvent('click', true, true);
focusedButton.dispatchEvent(e2);
1 Like

I have a somewhat related question here Converting "touch" items to android TV direction pad controllable – which is to do with the other part before programmatically clicking the item - how do you navigate to it? thanks

I didn’t find any generic solution to solve I navigation, so I had to write that logic myself. Basically checking which item has the focus, finding out where it is in relation to the other items and then deciding which will be the next item based on the keypress.

Thanks. I posted my findings here - some progress, but challenges remain

Hey @markusmr, could you elaborate a bit and maybe even include some code? I have to do the same things soonish, so I would appreciate any pointers and ideas.

Unfortunately the code is work related, so I can’t post detailed examples. But basically what I did is the following:

Mark all focusable elements

<button ion-button (click)="sendMessage()" block=true #focusable><ion-icon name="mail"></ion-icon>Message</button>

Make the elements available in code

@ViewChildren('focusable') myInput:QueryList<any>;

Transform it to an array, so we can iterate it

this.inputs = this.myInput.toArray();

Then, when a button is pressed determine which is currently focused

private getCurrentFocusIndex():number{
  for (var i = 0; i < this.inputs.length; i++) {
    if (this.isFocus(this.inputs[i])) {
      return i;
    }
  }

  return null;
}

static isFocus(element:any):boolean{
  if(element instanceof TextInput){
    return (element as TextInput).isFocus();
  }

  return this.getNativeElement(element) === document.activeElement;
}

static getNativeElement(element:any):any{
  if(element instanceof ElementRef){
    return (element as ElementRef).nativeElement
  }
  else if(element instanceof Ion){
    return (element as Ion).getNativeElement()
  }

  return element
}

Then set the focus on the next or previous element, depending on which button is pressed

static setFocus(element:any, renderer:Renderer) {
  if(element instanceof TextInput){
    (element as TextInput).setFocus();
  }
  else{
    renderer.invokeElementMethod(this.getNativeElement(element), 'focus', []);
  }
}

If you have anything other than a list, things get complicated since, rather than simply using the index, you have to involve the position the find the next element. But I never got around to actually implementing that. I hope that was at least a little helpful. Good luck!

1 Like