angularjsangular-strap

How can I reliably set focus on elements inside of my AngularStrap tooltip?


My team uses AngularStrap to integrate Bootstrap modals (e.g. popover, tooltip, etc.) into our Angular 1.5 app. Unfortunately, I have found it extremely difficult to reliably set focus on elements inside of these modals because of the funky way in which AngularStrap shows them. This logic lives here:

https://github.com/mgcrea/angular-strap/blob/b13098d9d658da9408930b25f79182df920718d2/src/tooltip/tooltip.js

Essentially, all AngularStrap modals start off in the hidden state:

// Set the initial positioning.  Make the tooltip invisible
// so IE doesn't try to focus on it off screen.
tipElement.css({top: '-9999px', left: '-9999px', right: 'auto', display: 'block', visibility: 'hidden'});

They are then made visible in response to some internal Angular requestAnimationFrame service callback:

$$rAF(function () {
  // Once the tooltip is placed and the animation starts, make the tooltip visible
  if (tipElement) tipElement.css({visibility: 'visible'});
  ...
});

Unfortunately, this means that at the time all of the tooltip's DOM elements are constructed the tooltip is typically not yet visible and any attempts to call focus() on these elements fail. Do note that in my experience this happens intermittently (~20% of the time for me).

I tried disabling animations on the tooltip but it doesn't seem to be smart enough to skip this whole hidden/visible dance in that case. I could obviously try something super hacky (e.g. use an arbitrary timeout before attempting to set focus) but I am looking for a more reliable option.


Solution

  • Why not use the onShow event? It is documented a little bit wrong, but you can attach a handler that focus elements to bs-on-show. Furthermore you could make the handler generic, looking for an element with a focus-me attribute and focus that :

    <div bs-popover
      data-content='this input should be focused: <input type="text" focus-me>'
      data-placement="bottom"
      bs-on-show="setFocus"
      data-html="true">
        click me to show popover
    </div>
    
    <div bs-tooltip
      data-title='this input should be focused: <input type="text" focus-me style="color:#000;">'
      data-placement="bottom"
      data-trigger="click"
      bs-on-show="setFocus"
      data-html="true">
        click me to show tooltip
    </div>   
    

    generic handler

    $scope.setFocus = function(e) {
      e.$element.find('[focus-me]').focus()    
    } 
    

    http://plnkr.co/edit/3wTinmNb5zsKnfUZQ9tT?p=preview