qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From eal...@apache.org
Subject [05/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin
Date Wed, 19 Oct 2016 12:49:04 GMT
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
new file mode 100644
index 0000000..545ef70
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
@@ -0,0 +1,28540 @@
+/*!
+ * ui-grid - v3.2.9 - 2016-09-21
+ * Copyright (c) 2016 ; License: MIT 
+ */
+
+(function () {
+  'use strict';
+  angular.module('ui.grid.i18n', []);
+  angular.module('ui.grid', ['ui.grid.i18n']);
+})();
+(function () {
+  'use strict';
+
+  /**
+   * @ngdoc object
+   * @name ui.grid.service:uiGridConstants
+   * @description Constants for use across many grid features
+   *
+   */
+
+
+  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',
+    ENTITY_BINDING: '$$this',
+    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
+    },
+     /**
+     * @ngdoc object
+     * @name ASC
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
+     * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
+     * to configure the sorting direction of the column
+     */
+    ASC: 'asc',
+     /**
+     * @ngdoc object
+     * @name DESC
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
+     * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
+     * to configure the sorting direction of the column
+     */
+    DESC: 'desc',
+
+
+     /**
+     * @ngdoc object
+     * @name filter
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_filter columnDef.filter}
+     * to configure filtering on the column
+     *
+     * `SELECT` and `INPUT` are used with the `type` property of the filter, the rest are used to specify
+     * one of the built-in conditions.
+     *
+     * Available `condition` options are:
+     * - `uiGridConstants.filter.STARTS_WITH`
+     * - `uiGridConstants.filter.ENDS_WITH`
+     * - `uiGridConstants.filter.CONTAINS`
+     * - `uiGridConstants.filter.GREATER_THAN`
+     * - `uiGridConstants.filter.GREATER_THAN_OR_EQUAL`
+     * - `uiGridConstants.filter.LESS_THAN`
+     * - `uiGridConstants.filter.LESS_THAN_OR_EQUAL`
+     * - `uiGridConstants.filter.NOT_EQUAL`
+     * - `uiGridConstants.filter.STARTS_WITH`
+     *
+     *
+     * Available `type` options are:
+     * - `uiGridConstants.filter.SELECT` - use a dropdown box for the cell header filter field
+     * - `uiGridConstants.filter.INPUT` - use a text box for the cell header filter field
+     */
+    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'
+    },
+
+    /**
+     * @ngdoc object
+     * @name aggregationTypes
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_aggregationType columnDef.aggregationType}
+     * to specify the type of built-in aggregation the column should use.
+     *
+     * Available options are:
+     * - `uiGridConstants.aggregationTypes.sum` - add the values in this column to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.count` - count the number of rows to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.avg` - average the values in this column to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.min` - use the minimum value in this column as the aggregated value
+     * - `uiGridConstants.aggregationTypes.max` - use the maximum value in this column as the aggregated value
+     */
+    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: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
+
+    /**
+     * @ngdoc object
+     * @name scrollDirection
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Set on {@link ui.grid.class:Grid#properties_scrollDirection Grid.scrollDirection},
+     * to indicate the direction the grid is currently scrolling in
+     *
+     * Available options are:
+     * - `uiGridConstants.scrollDirection.UP` - set when the grid is scrolling up
+     * - `uiGridConstants.scrollDirection.DOWN` - set when the grid is scrolling down
+     * - `uiGridConstants.scrollDirection.LEFT` - set when the grid is scrolling left
+     * - `uiGridConstants.scrollDirection.RIGHT` - set when the grid is scrolling right
+     * - `uiGridConstants.scrollDirection.NONE` - set when the grid is not scrolling, this is the default
+     */
+    scrollDirection: {
+      UP: 'up',
+      DOWN: 'down',
+      LEFT: 'left',
+      RIGHT: 'right',
+      NONE: 'none'
+
+    },
+
+    /**
+     * @ngdoc object
+     * @name dataChange
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used with {@link ui.grid.core.api:PublicApi#methods_notifyDataChange PublicApi.notifyDataChange},
+     * {@link ui.grid.class:Grid#methods_callDataChangeCallbacks Grid.callDataChangeCallbacks},
+     * and {@link ui.grid.class:Grid#methods_registerDataChangeCallback Grid.registerDataChangeCallback}
+     * to specify the type of the event(s).
+     *
+     * Available options are:
+     * - `uiGridConstants.dataChange.ALL` - listeners fired on any of these events, fires listeners on all events.
+     * - `uiGridConstants.dataChange.EDIT` - fired when the data in a cell is edited
+     * - `uiGridConstants.dataChange.ROW` - fired when a row is added or removed
+     * - `uiGridConstants.dataChange.COLUMN` - fired when the column definitions are modified
+     * - `uiGridConstants.dataChange.OPTIONS` - fired when the grid options are modified
+     */
+    dataChange: {
+      ALL: 'all',
+      EDIT: 'edit',
+      ROW: 'row',
+      COLUMN: 'column',
+      OPTIONS: 'options'
+    },
+
+    /**
+     * @ngdoc object
+     * @name scrollbars
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used with {@link ui.grid.class:GridOptions#properties_enableHorizontalScrollbar GridOptions.enableHorizontalScrollbar}
+     * and {@link ui.grid.class:GridOptions#properties_enableVerticalScrollbar GridOptions.enableVerticalScrollbar}
+     * to specify the scrollbar policy for that direction.
+     *
+     * Available options are:
+     * - `uiGridConstants.scrollbars.NEVER` - never show scrollbars in this direction
+     * - `uiGridConstants.scrollbars.ALWAYS` - always show scrollbars in this direction
+     */
+
+    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) {
+
+      $scope.dynamicStyles = '';
+
+      var setupHeightStyle = function(gridHeight) {
+        //menu appears under header row, so substract that height from it's total
+        // additional 20px for general padding
+        var gridMenuMaxHeight = gridHeight - uiGridCtrl.grid.headerHeight - 20;
+        $scope.dynamicStyles = [
+          '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
+          'max-height: ' + gridMenuMaxHeight + 'px;',
+          '}'
+        ].join(' ');
+      };
+
+      if (uiGridCtrl) {
+        setupHeightStyle(uiGridCtrl.grid.gridHeight);
+        uiGridCtrl.grid.api.core.on.gridDimensionChanged($scope, function(oldGridHeight, oldGridWidth, newGridHeight, newGridWidth) {
+          setupHeightStyle(newGridHeight);
+		});
+      }
+
+      $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) {
+            $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;
+              }
+
+   

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message