Help with aurelia-blur-attribute

I’m trying to write a simple component that as soon as the user clicks away from the component, an event fires onBlur() restoring the state of the component. I have tried using the aurelia-blur-attribute but can’t seem to get it work.

I guess I’m missing something really obvious here.

I would appreciate any thoughts.

My componenent as follows:

import { autoinject, bindable, bindingMode, observable } from "aurelia-framework";

@autoinject
export class SearchBox {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public value: string;
  @bindable public label: string = "";

  public showInput: boolean = false;
  public formIsBlur = true;

  public showInputField() {
    this.showInput = !this.showInput;
  }

  public updateInput() {
    this.showInput = (this.value && this.value.length > 0);
  }
}

<template>
  <form blur.bind="formIsBlur">
  <span class="u-flex u-flexJustifyStart">
    <i class="material-icons small left mr-1" click.delegate="showInputField()" md-waves style="font-size: 24px;">search</i>
    <input md-auto-focus if.bind="showInput" type="text" value.bind="value" style="height: 2rem; width: auto;" change.delegate="updateInput()" blur.trigger="updateInput()"/>
    <span else click.delegate="showInputField()">${label}</span>
  </span>
</form>
</template>

1 Like

assuming you configured the plugin correctly (like in the README)

turn this:

<form blur.bind="formIsBlur">

into this:

<form blur.delegate="formIsBlur = true">
1 Like

It seems to me you should aready have it working. What is lacking probably is how you react to the change of formIsBlur value

Taking your comments I changed the code to

export class SearchBox {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public value: string;
  @bindable public label: string = "";

  public showInput: boolean = false;
  @observable public formIsBlur = true;

  public showInputField() {
    this.showInput = !this.showInput;
  }

  public updateInput() {
    this.showInput = (this.value !== undefined && this.value.length > 0);
  }

  protected formIsBlurChanged() {
    this.updateInput();
  }
}

and

<form blur.delegate="formIsBlur = true">

The searchBox works fine, except when you mouseclick into the input, do not type anything and then mouseclick anywhere else on the page - in this case the formIsBlurChanged is not fired.

I also tried changing the config to

  const listeningModeOptions = {
    pointer: true, // listen for pointer event interaction
    touch: true, // listen for touch event interaction
    mouse: true, // listen for mouse event interaction
    focus: true, // listen for foucus event
    windowBlur: true // listen for window blur event (navigating away from window)
  };
1 Like

The blur attribute plugin was developed to solve a problem that I had: less boiler plate & error prone code determining focus state of an element.

To know why this is a problem, we need to get familiar with focus events of the web: the main ones that are used are “focus” and “blur” event. Unfortunately, they come with a bunch of annoyance when combined together, across multiple elements:

So there’s a need to have something simple, that can reliably reflect the focus state of an element. (1) An element is determined as “focused” when either itself is receiving focus, or (2) any of its descendants is receiving focus. The blur attribute was developed to reflect the absence of both (1) and (2). When neither (1) nor (2) is true, it sets its main bindable value to false, then gets reflected to any model bound to it.

<form blur.bind="formIsFocused">
  <!-- ...10 of inputs ... -->
</form>

With the above, whenever the loses its focus, formIsFocused will be set to false, then we can handle it easily via a change handler:

export class MyEl {
  formIsFocusedChanged(isFocused) {
    // do something
  }
}

Bear in mind that change handler is only invoked when the value gets changed, so if formIsFocused is never set to true, assigning false over and over won’t invoke our change handler. To solve this, we can use capture binding command to set formIsFocused to false whenever the form or anything inside it receives the focus:

<form blur.bind="formIsFocused" focus.capture="formIsFocused = true">
  <!-- ...10 of inputs ... -->
</form>

Online demo of the above explanation: https://codesandbox.io/s/readme-example-yxrz0?module=src/app.html

Thank you for the explanation.

With this code, I’ve managed to get the component to behave correctly on loss of focus. However, the first time I click into the input, I need to click twice on the label to hide the label and show the input.

  <form blur.bind="formIsFocused" focus.capture="formIsFocused = true">
  <span class="u-flex u-flexJustifyStart">
    <i class="material-icons small left mr-1" click.delegate="showInputField()" md-waves style="font-size: 24px;">search</i>
    <input md-auto-focus if.bind="showInput" type="text" value.bind="value" style="height: 2rem; width: auto;"/>
    <span else click.delegate="showInputField()">${label}</span>
  </span>
</form>
export class SearchBox {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public value: string;
  @bindable public label: string = "";

  public showInput: boolean = false;
  @observable public formIsFocused = false;

  public showInputField() {
    this.showInput = !this.showInput;
  }

  protected formIsFocusedChanged() {
    this.showInput = (this.value !== undefined && this.value.length > 0);
    console.log("formIsFocused", this.formIsFocused, "showInput", this.showInput);
  }
}
1 Like

to fix the issue of clicking twice:

try

focus.caputre="formIsFocused = true; return true;"

or

focus.caputre="handleFocus()"
handleFocus()
{
    this.formIsFocused = true;
    return true;
}
1 Like

I tried your suggestion, but I still need to click twice.

1 Like

It’s because focus.capture was never triggered, due to no focus event fired when clicking on the label. You can fix this via assigning a tabindex attribute to the label, demo here Readme Example - CodeSandbox

Guys - thanks so much for your help.

I’ve got so much to learn …

By the way in https://codesandbox.io/s/readme-example-yxrz0?module=src/app.html the tabindex=-1 should go on the label and not the icon button (exactly as you described)

  <form blur.bind="formIsFocused" focus.capture="formIsFocused = true">
  <span class="u-flex u-flexJustifyStart">
    <i class="material-icons small left mr-1" click.delegate="showInputField()" md-waves style="font-size: 24px;">search</i>
    <input md-auto-focus if.bind="showInput" type="text" value.bind="value" style="height: 2rem; width: auto;"/>
    <span tabindex=-1 else click.delegate="showInputField()">${label}</span>
  </span>
</form>
1 Like