AngularJS: Popover styled with Bootstrap-like-styled that stays open on hover

This popover is accessible and allows a user to open the popover through tabbing and allows selection into the popover through tabbing. Additionally the popover will stay alive when a user hovers it. Additionally you can easily create multiple popovers on the same page

by Max Poshusta

HTML

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-animate.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
<section ng-app="myApp">
    <section ng-controller="MainCtrl">
       <div class="top" data-ng-repeat="i in [0,1,2,3,4,5,6]">
          <section>
            <a href="#" 
               ng-mouseover="showPopover(i)" 
               ng-mouseleave="hidePopover(i)"
               ng-focus="showPopover(i)"
               ng-blur="hidePopover(i)">
                I trigger a popover - {{i}}
            </a>
            <popover popover-show="popover[i].popoverTracker">
              <div class="arrow"></div>
              <div class="custom-popover-content"
                   ng-mouseover="showPopover(i)" 
                   ng-mouseleave="hidePopover(i)"
                   ng-focus="showPopover(i)"
                   ng-blur="hidePopover(i)">
                <a href="#"
                   ng-focus="showPopover(i)"
                   ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
                <br/>
                <a href="#" 
                   ng-focus="showPopover(i)"
                   ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
              </div>
            </popover>
          </section>       
       </div>
    </section>
</section>

CSS

.custom-popover {
    position: absolute;
    z-index: 1010;
    max-width: 276px;
    padding: 1px;
    text-align: left;
    white-space: normal;
    background-color: #fff;
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 6px;
    box-shadow: 0 5px 10px rgba(0,0,0,0.2);
    background-clip: padding-box;
}

.custom-popover .arrow,
.custom-popover .arrow:after {
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
}

.custom-popover .arrow {
  border-width: 11px;
}

.custom-popover .arrow:after {
  border-width: 10px;
  content: "";
}

.custom-popover.bottom {
  margin-top: 10px;
}

.custom-popover.bottom .arrow {
  top: -11px;
  left: 50%;
  margin-left: -11px;
  border-bottom-color: rgba(0, 0, 0, 0.25);
  border-top-width: 0;
}

.custom-popover.bottom .arrow:after {
  top: 1px;
  margin-left: -10px;
  border-bottom-color: #ffffff;
  border-top-width: 0;
  content: " ";
}

.custom-popover-content {
  padding: 9px 14px;
}
















































/* Begin these are not important for you styles */ 
body {
    margin: 1em;
}

.top:not(:first-child){
  padding-top: 2em; 
}
/* End these are not important for you styles */

JavaScript

angular.module('controllers', []);
angular.module('directives', []);
angular.module('myApp', ['ngAnimate', 'controllers', 'directives']);

angular.module('controllers')
    .controller('MainCtrl', function ($scope, $timeout) {
    
    $scope.popover = [];
    
    (function init() {
    	// TODO: Make this dynamic so that we can pass it a value and it will generate the right amount
    	// Initializing the array of popovers on startup
      createPopoverTrackers(20);
		})();
    
    // Creating an array of popovers equal to the number of popovers on the page
    function createPopoverTrackers(count) {
    	for(var i = 0; i < count; i++) {
      	$scope.popover.push({
        	popoverTracker: false,
          popoverKeepAlive: false,
          timer: null
        })
      }
    }
    
    // A user has focused on an element that has an associated popover
  	$scope.queueOpenPopover = function(index) {
    // Show our specified tracker
    $scope.popover[index].popoverTracker = true;
     
     // Hide the rest
     Object.keys($scope.popover)
     	.filter(function(trackerIndex) {
          return trackerIndex != index
       })
      .forEach(function(trackerIndex) {
          $scope.popover[trackerIndex].popoverTracker = false;
          $scope.popover[trackerIndex].popoverKeepAlive = false;
          
          const timer = $scope.popover[trackerIndex].timer;
          if(timer) {
             $timeout.cancel(timer);
             $scope.popover[trackerIndex].timer = null;
          }
      })
 		};
    
    // Queuing up the demise of the popover
    $scope.queueKillPopover = function(index) {
      $scope.popover[index].timer = $timeout(function() {
			 if (!$scope.popover[index].popoverKeepAlive) {
          // Popover or the popover trigger were not hovered within the time limit, kill it!
          $scope.popover[index].popoverTracker = false;
        }
     	}, 700);
    };
    
    // When a user focuses into the actual popover itself or it's trigger, ...