<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <style>
    button,
    input {
      padding: 10px;
      margin-top: 20px;
      width: 200px;
    }
    
    ul {
      list-style: none;
      padding: 0px;
      width: 200px;
    }
    
    li {
      border-bottom: 1px solid black;
      padding: 10px;
    }
    
    li:first-child {
      border-top: 1px solid black;
    }
    
    button:focus,
    li:focus {
      border: 1px solid red;
      outline: none;
    }
  </style>
  <script src="script.js"></script>
</head>

<body ng-app="KbrdNavgtn" ng-controller="PageController">
  <input type="text" placeholder="First element">

  <div class="navigable">
    <button class="dropdown-toggle" 
      dropdown-toggler dropdown-selector="'.dropdown-toggle'" 
      dropdown-items-selector="'.dropdown-items li'" 
      is-dropdown-visible="{'isVisible': isVisible, 'attrName': 'ddMenu'}" 
      show-dropdown-onload="false">
      <span class="pull-left">Navigable dropdown</span>
      <span class="pull-right">
        <span class="glyphicon glyphicon-menu-{{isVisible.ddMenu ? 'up' : 'down'}}" aria-hidden="true"></span>
      </span>
    </button>
    <ul class="dropdown-items" ng-show="isVisible.ddMenu">
      <li tabindex="0" ng-repeat="item in list" navigate-menu-items 
        is-dropdown-visible="{'isVisible': isVisible, 'attrName': 'ddMenu'}" 
        on-selection="onSelect($index)" dropdown-selector="'.dropdown-toggle'" 
        dropdown-items-selector="'.dropdown-items li'">{{item.name}}</li>
    </ul>
  </div>
</body>

</html>
angular.module('KbrdNavgtn', []).
controller('PageController', function ($scope) {
  $scope.isVisible = {
    ddMenu: false
  };
  $scope.list = [
    { name: 'Item 1' },
    { name: 'Item 2' },
    { name: 'Item 3' }
  ]
}).
/**
 * Toggle dropdown using keyboard and mouse click. Select first item of the dropdown items, if keyboard is used.
 * @param  {
 *   dropdownSelector: CSS selector for dropdown,
 *   dropdownItemsSelector: CSS selector for dropdown items,
 *   isDropdownVisible: scope attribute to control dropdown visibility,
 *   showDropdownOnload: is dropdown visible on load or not
 * } 
 * @return {[type]}   [description]
 */
directive('dropdownToggler', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attribute) {
      scope.isDropdownVisible = scope.showDropdownOnload ? scope.showDropdownOnload : false;
      element.bind('keydown', function(event) {
        console.log('keydown event occured');
        var keycodes = [13, 40]
        if (keycodes.indexOf(event.keyCode) > -1) {
          event.stopPropagation();
          event.preventDefault();

          // On press of 'Enter' key
          if (event.keyCode == 13) {
            toggleDropdownVisibility();
            scope.$apply();
            if (scope.isDropdownVisible['isVisible'].attrName) {
              angular.element(scope.dropdownItemsSelector).eq(0).trigger('focus');
            } else {
              element.trigger('focus');
            }
          }
          // On press of 'Down arrow' key
          if (event.keyCode == 40) {
            toggleDropdownVisibility();
            scope.$apply();
            angular.element(scope.dropdownItemsSelector).eq(0).trigger('focus');
          }
        }
      });
      function toggleDropdownVisibility() {
        var isVisible = scope.isDropdownVisible['isVisible'];
        isVisible[scope.isDropdownVisible.attrName] = !isVisible[scope.isDropdownVisible.attrName];
      }
      element.bind('click', function(event) {
        console.log('Click event registerd')
        scope.isDropdownVisible = !scope.isDropdownVisible;
        scope.$apply();
      });
    },
    scope: {
      dropdownSelector: '=',
      dropdownItemsSelector: '=',
      isDropdownVisible: '=',
      showDropdownOnload: '='
    }
  }
}).
/**
 * Navigate menu items using keyboard
 * @param  {
 *   dropdownSelector: CSS selector for dropdown,
 *   dropdownItemsSelector: CSS selector for dropdown items,
 *   isDropdownVisible: scope attribute to control dropdown visibility,
 *   onSelection: action to be taken on selection of dropdown item
 * } 
 * @return {[type]}   [description]
 */
directive('navigateMenuItems', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attribute) {
      function onSelect() {
        scope.onSelection();
        hideDropdown();
      }

      function hideDropdown() {
        var isVisible = scope.isDropdownVisible['isVisible'];
        isVisible[scope.isDropdownVisible.attrName] = false;
        angular.element(scope.dropdownSelector).trigger('focus');
        scope.$apply();
      }

      element.bind('keydown', function(event) {
        var keycodes = [9, 13, 27, 38, 40];
        if (keycodes.indexOf(event.keyCode) > -1) {
          event.stopPropagation();
          event.preventDefault();

          // On press of 'Enter' key
          if (event.keyCode == 13) {
            onSelect();
          }
          // On press of 'Esc' key
          if (event.keyCode == 27) {
            hideDropdown();
          }

          var items = angular.element(scope.dropdownItemsSelector);
          navigateItems(event, element, items);
        }
      });
      element.bind('click', function(event) {
        onSelect();
      });
    },
    scope: {
      dropdownSelector: '=',
      dropdownItemsSelector: '=',
      isDropdownVisible: '=',
      onSelection: '&'
    }
  }

  function navigateItems(e, current, items) {
    var index = items.index(angular.element(current));

    // On press of 'Tab' and 'Down arrow' key
    if ((event.shiftKey && event.keyCode == 9) || event.keyCode == 38) {
      index = index - 1;

      if (index >= 0) {
        items.eq(index).trigger('focus');
      }
    }
    // On press of 'Shift Tab' and 'Up arrow' key
    else if (event.keyCode == 9 || event.keyCode == 40) {
      index = index + 1;

      if (index < items.length) {
        items.eq(index).trigger('focus');
      }
    }
  }
});
/* Styles go here */