Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out if something in a given div has focus?

Using angularjs, I'm showing a 2-level list like this

- first main item
  - first subitem of the first main item
  - second subitem of the first main item
  - AN EMPTY ITEM AS PLACEHOLDER TO ENTER THE NEXT SUBITEM
- second main item
  - first subitem of the second main item
  - second subitem of the second main item
  - AN EMPTY ITEM AS PLACEHOLDER TO ENTER THE NEXT SUBITEM

In order to save place, I'd like to show the PLACEHOLDER only if anything in the corresponding div has focus, so that there's only one such placeholder. I know that there's ngFocus, but I'd prefer something simpler than creating tons of event handlers. Maybe something like this :

<div ng-focus-model="mainItem.hasFocus" ng-repeat="mainItem in list">
   ... main item line
   ... all subitems
</div>

A unidirectional binding would be sufficient as I don't need to set the focus.

like image 664
maaartinus Avatar asked Apr 04 '15 19:04

maaartinus


2 Answers

The problem here is the following; we want to avoid adding event listener to each and every child, but add it only to the parent. The parent will be responsible for taking the appropriate action. The general solution to this, is to use even propagation (delegation). We attach only one listener to the parent, when an event occurs on the child (focus on input element in this example), it will bubble up to the parent and the parent will execute the listener.

Here's the directive:

app.directive('ngFocusModel', function () {
    return function (scope, element) {

      var focusListener = function () {
          scope.hasFocus = true;
          scope.$digest();
      };

      var blurListener = function () {
          scope.hasFocus = false;
          scope.$digest();
      };


      element[0].addEventListener('focus', focusListener, true);
      element[0].addEventListener('blur', blurListener, true);
   };
});

The directive listens for events and accordingly sets the value on scope, so we can make conditional changes.

There are several things to notice here.

focus and blur events don't "bubble", we need to use "event capturing" to catch them. That's why element.on('focus/blur') is not used (it doesn't allow for capture, afaik) but an addEventListener method. This method allows us to specify if the listener will be executed on "event bubbling" or "event capturing" by setting the third argument to false or true accordingly.

We could have used focusin and focusout events which "bubble", unfortunatelly these aren't supported in Firefox (focusin and focusout).

Here's a plunker with the implementation.

Update: It occurred to me that this can be done with pure CSS using the :focus pseudo-class, the only downside is that the placeholder needs to be in proper position (sibling) relative to the input elements. See codepen.

like image 156
jcz Avatar answered Oct 04 '22 22:10

jcz


Unfortunately the only rock solid way to do what you want is to respond to the focus\blur events on the inputs...that's the only way to get notified.

You could put a hidden input as the first element in each div and put the NgFocus attribute on it but that only works if a user tabs into it.

like image 21
SKYWALKR Avatar answered Oct 04 '22 22:10

SKYWALKR