kylin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lid...@apache.org
Subject [09/14] kylin git commit: Fix ui-grid font issue and auto-add space issue
Date Mon, 16 May 2016 08:32:31 GMT
http://git-wip-us.apache.org/repos/asf/kylin/blob/19f5cf7d/webapp/app/js/directives/ui-grid.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/directives/ui-grid.js b/webapp/app/js/directives/ui-grid.js
new file mode 100644
index 0000000..352e919
--- /dev/null
+++ b/webapp/app/js/directives/ui-grid.js
@@ -0,0 +1,28079 @@
+/*!
+ * ui-grid - v3.1.1 - 2016-02-09
+ * Copyright (c) 2016 ; License: MIT
+ */
+
+(function () {
+  'use strict';
+  angular.module('ui.grid.i18n', []);
+  angular.module('ui.grid', ['ui.grid.i18n']);
+})();
+(function () {
+  'use strict';
+  angular.module('ui.grid').constant('uiGridConstants', {
+    LOG_DEBUG_MESSAGES: true,
+    LOG_WARN_MESSAGES: true,
+    LOG_ERROR_MESSAGES: true,
+    CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
+    COL_FIELD: /COL_FIELD/g,
+    MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
+    TOOLTIP: /title=\"TOOLTIP\"/g,
+    DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
+    TEMPLATE_REGEXP: /<.+>/,
+    FUNC_REGEXP: /(\([^)]*\))?$/,
+    DOT_REGEXP: /\./g,
+    APOS_REGEXP: /'/g,
+    BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
+    COL_CLASS_PREFIX: 'ui-grid-col',
+    events: {
+      GRID_SCROLL: 'uiGridScroll',
+      COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
+      ITEM_DRAGGING: 'uiGridItemDragStart', // For any item being dragged
+      COLUMN_HEADER_CLICK: 'uiGridColumnHeaderClick'
+    },
+    // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
+    keymap: {
+      TAB: 9,
+      STRG: 17,
+      CAPSLOCK: 20,
+      CTRL: 17,
+      CTRLRIGHT: 18,
+      CTRLR: 18,
+      SHIFT: 16,
+      RETURN: 13,
+      ENTER: 13,
+      BACKSPACE: 8,
+      BCKSP: 8,
+      ALT: 18,
+      ALTR: 17,
+      ALTRIGHT: 17,
+      SPACE: 32,
+      WIN: 91,
+      MAC: 91,
+      FN: null,
+      PG_UP: 33,
+      PG_DOWN: 34,
+      UP: 38,
+      DOWN: 40,
+      LEFT: 37,
+      RIGHT: 39,
+      ESC: 27,
+      DEL: 46,
+      F1: 112,
+      F2: 113,
+      F3: 114,
+      F4: 115,
+      F5: 116,
+      F6: 117,
+      F7: 118,
+      F8: 119,
+      F9: 120,
+      F10: 121,
+      F11: 122,
+      F12: 123
+    },
+    ASC: 'asc',
+    DESC: 'desc',
+    filter: {
+      STARTS_WITH: 2,
+      ENDS_WITH: 4,
+      EXACT: 8,
+      CONTAINS: 16,
+      GREATER_THAN: 32,
+      GREATER_THAN_OR_EQUAL: 64,
+      LESS_THAN: 128,
+      LESS_THAN_OR_EQUAL: 256,
+      NOT_EQUAL: 512,
+      SELECT: 'select',
+      INPUT: 'input'
+    },
+
+    aggregationTypes: {
+      sum: 2,
+      count: 4,
+      avg: 8,
+      min: 16,
+      max: 32
+    },
+
+    // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
+    CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
+
+    scrollDirection: {
+      UP: 'up',
+      DOWN: 'down',
+      LEFT: 'left',
+      RIGHT: 'right',
+      NONE: 'none'
+
+    },
+
+    dataChange: {
+      ALL: 'all',
+      EDIT: 'edit',
+      ROW: 'row',
+      COLUMN: 'column',
+      OPTIONS: 'options'
+    },
+    scrollbars: {
+      NEVER: 0,
+      ALWAYS: 1
+      //WHEN_NEEDED: 2
+    }
+  });
+
+})();
+angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
+  var uiGridCell = {
+    priority: 0,
+    scope: false,
+    require: '?^uiGrid',
+    compile: function() {
+      return {
+        pre: function($scope, $elm, $attrs, uiGridCtrl) {
+          function compileTemplate() {
+            var compiledElementFn = $scope.col.compiledElementFn;
+
+            compiledElementFn($scope, function(clonedElement, scope) {
+              $elm.append(clonedElement);
+            });
+          }
+
+          // If the grid controller is present, use it to get the compiled cell template function
+          if (uiGridCtrl && $scope.col.compiledElementFn) {
+             compileTemplate();
+          }
+          // No controller, compile the element manually (for unit tests)
+          else {
+            if ( uiGridCtrl && !$scope.col.compiledElementFn ){
+              // gridUtil.logError('Render has been called before precompile.  Please log a ui-grid issue');
+
+              $scope.col.getCompiledElementFn()
+                .then(function (compiledElementFn) {
+                  compiledElementFn($scope, function(clonedElement, scope) {
+                    $elm.append(clonedElement);
+                  });
+                });
+            }
+            else {
+              var html = $scope.col.cellTemplate
+                .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
+                .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
+
+              var cellElement = $compile(html)($scope);
+              $elm.append(cellElement);
+            }
+          }
+        },
+        post: function($scope, $elm, $attrs, uiGridCtrl) {
+          var initColClass = $scope.col.getColClass(false);
+          $elm.addClass(initColClass);
+
+          var classAdded;
+          var updateClass = function( grid ){
+            var contents = $elm;
+            if ( classAdded ){
+              contents.removeClass( classAdded );
+              classAdded = null;
+            }
+
+            if (angular.isFunction($scope.col.cellClass)) {
+              classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+            }
+            else {
+              classAdded = $scope.col.cellClass;
+            }
+            contents.addClass(classAdded);
+          };
+
+          if ($scope.col.cellClass) {
+            updateClass();
+          }
+
+          // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+          var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
+
+          // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
+          // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
+          var cellChangeFunction = function( n, o ){
+            if ( n !== o ) {
+              if ( classAdded || $scope.col.cellClass ){
+                updateClass();
+              }
+
+              // See if the column's internal class has changed
+              var newColClass = $scope.col.getColClass(false);
+              if (newColClass !== initColClass) {
+                $elm.removeClass(initColClass);
+                $elm.addClass(newColClass);
+                initColClass = newColClass;
+              }
+            }
+          };
+
+          // TODO(c0bra): Turn this into a deep array watch
+/*        shouldn't be needed any more given track by col.name
+          var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
+*/
+          var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
+
+
+          var deregisterFunction = function() {
+            dataChangeDereg();
+//            colWatchDereg();
+            rowWatchDereg();
+          };
+
+          $scope.$on( '$destroy', deregisterFunction );
+          $elm.on( '$destroy', deregisterFunction );
+        }
+      };
+    }
+  };
+
+  return uiGridCell;
+}]);
+
+
+(function(){
+
+angular.module('ui.grid')
+.service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
+function ( i18nService, uiGridConstants, gridUtil ) {
+/**
+ *  @ngdoc service
+ *  @name ui.grid.service:uiGridColumnMenuService
+ *
+ *  @description Services for working with column menus, factored out
+ *  to make the code easier to understand
+ */
+
+  var service = {
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name initialize
+     * @description  Sets defaults, puts a reference to the $scope on
+     * the uiGridController
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {controller} uiGridCtrl the uiGridController for the grid
+     * we're on
+     *
+     */
+    initialize: function( $scope, uiGridCtrl ){
+      $scope.grid = uiGridCtrl.grid;
+
+      // Store a reference to this link/controller in the main uiGrid controller
+      // to allow showMenu later
+      uiGridCtrl.columnMenuScope = $scope;
+
+      // Save whether we're shown or not so the columns can check
+      $scope.menuShown = false;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name setColMenuItemWatch
+     * @description  Setup a watch on $scope.col.menuItems, and update
+     * menuItems based on this.  $scope.col needs to be set by the column
+     * before calling the menu.
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {controller} uiGridCtrl the uiGridController for the grid
+     * we're on
+     *
+     */
+    setColMenuItemWatch: function ( $scope ){
+      var deregFunction = $scope.$watch('col.menuItems', function (n) {
+        if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
+          n.forEach(function (item) {
+            if (typeof(item.context) === 'undefined' || !item.context) {
+              item.context = {};
+            }
+            item.context.col = $scope.col;
+          });
+
+          $scope.menuItems = $scope.defaultMenuItems.concat(n);
+        }
+        else {
+          $scope.menuItems = $scope.defaultMenuItems;
+        }
+      });
+
+      $scope.$on( '$destroy', deregFunction );
+    },
+
+
+    /**
+     * @ngdoc boolean
+     * @name enableSorting
+     * @propertyOf ui.grid.class:GridOptions.columnDef
+     * @description (optional) True by default. When enabled, this setting adds sort
+     * widgets to the column header, allowing sorting of the data in the individual column.
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name sortable
+     * @description  determines whether this column is sortable
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    sortable: function( $scope ) {
+      if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
+        return true;
+      }
+      else {
+        return false;
+      }
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name isActiveSort
+     * @description  determines whether the requested sort direction is current active, to
+     * allow highlighting in the menu
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {string} direction the direction that we'd have selected for us to be active
+     *
+     */
+    isActiveSort: function( $scope, direction ){
+      return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
+              typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
+
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name suppressRemoveSort
+     * @description  determines whether we should suppress the removeSort option
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    suppressRemoveSort: function( $scope ) {
+      if ($scope.col && $scope.col.suppressRemoveSort) {
+        return true;
+      }
+      else {
+        return false;
+      }
+    },
+
+
+    /**
+     * @ngdoc boolean
+     * @name enableHiding
+     * @propertyOf ui.grid.class:GridOptions.columnDef
+     * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
+     * using the column menu or the grid menu.
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name hideable
+     * @description  determines whether a column can be hidden, by checking the enableHiding columnDef option
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    hideable: function( $scope ) {
+      if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
+        return false;
+      }
+      else {
+        return true;
+      }
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name getDefaultMenuItems
+     * @description  returns the default menu items for a column menu
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    getDefaultMenuItems: function( $scope ){
+      return [
+        {
+          title: i18nService.getSafeText('sort.ascending'),
+          icon: 'ui-grid-icon-sort-alt-up',
+          action: function($event) {
+            $event.stopPropagation();
+            $scope.sortColumn($event, uiGridConstants.ASC);
+          },
+          shown: function () {
+            return service.sortable( $scope );
+          },
+          active: function() {
+            return service.isActiveSort( $scope, uiGridConstants.ASC);
+          }
+        },
+        {
+          title: i18nService.getSafeText('sort.descending'),
+          icon: 'ui-grid-icon-sort-alt-down',
+          action: function($event) {
+            $event.stopPropagation();
+            $scope.sortColumn($event, uiGridConstants.DESC);
+          },
+          shown: function() {
+            return service.sortable( $scope );
+          },
+          active: function() {
+            return service.isActiveSort( $scope, uiGridConstants.DESC);
+          }
+        },
+        {
+          title: i18nService.getSafeText('sort.remove'),
+          icon: 'ui-grid-icon-cancel',
+          action: function ($event) {
+            $event.stopPropagation();
+            $scope.unsortColumn();
+          },
+          shown: function() {
+            return service.sortable( $scope ) &&
+                   typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
+                   typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
+                  !service.suppressRemoveSort( $scope );
+          }
+        },
+        {
+          title: i18nService.getSafeText('column.hide'),
+          icon: 'ui-grid-icon-cancel',
+          shown: function() {
+            return service.hideable( $scope );
+          },
+          action: function ($event) {
+            $event.stopPropagation();
+            $scope.hideColumn();
+          }
+        }
+      ];
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name getColumnElementPosition
+     * @description  gets the position information needed to place the column
+     * menu below the column header
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {GridCol} column the column we want to position below
+     * @param {element} $columnElement the column element we want to position below
+     * @returns {hash} containing left, top, offset, height, width
+     *
+     */
+    getColumnElementPosition: function( $scope, column, $columnElement ){
+      var positionData = {};
+      positionData.left = $columnElement[0].offsetLeft;
+      positionData.top = $columnElement[0].offsetTop;
+      positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
+
+      // Get the grid scrollLeft
+      positionData.offset = 0;
+      if (column.grid.options.offsetLeft) {
+        positionData.offset = column.grid.options.offsetLeft;
+      }
+
+      positionData.height = gridUtil.elementHeight($columnElement, true);
+      positionData.width = gridUtil.elementWidth($columnElement, true);
+
+      return positionData;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name repositionMenu
+     * @description  Reposition the menu below the new column.  If the menu has no child nodes
+     * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
+     * later to fix it
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {GridCol} column the column we want to position below
+     * @param {hash} positionData a hash containing left, top, offset, height, width
+     * @param {element} $elm the column menu element that we want to reposition
+     * @param {element} $columnElement the column element that we want to reposition underneath
+     *
+     */
+    repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
+      var menu = $elm[0].querySelectorAll('.ui-grid-menu');
+
+      // It's possible that the render container of the column we're attaching to is
+      // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
+      // between the render container and the grid
+      var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
+      var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
+
+      var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
+
+      // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
+      var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
+      var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
+
+      if ( menu.length !== 0 ){
+        var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
+        if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
+          myWidth = gridUtil.elementWidth(menu, true);
+          $scope.lastMenuWidth = myWidth;
+          column.lastMenuWidth = myWidth;
+
+          // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
+          // Get the column menu right padding
+          paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
+          $scope.lastMenuPaddingRight = paddingRight;
+          column.lastMenuPaddingRight = paddingRight;
+        }
+      }
+
+      var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
+      if (left < positionData.offset){
+        left = positionData.offset;
+      }
+
+      $elm.css('left', left + 'px');
+      $elm.css('top', (positionData.top + positionData.height) + 'px');
+    }
+
+  };
+
+  return service;
+}])
+
+
+.directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
+function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
+/**
+ * @ngdoc directive
+ * @name ui.grid.directive:uiGridColumnMenu
+ * @description  Provides the column menu framework, leverages uiGridMenu underneath
+ *
+ */
+
+  var uiGridColumnMenu = {
+    priority: 0,
+    scope: true,
+    require: '^uiGrid',
+    templateUrl: 'ui-grid/uiGridColumnMenu',
+    replace: true,
+    link: function ($scope, $elm, $attrs, uiGridCtrl) {
+      uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
+
+      $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
+
+      // Set the menu items for use with the column menu. The user can later add additional items via the watch
+      $scope.menuItems = $scope.defaultMenuItems;
+      uiGridColumnMenuService.setColMenuItemWatch( $scope );
+
+
+      /**
+       * @ngdoc method
+       * @methodOf ui.grid.directive:uiGridColumnMenu
+       * @name showMenu
+       * @description Shows the column menu.  If the menu is already displayed it
+       * calls the menu to ask it to hide (it will animate), then it repositions the menu
+       * to the right place whilst hidden (it will make an assumption on menu width),
+       * then it asks the menu to show (it will animate), then it repositions the menu again
+       * once we can calculate it's size.
+       * @param {GridCol} column the column we want to position below
+       * @param {element} $columnElement the column element we want to position below
+       */
+      $scope.showMenu = function(column, $columnElement, event) {
+        // Swap to this column
+        $scope.col = column;
+
+        // Get the position information for the column element
+        var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
+
+        if ($scope.menuShown) {
+          // we want to hide, then reposition, then show, but we want to wait for animations
+          // we set a variable, and then rely on the menu-hidden event to call the reposition and show
+          $scope.colElement = $columnElement;
+          $scope.colElementPosition = colElementPosition;
+          $scope.hideThenShow = true;
+
+          $scope.$broadcast('hide-menu', { originalEvent: event });
+        } else {
+          $scope.menuShown = true;
+          uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
+
+          $scope.colElement = $columnElement;
+          $scope.colElementPosition = colElementPosition;
+          $scope.$broadcast('show-menu', { originalEvent: event });
+
+        }
+      };
+
+
+      /**
+       * @ngdoc method
+       * @methodOf ui.grid.directive:uiGridColumnMenu
+       * @name hideMenu
+       * @description Hides the column menu.
+       * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
+       * from the menu itself - in which case don't broadcast again as we'll get
+       * an infinite loop
+       */
+      $scope.hideMenu = function( broadcastTrigger ) {
+        $scope.menuShown = false;
+        if ( !broadcastTrigger ){
+          $scope.$broadcast('hide-menu');
+        }
+      };
+
+
+      $scope.$on('menu-hidden', function() {
+        if ( $scope.hideThenShow ){
+          delete $scope.hideThenShow;
+
+          uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
+          $scope.$broadcast('show-menu');
+
+          $scope.menuShown = true;
+        } else {
+          $scope.hideMenu( true );
+
+          if ($scope.col) {
+            //Focus on the menu button
+            gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
+          }
+        }
+      });
+
+      $scope.$on('menu-shown', function() {
+        $timeout( function() {
+          uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
+          //Focus on the first item
+          gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item', true);
+          delete $scope.colElementPosition;
+          delete $scope.columnElement;
+        }, 200);
+      });
+
+
+      /* Column methods */
+      $scope.sortColumn = function (event, dir) {
+        event.stopPropagation();
+
+        $scope.grid.sortColumn($scope.col, dir, true)
+          .then(function () {
+            $scope.grid.refresh();
+            $scope.hideMenu();
+          });
+      };
+
+      $scope.unsortColumn = function () {
+        $scope.col.unsort();
+
+        $scope.grid.refresh();
+        $scope.hideMenu();
+      };
+
+      //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
+      var setFocusOnHideColumn = function(){
+        $timeout(function(){
+          // Get the UID of the first
+          var focusToGridMenu = function(){
+            return gridUtil.focus.byId('grid-menu', $scope.grid);
+          };
+
+          var thisIndex;
+          $scope.grid.columns.some(function(element, index){
+            if (angular.equals(element, $scope.col)) {
+              thisIndex = index;
+              return true;
+            }
+          });
+
+          var previousVisibleCol;
+          // Try and find the next lower or nearest column to focus on
+          $scope.grid.columns.some(function(element, index){
+            if (!element.visible){
+              return false;
+            } // This columns index is below the current column index
+            else if ( index < thisIndex){
+              previousVisibleCol = element;
+            } // This elements index is above this column index and we haven't found one that is lower
+            else if ( index > thisIndex && !previousVisibleCol) {
+              // This is the next best thing
+              previousVisibleCol = element;
+              // We've found one so use it.
+              return true;
+            } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
+            else if (index > thisIndex && previousVisibleCol) {
+              // We are done.
+              return true;
+            }
+          });
+          // If found then focus on it
+          if (previousVisibleCol){
+            var colClass = previousVisibleCol.getColClass();
+            gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
+              if (reason !== 'canceled'){ // If this is canceled then don't perform the action
+                //The fallback action is to focus on the grid menu
+                return focusToGridMenu();
+              }
+            });
+          } else {
+            // Fallback action to focus on the grid menu
+            focusToGridMenu();
+          }
+        });
+      };
+
+      $scope.hideColumn = function () {
+        $scope.col.colDef.visible = false;
+        $scope.col.visible = false;
+
+        $scope.grid.queueGridRefresh();
+        $scope.hideMenu();
+        $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+        $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
+
+        // We are hiding so the default action of focusing on the button that opened this menu will fail.
+        setFocusOnHideColumn();
+      };
+    },
+
+
+
+    controller: ['$scope', function ($scope) {
+      var self = this;
+
+      $scope.$watch('menuItems', function (n) {
+        self.menuItems = n;
+      });
+    }]
+  };
+
+  return uiGridColumnMenu;
+
+}]);
+
+})();
+
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
+
+    return {
+      compile: function() {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            $scope.col.updateFilters = function( filterable ){
+              $elm.children().remove();
+              if ( filterable ){
+                var template = $scope.col.filterHeaderTemplate;
+
+                $elm.append($compile(template)($scope));
+              }
+            };
+
+            $scope.$on( '$destroy', function() {
+              delete $scope.col.updateFilters;
+            });
+          },
+          post: function ($scope, $elm, $attrs, controllers){
+            $scope.aria = i18nService.getSafeText('headerCell.aria');
+            $scope.removeFilter = function(colFilter, index){
+              colFilter.term = null;
+              //Set the focus to the filter input after the action disables the button
+              gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
+            };
+          }
+        };
+      }
+    };
+  }]);
+})();
+
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
+  function ($timeout, gridUtil, uiGridConstants, $compile) {
+    var uiGridFooterCell = {
+      priority: 0,
+      scope: {
+        col: '=',
+        row: '=',
+        renderIndex: '='
+      },
+      replace: true,
+      require: '^uiGrid',
+      compile: function compile(tElement, tAttrs, transclude) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+            var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
+            $elm.append(cellFooter);
+          },
+          post: function ($scope, $elm, $attrs, uiGridCtrl) {
+            //$elm.addClass($scope.col.getColClass(false));
+            $scope.grid = uiGridCtrl.grid;
+
+            var initColClass = $scope.col.getColClass(false);
+            $elm.addClass(initColClass);
+
+            // apply any footerCellClass
+            var classAdded;
+            var updateClass = function( grid ){
+              var contents = $elm;
+              if ( classAdded ){
+                contents.removeClass( classAdded );
+                classAdded = null;
+              }
+
+              if (angular.isFunction($scope.col.footerCellClass)) {
+                classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+              }
+              else {
+                classAdded = $scope.col.footerCellClass;
+              }
+              contents.addClass(classAdded);
+            };
+
+            if ($scope.col.footerCellClass) {
+              updateClass();
+            }
+
+            $scope.col.updateAggregationValue();
+
+            // Watch for column changes so we can alter the col cell class properly
+/* shouldn't be needed any more, given track by col.name
+            $scope.$watch('col', function (n, o) {
+              if (n !== o) {
+                // See if the column's internal class has changed
+                var newColClass = $scope.col.getColClass(false);
+                if (newColClass !== initColClass) {
+                  $elm.removeClass(initColClass);
+                  $elm.addClass(newColClass);
+                  initColClass = newColClass;
+                }
+              }
+            });
+*/
+
+
+            // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+            var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
+            // listen for visible rows change and update aggregation values
+            $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
+            $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
+            $scope.$on( '$destroy', dataChangeDereg );
+          }
+        };
+      }
+    };
+
+    return uiGridFooterCell;
+  }]);
+
+})();
+
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      // priority: 1000,
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      scope: true,
+      compile: function ($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            $scope.grid = uiGridCtrl.grid;
+            $scope.colContainer = containerCtrl.colContainer;
+
+            containerCtrl.footer = $elm;
+
+            var footerTemplate = $scope.grid.options.footerTemplate;
+            gridUtil.getTemplate(footerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+
+                if (containerCtrl) {
+                  // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
+                  var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
+
+                  if (footerViewport) {
+                    containerCtrl.footerViewport = footerViewport;
+                  }
+                }
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            // gridUtil.logDebug('ui-grid-footer link');
+
+            var grid = uiGridCtrl.grid;
+
+            // Don't animate footer cells
+            gridUtil.disableAnimations($elm);
+
+            containerCtrl.footer = $elm;
+
+            var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
+            if (footerViewport) {
+              containerCtrl.footerViewport = footerViewport;
+            }
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      // priority: 1000,
+      require: '^uiGrid',
+      scope: true,
+      compile: function ($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+
+            $scope.grid = uiGridCtrl.grid;
+
+
+
+            var footerTemplate = $scope.grid.options.gridFooterTemplate;
+            gridUtil.getTemplate(footerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
+    var defaultTemplate = 'ui-grid/ui-grid-group-panel';
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      require: '?^uiGrid',
+      scope: false,
+      compile: function($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+            var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
+
+             gridUtil.getTemplate(groupPanelTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, uiGridCtrl) {
+            $elm.bind('$destroy', function() {
+              // scrollUnbinder();
+            });
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
+  function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
+    // Do stuff after mouse has been down this many ms on the header cell
+    var mousedownTimeout = 500;
+    var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
+
+    var uiGridHeaderCell = {
+      priority: 0,
+      scope: {
+        col: '=',
+        row: '=',
+        renderIndex: '='
+      },
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      replace: true,
+      compile: function() {
+        return {
+          pre: function ($scope, $elm, $attrs) {
+            var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
+            $elm.append(cellHeader);
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var renderContainerCtrl = controllers[1];
+
+            $scope.i18n = {
+              headerCell: i18nService.getSafeText('headerCell'),
+              sort: i18nService.getSafeText('sort')
+            };
+            $scope.isSortPriorityVisible = function() {
+              //show sort priority if column is sorted and there is at least one other sorted column
+              return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index){
+                  return angular.isNumber(element.sort.priority) && element !== $scope.col;
+                });
+            };
+            $scope.getSortDirectionAriaLabel = function(){
+              var col = $scope.col;
+              //Trying to recreate this sort of thing but it was getting messy having it in the template.
+              //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
+              var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
+              var label = sortDirectionText;
+
+              if ($scope.isSortPriorityVisible()) {
+                label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
+              }
+              return label;
+            };
+
+            $scope.grid = uiGridCtrl.grid;
+
+            $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
+
+            var initColClass = $scope.col.getColClass(false);
+            $elm.addClass(initColClass);
+
+            // Hide the menu by default
+            $scope.menuShown = false;
+
+            // Put asc and desc sort directions in scope
+            $scope.asc = uiGridConstants.ASC;
+            $scope.desc = uiGridConstants.DESC;
+
+            // Store a reference to menu element
+            var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
+
+            var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
+
+
+            // apply any headerCellClass
+            var classAdded;
+            var previousMouseX;
+
+            // filter watchers
+            var filterDeregisters = [];
+
+
+            /*
+             * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
+             * Once we have a down event, we need to work out whether we have a click, a drag, or a
+             * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
+             * we ignore it.  A hold would open the menu.
+             *
+             * So, on down event, we put in place handlers for move and up events, and a timer.  If the
+             * timer expires before we see a move or up, then we have a long press and hence a column menu open.
+             * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
+             * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
+             * will handle it.
+             *
+             * To deal with touch enabled devices that also have mice, we only create our handlers when
+             * we get the down event, and we create the corresponding handlers - if we're touchstart then
+             * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
+             *
+             * We also suppress the click action whilst this is happening - otherwise after the mouseup there
+             * will be a click event and that can cause the column menu to close
+             *
+             */
+
+            $scope.downFn = function( event ){
+              event.stopPropagation();
+
+              if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
+                event = event.originalEvent;
+              }
+
+              // Don't show the menu if it's not the left button
+              if (event.button && event.button !== 0) {
+                return;
+              }
+              previousMouseX = event.pageX;
+
+              $scope.mousedownStartTime = (new Date()).getTime();
+              $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
+
+              $scope.mousedownTimeout.then(function () {
+                if ( $scope.colMenu ) {
+                  uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
+                }
+              });
+
+              uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
+
+              $scope.offAllEvents();
+              if ( event.type === 'touchstart'){
+                $document.on('touchend', $scope.upFn);
+                $document.on('touchmove', $scope.moveFn);
+              } else if ( event.type === 'mousedown' ){
+                $document.on('mouseup', $scope.upFn);
+                $document.on('mousemove', $scope.moveFn);
+              }
+            };
+
+            $scope.upFn = function( event ){
+              event.stopPropagation();
+              $timeout.cancel($scope.mousedownTimeout);
+              $scope.offAllEvents();
+              $scope.onDownEvents(event.type);
+
+              var mousedownEndTime = (new Date()).getTime();
+              var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
+
+              if (mousedownTime > mousedownTimeout) {
+                // long click, handled above with mousedown
+              }
+              else {
+                // short click
+                if ( $scope.sortable ){
+                  $scope.handleClick(event);
+                }
+              }
+            };
+
+            $scope.moveFn = function( event ){
+              // Chrome is known to fire some bogus move events.
+              var changeValue = event.pageX - previousMouseX;
+              if ( changeValue === 0 ){ return; }
+
+              // we're a move, so do nothing and leave for column move (if enabled) to take over
+              $timeout.cancel($scope.mousedownTimeout);
+              $scope.offAllEvents();
+              $scope.onDownEvents(event.type);
+            };
+
+            $scope.clickFn = function ( event ){
+              event.stopPropagation();
+              $contentsElm.off('click', $scope.clickFn);
+            };
+
+
+            $scope.offAllEvents = function(){
+              $contentsElm.off('touchstart', $scope.downFn);
+              $contentsElm.off('mousedown', $scope.downFn);
+
+              $document.off('touchend', $scope.upFn);
+              $document.off('mouseup', $scope.upFn);
+
+              $document.off('touchmove', $scope.moveFn);
+              $document.off('mousemove', $scope.moveFn);
+
+              $contentsElm.off('click', $scope.clickFn);
+            };
+
+            $scope.onDownEvents = function( type ){
+              // If there is a previous event, then wait a while before
+              // activating the other mode - i.e. if the last event was a touch event then
+              // don't enable mouse events for a wee while (500ms or so)
+              // Avoids problems with devices that emulate mouse events when you have touch events
+
+              switch (type){
+                case 'touchmove':
+                case 'touchend':
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('touchstart', $scope.downFn);
+                  $timeout(function(){
+                    $contentsElm.on('mousedown', $scope.downFn);
+                  }, changeModeTimeout);
+                  break;
+                case 'mousemove':
+                case 'mouseup':
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('mousedown', $scope.downFn);
+                  $timeout(function(){
+                    $contentsElm.on('touchstart', $scope.downFn);
+                  }, changeModeTimeout);
+                  break;
+                default:
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('touchstart', $scope.downFn);
+                  $contentsElm.on('mousedown', $scope.downFn);
+              }
+            };
+
+
+            var updateHeaderOptions = function( grid ){
+              var contents = $elm;
+              if ( classAdded ){
+                contents.removeClass( classAdded );
+                classAdded = null;
+              }
+
+              if (angular.isFunction($scope.col.headerCellClass)) {
+                classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+              }
+              else {
+                classAdded = $scope.col.headerCellClass;
+              }
+              contents.addClass(classAdded);
+
+              $timeout(function (){
+                var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
+                $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
+              });
+
+              // Figure out whether this column is sortable or not
+              if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
+                $scope.sortable = true;
+              }
+              else {
+                $scope.sortable = false;
+              }
+
+              // Figure out whether this column is filterable or not
+              var oldFilterable = $scope.filterable;
+              if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
+                $scope.filterable = true;
+              }
+              else {
+                $scope.filterable = false;
+              }
+
+              if ( oldFilterable !== $scope.filterable){
+                if ( typeof($scope.col.updateFilters) !== 'undefined' ){
+                  $scope.col.updateFilters($scope.filterable);
+                }
+
+                // if column is filterable add a filter watcher
+                if ($scope.filterable) {
+                  $scope.col.filters.forEach( function(filter, i) {
+                    filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
+                      if (n !== o) {
+                        uiGridCtrl.grid.api.core.raise.filterChanged();
+                        uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+                        uiGridCtrl.grid.queueGridRefresh();
+                      }
+                    }));
+                  });
+                  $scope.$on('$destroy', function() {
+                    filterDeregisters.forEach( function(filterDeregister) {
+                      filterDeregister();
+                    });
+                  });
+                } else {
+                  filterDeregisters.forEach( function(filterDeregister) {
+                    filterDeregister();
+                  });
+                }
+
+              }
+
+              // figure out whether we support column menus
+              if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
+                      $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
+                $scope.colMenu = true;
+              } else {
+                $scope.colMenu = false;
+              }
+
+              /**
+              * @ngdoc property
+              * @name enableColumnMenu
+              * @propertyOf ui.grid.class:GridOptions.columnDef
+              * @description if column menus are enabled, controls the column menus for this specific
+              * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
+              * using this option. If gridOptions.enableColumnMenus === false then you get no column
+              * menus irrespective of the value of this option ).  Defaults to true.
+              *
+              */
+              /**
+              * @ngdoc property
+              * @name enableColumnMenus
+              * @propertyOf ui.grid.class:GridOptions.columnDef
+              * @description Override for column menus everywhere - if set to false then you get no
+              * column menus.  Defaults to true.
+              *
+              */
+
+              $scope.offAllEvents();
+
+              if ($scope.sortable || $scope.colMenu) {
+                $scope.onDownEvents();
+
+                $scope.$on('$destroy', function () {
+                  $scope.offAllEvents();
+                });
+              }
+            };
+
+/*
+            $scope.$watch('col', function (n, o) {
+              if (n !== o) {
+                // See if the column's internal class has changed
+                var newColClass = $scope.col.getColClass(false);
+                if (newColClass !== initColClass) {
+                  $elm.removeClass(initColClass);
+                  $elm.addClass(newColClass);
+                  initColClass = newColClass;
+                }
+              }
+            });
+*/
+            updateHeaderOptions();
+
+            // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+            var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
+
+            $scope.$on( '$destroy', dataChangeDereg );
+
+            $scope.handleClick = function(event) {
+              // If the shift key is being held down, add this column to the sort
+              var add = false;
+              if (event.shiftKey) {
+                add = true;
+              }
+
+              // Sort this column then rebuild the grid's rows
+              uiGridCtrl.grid.sortColumn($scope.col, add)
+                .then(function () {
+                  if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
+                  uiGridCtrl.grid.refresh();
+                });
+            };
+
+
+            $scope.toggleMenu = function(event) {
+              event.stopPropagation();
+
+              // If the menu is already showing...
+              if (uiGridCtrl.columnMenuScope.menuShown) {
+                // ... and we're the column the menu is on...
+                if (uiGridCtrl.columnMenuScope.col === $scope.col) {
+                  // ... hide it
+                  uiGridCtrl.columnMenuScope.hideMenu();
+                }
+                // ... and we're NOT the column the menu is on
+                else {
+                  // ... move the menu to our column
+                  uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
+                }
+              }
+              // If the menu is NOT showing
+              else {
+                // ... show it on our column
+                uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
+              }
+            };
+          }
+        };
+      }
+    };
+
+    return uiGridHeaderCell;
+  }]);
+
+})();
+
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
+    function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
+    var defaultTemplate = 'ui-grid/ui-grid-header';
+    var emptyTemplate = 'ui-grid/ui-grid-no-header';
+
+    return {
+      restrict: 'EA',
+      // templateUrl: 'ui-grid/ui-grid-header',
+      replace: true,
+      // priority: 1000,
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      scope: true,
+      compile: function($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            $scope.grid = uiGridCtrl.grid;
+            $scope.colContainer = containerCtrl.colContainer;
+
+            updateHeaderReferences();
+
+            var headerTemplate;
+            if (!$scope.grid.options.showHeader) {
+              headerTemplate = emptyTemplate;
+            }
+            else {
+              headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
+            }
+
+            gridUtil.getTemplate(headerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.replaceWith(newElm);
+
+                // And update $elm to be the new element
+                $elm = newElm;
+
+                updateHeaderReferences();
+
+                if (containerCtrl) {
+                  // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
+                  var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
+
+
+                  if (headerViewport) {
+                    containerCtrl.headerViewport = headerViewport;
+                    angular.element(headerViewport).on('scroll', scrollHandler);
+                    $scope.$on('$destroy', function () {
+                      angular.element(headerViewport).off('scroll', scrollHandler);
+                    });
+                  }
+                }
+
+                $scope.grid.queueRefresh();
+              });
+
+            function updateHeaderReferences() {
+              containerCtrl.header = containerCtrl.colContainer.header = $elm;
+
+              var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
+
+              if (headerCanvases.length > 0) {
+                containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
+              }
+              else {
+                containerCtrl.headerCanvas = null;
+              }
+            }
+
+            function scrollHandler(evt) {
+              if (uiGridCtrl.grid.isScrollingHorizontally) {
+                return;
+              }
+              var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
+              var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
+
+              var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
+              scrollEvent.newScrollLeft = newScrollLeft;
+              if ( horizScrollPercentage > -1 ){
+                scrollEvent.x = { percentage: horizScrollPercentage };
+              }
+
+              uiGridCtrl.grid.scrollContainers(null, scrollEvent);
+            }
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            // gridUtil.logDebug('ui-grid-header link');
+
+            var grid = uiGridCtrl.grid;
+
+            // Don't animate header cells
+            gridUtil.disableAnimations($elm);
+
+            function updateColumnWidths() {
+              // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
+              // already being populated correctly
+
+              var columnCache = containerCtrl.colContainer.visibleColumnCache;
+
+              // Build the CSS
+              // uiGridCtrl.grid.columns.forEach(function (column) {
+              var ret = '';
+              var canvasWidth = 0;
+              columnCache.forEach(function (column) {
+                ret = ret + column.getColClassDefinition();
+                canvasWidth += column.drawnWidth;
+              });
+
+              containerCtrl.colContainer.canvasWidth = canvasWidth;
+
+              // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
+              return ret;
+            }
+
+            containerCtrl.header = $elm;
+
+            var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
+            if (headerViewport) {
+              containerCtrl.headerViewport = headerViewport;
+            }
+
+            //todo: remove this if by injecting gridCtrl into unit tests
+            if (uiGridCtrl) {
+              uiGridCtrl.grid.registerStyleComputation({
+                priority: 15,
+                func: updateColumnWidths
+              });
+            }
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+
+(function(){
+
+angular.module('ui.grid')
+.service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
+  /**
+   *  @ngdoc service
+   *  @name ui.grid.gridMenuService
+   *
+   *  @description Methods for working with the grid menu
+   */
+
+  var service = {
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name initialize
+     * @description Sets up the gridMenu. Most importantly, sets our
+     * scope onto the grid object as grid.gridMenuScope, allowing us
+     * to operate when passed only the grid.  Second most importantly,
+     * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
+     * on the core api.
+     * @param {$scope} $scope the scope of this gridMenu
+     * @param {Grid} grid the grid to which this gridMenu is associated
+     */
+    initialize: function( $scope, grid ){
+      grid.gridMenuScope = $scope;
+      $scope.grid = grid;
+      $scope.registeredMenuItems = [];
+
+      // not certain this is needed, but would be bad to create a memory leak
+      $scope.$on('$destroy', function() {
+        if ( $scope.grid && $scope.grid.gridMenuScope ){
+          $scope.grid.gridMenuScope = null;
+        }
+        if ( $scope.grid ){
+          $scope.grid = null;
+        }
+        if ( $scope.registeredMenuItems ){
+          $scope.registeredMenuItems = null;
+        }
+      });
+
+      $scope.registeredMenuItems = [];
+
+      /**
+       * @ngdoc function
+       * @name addToGridMenu
+       * @methodOf ui.grid.core.api:PublicApi
+       * @description add items to the grid menu.  Used by features
+       * to add their menu items if they are enabled, can also be used by
+       * end users to add menu items.  This method has the advantage of allowing
+       * remove again, which can simplify management of which items are included
+       * in the menu when.  (Noting that in most cases the shown and active functions
+       * provide a better way to handle visibility of menu items)
+       * @param {Grid} grid the grid on which we are acting
+       * @param {array} items menu items in the format as described in the tutorial, with
+       * the added note that if you want to use remove you must also specify an `id` field,
+       * which is provided when you want to remove an item.  The id should be unique.
+       *
+       */
+      grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
+
+      /**
+       * @ngdoc function
+       * @name removeFromGridMenu
+       * @methodOf ui.grid.core.api:PublicApi
+       * @description Remove an item from the grid menu based on a provided id. Assumes
+       * that the id is unique, removes only the last instance of that id. Does nothing if
+       * the specified id is not found
+       * @param {Grid} grid the grid on which we are acting
+       * @param {string} id the id we'd like to remove from the menu
+       *
+       */
+      grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
+    },
+
+
+    /**
+     * @ngdoc function
+     * @name addToGridMenu
+     * @propertyOf ui.grid.gridMenuService
+     * @description add items to the grid menu.  Used by features
+     * to add their menu items if they are enabled, can also be used by
+     * end users to add menu items.  This method has the advantage of allowing
+     * remove again, which can simplify management of which items are included
+     * in the menu when.  (Noting that in most cases the shown and active functions
+     * provide a better way to handle visibility of menu items)
+     * @param {Grid} grid the grid on which we are acting
+     * @param {array} items menu items in the format as described in the tutorial, with
+     * the added note that if you want to use remove you must also specify an `id` field,
+     * which is provided when you want to remove an item.  The id should be unique.
+     *
+     */
+    addToGridMenu: function( grid, menuItems ) {
+      if ( !angular.isArray( menuItems ) ) {
+        gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
+      } else {
+        if ( grid.gridMenuScope ){
+          grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
+          grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
+        } else {
+          gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
+        }
+      }
+    },
+
+
+    /**
+     * @ngdoc function
+     * @name removeFromGridMenu
+     * @methodOf ui.grid.gridMenuService
+     * @description Remove an item from the grid menu based on a provided id.  Assumes
+     * that the id is unique, removes only the last instance of that id.  Does nothing if
+     * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
+     * then do nothing silently - the desired result is those menu items not be present and they
+     * aren't.
+     * @param {Grid} grid the grid on which we are acting
+     * @param {string} id the id we'd like to remove from the menu
+     *
+     */
+    removeFromGridMenu: function( grid, id ){
+      var foundIndex = -1;
+
+      if ( grid && grid.gridMenuScope ){
+        grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
+          if ( value.id === id ){
+            if (foundIndex > -1) {
+              gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
+            } else {
+
+              foundIndex = index;
+            }
+          }
+        });
+      }
+
+      if ( foundIndex > -1 ){
+        grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
+      }
+    },
+
+
+    /**
+     * @ngdoc array
+     * @name gridMenuCustomItems
+     * @propertyOf ui.grid.class:GridOptions
+     * @description (optional) An array of menu items that should be added to
+     * the gridMenu.  Follow the format documented in the tutorial for column
+     * menu customisation.  The context provided to the action function will
+     * include context.grid.  An alternative if working with dynamic menus is to use the
+     * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
+     * some of the management of items for you.
+     *
+     */
+    /**
+     * @ngdoc boolean
+     * @name gridMenuShowHideColumns
+     * @propertyOf ui.grid.class:GridOptions
+     * @description true by default, whether the grid menu should allow hide/show
+     * of columns
+     *
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name getMenuItems
+     * @description Decides the menu items to show in the menu.  This is a
+     * combination of:
+     *
+     * - the default menu items that are always included,
+     * - any menu items that have been provided through the addMenuItem api. These
+     *   are typically added by features within the grid
+     * - any menu items included in grid.options.gridMenuCustomItems.  These can be
+     *   changed dynamically, as they're always recalculated whenever we show the
+     *   menu
+     * @param {$scope} $scope the scope of this gridMenu, from which we can find all
+     * the information that we need
+     * @returns {array} an array of menu items that can be shown
+     */
+    getMenuItems: function( $scope ) {
+      var menuItems = [
+        // this is where we add any menu items we want to always include
+      ];
+
+      if ( $scope.grid.options.gridMenuCustomItems ){
+        if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
+          gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
+        } else {
+          menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
+        }
+      }
+
+      var clearFilters = [{
+        title: i18nService.getSafeText('gridMenu.clearAllFilters'),
+        action: function ($event) {
+          $scope.grid.clearAllFilters(undefined, true, undefined);
+        },
+        shown: function() {
+          return $scope.grid.options.enableFiltering;
+        },
+        order: 100
+      }];
+      menuItems = menuItems.concat( clearFilters );
+
+      menuItems = menuItems.concat( $scope.registeredMenuItems );
+
+      if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
+        menuItems = menuItems.concat( service.showHideColumns( $scope ) );
+      }
+
+      menuItems.sort(function(a, b){
+        return a.order - b.order;
+      });
+
+      return menuItems;
+    },
+
+
+    /**
+     * @ngdoc array
+     * @name gridMenuTitleFilter
+     * @propertyOf ui.grid.class:GridOptions
+     * @description (optional) A function that takes a title string
+     * (usually the col.displayName), and converts it into a display value.  The function
+     * must return either a string or a promise.
+     *
+     * Used for internationalization of the grid menu column names - for angular-translate
+     * you can pass $translate as the function, for i18nService you can pass getSafeText as the
+     * function
+     * @example
+     * <pre>
+     *   gridOptions = {
+     *     gridMenuTitleFilter: $translate
+     *   }
+     * </pre>
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name showHideColumns
+     * @description Adds two menu items for each of the columns in columnDefs.  One
+     * menu item for hide, one menu item for show.  Each is visible when appropriate
+     * (show when column is not visible, hide when column is visible).  Each toggles
+     * the visible property on the columnDef using toggleColumnVisibility
+     * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
+     */
+    showHideColumns: function( $scope ){
+      var showHideColumns = [];
+      if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
+        return showHideColumns;
+      }
+
+      // add header for columns
+      showHideColumns.push({
+        title: i18nService.getSafeText('gridMenu.columns'),
+        order: 300
+      });
+
+      $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
+
+      $scope.grid.options.columnDefs.forEach( function( colDef, index ){
+        if ( colDef.enableHiding !== false ){
+          // add hide menu item - shows an OK icon as we only show when column is already visible
+          var menuItem = {
+            icon: 'ui-grid-icon-ok',
+            action: function($event) {
+              $event.stopPropagation();
+              service.toggleColumnVisibility( this.context.gridCol );
+            },
+            shown: function() {
+              return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
+            },
+            context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
+            leaveOpen: true,
+            order: 301 + index * 2
+          };
+          service.setMenuItemTitle( menuItem, colDef, $scope.grid );
+          showHideColumns.push( menuItem );
+
+          // add show menu item - shows no icon as we only show when column is invisible
+          menuItem = {
+            icon: 'ui-grid-icon-cancel',
+            action: function($event) {
+              $event.stopPropagation();
+              service.toggleColumnVisibility( this.context.gridCol );
+            },
+            shown: function() {
+              return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
+            },
+            context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
+            leaveOpen: true,
+            order: 301 + index * 2 + 1
+          };
+          service.setMenuItemTitle( menuItem, colDef, $scope.grid );
+          showHideColumns.push( menuItem );
+        }
+      });
+      return showHideColumns;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name setMenuItemTitle
+     * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
+     * item if it returns a string, otherwise waiting for the promise to resolve or reject then
+     * putting the result into the title
+     * @param {object} menuItem the menuItem we want to put the title on
+     * @param {object} colDef the colDef from which we can get displayName, name or field
+     * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
+     *
+     */
+    setMenuItemTitle: function( menuItem, colDef, grid ){
+      var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
+
+      if ( typeof(title) === 'string' ){
+        menuItem.title = title;
+      } else if ( title.then ){
+        // must be a promise
+        menuItem.title = "";
+        title.then( function( successValue ) {
+          menuItem.title = successValue;
+        }, function( errorValue ) {
+          menuItem.title = errorValue;
+        });
+      } else {
+        gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
+        menuItem.title = 'badconfig';
+      }
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name toggleColumnVisibility
+     * @description Toggles the visibility of an individual column.  Expects to be
+     * provided a context that has on it a gridColumn, which is the column that
+     * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
+     * @param {GridCol} gridCol the column that we want to toggle
+     *
+     */
+    toggleColumnVisibility: function( gridCol ) {
+      gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
+
+      gridCol.grid.refresh();
+      gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+      gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
+    }
+  };
+
+  return service;
+}])
+
+
+
+.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
+function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
+
+  return {
+    priority: 0,
+    scope: true,
+    require: ['^uiGrid'],
+    templateUrl: 'ui-grid/ui-grid-menu-button',
+    replace: true,
+
+    link: function ($scope, $elm, $attrs, controllers) {
+      var uiGridCtrl = controllers[0];
+
+      // For the aria label
+      $scope.i18n = {
+        aria: i18nService.getSafeText('gridMenu.aria')
+      };
+
+      uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
+
+      $scope.shown = false;
+
+      $scope.toggleMenu = function () {
+        if ( $scope.shown ){
+          $scope.$broadcast('hide-menu');
+          $scope.shown = false;
+        } else {
+          $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
+          $scope.$broadcast('show-menu');
+          $scope.shown = true;
+        }
+      };
+
+      $scope.$on('menu-hidden', function() {
+        $scope.shown = false;
+        gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
+      });
+    }
+  };
+
+}]);
+
+})();
+
+(function(){
+
+/**
+ * @ngdoc directive
+ * @name ui.grid.directive:uiGridMenu
+ * @element style
+ * @restrict A
+ *
+ * @description
+ * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
+ *
+ * @example
+ <doc:example module="app">
+ <doc:source>
+ <script>
+ var app = angular.module('app', ['ui.grid']);
+
+ app.controller('MainCtrl', ['$scope', function ($scope) {
+
+ }]);
+ </script>
+
+ <div ng-controller="MainCtrl">
+   <div ui-grid-menu shown="true"  ></div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ </doc:scenario>
+ </doc:example>
+ */
+angular.module('ui.grid')
+
+.directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
+function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
+  var uiGridMenu = {
+    priority: 0,
+    scope: {
+      // shown: '&',
+      menuItems: '=',
+      autoHide: '=?'
+    },
+    require: '?^uiGrid',
+    templateUrl: 'ui-grid/uiGridMenu',
+    replace: false,
+    link: function ($scope, $elm, $attrs, uiGridCtrl) {
+      var gridMenuMaxHeight;
+
+      $scope.dynamicStyles = '';
+
+      if (uiGridCtrl) {
+        // magic number of 30 because the grid menu displays somewhat below
+        // the top of the grid. It is approximately 30px.
+        gridMenuMaxHeight = uiGridCtrl.grid.gridHeight - 30;
+        $scope.dynamicStyles = [
+          '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
+            'max-height: ' + gridMenuMaxHeight + 'px;',
+          '}'
+        ].join(' ');
+      }
+
+      $scope.i18n = {
+        close: i18nService.getSafeText('columnMenu.close')
+      };
+
+    // *** Show/Hide functions ******
+      $scope.showMenu = function(event, args) {
+        if ( !$scope.shown ){
+
+          /*
+           * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
+           * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
+           * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
+           * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
+           * on scroll events.
+           *
+           * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
+           * like they do, but angular has a default $animate provider that is just a stub, and that's what's
+           * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
+           * angular-translate.js, it's not using it.  You need to test animations in an external application.
+           */
+          $scope.shown = true;
+
+          $timeout( function() {
+            $scope.shownMid = true;
+            $scope.$emit('menu-shown');
+          });
+        } else if ( !$scope.shownMid ) {
+          // we're probably doing a hide then show, so we don't need to wait for ng-if
+          $scope.shownMid = true;
+          $scope.$emit('menu-shown');
+        }
+
+        var docEventType = 'click';
+        if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
+          docEventType = args.originalEvent.type;
+        }
+
+        // Turn off an existing document click handler
+        angular.element(document).off('click touchstart', applyHideMenu);
+        $elm.off('keyup', checkKeyUp);
+        $elm.off('keydown', checkKeyDown);
+
+        // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
+        $timeout(function() {
+          angular.element(document).on(docEventType, applyHideMenu);
+          $elm.on('keyup', checkKeyUp);
+          $elm.on('keydown', checkKeyDown);
+
+        });
+        //automatically set the focus to the first button element in the now open menu.
+        gridUtil.focus.bySelector($elm, 'button[type=button]', true);
+      };
+
+
+      $scope.hideMenu = function(event) {
+        if ( $scope.shown ){
+          /*
+           * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
+           * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
+           * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
+           *
+           * The user may have clicked on the menu again whilst
+           * we're waiting, so we check that the mid isn't shown before applying the ng-if.
+           */
+          $scope.shownMid = false;
+          $timeout( function() {
+            if ( !$scope.shownMid ){
+              $scope.shown = false;
+              $scope.$emit('menu-hidden');
+            }
+          }, 200);
+        }
+
+        angular.element(document).off('click touchstart', applyHideMenu);
+        $elm.off('keyup', checkKeyUp);
+        $elm.off('keydown', checkKeyDown);
+      };
+
+      $scope.$on('hide-menu', function (event, args) {
+        $scope.hideMenu(event, args);
+      });
+
+      $scope.$on('show-menu', function (event, args) {
+        $scope.showMenu(event, args);
+      });
+
+
+    // *** Auto hide when click elsewhere ******
+      var applyHideMenu = function(){
+        if ($scope.shown) {
+          $scope.$apply(function () {
+            $scope.hideMenu();
+          });
+        }
+      };
+
+      // close menu on ESC and keep tab cyclical
+      var checkKeyUp = function(event) {
+        if (event.keyCode === 27) {
+          $scope.hideMenu();
+        }
+      };
+
+      var checkKeyDown = function(event) {
+        var setFocus = function(elm) {
+          elm.focus();
+          event.preventDefault();
+          return false;
+        };
+        if (event.keyCode === 9) {
+          var firstMenuItem, lastMenuItem;
+          var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
+          if (menuItemButtons.length > 0) {
+            firstMenuItem = menuItemButtons[0];
+            lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
+            if (event.target === lastMenuItem && !event.shiftKey) {
+              setFocus(firstMenuItem);
+            } else if (event.target === firstMenuItem && event.shiftKey) {
+              setFocus(lastMenuItem);
+            }
+          }
+        }
+      };
+
+      if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
+        $scope.autoHide = true;
+      }
+
+      if ($scope.autoHide) {
+        angular.element($window).on('resize', applyHideMenu);
+      }
+
+      $scope.$on('$destroy', function () {
+        angular.element(document).off('click touchstart', applyHideMenu);
+      });
+
+
+      $scope.$on('$destroy', function() {
+        angular.element($window).off('resize', applyHideMenu);
+      });
+
+      if (uiGridCtrl) {
+       $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
+      }
+
+      $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
+    }
+  };
+
+  return uiGridMenu;
+}])
+
+.directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
+  var uiGridMenuItem = {
+    priority: 0,
+    scope: {
+      name: '=',
+      active: '=',
+      action: '=',
+      icon: '=',
+      shown: '=',
+      context: '=',
+      templateUrl: '=',
+      leaveOpen: '=',
+      screenReaderOnly: '='
+    },
+    require: ['?^uiGrid'],
+    templateUrl: 'ui-grid/uiGridMenuItem',
+    replace: false,
+    compile: function() {
+      return {
+        pre: function ($scope, $elm) {
+          if ($scope.templateUrl) {
+            gridUtil.getTemplate($scope.templateUrl)
+                .then(function (contents) {
+                  var template = angular.element(contents);
+
+                  var newElm = $compile(template)($scope);
+                  $elm.replaceWith(newElm);
+                });
+          }
+        },
+        post: function ($scope, $elm, $attrs, controllers) {
+          var uiGridCtrl = controllers[0];
+
+          // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
+          // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
+          //   throw new TypeError("$scope.shown is defined but not a function");
+          // }
+          if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
+            $scope.shown = function() { return true; };
+          }
+
+          $scope.itemShown = function () {
+            var context = {};
+            if ($scope.context) {
+              context.context = $scope.context;
+            }
+
+            if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
+              context.grid = uiGridCtrl.grid;
+            }
+
+            return $scope.shown.call(context);
+          };
+
+          $scope.itemAction = function($event,title) {
+            gridUtil.logDebug('itemAction');
+            $event.stopPropagation();
+
+            if (typeof($scope.action) === 'function') {
+              var context = {};
+
+              if ($scope.context) {
+                context.context = $scope.context;
+              }
+
+              // Add the grid to the function call context if the uiGrid controller is present
+              if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
+                context.grid = uiGridCtrl.grid;
+              }
+
+              $scope.action.call(context, $event, title);
+
+              if ( !$scope.leaveOpen ){
+                $scope.$emit('hide-menu');
+              } else {
+                /*
+                 * XXX: Fix after column refactor
+                 * Ideally the focus would remain on the item.
+                 * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
+                 */
+                gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
+              }
+            }
+          };
+
+          $scope.i18n = i18nService.get();
+        }
+      };
+    }
+  };
+
+  return uiGridMenuItem;
+}]);
+
+})();
+
+(function(){
+  'use strict';
+  /**
+   * @ngdoc overview
+   * @name ui.grid.directive:uiGridOneBind
+   * @summary A group of directives that provide a one time bind to a dom element.
+   * @description A group of directives that provide a one time bind to a dom element.
+   * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
+   * This is done to reduce the number of watchers on the dom.
+   * <br/>
+   * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
+   * <pre>
+        <div ng-init="imageName = 'myImageDir.jpg'">
+          <img ui-grid-one-bind-src="imageName"></img>
+        </div>
+     </pre>
+   * Will become:
+   * <pre>
+       <div ng-init="imageName = 'myImageDir.jpg'">
+         <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
+       </div>
+     </pre>
+     </br>
+     <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
+   * <pre>
+        <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
+     </pre>
+   * Will become:
+   * <pre>
+   <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
+     </pre>
+     </br>
+   * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
+   * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
+   *
+   */
+  //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
+  var oneBinders = angular.module('ui.grid');
+  angular.forEach([
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindSrc
+       * @memberof ui.grid.directive:uiGridOneBind
+       * @element img
+       * @restrict A
+       * @param {String} uiGridOneBindSrc The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @description One time binding for the src dom tag.
+       *
+       */
+      {tag: 'Src', method: 'attr'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindText
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindText The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @description One time binding for the text dom tag.
+       */
+      {tag: 'Text', method: 'text'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindHref
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindHref The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
+       */
+      {tag: 'Href', method: 'attr'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindClass
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindClass The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @param {Object} uiGridOneBindClass The object that you want to bind. At least one of the values in the object must be something other than null or undefined for the watcher to be removed.
+       *                                    this is to prevent the watcher from being removed before the scope is initialized.
+       * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
+       * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
+       */
+      {tag: 'Class', method: 'addClass'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindHtml
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindHtml The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
+       */
+      {tag: 'Html', method: 'html'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindAlt
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindAlt The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
+       * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
+       */
+      {tag: 'Alt', method: 'attr'},
+      /**
+       * @ngdoc directive
+       * @name ui.grid.directive:uiGridOneBindStyle
+       * @element div
+       * @restrict A
+       * @param {String} uiGridOneBindStyle The angular string you want to bind. Does not support interpolation. Don't use <code>{{

<TRUNCATED>

Mime
View raw message