Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / raptor / uigrid / ui-grid.js
1 /*!
2  * ui-grid - v3.0.7 - 2015-10-06
3  * Copyright (c) 2015 ; License: MIT 
4  */
5
6 (function () {
7   'use strict';
8   angular.module('ui.grid.i18n', []);
9   angular.module('ui.grid', ['ui.grid.i18n']);
10 })();
11 (function () {
12   'use strict';
13   angular.module('ui.grid').constant('uiGridConstants', {
14     LOG_DEBUG_MESSAGES: true,
15     LOG_WARN_MESSAGES: true,
16     LOG_ERROR_MESSAGES: true,
17     CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
18     COL_FIELD: /COL_FIELD/g,
19     MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
20     TOOLTIP: /title=\"TOOLTIP\"/g,
21     DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
22     TEMPLATE_REGEXP: /<.+>/,
23     FUNC_REGEXP: /(\([^)]*\))?$/,
24     DOT_REGEXP: /\./g,
25     APOS_REGEXP: /'/g,
26     BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
27     COL_CLASS_PREFIX: 'ui-grid-col',
28     events: {
29       GRID_SCROLL: 'uiGridScroll',
30       COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
31       ITEM_DRAGGING: 'uiGridItemDragStart', // For any item being dragged
32       COLUMN_HEADER_CLICK: 'uiGridColumnHeaderClick'
33     },
34     // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
35     keymap: {
36       TAB: 9,
37       STRG: 17,
38       CAPSLOCK: 20,
39       CTRL: 17,
40       CTRLRIGHT: 18,
41       CTRLR: 18,
42       SHIFT: 16,
43       RETURN: 13,
44       ENTER: 13,
45       BACKSPACE: 8,
46       BCKSP: 8,
47       ALT: 18,
48       ALTR: 17,
49       ALTRIGHT: 17,
50       SPACE: 32,
51       WIN: 91,
52       MAC: 91,
53       FN: null,
54       PG_UP: 33,
55       PG_DOWN: 34,
56       UP: 38,
57       DOWN: 40,
58       LEFT: 37,
59       RIGHT: 39,
60       ESC: 27,
61       DEL: 46,
62       F1: 112,
63       F2: 113,
64       F3: 114,
65       F4: 115,
66       F5: 116,
67       F6: 117,
68       F7: 118,
69       F8: 119,
70       F9: 120,
71       F10: 121,
72       F11: 122,
73       F12: 123
74     },
75     ASC: 'asc',
76     DESC: 'desc',
77     filter: {
78       STARTS_WITH: 2,
79       ENDS_WITH: 4,
80       EXACT: 8,
81       CONTAINS: 16,
82       GREATER_THAN: 32,
83       GREATER_THAN_OR_EQUAL: 64,
84       LESS_THAN: 128,
85       LESS_THAN_OR_EQUAL: 256,
86       NOT_EQUAL: 512,
87       SELECT: 'select',
88       INPUT: 'input'
89     },
90
91     aggregationTypes: {
92       sum: 2,
93       count: 4,
94       avg: 8,
95       min: 16,
96       max: 32
97     },
98
99     // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
100     CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
101
102     scrollDirection: {
103       UP: 'up',
104       DOWN: 'down',
105       LEFT: 'left',
106       RIGHT: 'right',
107       NONE: 'none'
108
109     },
110
111     dataChange: {
112       ALL: 'all',
113       EDIT: 'edit',
114       ROW: 'row',
115       COLUMN: 'column',
116       OPTIONS: 'options'
117     },
118     scrollbars: {
119       NEVER: 0,
120       ALWAYS: 1
121       //WHEN_NEEDED: 2
122     }
123   });
124
125 })();
126 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
127   var uiGridCell = {
128     priority: 0,
129     scope: false,
130     require: '?^uiGrid',
131     compile: function() {
132       return {
133         pre: function($scope, $elm, $attrs, uiGridCtrl) {
134           function compileTemplate() {
135             var compiledElementFn = $scope.col.compiledElementFn;
136
137             compiledElementFn($scope, function(clonedElement, scope) {
138               $elm.append(clonedElement);
139             });
140           }
141
142           // If the grid controller is present, use it to get the compiled cell template function
143           if (uiGridCtrl && $scope.col.compiledElementFn) {
144              compileTemplate();
145           }
146           // No controller, compile the element manually (for unit tests)
147           else {
148             if ( uiGridCtrl && !$scope.col.compiledElementFn ){
149               // gridUtil.logError('Render has been called before precompile.  Please log a ui-grid issue');  
150
151               $scope.col.getCompiledElementFn()
152                 .then(function (compiledElementFn) {
153                   compiledElementFn($scope, function(clonedElement, scope) {
154                     $elm.append(clonedElement);
155                   });
156                 });
157             }
158             else {
159               var html = $scope.col.cellTemplate
160                 .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
161                 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
162
163               var cellElement = $compile(html)($scope);
164               $elm.append(cellElement);
165             }
166           }
167         },
168         post: function($scope, $elm, $attrs, uiGridCtrl) {
169           var initColClass = $scope.col.getColClass(false);
170           $elm.addClass(initColClass);
171
172           var classAdded;
173           var updateClass = function( grid ){
174             var contents = $elm;
175             if ( classAdded ){
176               contents.removeClass( classAdded );
177               classAdded = null;
178             }
179
180             if (angular.isFunction($scope.col.cellClass)) {
181               classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
182             }
183             else {
184               classAdded = $scope.col.cellClass;
185             }
186             contents.addClass(classAdded);
187           };
188
189           if ($scope.col.cellClass) {
190             updateClass();
191           }
192           
193           // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
194           var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
195           
196           // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
197           // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
198           var cellChangeFunction = function( n, o ){
199             if ( n !== o ) {
200               if ( classAdded || $scope.col.cellClass ){
201                 updateClass();
202               }
203
204               // See if the column's internal class has changed
205               var newColClass = $scope.col.getColClass(false);
206               if (newColClass !== initColClass) {
207                 $elm.removeClass(initColClass);
208                 $elm.addClass(newColClass);
209                 initColClass = newColClass;
210               }
211             }
212           };
213
214           // TODO(c0bra): Turn this into a deep array watch
215 /*        shouldn't be needed any more given track by col.name
216           var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
217 */
218           var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
219           
220           
221           var deregisterFunction = function() {
222             dataChangeDereg();
223 //            colWatchDereg();
224             rowWatchDereg(); 
225           };
226           
227           $scope.$on( '$destroy', deregisterFunction );
228           $elm.on( '$destroy', deregisterFunction );
229         }
230       };
231     }
232   };
233
234   return uiGridCell;
235 }]);
236
237
238 (function(){
239
240 angular.module('ui.grid')
241 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
242 function ( i18nService, uiGridConstants, gridUtil ) {
243 /**
244  *  @ngdoc service
245  *  @name ui.grid.service:uiGridColumnMenuService
246  *
247  *  @description Services for working with column menus, factored out
248  *  to make the code easier to understand
249  */
250
251   var service = {
252     /**
253      * @ngdoc method
254      * @methodOf ui.grid.service:uiGridColumnMenuService
255      * @name initialize
256      * @description  Sets defaults, puts a reference to the $scope on
257      * the uiGridController
258      * @param {$scope} $scope the $scope from the uiGridColumnMenu
259      * @param {controller} uiGridCtrl the uiGridController for the grid
260      * we're on
261      *
262      */
263     initialize: function( $scope, uiGridCtrl ){
264       $scope.grid = uiGridCtrl.grid;
265
266       // Store a reference to this link/controller in the main uiGrid controller
267       // to allow showMenu later
268       uiGridCtrl.columnMenuScope = $scope;
269
270       // Save whether we're shown or not so the columns can check
271       $scope.menuShown = false;
272     },
273
274
275     /**
276      * @ngdoc method
277      * @methodOf ui.grid.service:uiGridColumnMenuService
278      * @name setColMenuItemWatch
279      * @description  Setup a watch on $scope.col.menuItems, and update
280      * menuItems based on this.  $scope.col needs to be set by the column
281      * before calling the menu.
282      * @param {$scope} $scope the $scope from the uiGridColumnMenu
283      * @param {controller} uiGridCtrl the uiGridController for the grid
284      * we're on
285      *
286      */
287     setColMenuItemWatch: function ( $scope ){
288       var deregFunction = $scope.$watch('col.menuItems', function (n, o) {
289         if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
290           n.forEach(function (item) {
291             if (typeof(item.context) === 'undefined' || !item.context) {
292               item.context = {};
293             }
294             item.context.col = $scope.col;
295           });
296
297           $scope.menuItems = $scope.defaultMenuItems.concat(n);
298         }
299         else {
300           $scope.menuItems = $scope.defaultMenuItems;
301         }
302       });
303
304       $scope.$on( '$destroy', deregFunction );
305     },
306
307
308     /**
309      * @ngdoc boolean
310      * @name enableSorting
311      * @propertyOf ui.grid.class:GridOptions.columnDef
312      * @description (optional) True by default. When enabled, this setting adds sort
313      * widgets to the column header, allowing sorting of the data in the individual column.
314      */
315     /**
316      * @ngdoc method
317      * @methodOf ui.grid.service:uiGridColumnMenuService
318      * @name sortable
319      * @description  determines whether this column is sortable
320      * @param {$scope} $scope the $scope from the uiGridColumnMenu
321      *
322      */
323     sortable: function( $scope ) {
324       if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
325         return true;
326       }
327       else {
328         return false;
329       }
330     },
331
332     /**
333      * @ngdoc method
334      * @methodOf ui.grid.service:uiGridColumnMenuService
335      * @name isActiveSort
336      * @description  determines whether the requested sort direction is current active, to
337      * allow highlighting in the menu
338      * @param {$scope} $scope the $scope from the uiGridColumnMenu
339      * @param {string} direction the direction that we'd have selected for us to be active
340      *
341      */
342     isActiveSort: function( $scope, direction ){
343       return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
344               typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
345
346     },
347
348     /**
349      * @ngdoc method
350      * @methodOf ui.grid.service:uiGridColumnMenuService
351      * @name suppressRemoveSort
352      * @description  determines whether we should suppress the removeSort option
353      * @param {$scope} $scope the $scope from the uiGridColumnMenu
354      *
355      */
356     suppressRemoveSort: function( $scope ) {
357       if ($scope.col && $scope.col.suppressRemoveSort) {
358         return true;
359       }
360       else {
361         return false;
362       }
363     },
364
365
366     /**
367      * @ngdoc boolean
368      * @name enableHiding
369      * @propertyOf ui.grid.class:GridOptions.columnDef
370      * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
371      * using the column menu or the grid menu.
372      */
373     /**
374      * @ngdoc method
375      * @methodOf ui.grid.service:uiGridColumnMenuService
376      * @name hideable
377      * @description  determines whether a column can be hidden, by checking the enableHiding columnDef option
378      * @param {$scope} $scope the $scope from the uiGridColumnMenu
379      *
380      */
381     hideable: function( $scope ) {
382       if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
383         return false;
384       }
385       else {
386         return true;
387       }
388     },
389
390
391     /**
392      * @ngdoc method
393      * @methodOf ui.grid.service:uiGridColumnMenuService
394      * @name getDefaultMenuItems
395      * @description  returns the default menu items for a column menu
396      * @param {$scope} $scope the $scope from the uiGridColumnMenu
397      *
398      */
399     getDefaultMenuItems: function( $scope ){
400       return [
401         {
402           title: i18nService.getSafeText('sort.ascending'),
403           icon: 'ui-grid-icon-sort-alt-up',
404           action: function($event) {
405             $event.stopPropagation();
406             $scope.sortColumn($event, uiGridConstants.ASC);
407           },
408           shown: function () {
409             return service.sortable( $scope );
410           },
411           active: function() {
412             return service.isActiveSort( $scope, uiGridConstants.ASC);
413           }
414         },
415         {
416           title: i18nService.getSafeText('sort.descending'),
417           icon: 'ui-grid-icon-sort-alt-down',
418           action: function($event) {
419             $event.stopPropagation();
420             $scope.sortColumn($event, uiGridConstants.DESC);
421           },
422           shown: function() {
423             return service.sortable( $scope );
424           },
425           active: function() {
426             return service.isActiveSort( $scope, uiGridConstants.DESC);
427           }
428         },
429         {
430           title: i18nService.getSafeText('sort.remove'),
431           icon: 'ui-grid-icon-cancel',
432           action: function ($event) {
433             $event.stopPropagation();
434             $scope.unsortColumn();
435           },
436           shown: function() {
437             return service.sortable( $scope ) &&
438                    typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
439                    typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
440                   !service.suppressRemoveSort( $scope );
441           }
442         },
443         {
444           title: i18nService.getSafeText('column.hide'),
445           icon: 'ui-grid-icon-cancel',
446           shown: function() {
447             return service.hideable( $scope );
448           },
449           action: function ($event) {
450             $event.stopPropagation();
451             $scope.hideColumn();
452           }
453         },
454         {
455           title: i18nService.getSafeText('columnMenu.close'),
456           screenReaderOnly: true,
457           shown: function(){
458             return true;
459           },
460           action: function($event){
461             $event.stopPropagation();
462           }
463         }
464       ];
465     },
466
467
468     /**
469      * @ngdoc method
470      * @methodOf ui.grid.service:uiGridColumnMenuService
471      * @name getColumnElementPosition
472      * @description  gets the position information needed to place the column
473      * menu below the column header
474      * @param {$scope} $scope the $scope from the uiGridColumnMenu
475      * @param {GridCol} column the column we want to position below
476      * @param {element} $columnElement the column element we want to position below
477      * @returns {hash} containing left, top, offset, height, width
478      *
479      */
480     getColumnElementPosition: function( $scope, column, $columnElement ){
481       var positionData = {};
482       positionData.left = $columnElement[0].offsetLeft;
483       positionData.top = $columnElement[0].offsetTop;
484       positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
485
486       // Get the grid scrollLeft
487       positionData.offset = 0;
488       if (column.grid.options.offsetLeft) {
489         positionData.offset = column.grid.options.offsetLeft;
490       }
491
492       positionData.height = gridUtil.elementHeight($columnElement, true);
493       positionData.width = gridUtil.elementWidth($columnElement, true);
494
495       return positionData;
496     },
497
498
499     /**
500      * @ngdoc method
501      * @methodOf ui.grid.service:uiGridColumnMenuService
502      * @name repositionMenu
503      * @description  Reposition the menu below the new column.  If the menu has no child nodes
504      * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
505      * later to fix it
506      * @param {$scope} $scope the $scope from the uiGridColumnMenu
507      * @param {GridCol} column the column we want to position below
508      * @param {hash} positionData a hash containing left, top, offset, height, width
509      * @param {element} $elm the column menu element that we want to reposition
510      * @param {element} $columnElement the column element that we want to reposition underneath
511      *
512      */
513     repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
514       var menu = $elm[0].querySelectorAll('.ui-grid-menu');
515       var containerId = column.renderContainer ? column.renderContainer : 'body';
516       var renderContainer = column.grid.renderContainers[containerId];
517
518       // It's possible that the render container of the column we're attaching to is
519       // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
520       // between the render container and the grid
521       var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
522       var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
523
524       var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
525
526       // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
527       var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
528       var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
529
530       if ( menu.length !== 0 ){
531         var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
532         if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
533           myWidth = gridUtil.elementWidth(menu, true);
534           $scope.lastMenuWidth = myWidth;
535           column.lastMenuWidth = myWidth;
536
537           // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
538           // Get the column menu right padding
539           paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
540           $scope.lastMenuPaddingRight = paddingRight;
541           column.lastMenuPaddingRight = paddingRight;
542         }
543       }
544
545       var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
546       if (left < positionData.offset){
547         left = positionData.offset;
548       }
549
550       $elm.css('left', left + 'px');
551       $elm.css('top', (positionData.top + positionData.height) + 'px');
552     }
553
554   };
555
556   return service;
557 }])
558
559
560 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
561 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
562 /**
563  * @ngdoc directive
564  * @name ui.grid.directive:uiGridColumnMenu
565  * @description  Provides the column menu framework, leverages uiGridMenu underneath
566  *
567  */
568
569   var uiGridColumnMenu = {
570     priority: 0,
571     scope: true,
572     require: '^uiGrid',
573     templateUrl: 'ui-grid/uiGridColumnMenu',
574     replace: true,
575     link: function ($scope, $elm, $attrs, uiGridCtrl) {
576       var self = this;
577
578       uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
579
580       $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
581
582       // Set the menu items for use with the column menu. The user can later add additional items via the watch
583       $scope.menuItems = $scope.defaultMenuItems;
584       uiGridColumnMenuService.setColMenuItemWatch( $scope );
585
586
587       /**
588        * @ngdoc method
589        * @methodOf ui.grid.directive:uiGridColumnMenu
590        * @name showMenu
591        * @description Shows the column menu.  If the menu is already displayed it
592        * calls the menu to ask it to hide (it will animate), then it repositions the menu
593        * to the right place whilst hidden (it will make an assumption on menu width),
594        * then it asks the menu to show (it will animate), then it repositions the menu again
595        * once we can calculate it's size.
596        * @param {GridCol} column the column we want to position below
597        * @param {element} $columnElement the column element we want to position below
598        */
599       $scope.showMenu = function(column, $columnElement, event) {
600         // Swap to this column
601         $scope.col = column;
602
603         // Get the position information for the column element
604         var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
605
606         if ($scope.menuShown) {
607           // we want to hide, then reposition, then show, but we want to wait for animations
608           // we set a variable, and then rely on the menu-hidden event to call the reposition and show
609           $scope.colElement = $columnElement;
610           $scope.colElementPosition = colElementPosition;
611           $scope.hideThenShow = true;
612
613           $scope.$broadcast('hide-menu', { originalEvent: event });
614         } else {
615           self.shown = $scope.menuShown = true;
616           uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
617
618           $scope.colElement = $columnElement;
619           $scope.colElementPosition = colElementPosition;
620           $scope.$broadcast('show-menu', { originalEvent: event });
621         }
622       };
623
624
625       /**
626        * @ngdoc method
627        * @methodOf ui.grid.directive:uiGridColumnMenu
628        * @name hideMenu
629        * @description Hides the column menu.
630        * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
631        * from the menu itself - in which case don't broadcast again as we'll get
632        * an infinite loop
633        */
634       $scope.hideMenu = function( broadcastTrigger ) {
635         $scope.menuShown = false;
636         if ( !broadcastTrigger ){
637           $scope.$broadcast('hide-menu');
638         }
639       };
640
641
642       $scope.$on('menu-hidden', function() {
643         if ( $scope.hideThenShow ){
644           delete $scope.hideThenShow;
645
646           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
647           $scope.$broadcast('show-menu');
648
649           $scope.menuShown = true;
650         } else {
651           $scope.hideMenu( true );
652
653           if ($scope.col) {
654             //Focus on the menu button
655             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
656           }
657         }
658       });
659
660       $scope.$on('menu-shown', function() {
661         $timeout( function() {
662           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
663           delete $scope.colElementPosition;
664           delete $scope.columnElement;
665         }, 200);
666       });
667
668
669       /* Column methods */
670       $scope.sortColumn = function (event, dir) {
671         event.stopPropagation();
672
673         $scope.grid.sortColumn($scope.col, dir, true)
674           .then(function () {
675             $scope.grid.refresh();
676             $scope.hideMenu();
677           });
678       };
679
680       $scope.unsortColumn = function () {
681         $scope.col.unsort();
682
683         $scope.grid.refresh();
684         $scope.hideMenu();
685       };
686
687       //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
688       var setFocusOnHideColumn = function(){
689         $timeout(function(){
690           // Get the UID of the first
691           var focusToGridMenu = function(){
692             return gridUtil.focus.byId('grid-menu', $scope.grid);
693           };
694
695           var thisIndex;
696           $scope.grid.columns.some(function(element, index){
697             if (angular.equals(element, $scope.col)) {
698               thisIndex = index;
699               return true;
700             }
701           });
702
703           var previousVisibleCol;
704           // Try and find the next lower or nearest column to focus on
705           $scope.grid.columns.some(function(element, index){
706             if (!element.visible){
707               return false;
708             } // This columns index is below the current column index
709             else if ( index < thisIndex){
710               previousVisibleCol = element;
711             } // This elements index is above this column index and we haven't found one that is lower
712             else if ( index > thisIndex && !previousVisibleCol) {
713               // This is the next best thing
714               previousVisibleCol = element;
715               // We've found one so use it.
716               return true;
717             } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
718             else if (index > thisIndex && previousVisibleCol) {
719               // We are done.
720               return true;
721             }
722           });
723           // If found then focus on it
724           if (previousVisibleCol){
725             var colClass = previousVisibleCol.getColClass();
726             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
727               if (reason !== 'canceled'){ // If this is canceled then don't perform the action
728                 //The fallback action is to focus on the grid menu
729                 return focusToGridMenu();
730               }
731             });
732           } else {
733             // Fallback action to focus on the grid menu
734             focusToGridMenu();
735           }
736         });
737       };
738
739       $scope.hideColumn = function () {
740         $scope.col.colDef.visible = false;
741         $scope.col.visible = false;
742
743         $scope.grid.queueGridRefresh();
744         $scope.hideMenu();
745         $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
746         $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
747
748         // We are hiding so the default action of focusing on the button that opened this menu will fail.
749         setFocusOnHideColumn();
750       };
751     },
752
753
754
755     controller: ['$scope', function ($scope) {
756       var self = this;
757
758       $scope.$watch('menuItems', function (n, o) {
759         self.menuItems = n;
760       });
761     }]
762   };
763
764   return uiGridColumnMenu;
765
766 }]);
767
768 })();
769
770 (function(){
771   'use strict';
772
773   angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
774
775     return {
776       compile: function() {
777         return {
778           pre: function ($scope, $elm, $attrs, controllers) {
779             $scope.col.updateFilters = function( filterable ){
780               $elm.children().remove();
781               if ( filterable ){
782                 var template = $scope.col.filterHeaderTemplate;
783
784                 $elm.append($compile(template)($scope));
785               }
786             };
787
788             $scope.$on( '$destroy', function() {
789               delete $scope.col.updateFilters;
790             });
791           },
792           post: function ($scope, $elm, $attrs, controllers){
793             $scope.aria = i18nService.getSafeText('headerCell.aria');
794             $scope.removeFilter = function(colFilter, index){
795               colFilter.term = null;
796               //Set the focus to the filter input after the action disables the button
797               gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
798             };
799           }
800         };
801       }
802     };
803   }]);
804 })();
805
806 (function () {
807   'use strict';
808
809   angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
810   function ($timeout, gridUtil, uiGridConstants, $compile) {
811     var uiGridFooterCell = {
812       priority: 0,
813       scope: {
814         col: '=',
815         row: '=',
816         renderIndex: '='
817       },
818       replace: true,
819       require: '^uiGrid',
820       compile: function compile(tElement, tAttrs, transclude) {
821         return {
822           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
823             var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
824             $elm.append(cellFooter);
825           },
826           post: function ($scope, $elm, $attrs, uiGridCtrl) {
827             //$elm.addClass($scope.col.getColClass(false));
828             $scope.grid = uiGridCtrl.grid;
829
830             var initColClass = $scope.col.getColClass(false);
831             $elm.addClass(initColClass);
832
833             // apply any footerCellClass
834             var classAdded;
835             var updateClass = function( grid ){
836               var contents = $elm;
837               if ( classAdded ){
838                 contents.removeClass( classAdded );
839                 classAdded = null;
840               }
841   
842               if (angular.isFunction($scope.col.footerCellClass)) {
843                 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
844               }
845               else {
846                 classAdded = $scope.col.footerCellClass;
847               }
848               contents.addClass(classAdded);
849             };
850   
851             if ($scope.col.footerCellClass) {
852               updateClass();
853             }
854
855             $scope.col.updateAggregationValue();
856
857             // Watch for column changes so we can alter the col cell class properly
858 /* shouldn't be needed any more, given track by col.name
859             $scope.$watch('col', function (n, o) {
860               if (n !== o) {
861                 // See if the column's internal class has changed
862                 var newColClass = $scope.col.getColClass(false);
863                 if (newColClass !== initColClass) {
864                   $elm.removeClass(initColClass);
865                   $elm.addClass(newColClass);
866                   initColClass = newColClass;
867                 }
868               }
869             });
870 */
871
872
873             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
874             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
875             // listen for visible rows change and update aggregation values
876             $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
877             $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
878             $scope.$on( '$destroy', dataChangeDereg );
879           }
880         };
881       }
882     };
883
884     return uiGridFooterCell;
885   }]);
886
887 })();
888
889 (function () {
890   'use strict';
891
892   angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
893
894     return {
895       restrict: 'EA',
896       replace: true,
897       // priority: 1000,
898       require: ['^uiGrid', '^uiGridRenderContainer'],
899       scope: true,
900       compile: function ($elm, $attrs) {
901         return {
902           pre: function ($scope, $elm, $attrs, controllers) {
903             var uiGridCtrl = controllers[0];
904             var containerCtrl = controllers[1];
905
906             $scope.grid = uiGridCtrl.grid;
907             $scope.colContainer = containerCtrl.colContainer;
908
909             containerCtrl.footer = $elm;
910
911             var footerTemplate = $scope.grid.options.footerTemplate;
912             gridUtil.getTemplate(footerTemplate)
913               .then(function (contents) {
914                 var template = angular.element(contents);
915
916                 var newElm = $compile(template)($scope);
917                 $elm.append(newElm);
918
919                 if (containerCtrl) {
920                   // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
921                   var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
922
923                   if (footerViewport) {
924                     containerCtrl.footerViewport = footerViewport;
925                   }
926                 }
927               });
928           },
929
930           post: function ($scope, $elm, $attrs, controllers) {
931             var uiGridCtrl = controllers[0];
932             var containerCtrl = controllers[1];
933
934             // gridUtil.logDebug('ui-grid-footer link');
935
936             var grid = uiGridCtrl.grid;
937
938             // Don't animate footer cells
939             gridUtil.disableAnimations($elm);
940
941             containerCtrl.footer = $elm;
942
943             var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
944             if (footerViewport) {
945               containerCtrl.footerViewport = footerViewport;
946             }
947           }
948         };
949       }
950     };
951   }]);
952
953 })();
954 (function () {
955   'use strict';
956
957   angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
958
959     return {
960       restrict: 'EA',
961       replace: true,
962       // priority: 1000,
963       require: '^uiGrid',
964       scope: true,
965       compile: function ($elm, $attrs) {
966         return {
967           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
968
969             $scope.grid = uiGridCtrl.grid;
970
971
972
973             var footerTemplate = $scope.grid.options.gridFooterTemplate;
974             gridUtil.getTemplate(footerTemplate)
975               .then(function (contents) {
976                 var template = angular.element(contents);
977
978                 var newElm = $compile(template)($scope);
979                 $elm.append(newElm);
980               });
981           },
982
983           post: function ($scope, $elm, $attrs, controllers) {
984
985           }
986         };
987       }
988     };
989   }]);
990
991 })();
992 (function(){
993   'use strict';
994
995   angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
996     var defaultTemplate = 'ui-grid/ui-grid-group-panel';
997
998     return {
999       restrict: 'EA',
1000       replace: true,
1001       require: '?^uiGrid',
1002       scope: false,
1003       compile: function($elm, $attrs) {
1004         return {
1005           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1006             var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
1007
1008              gridUtil.getTemplate(groupPanelTemplate)
1009               .then(function (contents) {
1010                 var template = angular.element(contents);
1011                 
1012                 var newElm = $compile(template)($scope);
1013                 $elm.append(newElm);
1014               });
1015           },
1016
1017           post: function ($scope, $elm, $attrs, uiGridCtrl) {
1018             $elm.bind('$destroy', function() {
1019               // scrollUnbinder();
1020             });
1021           }
1022         };
1023       }
1024     };
1025   }]);
1026
1027 })();
1028 (function(){
1029   'use strict';
1030
1031   angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
1032   function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
1033     // Do stuff after mouse has been down this many ms on the header cell
1034     var mousedownTimeout = 500;
1035     var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
1036
1037     var uiGridHeaderCell = {
1038       priority: 0,
1039       scope: {
1040         col: '=',
1041         row: '=',
1042         renderIndex: '='
1043       },
1044       require: ['^uiGrid', '^uiGridRenderContainer'],
1045       replace: true,
1046       compile: function() {
1047         return {
1048           pre: function ($scope, $elm, $attrs) {
1049             var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1050             $elm.append(cellHeader);
1051           },
1052
1053           post: function ($scope, $elm, $attrs, controllers) {
1054             var uiGridCtrl = controllers[0];
1055             var renderContainerCtrl = controllers[1];
1056
1057             $scope.i18n = {
1058               headerCell: i18nService.getSafeText('headerCell'),
1059               sort: i18nService.getSafeText('sort')
1060             };
1061             $scope.getSortDirectionAriaLabel = function(){
1062               var col = $scope.col;
1063               //Trying to recreate this sort of thing but it was getting messy having it in the template.
1064               //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1065               var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1066               var label = sortDirectionText;
1067               //Append the priority if it exists
1068               if (col.sort.priority) {
1069                 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1070               }
1071               return label;
1072             };
1073
1074             $scope.grid = uiGridCtrl.grid;
1075
1076             $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
1077
1078             var initColClass = $scope.col.getColClass(false);
1079             $elm.addClass(initColClass);
1080
1081             // Hide the menu by default
1082             $scope.menuShown = false;
1083
1084             // Put asc and desc sort directions in scope
1085             $scope.asc = uiGridConstants.ASC;
1086             $scope.desc = uiGridConstants.DESC;
1087
1088             // Store a reference to menu element
1089             var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1090
1091             var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
1092
1093
1094             // apply any headerCellClass
1095             var classAdded;
1096             var previousMouseX;
1097
1098             // filter watchers
1099             var filterDeregisters = [];
1100
1101
1102             /*
1103              * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
1104              * Once we have a down event, we need to work out whether we have a click, a drag, or a
1105              * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
1106              * we ignore it.  A hold would open the menu.
1107              *
1108              * So, on down event, we put in place handlers for move and up events, and a timer.  If the
1109              * timer expires before we see a move or up, then we have a long press and hence a column menu open.
1110              * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
1111              * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
1112              * will handle it.
1113              *
1114              * To deal with touch enabled devices that also have mice, we only create our handlers when
1115              * we get the down event, and we create the corresponding handlers - if we're touchstart then
1116              * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
1117              *
1118              * We also suppress the click action whilst this is happening - otherwise after the mouseup there
1119              * will be a click event and that can cause the column menu to close
1120              *
1121              */
1122
1123             $scope.downFn = function( event ){
1124               event.stopPropagation();
1125
1126               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
1127                 event = event.originalEvent;
1128               }
1129
1130               // Don't show the menu if it's not the left button
1131               if (event.button && event.button !== 0) {
1132                 return;
1133               }
1134               previousMouseX = event.pageX;
1135
1136               $scope.mousedownStartTime = (new Date()).getTime();
1137               $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
1138
1139               $scope.mousedownTimeout.then(function () {
1140                 if ( $scope.colMenu ) {
1141                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
1142                 }
1143               });
1144
1145               uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
1146
1147               $scope.offAllEvents();
1148               if ( event.type === 'touchstart'){
1149                 $document.on('touchend', $scope.upFn);
1150                 $document.on('touchmove', $scope.moveFn);
1151               } else if ( event.type === 'mousedown' ){
1152                 $document.on('mouseup', $scope.upFn);
1153                 $document.on('mousemove', $scope.moveFn);
1154               }
1155             };
1156
1157             $scope.upFn = function( event ){
1158               event.stopPropagation();
1159               $timeout.cancel($scope.mousedownTimeout);
1160               $scope.offAllEvents();
1161               $scope.onDownEvents(event.type);
1162
1163               var mousedownEndTime = (new Date()).getTime();
1164               var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
1165
1166               if (mousedownTime > mousedownTimeout) {
1167                 // long click, handled above with mousedown
1168               }
1169               else {
1170                 // short click
1171                 if ( $scope.sortable ){
1172                   $scope.handleClick(event);
1173                 }
1174               }
1175             };
1176
1177             $scope.moveFn = function( event ){
1178               // Chrome is known to fire some bogus move events.
1179               var changeValue = event.pageX - previousMouseX;
1180               if ( changeValue === 0 ){ return; }
1181
1182               // we're a move, so do nothing and leave for column move (if enabled) to take over
1183               $timeout.cancel($scope.mousedownTimeout);
1184               $scope.offAllEvents();
1185               $scope.onDownEvents(event.type);
1186             };
1187
1188             $scope.clickFn = function ( event ){
1189               event.stopPropagation();
1190               $contentsElm.off('click', $scope.clickFn);
1191             };
1192
1193
1194             $scope.offAllEvents = function(){
1195               $contentsElm.off('touchstart', $scope.downFn);
1196               $contentsElm.off('mousedown', $scope.downFn);
1197
1198               $document.off('touchend', $scope.upFn);
1199               $document.off('mouseup', $scope.upFn);
1200
1201               $document.off('touchmove', $scope.moveFn);
1202               $document.off('mousemove', $scope.moveFn);
1203
1204               $contentsElm.off('click', $scope.clickFn);
1205             };
1206
1207             $scope.onDownEvents = function( type ){
1208               // If there is a previous event, then wait a while before
1209               // activating the other mode - i.e. if the last event was a touch event then
1210               // don't enable mouse events for a wee while (500ms or so)
1211               // Avoids problems with devices that emulate mouse events when you have touch events
1212
1213               switch (type){
1214                 case 'touchmove':
1215                 case 'touchend':
1216                   $contentsElm.on('click', $scope.clickFn);
1217                   $contentsElm.on('touchstart', $scope.downFn);
1218                   $timeout(function(){
1219                     $contentsElm.on('mousedown', $scope.downFn);
1220                   }, changeModeTimeout);
1221                   break;
1222                 case 'mousemove':
1223                 case 'mouseup':
1224                   $contentsElm.on('click', $scope.clickFn);
1225                   $contentsElm.on('mousedown', $scope.downFn);
1226                   $timeout(function(){
1227                     $contentsElm.on('touchstart', $scope.downFn);
1228                   }, changeModeTimeout);
1229                   break;
1230                 default:
1231                   $contentsElm.on('click', $scope.clickFn);
1232                   $contentsElm.on('touchstart', $scope.downFn);
1233                   $contentsElm.on('mousedown', $scope.downFn);
1234               }
1235             };
1236
1237
1238             var updateHeaderOptions = function( grid ){
1239               var contents = $elm;
1240               if ( classAdded ){
1241                 contents.removeClass( classAdded );
1242                 classAdded = null;
1243               }
1244
1245               if (angular.isFunction($scope.col.headerCellClass)) {
1246                 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
1247               }
1248               else {
1249                 classAdded = $scope.col.headerCellClass;
1250               }
1251               contents.addClass(classAdded);
1252
1253               var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1254               $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1255
1256               // Figure out whether this column is sortable or not
1257               if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
1258                 $scope.sortable = true;
1259               }
1260               else {
1261                 $scope.sortable = false;
1262               }
1263
1264               // Figure out whether this column is filterable or not
1265               var oldFilterable = $scope.filterable;
1266               if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1267                 $scope.filterable = true;
1268               }
1269               else {
1270                 $scope.filterable = false;
1271               }
1272
1273               if ( oldFilterable !== $scope.filterable){
1274                 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1275                   $scope.col.updateFilters($scope.filterable);
1276                 }
1277
1278                 // if column is filterable add a filter watcher
1279                 if ($scope.filterable) {
1280                   $scope.col.filters.forEach( function(filter, i) {
1281                     filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1282                       if (n !== o) {
1283                         uiGridCtrl.grid.api.core.raise.filterChanged();
1284                         uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1285                         uiGridCtrl.grid.queueGridRefresh();
1286                       }
1287                     }));
1288                   });
1289                   $scope.$on('$destroy', function() {
1290                     filterDeregisters.forEach( function(filterDeregister) {
1291                       filterDeregister();
1292                     });
1293                   });
1294                 } else {
1295                   filterDeregisters.forEach( function(filterDeregister) {
1296                     filterDeregister();
1297                   });
1298                 }
1299
1300               }
1301
1302               // figure out whether we support column menus
1303               if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1304                       $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1305                 $scope.colMenu = true;
1306               } else {
1307                 $scope.colMenu = false;
1308               }
1309
1310               /**
1311               * @ngdoc property
1312               * @name enableColumnMenu
1313               * @propertyOf ui.grid.class:GridOptions.columnDef
1314               * @description if column menus are enabled, controls the column menus for this specific
1315               * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1316               * using this option. If gridOptions.enableColumnMenus === false then you get no column
1317               * menus irrespective of the value of this option ).  Defaults to true.
1318               *
1319               */
1320               /**
1321               * @ngdoc property
1322               * @name enableColumnMenus
1323               * @propertyOf ui.grid.class:GridOptions.columnDef
1324               * @description Override for column menus everywhere - if set to false then you get no
1325               * column menus.  Defaults to true.
1326               *
1327               */
1328
1329               $scope.offAllEvents();
1330
1331               if ($scope.sortable || $scope.colMenu) {
1332                 $scope.onDownEvents();
1333
1334                 $scope.$on('$destroy', function () {
1335                   $scope.offAllEvents();
1336                 });
1337               }
1338             };
1339
1340 /*
1341             $scope.$watch('col', function (n, o) {
1342               if (n !== o) {
1343                 // See if the column's internal class has changed
1344                 var newColClass = $scope.col.getColClass(false);
1345                 if (newColClass !== initColClass) {
1346                   $elm.removeClass(initColClass);
1347                   $elm.addClass(newColClass);
1348                   initColClass = newColClass;
1349                 }
1350               }
1351             });
1352 */
1353             updateHeaderOptions();
1354
1355             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
1356             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
1357
1358             $scope.$on( '$destroy', dataChangeDereg );
1359
1360             $scope.handleClick = function(event) {
1361               // If the shift key is being held down, add this column to the sort
1362               var add = false;
1363               if (event.shiftKey) {
1364                 add = true;
1365               }
1366
1367               // Sort this column then rebuild the grid's rows
1368               uiGridCtrl.grid.sortColumn($scope.col, add)
1369                 .then(function () {
1370                   if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
1371                   uiGridCtrl.grid.refresh();
1372                 });
1373             };
1374
1375
1376             $scope.toggleMenu = function(event) {
1377               event.stopPropagation();
1378
1379               // If the menu is already showing...
1380               if (uiGridCtrl.columnMenuScope.menuShown) {
1381                 // ... and we're the column the menu is on...
1382                 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1383                   // ... hide it
1384                   uiGridCtrl.columnMenuScope.hideMenu();
1385                 }
1386                 // ... and we're NOT the column the menu is on
1387                 else {
1388                   // ... move the menu to our column
1389                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1390                 }
1391               }
1392               // If the menu is NOT showing
1393               else {
1394                 // ... show it on our column
1395                 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1396               }
1397             };
1398           }
1399         };
1400       }
1401     };
1402
1403     return uiGridHeaderCell;
1404   }]);
1405
1406 })();
1407
1408 (function(){
1409   'use strict';
1410
1411   angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
1412     function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1413     var defaultTemplate = 'ui-grid/ui-grid-header';
1414     var emptyTemplate = 'ui-grid/ui-grid-no-header';
1415
1416     return {
1417       restrict: 'EA',
1418       // templateUrl: 'ui-grid/ui-grid-header',
1419       replace: true,
1420       // priority: 1000,
1421       require: ['^uiGrid', '^uiGridRenderContainer'],
1422       scope: true,
1423       compile: function($elm, $attrs) {
1424         return {
1425           pre: function ($scope, $elm, $attrs, controllers) {
1426             var uiGridCtrl = controllers[0];
1427             var containerCtrl = controllers[1];
1428
1429             $scope.grid = uiGridCtrl.grid;
1430             $scope.colContainer = containerCtrl.colContainer;
1431
1432             updateHeaderReferences();
1433             
1434             var headerTemplate;
1435             if (!$scope.grid.options.showHeader) {
1436               headerTemplate = emptyTemplate;
1437             }
1438             else {
1439               headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
1440             }
1441
1442             gridUtil.getTemplate(headerTemplate)
1443               .then(function (contents) {
1444                 var template = angular.element(contents);
1445                 
1446                 var newElm = $compile(template)($scope);
1447                 $elm.replaceWith(newElm);
1448
1449                 // And update $elm to be the new element
1450                 $elm = newElm;
1451
1452                 updateHeaderReferences();
1453
1454                 if (containerCtrl) {
1455                   // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1456                   var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1457
1458
1459                   if (headerViewport) {
1460                     containerCtrl.headerViewport = headerViewport;
1461                     angular.element(headerViewport).on('scroll', scrollHandler);
1462                     $scope.$on('$destroy', function () {
1463                       angular.element(headerViewport).off('scroll', scrollHandler);
1464                     });
1465                   }
1466                 }
1467
1468                 $scope.grid.queueRefresh();
1469               });
1470
1471             function updateHeaderReferences() {
1472               containerCtrl.header = containerCtrl.colContainer.header = $elm;
1473
1474               var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
1475
1476               if (headerCanvases.length > 0) {
1477                 containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
1478               }
1479               else {
1480                 containerCtrl.headerCanvas = null;
1481               }
1482             }
1483
1484             function scrollHandler(evt) {
1485               if (uiGridCtrl.grid.isScrollingHorizontally) {
1486                 return;
1487               }
1488               var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
1489               var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
1490
1491               var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
1492               scrollEvent.newScrollLeft = newScrollLeft;
1493               if ( horizScrollPercentage > -1 ){
1494                 scrollEvent.x = { percentage: horizScrollPercentage };
1495               }
1496
1497               uiGridCtrl.grid.scrollContainers(null, scrollEvent);
1498             }
1499           },
1500
1501           post: function ($scope, $elm, $attrs, controllers) {
1502             var uiGridCtrl = controllers[0];
1503             var containerCtrl = controllers[1];
1504
1505             // gridUtil.logDebug('ui-grid-header link');
1506
1507             var grid = uiGridCtrl.grid;
1508
1509             // Don't animate header cells
1510             gridUtil.disableAnimations($elm);
1511
1512             function updateColumnWidths() {
1513               // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
1514               // already being populated correctly
1515
1516               var columnCache = containerCtrl.colContainer.visibleColumnCache;
1517               
1518               // Build the CSS
1519               // uiGridCtrl.grid.columns.forEach(function (column) {
1520               var ret = '';
1521               var canvasWidth = 0;
1522               columnCache.forEach(function (column) {
1523                 ret = ret + column.getColClassDefinition();
1524                 canvasWidth += column.drawnWidth;
1525               });
1526
1527               containerCtrl.colContainer.canvasWidth = canvasWidth;
1528               
1529               // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1530               return ret;
1531             }
1532             
1533             containerCtrl.header = $elm;
1534             
1535             var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1536             if (headerViewport) {
1537               containerCtrl.headerViewport = headerViewport;
1538             }
1539
1540             //todo: remove this if by injecting gridCtrl into unit tests
1541             if (uiGridCtrl) {
1542               uiGridCtrl.grid.registerStyleComputation({
1543                 priority: 15,
1544                 func: updateColumnWidths
1545               });
1546             }
1547           }
1548         };
1549       }
1550     };
1551   }]);
1552
1553 })();
1554
1555 (function(){
1556
1557 angular.module('ui.grid')
1558 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
1559   /**
1560    *  @ngdoc service
1561    *  @name ui.grid.gridMenuService
1562    *
1563    *  @description Methods for working with the grid menu
1564    */
1565
1566   var service = {
1567     /**
1568      * @ngdoc method
1569      * @methodOf ui.grid.gridMenuService
1570      * @name initialize
1571      * @description Sets up the gridMenu. Most importantly, sets our
1572      * scope onto the grid object as grid.gridMenuScope, allowing us
1573      * to operate when passed only the grid.  Second most importantly,
1574      * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1575      * on the core api.
1576      * @param {$scope} $scope the scope of this gridMenu
1577      * @param {Grid} grid the grid to which this gridMenu is associated
1578      */
1579     initialize: function( $scope, grid ){
1580       grid.gridMenuScope = $scope;
1581       $scope.grid = grid;
1582       $scope.registeredMenuItems = [];
1583
1584       // not certain this is needed, but would be bad to create a memory leak
1585       $scope.$on('$destroy', function() {
1586         if ( $scope.grid && $scope.grid.gridMenuScope ){
1587           $scope.grid.gridMenuScope = null;
1588         }
1589         if ( $scope.grid ){
1590           $scope.grid = null;
1591         }
1592         if ( $scope.registeredMenuItems ){
1593           $scope.registeredMenuItems = null;
1594         }
1595       });
1596
1597       $scope.registeredMenuItems = [];
1598
1599       /**
1600        * @ngdoc function
1601        * @name addToGridMenu
1602        * @methodOf ui.grid.core.api:PublicApi
1603        * @description add items to the grid menu.  Used by features
1604        * to add their menu items if they are enabled, can also be used by
1605        * end users to add menu items.  This method has the advantage of allowing
1606        * remove again, which can simplify management of which items are included
1607        * in the menu when.  (Noting that in most cases the shown and active functions
1608        * provide a better way to handle visibility of menu items)
1609        * @param {Grid} grid the grid on which we are acting
1610        * @param {array} items menu items in the format as described in the tutorial, with
1611        * the added note that if you want to use remove you must also specify an `id` field,
1612        * which is provided when you want to remove an item.  The id should be unique.
1613        *
1614        */
1615       grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1616
1617       /**
1618        * @ngdoc function
1619        * @name removeFromGridMenu
1620        * @methodOf ui.grid.core.api:PublicApi
1621        * @description Remove an item from the grid menu based on a provided id. Assumes
1622        * that the id is unique, removes only the last instance of that id. Does nothing if
1623        * the specified id is not found
1624        * @param {Grid} grid the grid on which we are acting
1625        * @param {string} id the id we'd like to remove from the menu
1626        *
1627        */
1628       grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1629     },
1630
1631
1632     /**
1633      * @ngdoc function
1634      * @name addToGridMenu
1635      * @propertyOf ui.grid.gridMenuService
1636      * @description add items to the grid menu.  Used by features
1637      * to add their menu items if they are enabled, can also be used by
1638      * end users to add menu items.  This method has the advantage of allowing
1639      * remove again, which can simplify management of which items are included
1640      * in the menu when.  (Noting that in most cases the shown and active functions
1641      * provide a better way to handle visibility of menu items)
1642      * @param {Grid} grid the grid on which we are acting
1643      * @param {array} items menu items in the format as described in the tutorial, with
1644      * the added note that if you want to use remove you must also specify an `id` field,
1645      * which is provided when you want to remove an item.  The id should be unique.
1646      *
1647      */
1648     addToGridMenu: function( grid, menuItems ) {
1649       if ( !angular.isArray( menuItems ) ) {
1650         gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1651       } else {
1652         if ( grid.gridMenuScope ){
1653           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1654           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1655         } else {
1656           gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
1657         }
1658       }
1659     },
1660
1661
1662     /**
1663      * @ngdoc function
1664      * @name removeFromGridMenu
1665      * @methodOf ui.grid.gridMenuService
1666      * @description Remove an item from the grid menu based on a provided id.  Assumes
1667      * that the id is unique, removes only the last instance of that id.  Does nothing if
1668      * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
1669      * then do nothing silently - the desired result is those menu items not be present and they
1670      * aren't.
1671      * @param {Grid} grid the grid on which we are acting
1672      * @param {string} id the id we'd like to remove from the menu
1673      *
1674      */
1675     removeFromGridMenu: function( grid, id ){
1676       var foundIndex = -1;
1677
1678       if ( grid && grid.gridMenuScope ){
1679         grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1680           if ( value.id === id ){
1681             if (foundIndex > -1) {
1682               gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1683             } else {
1684
1685               foundIndex = index;
1686             }
1687           }
1688         });
1689       }
1690
1691       if ( foundIndex > -1 ){
1692         grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1693       }
1694     },
1695
1696
1697     /**
1698      * @ngdoc array
1699      * @name gridMenuCustomItems
1700      * @propertyOf ui.grid.class:GridOptions
1701      * @description (optional) An array of menu items that should be added to
1702      * the gridMenu.  Follow the format documented in the tutorial for column
1703      * menu customisation.  The context provided to the action function will
1704      * include context.grid.  An alternative if working with dynamic menus is to use the
1705      * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1706      * some of the management of items for you.
1707      *
1708      */
1709     /**
1710      * @ngdoc boolean
1711      * @name gridMenuShowHideColumns
1712      * @propertyOf ui.grid.class:GridOptions
1713      * @description true by default, whether the grid menu should allow hide/show
1714      * of columns
1715      *
1716      */
1717     /**
1718      * @ngdoc method
1719      * @methodOf ui.grid.gridMenuService
1720      * @name getMenuItems
1721      * @description Decides the menu items to show in the menu.  This is a
1722      * combination of:
1723      *
1724      * - the default menu items that are always included,
1725      * - any menu items that have been provided through the addMenuItem api. These
1726      *   are typically added by features within the grid
1727      * - any menu items included in grid.options.gridMenuCustomItems.  These can be
1728      *   changed dynamically, as they're always recalculated whenever we show the
1729      *   menu
1730      * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1731      * the information that we need
1732      * @returns {array} an array of menu items that can be shown
1733      */
1734     getMenuItems: function( $scope ) {
1735       var menuItems = [
1736         // this is where we add any menu items we want to always include
1737       ];
1738
1739       if ( $scope.grid.options.gridMenuCustomItems ){
1740         if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1741           gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1742         } else {
1743           menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1744         }
1745       }
1746
1747       var clearFilters = [{
1748         title: i18nService.getSafeText('gridMenu.clearAllFilters'),
1749         action: function ($event) {
1750           $scope.grid.clearAllFilters(undefined, true, undefined);
1751         },
1752         shown: function() {
1753           return $scope.grid.options.enableFiltering;
1754         },
1755         order: 100
1756       }];
1757       menuItems = menuItems.concat( clearFilters );
1758
1759       menuItems = menuItems.concat( $scope.registeredMenuItems );
1760
1761       if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1762         menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1763       }
1764
1765       menuItems.sort(function(a, b){
1766         return a.order - b.order;
1767       });
1768
1769       return menuItems;
1770     },
1771
1772
1773     /**
1774      * @ngdoc array
1775      * @name gridMenuTitleFilter
1776      * @propertyOf ui.grid.class:GridOptions
1777      * @description (optional) A function that takes a title string
1778      * (usually the col.displayName), and converts it into a display value.  The function
1779      * must return either a string or a promise.
1780      *
1781      * Used for internationalization of the grid menu column names - for angular-translate
1782      * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1783      * function
1784      * @example
1785      * <pre>
1786      *   gridOptions = {
1787      *     gridMenuTitleFilter: $translate
1788      *   }
1789      * </pre>
1790      */
1791     /**
1792      * @ngdoc method
1793      * @methodOf ui.grid.gridMenuService
1794      * @name showHideColumns
1795      * @description Adds two menu items for each of the columns in columnDefs.  One
1796      * menu item for hide, one menu item for show.  Each is visible when appropriate
1797      * (show when column is not visible, hide when column is visible).  Each toggles
1798      * the visible property on the columnDef using toggleColumnVisibility
1799      * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1800      */
1801     showHideColumns: function( $scope ){
1802       var showHideColumns = [];
1803       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1804         return showHideColumns;
1805       }
1806
1807       // add header for columns
1808       showHideColumns.push({
1809         title: i18nService.getSafeText('gridMenu.columns'),
1810         order: 300
1811       });
1812
1813       $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1814
1815       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1816         if ( colDef.enableHiding !== false ){
1817           // add hide menu item - shows an OK icon as we only show when column is already visible
1818           var menuItem = {
1819             icon: 'ui-grid-icon-ok',
1820             action: function($event) {
1821               $event.stopPropagation();
1822               service.toggleColumnVisibility( this.context.gridCol );
1823             },
1824             shown: function() {
1825               return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1826             },
1827             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1828             leaveOpen: true,
1829             order: 301 + index * 2
1830           };
1831           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1832           showHideColumns.push( menuItem );
1833
1834           // add show menu item - shows no icon as we only show when column is invisible
1835           menuItem = {
1836             icon: 'ui-grid-icon-cancel',
1837             action: function($event) {
1838               $event.stopPropagation();
1839               service.toggleColumnVisibility( this.context.gridCol );
1840             },
1841             shown: function() {
1842               return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1843             },
1844             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1845             leaveOpen: true,
1846             order: 301 + index * 2 + 1
1847           };
1848           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1849           showHideColumns.push( menuItem );
1850         }
1851       });
1852       return showHideColumns;
1853     },
1854
1855
1856     /**
1857      * @ngdoc method
1858      * @methodOf ui.grid.gridMenuService
1859      * @name setMenuItemTitle
1860      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1861      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1862      * putting the result into the title
1863      * @param {object} menuItem the menuItem we want to put the title on
1864      * @param {object} colDef the colDef from which we can get displayName, name or field
1865      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1866      *
1867      */
1868     setMenuItemTitle: function( menuItem, colDef, grid ){
1869       var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
1870
1871       if ( typeof(title) === 'string' ){
1872         menuItem.title = title;
1873       } else if ( title.then ){
1874         // must be a promise
1875         menuItem.title = "";
1876         title.then( function( successValue ) {
1877           menuItem.title = successValue;
1878         }, function( errorValue ) {
1879           menuItem.title = errorValue;
1880         });
1881       } else {
1882         gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
1883         menuItem.title = 'badconfig';
1884       }
1885     },
1886
1887     /**
1888      * @ngdoc method
1889      * @methodOf ui.grid.gridMenuService
1890      * @name toggleColumnVisibility
1891      * @description Toggles the visibility of an individual column.  Expects to be
1892      * provided a context that has on it a gridColumn, which is the column that
1893      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
1894      * @param {GridCol} gridCol the column that we want to toggle
1895      *
1896      */
1897     toggleColumnVisibility: function( gridCol ) {
1898       gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
1899
1900       gridCol.grid.refresh();
1901       gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1902       gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
1903     }
1904   };
1905
1906   return service;
1907 }])
1908
1909
1910
1911 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
1912 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
1913
1914   return {
1915     priority: 0,
1916     scope: true,
1917     require: ['^uiGrid'],
1918     templateUrl: 'ui-grid/ui-grid-menu-button',
1919     replace: true,
1920
1921     link: function ($scope, $elm, $attrs, controllers) {
1922       var uiGridCtrl = controllers[0];
1923
1924       // For the aria label
1925       $scope.i18n = {
1926         aria: i18nService.getSafeText('gridMenu.aria')
1927       };
1928
1929       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
1930
1931       $scope.shown = false;
1932
1933       $scope.toggleMenu = function () {
1934         if ( $scope.shown ){
1935           $scope.$broadcast('hide-menu');
1936           $scope.shown = false;
1937         } else {
1938           $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
1939           $scope.$broadcast('show-menu');
1940           $scope.shown = true;
1941         }
1942       };
1943
1944       $scope.$on('menu-hidden', function() {
1945         $scope.shown = false;
1946         gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
1947       });
1948     }
1949   };
1950
1951 }]);
1952
1953 })();
1954
1955 (function(){
1956
1957 /**
1958  * @ngdoc directive
1959  * @name ui.grid.directive:uiGridMenu
1960  * @element style
1961  * @restrict A
1962  *
1963  * @description
1964  * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1965  *
1966  * @example
1967  <doc:example module="app">
1968  <doc:source>
1969  <script>
1970  var app = angular.module('app', ['ui.grid']);
1971
1972  app.controller('MainCtrl', ['$scope', function ($scope) {
1973
1974  }]);
1975  </script>
1976
1977  <div ng-controller="MainCtrl">
1978    <div ui-grid-menu shown="true"  ></div>
1979  </div>
1980  </doc:source>
1981  <doc:scenario>
1982  </doc:scenario>
1983  </doc:example>
1984  */
1985 angular.module('ui.grid')
1986
1987 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
1988 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
1989   var uiGridMenu = {
1990     priority: 0,
1991     scope: {
1992       // shown: '&',
1993       menuItems: '=',
1994       autoHide: '=?'
1995     },
1996     require: '?^uiGrid',
1997     templateUrl: 'ui-grid/uiGridMenu',
1998     replace: false,
1999     link: function ($scope, $elm, $attrs, uiGridCtrl) {
2000       var self = this;
2001       var menuMid;
2002       var $animate;
2003
2004       $scope.i18n = {
2005         close: i18nService.getSafeText('columnMenu.close')
2006       };
2007
2008     // *** Show/Hide functions ******
2009       self.showMenu = $scope.showMenu = function(event, args) {
2010         if ( !$scope.shown ){
2011
2012           /*
2013            * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
2014            * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
2015            * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
2016            * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
2017            * on scroll events.
2018            *
2019            * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
2020            * like they do, but angular has a default $animate provider that is just a stub, and that's what's
2021            * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
2022            * angular-translate.js, it's not using it.  You need to test animations in an external application.
2023            */
2024           $scope.shown = true;
2025
2026           $timeout( function() {
2027             $scope.shownMid = true;
2028             $scope.$emit('menu-shown');
2029           });
2030         } else if ( !$scope.shownMid ) {
2031           // we're probably doing a hide then show, so we don't need to wait for ng-if
2032           $scope.shownMid = true;
2033           $scope.$emit('menu-shown');
2034         }
2035
2036         var docEventType = 'click';
2037         if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
2038           docEventType = args.originalEvent.type;
2039         }
2040
2041         // Turn off an existing document click handler
2042         angular.element(document).off('click touchstart', applyHideMenu);
2043
2044         // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
2045         $timeout(function() {
2046           angular.element(document).on(docEventType, applyHideMenu);
2047         });
2048         //automatically set the focus to the first button element in the now open menu.
2049         gridUtil.focus.bySelector($elm, 'button[type=button]', true);
2050       };
2051
2052
2053       self.hideMenu = $scope.hideMenu = function(event, args) {
2054         if ( $scope.shown ){
2055           /*
2056            * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
2057            * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
2058            * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
2059            *
2060            * The user may have clicked on the menu again whilst
2061            * we're waiting, so we check that the mid isn't shown before applying the ng-if.
2062            */
2063           $scope.shownMid = false;
2064           $timeout( function() {
2065             if ( !$scope.shownMid ){
2066               $scope.shown = false;
2067               $scope.$emit('menu-hidden');
2068             }
2069           }, 200);
2070         }
2071
2072         angular.element(document).off('click touchstart', applyHideMenu);
2073       };
2074
2075       $scope.$on('hide-menu', function (event, args) {
2076         $scope.hideMenu(event, args);
2077       });
2078
2079       $scope.$on('show-menu', function (event, args) {
2080         $scope.showMenu(event, args);
2081       });
2082
2083
2084     // *** Auto hide when click elsewhere ******
2085       var applyHideMenu = function(){
2086         if ($scope.shown) {
2087           $scope.$apply(function () {
2088             $scope.hideMenu();
2089           });
2090         }
2091       };
2092
2093       if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
2094         $scope.autoHide = true;
2095       }
2096
2097       if ($scope.autoHide) {
2098         angular.element($window).on('resize', applyHideMenu);
2099       }
2100
2101       $scope.$on('$destroy', function () {
2102         angular.element(document).off('click touchstart', applyHideMenu);
2103       });
2104
2105
2106       $scope.$on('$destroy', function() {
2107         angular.element($window).off('resize', applyHideMenu);
2108       });
2109
2110       if (uiGridCtrl) {
2111        $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
2112       }
2113
2114       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2115     },
2116
2117
2118     controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
2119       var self = this;
2120     }]
2121   };
2122
2123   return uiGridMenu;
2124 }])
2125
2126 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2127   var uiGridMenuItem = {
2128     priority: 0,
2129     scope: {
2130       name: '=',
2131       active: '=',
2132       action: '=',
2133       icon: '=',
2134       shown: '=',
2135       context: '=',
2136       templateUrl: '=',
2137       leaveOpen: '=',
2138       screenReaderOnly: '='
2139     },
2140     require: ['?^uiGrid', '^uiGridMenu'],
2141     templateUrl: 'ui-grid/uiGridMenuItem',
2142     replace: false,
2143     compile: function($elm, $attrs) {
2144       return {
2145         pre: function ($scope, $elm, $attrs, controllers) {
2146           var uiGridCtrl = controllers[0],
2147               uiGridMenuCtrl = controllers[1];
2148
2149           if ($scope.templateUrl) {
2150             gridUtil.getTemplate($scope.templateUrl)
2151                 .then(function (contents) {
2152                   var template = angular.element(contents);
2153
2154                   var newElm = $compile(template)($scope);
2155                   $elm.replaceWith(newElm);
2156                 });
2157           }
2158         },
2159         post: function ($scope, $elm, $attrs, controllers) {
2160           var uiGridCtrl = controllers[0],
2161               uiGridMenuCtrl = controllers[1];
2162
2163           // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
2164           // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
2165           //   throw new TypeError("$scope.shown is defined but not a function");
2166           // }
2167           if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
2168             $scope.shown = function() { return true; };
2169           }
2170
2171           $scope.itemShown = function () {
2172             var context = {};
2173             if ($scope.context) {
2174               context.context = $scope.context;
2175             }
2176
2177             if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2178               context.grid = uiGridCtrl.grid;
2179             }
2180
2181             return $scope.shown.call(context);
2182           };
2183
2184           $scope.itemAction = function($event,title) {
2185             gridUtil.logDebug('itemAction');
2186             $event.stopPropagation();
2187
2188             if (typeof($scope.action) === 'function') {
2189               var context = {};
2190
2191               if ($scope.context) {
2192                 context.context = $scope.context;
2193               }
2194
2195               // Add the grid to the function call context if the uiGrid controller is present
2196               if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2197                 context.grid = uiGridCtrl.grid;
2198               }
2199
2200               $scope.action.call(context, $event, title);
2201
2202               if ( !$scope.leaveOpen ){
2203                 $scope.$emit('hide-menu');
2204               } else {
2205                 /*
2206                  * XXX: Fix after column refactor
2207                  * Ideally the focus would remain on the item.
2208                  * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2209                  */
2210                 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2211               }
2212             }
2213           };
2214
2215           $scope.i18n = i18nService.get();
2216         }
2217       };
2218     }
2219   };
2220
2221   return uiGridMenuItem;
2222 }]);
2223
2224 })();
2225
2226 (function(){
2227   'use strict';
2228   /**
2229    * @ngdoc overview
2230    * @name ui.grid.directive:uiGridOneBind
2231    * @summary A group of directives that provide a one time bind to a dom element.
2232    * @description A group of directives that provide a one time bind to a dom element.
2233    * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
2234    * This is done to reduce the number of watchers on the dom.
2235    * <br/>
2236    * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
2237    * <pre>
2238         <div ng-init="imageName = 'myImageDir.jpg'">
2239           <img ui-grid-one-bind-src="imageName"></img>
2240         </div>
2241      </pre>
2242    * Will become:
2243    * <pre>
2244        <div ng-init="imageName = 'myImageDir.jpg'">
2245          <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
2246        </div>
2247      </pre>
2248      </br>
2249      <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
2250    * <pre>
2251         <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
2252      </pre>
2253    * Will become:
2254    * <pre>
2255    <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
2256      </pre>
2257      </br>
2258    * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
2259    * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
2260    *
2261    */
2262   //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2263   var oneBinders = angular.module('ui.grid');
2264   angular.forEach([
2265       /**
2266        * @ngdoc directive
2267        * @name ui.grid.directive:uiGridOneBindSrc
2268        * @memberof ui.grid.directive:uiGridOneBind
2269        * @element img
2270        * @restrict A
2271        * @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>.
2272        * @description One time binding for the src dom tag.
2273        *
2274        */
2275       {tag: 'Src', method: 'attr'},
2276       /**
2277        * @ngdoc directive
2278        * @name ui.grid.directive:uiGridOneBindText
2279        * @element div
2280        * @restrict A
2281        * @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>.
2282        * @description One time binding for the text dom tag.
2283        */
2284       {tag: 'Text', method: 'text'},
2285       /**
2286        * @ngdoc directive
2287        * @name ui.grid.directive:uiGridOneBindHref
2288        * @element div
2289        * @restrict A
2290        * @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>.
2291        * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2292        */
2293       {tag: 'Href', method: 'attr'},
2294       /**
2295        * @ngdoc directive
2296        * @name ui.grid.directive:uiGridOneBindClass
2297        * @element div
2298        * @restrict A
2299        * @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>.
2300        * @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.
2301        *                                    this is to prevent the watcher from being removed before the scope is initialized.
2302        * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
2303        * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2304        */
2305       {tag: 'Class', method: 'addClass'},
2306       /**
2307        * @ngdoc directive
2308        * @name ui.grid.directive:uiGridOneBindHtml
2309        * @element div
2310        * @restrict A
2311        * @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>.
2312        * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
2313        */
2314       {tag: 'Html', method: 'html'},
2315       /**
2316        * @ngdoc directive
2317        * @name ui.grid.directive:uiGridOneBindAlt
2318        * @element div
2319        * @restrict A
2320        * @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>.
2321        * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2322        */
2323       {tag: 'Alt', method: 'attr'},
2324       /**
2325        * @ngdoc directive
2326        * @name ui.grid.directive:uiGridOneBindStyle
2327        * @element div
2328        * @restrict A
2329        * @param {String} uiGridOneBindStyle The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2330        * @description One time binding for the style dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2331        */
2332       {tag: 'Style', method: 'css'},
2333       /**
2334        * @ngdoc directive
2335        * @name ui.grid.directive:uiGridOneBindValue
2336        * @element div
2337        * @restrict A
2338        * @param {String} uiGridOneBindValue The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2339        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2340        */
2341       {tag: 'Value', method: 'attr'},
2342       /**
2343        * @ngdoc directive
2344        * @name ui.grid.directive:uiGridOneBindId
2345        * @element div
2346        * @restrict A
2347        * @param {String} uiGridOneBindId The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2348        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2349        */
2350       {tag: 'Id', method: 'attr'},
2351       /**
2352        * @ngdoc directive
2353        * @name ui.grid.directive:uiGridOneBindIdGrid
2354        * @element div
2355        * @restrict A
2356        * @param {String} uiGridOneBindIdGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2357        * @description One time binding for the id dom tag.
2358        * <h1>Important Note!</h1>
2359        * If the id tag passed as a parameter does <b>not</b> contain the grid id as a substring
2360        * then the directive will search the scope and the parent controller (if it is a uiGridController) for the grid.id value.
2361        * If this value is found then it is appended to the begining of the id tag. If the grid is not found then the directive throws an error.
2362        * This is done in order to ensure uniqueness of id tags across the grid.
2363        * This is to prevent two grids in the same document having duplicate id tags.
2364        */
2365       {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2366       /**
2367        * @ngdoc directive
2368        * @name ui.grid.directive:uiGridOneBindTitle
2369        * @element div
2370        * @restrict A
2371        * @param {String} uiGridOneBindTitle The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2372        * @description One time binding for the title dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2373        */
2374       {tag: 'Title', method: 'attr'},
2375       /**
2376        * @ngdoc directive
2377        * @name ui.grid.directive:uiGridOneBindAriaLabel
2378        * @element div
2379        * @restrict A
2380        * @param {String} uiGridOneBindAriaLabel The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2381        * @description One time binding for the aria-label dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2382        *<br/>
2383        * <pre>
2384             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text"></div>
2385          </pre>
2386        * Will become:
2387        * <pre>
2388             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
2389          </pre>
2390        */
2391       {tag: 'Label', method: 'attr', aria:true},
2392       /**
2393        * @ngdoc directive
2394        * @name ui.grid.directive:uiGridOneBindAriaLabelledby
2395        * @element div
2396        * @restrict A
2397        * @param {String} uiGridOneBindAriaLabelledby The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2398        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2399        *<br/>
2400        * <pre>
2401             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId"></div>
2402          </pre>
2403        * Will become:
2404        * <pre>
2405             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
2406          </pre>
2407        */
2408       {tag: 'Labelledby', method: 'attr', aria:true},
2409       /**
2410        * @ngdoc directive
2411        * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
2412        * @element div
2413        * @restrict A
2414        * @param {String} uiGridOneBindAriaLabelledbyGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2415        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2416        * Works somewhat like {@link ui.grid.directive:uiGridOneBindIdGrid} however this one supports a list of ids (seperated by a space) and will dynamically add the
2417        * grid id to each one.
2418        *<br/>
2419        * <pre>
2420             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId"></div>
2421          </pre>
2422        * Will become ([grid.id] will be replaced by the actual grid id):
2423        * <pre>
2424             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
2425          </pre>
2426        */
2427       {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2428       /**
2429        * @ngdoc directive
2430        * @name ui.grid.directive:uiGridOneBindAriaDescribedby
2431        * @element ANY
2432        * @restrict A
2433        * @param {String} uiGridOneBindAriaDescribedby The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2434        * @description One time binding for the aria-describedby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2435        *<br/>
2436        * <pre>
2437             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId"></div>
2438          </pre>
2439        * Will become:
2440        * <pre>
2441             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
2442          </pre>
2443        */
2444       {tag: 'Describedby', method: 'attr', aria:true},
2445       /**
2446        * @ngdoc directive
2447        * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
2448        * @element ANY
2449        * @restrict A
2450        * @param {String} uiGridOneBindAriaDescribedbyGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2451        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2452        * Works somewhat like {@link ui.grid.directive:uiGridOneBindIdGrid} however this one supports a list of ids (seperated by a space) and will dynamically add the
2453        * grid id to each one.
2454        *<br/>
2455        * <pre>
2456             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId"></div>
2457          </pre>
2458        * Will become ([grid.id] will be replaced by the actual grid id):
2459        * <pre>
2460             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
2461          </pre>
2462        */
2463       {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2464     function(v){
2465
2466       var baseDirectiveName = 'uiGridOneBind';
2467       //If it is an aria tag then append the aria label seperately
2468       //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2469       //If the diretiveName has to be overridden then it does so here. This is because the tag being modified and the directive sometimes don't match up.
2470       var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2471       oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2472         return {
2473           restrict: 'A',
2474           require: ['?uiGrid','?^uiGrid'],
2475           link: function(scope, iElement, iAttrs, controllers){
2476             /* Appends the grid id to the beginnig of the value. */
2477             var appendGridId = function(val){
2478               var grid; //Get an instance of the grid if its available
2479               //If its available in the scope then we don't need to try to find it elsewhere
2480               if (scope.grid) {
2481                 grid = scope.grid;
2482               }
2483               //Another possible location to try to find the grid
2484               else if (scope.col && scope.col.grid){
2485                 grid = scope.col.grid;
2486               }
2487               //Last ditch effort: Search through the provided controllers.
2488               else if (!controllers.some( //Go through the controllers till one has the element we need
2489                 function(controller){
2490                   if (controller && controller.grid) {
2491                     grid = controller.grid;
2492                     return true; //We've found the grid
2493                   }
2494               })){
2495                 //We tried our best to find it for you
2496                 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
2497                                  "within the correct scope? Trying to generate id: [gridID]-" + val);
2498                 throw new Error("No valid grid could be found");
2499               }
2500
2501               if (grid){
2502                 var idRegex = new RegExp(grid.id.toString());
2503                 //If the grid id hasn't been appended already in the template declaration
2504                 if (!idRegex.test(val)){
2505                   val = grid.id.toString() + '-' + val;
2506                 }
2507               }
2508               return val;
2509             };
2510
2511             // The watch returns a function to remove itself.
2512             var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2513               if (newV){
2514                 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2515                 if (v.appendGridId) {
2516                   var newIdString = null;
2517                   //Append the id to all of the new ids.
2518                   angular.forEach( newV.split(' '), function(s){
2519                     newIdString = (newIdString ? (newIdString + ' ') : '') +  appendGridId(s);
2520                   });
2521                   newV = newIdString;
2522                 }
2523
2524                 // Append this newValue to the dom element.
2525                 switch (v.method) {
2526                   case 'attr': //The attr method takes two paraams the tag and the value
2527                     if (v.aria) {
2528                       //If it is an aria element then append the aria prefix
2529                       iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
2530                     } else {
2531                       iElement[v.method](v.tag.toLowerCase(),newV);
2532                     }
2533                     break;
2534                   case 'addClass':
2535                     //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2536                     if (angular.isObject(newV) && !angular.isArray(newV)) {
2537                       var results = [];
2538                       var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2539                       angular.forEach(newV, function (value, index) {
2540                         if (value !== null && typeof(value) !== "undefined"){
2541                           nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2542                           if (value) {results.push(index);}
2543                         }
2544                       });
2545                       //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2546                       if (!nonNullFound){
2547                         return; // If not initialized then the watcher should not be removed yet.
2548                       }
2549                       newV = results;
2550                     }
2551
2552                     if (newV) {
2553                       iElement.addClass(angular.isArray(newV) ? newV.join(' ') : newV);
2554                     } else {
2555                       return;
2556                     }
2557                     break;
2558                   default:
2559                     iElement[v.method](newV);
2560                     break;
2561                 }
2562
2563                 //Removes the watcher on itself after the bind
2564                 rmWatcher();
2565               }
2566             // True ensures that equality is determined using angular.equals instead of ===
2567             }, true); //End rm watchers
2568           } //End compile function
2569         }; //End directive return
2570       } // End directive function
2571     ]); //End directive
2572   }); // End angular foreach
2573 })();
2574
2575 (function () {
2576   'use strict';
2577
2578   var module = angular.module('ui.grid');
2579
2580   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2581     function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2582     return {
2583       replace: true,
2584       transclude: true,
2585       templateUrl: 'ui-grid/uiGridRenderContainer',
2586       require: ['^uiGrid', 'uiGridRenderContainer'],
2587       scope: {
2588         containerId: '=',
2589         rowContainerName: '=',
2590         colContainerName: '=',
2591         bindScrollHorizontal: '=',
2592         bindScrollVertical: '=',
2593         enableVerticalScrollbar: '=',
2594         enableHorizontalScrollbar: '='
2595       },
2596       controller: 'uiGridRenderContainer as RenderContainer',
2597       compile: function () {
2598         return {
2599           pre: function prelink($scope, $elm, $attrs, controllers) {
2600
2601             var uiGridCtrl = controllers[0];
2602             var containerCtrl = controllers[1];
2603             var grid = $scope.grid = uiGridCtrl.grid;
2604
2605             // Verify that the render container for this element exists
2606             if (!$scope.rowContainerName) {
2607               throw "No row render container name specified";
2608             }
2609             if (!$scope.colContainerName) {
2610               throw "No column render container name specified";
2611             }
2612
2613             if (!grid.renderContainers[$scope.rowContainerName]) {
2614               throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2615             }
2616             if (!grid.renderContainers[$scope.colContainerName]) {
2617               throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2618             }
2619
2620             var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2621             var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2622
2623             containerCtrl.containerId = $scope.containerId;
2624             containerCtrl.rowContainer = rowContainer;
2625             containerCtrl.colContainer = colContainer;
2626           },
2627           post: function postlink($scope, $elm, $attrs, controllers) {
2628
2629             var uiGridCtrl = controllers[0];
2630             var containerCtrl = controllers[1];
2631
2632             var grid = uiGridCtrl.grid;
2633             var rowContainer = containerCtrl.rowContainer;
2634             var colContainer = containerCtrl.colContainer;
2635             var scrollTop = null;
2636             var scrollLeft = null;
2637
2638
2639             var renderContainer = grid.renderContainers[$scope.containerId];
2640
2641             // Put the container name on this element as a class
2642             $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2643
2644             // Scroll the render container viewport when the mousewheel is used
2645             gridUtil.on.mousewheel($elm, function (event) {
2646               var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2647               if (event.deltaY !== 0) {
2648                 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2649
2650                 scrollTop = containerCtrl.viewport[0].scrollTop;
2651
2652                 // Get the scroll percentage
2653                 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2654                 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2655
2656                 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2657                 //   Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2658                 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2659                   containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2660                 }
2661
2662                 // Keep scrollPercentage within the range 0-1.
2663                 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2664                 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2665
2666                 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2667               }
2668               if (event.deltaX !== 0) {
2669                 var scrollXAmount = event.deltaX * event.deltaFactor;
2670
2671                 // Get the scroll percentage
2672                 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2673                 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2674                 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2675
2676                 // Keep scrollPercentage within the range 0-1.
2677                 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2678                 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2679
2680                 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2681               }
2682
2683               // Let the parent container scroll if the grid is already at the top/bottom
2684               if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2685                   (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2686                 //parent controller scrolls
2687               }
2688               else {
2689                 event.preventDefault();
2690                 event.stopPropagation();
2691                 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2692               }
2693
2694             });
2695
2696             $elm.bind('$destroy', function() {
2697               $elm.unbind('keydown');
2698
2699               ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2700                 $elm.unbind(eventName);
2701               });
2702             });
2703
2704             // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2705             function update() {
2706               var ret = '';
2707
2708               var canvasWidth = colContainer.canvasWidth;
2709               var viewportWidth = colContainer.getViewportWidth();
2710
2711               var canvasHeight = rowContainer.getCanvasHeight();
2712
2713               //add additional height for scrollbar on left and right container
2714               //if ($scope.containerId !== 'body') {
2715               //  canvasHeight -= grid.scrollbarHeight;
2716               //}
2717
2718               var viewportHeight = rowContainer.getViewportHeight();
2719               //shorten the height to make room for a scrollbar placeholder
2720               if (colContainer.needsHScrollbarPlaceholder()) {
2721                 viewportHeight -= grid.scrollbarHeight;
2722               }
2723
2724               var headerViewportWidth,
2725                   footerViewportWidth;
2726               headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2727
2728               // Set canvas dimensions
2729               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2730
2731               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2732
2733               if (renderContainer.explicitHeaderCanvasHeight) {
2734                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2735               }
2736               else {
2737                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2738               }
2739
2740               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2741               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2742
2743               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2744               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2745
2746               return ret;
2747             }
2748
2749             uiGridCtrl.grid.registerStyleComputation({
2750               priority: 6,
2751               func: update
2752             });
2753           }
2754         };
2755       }
2756     };
2757
2758   }]);
2759
2760   module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2761
2762   }]);
2763
2764 })();
2765
2766 (function(){
2767   'use strict';
2768
2769   angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2770     return {
2771       replace: true,
2772       // priority: 2001,
2773       // templateUrl: 'ui-grid/ui-grid-row',
2774       require: ['^uiGrid', '^uiGridRenderContainer'],
2775       scope: {
2776          row: '=uiGridRow',
2777          //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2778          rowRenderIndex: '='
2779       },
2780       compile: function() {
2781         return {
2782           pre: function($scope, $elm, $attrs, controllers) {
2783             var uiGridCtrl = controllers[0];
2784             var containerCtrl = controllers[1];
2785
2786             var grid = uiGridCtrl.grid;
2787
2788             $scope.grid = uiGridCtrl.grid;
2789             $scope.colContainer = containerCtrl.colContainer;
2790
2791             // Function for attaching the template to this scope
2792             var clonedElement, cloneScope;
2793             function compileTemplate() {
2794               $scope.row.getRowTemplateFn.then(function (compiledElementFn) {
2795                 // var compiledElementFn = $scope.row.compiledElementFn;
2796
2797                 // Create a new scope for the contents of this row, so we can destroy it later if need be
2798                 var newScope = $scope.$new();
2799
2800                 compiledElementFn(newScope, function (newElm, scope) {
2801                   // If we already have a cloned element, we need to remove it and destroy its scope
2802                   if (clonedElement) {
2803                     clonedElement.remove();
2804                     cloneScope.$destroy();
2805                   }
2806
2807                   // Empty the row and append the new element
2808                   $elm.empty().append(newElm);
2809
2810                   // Save the new cloned element and scope
2811                   clonedElement = newElm;
2812                   cloneScope = newScope;
2813                 });
2814               });
2815             }
2816
2817             // Initially attach the compiled template to this scope
2818             compileTemplate();
2819
2820             // If the row's compiled element function changes, we need to replace this element's contents with the new compiled template
2821             $scope.$watch('row.getRowTemplateFn', function (newFunc, oldFunc) {
2822               if (newFunc !== oldFunc) {
2823                 compileTemplate();
2824               }
2825             });
2826           },
2827           post: function($scope, $elm, $attrs, controllers) {
2828
2829           }
2830         };
2831       }
2832     };
2833   }]);
2834
2835 })();
2836 (function(){
2837 // 'use strict';
2838
2839   /**
2840    * @ngdoc directive
2841    * @name ui.grid.directive:uiGridStyle
2842    * @element style
2843    * @restrict A
2844    *
2845    * @description
2846    * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2847    *
2848    * @example
2849    <doc:example module="app">
2850    <doc:source>
2851    <script>
2852    var app = angular.module('app', ['ui.grid']);
2853
2854    app.controller('MainCtrl', ['$scope', function ($scope) {
2855           $scope.myStyle = '.blah { border: 1px solid }';
2856         }]);
2857    </script>
2858
2859    <div ng-controller="MainCtrl">
2860    <style ui-grid-style>{{ myStyle }}</style>
2861    <span class="blah">I am in a box.</span>
2862    </div>
2863    </doc:source>
2864    <doc:scenario>
2865       it('should apply the right class to the element', function () {
2866         element(by.css('.blah')).getCssValue('border-top-width')
2867           .then(function(c) {
2868             expect(c).toContain('1px');
2869           });
2870       });
2871    </doc:scenario>
2872    </doc:example>
2873    */
2874
2875
2876   angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
2877     return {
2878       // restrict: 'A',
2879       // priority: 1000,
2880       // require: '?^uiGrid',
2881       link: function($scope, $elm, $attrs, uiGridCtrl) {
2882         // gridUtil.logDebug('ui-grid-style link');
2883         // if (uiGridCtrl === undefined) {
2884         //    gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2885         // }
2886
2887         var interpolateFn = $interpolate($elm.text(), true);
2888
2889         if (interpolateFn) {
2890           $scope.$watch(interpolateFn, function(value) {
2891             $elm.text(value);
2892           });
2893         }
2894
2895           // uiGridCtrl.recalcRowStyles = function() {
2896           //   var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2897           //   var rowHeight = scope.options.rowHeight;
2898
2899           //   var ret = '';
2900           //   var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2901           //   for (var i = 1; i <= rowStyleCount; i++) {
2902           //     ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2903           //     offset = offset + rowHeight;
2904           //   }
2905
2906           //   scope.rowStyles = ret;
2907           // };
2908
2909           // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2910
2911       }
2912     };
2913   }]);
2914
2915 })();
2916
2917 (function(){
2918   'use strict';
2919
2920   angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
2921     function(gridUtil, ScrollEvent, uiGridConstants, $log) {
2922       return {
2923         replace: true,
2924         scope: {},
2925         controllerAs: 'Viewport',
2926         templateUrl: 'ui-grid/uiGridViewport',
2927         require: ['^uiGrid', '^uiGridRenderContainer'],
2928         link: function($scope, $elm, $attrs, controllers) {
2929           // gridUtil.logDebug('viewport post-link');
2930
2931           var uiGridCtrl = controllers[0];
2932           var containerCtrl = controllers[1];
2933
2934           $scope.containerCtrl = containerCtrl;
2935
2936           var rowContainer = containerCtrl.rowContainer;
2937           var colContainer = containerCtrl.colContainer;
2938
2939           var grid = uiGridCtrl.grid;
2940
2941           $scope.grid = uiGridCtrl.grid;
2942
2943           // Put the containers in scope so we can get rows and columns from them
2944           $scope.rowContainer = containerCtrl.rowContainer;
2945           $scope.colContainer = containerCtrl.colContainer;
2946
2947           // Register this viewport with its container
2948           containerCtrl.viewport = $elm;
2949
2950
2951           $elm.on('scroll', scrollHandler);
2952
2953           var ignoreScroll = false;
2954
2955           function scrollHandler(evt) {
2956             //Leaving in this commented code in case it can someday be used
2957             //It does improve performance, but because the horizontal scroll is normalized,
2958             //  using this code will lead to the column header getting slightly out of line with columns
2959             //
2960             //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
2961             //  //don't ask for scrollTop if we just set it
2962             //  ignoreScroll = false;
2963             //  return;
2964             //}
2965             //ignoreScroll = true;
2966
2967             var newScrollTop = $elm[0].scrollTop;
2968             var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
2969
2970             var vertScrollPercentage = rowContainer.scrollVertical(newScrollTop);
2971             var horizScrollPercentage = colContainer.scrollHorizontal(newScrollLeft);
2972
2973             var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
2974             scrollEvent.newScrollLeft = newScrollLeft;
2975             scrollEvent.newScrollTop = newScrollTop;
2976             if ( horizScrollPercentage > -1 ){
2977               scrollEvent.x = { percentage: horizScrollPercentage };
2978             }
2979
2980             if ( vertScrollPercentage > -1 ){
2981               scrollEvent.y = { percentage: vertScrollPercentage };
2982             }
2983
2984             grid.scrollContainers($scope.$parent.containerId, scrollEvent);
2985           }
2986
2987           if ($scope.$parent.bindScrollVertical) {
2988             grid.addVerticalScrollSync($scope.$parent.containerId, syncVerticalScroll);
2989           }
2990
2991           if ($scope.$parent.bindScrollHorizontal) {
2992             grid.addHorizontalScrollSync($scope.$parent.containerId, syncHorizontalScroll);
2993             grid.addHorizontalScrollSync($scope.$parent.containerId + 'header', syncHorizontalHeader);
2994             grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
2995           }
2996
2997           function syncVerticalScroll(scrollEvent){
2998             containerCtrl.prevScrollArgs = scrollEvent;
2999             var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3000             $elm[0].scrollTop = newScrollTop;
3001
3002           }
3003
3004           function syncHorizontalScroll(scrollEvent){
3005             containerCtrl.prevScrollArgs = scrollEvent;
3006             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3007             $elm[0].scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3008           }
3009
3010           function syncHorizontalHeader(scrollEvent){
3011             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3012             if (containerCtrl.headerViewport) {
3013               containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3014             }
3015           }
3016
3017           function syncHorizontalFooter(scrollEvent){
3018             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3019             if (containerCtrl.footerViewport) {
3020               containerCtrl.footerViewport.scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3021             }
3022           }
3023
3024
3025         },
3026         controller: ['$scope', function ($scope) {
3027           this.rowStyle = function (index) {
3028             var rowContainer = $scope.rowContainer;
3029             var colContainer = $scope.colContainer;
3030
3031             var styles = {};
3032
3033             if (index === 0 && rowContainer.currentTopRow !== 0) {
3034               // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
3035               var hiddenRowWidth = (rowContainer.currentTopRow) * rowContainer.grid.options.rowHeight;
3036
3037               // return { 'margin-top': hiddenRowWidth + 'px' };
3038               styles['margin-top'] = hiddenRowWidth + 'px';
3039             }
3040
3041             if (colContainer.currentFirstColumn !== 0) {
3042               if (colContainer.grid.isRTL()) {
3043                 styles['margin-right'] = colContainer.columnOffset + 'px';
3044               }
3045               else {
3046                 styles['margin-left'] = colContainer.columnOffset + 'px';
3047               }
3048             }
3049
3050             return styles;
3051           };
3052         }]
3053       };
3054     }
3055   ]);
3056
3057 })();
3058
3059 (function() {
3060
3061 angular.module('ui.grid')
3062 .directive('uiGridVisible', function uiGridVisibleAction() {
3063   return function ($scope, $elm, $attr) {
3064     $scope.$watch($attr.uiGridVisible, function (visible) {
3065         // $elm.css('visibility', visible ? 'visible' : 'hidden');
3066         $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3067     });
3068   };
3069 });
3070
3071 })();
3072 (function () {
3073   'use strict';
3074
3075   angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3076                     '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3077     function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3078               $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3079       // gridUtil.logDebug('ui-grid controller');
3080
3081       var self = this;
3082
3083       self.grid = gridClassFactory.createGrid($scope.uiGrid);
3084
3085       //assign $scope.$parent if appScope not already assigned
3086       self.grid.appScope = self.grid.appScope || $scope.$parent;
3087
3088       $elm.addClass('grid' + self.grid.id);
3089       self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
3090
3091
3092       // angular.extend(self.grid.options, );
3093
3094       //all properties of grid are available on scope
3095       $scope.grid = self.grid;
3096
3097       if ($attrs.uiGridColumns) {
3098         $attrs.$observe('uiGridColumns', function(value) {
3099           self.grid.options.columnDefs = value;
3100           self.grid.buildColumns()
3101             .then(function(){
3102               self.grid.preCompileCellTemplates();
3103
3104               self.grid.refreshCanvas(true);
3105             });
3106         });
3107       }
3108
3109
3110       // if fastWatch is set we watch only the length and the reference, not every individual object
3111       var deregFunctions = [];
3112       if (self.grid.options.fastWatch) {
3113         self.uiGrid = $scope.uiGrid;
3114         if (angular.isString($scope.uiGrid.data)) {
3115           deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
3116           deregFunctions.push( $scope.$parent.$watch(function() {
3117             if ( self.grid.appScope[$scope.uiGrid.data] ){
3118               return self.grid.appScope[$scope.uiGrid.data].length; 
3119             } else {
3120               return undefined;
3121             } 
3122           }, dataWatchFunction) );
3123         } else {
3124           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3125           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, dataWatchFunction) );
3126         }
3127         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3128         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, columnDefsWatchFunction) );
3129       } else {
3130         if (angular.isString($scope.uiGrid.data)) {
3131           deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
3132         } else {
3133           deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3134         }
3135         deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3136       }
3137       
3138
3139       function columnDefsWatchFunction(n, o) {
3140         if (n && n !== o) {
3141           self.grid.options.columnDefs = n;
3142           self.grid.buildColumns({ orderByColumnDefs: true })
3143             .then(function(){
3144
3145               self.grid.preCompileCellTemplates();
3146
3147               self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3148             });
3149         }
3150       }
3151
3152       function dataWatchFunction(newData) {
3153         // gridUtil.logDebug('dataWatch fired');
3154         var promises = [];
3155         
3156         if ( self.grid.options.fastWatch ){
3157           if (angular.isString($scope.uiGrid.data)) {
3158             newData = self.grid.appScope[$scope.uiGrid.data];
3159           } else {
3160             newData = $scope.uiGrid.data;
3161           }
3162         }
3163         
3164         if (newData) {
3165           // columns length is greater than the number of row header columns, which don't count because they're created automatically
3166           var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
3167
3168           if (
3169             // If we have no columns
3170             !hasColumns &&
3171             // ... and we don't have a ui-grid-columns attribute, which would define columns for us
3172             !$attrs.uiGridColumns &&
3173             // ... and we have no pre-defined columns
3174             self.grid.options.columnDefs.length === 0 &&
3175             // ... but we DO have data
3176             newData.length > 0
3177           ) {
3178             // ... then build the column definitions from the data that we have
3179             self.grid.buildColumnDefsFromData(newData);
3180           }
3181
3182           // If we haven't built columns before and either have some columns defined or some data defined
3183           if (!hasColumns && (self.grid.options.columnDefs.length > 0 || newData.length > 0)) {
3184             // Build the column set, then pre-compile the column cell templates
3185             promises.push(self.grid.buildColumns()
3186               .then(function() {
3187                 self.grid.preCompileCellTemplates();
3188               }));
3189           }
3190
3191           $q.all(promises).then(function() {
3192             self.grid.modifyRows(newData)
3193               .then(function () {
3194                 // if (self.viewport) {
3195                   self.grid.redrawInPlace(true);
3196                 // }
3197
3198                 $scope.$evalAsync(function() {
3199                   self.grid.refreshCanvas(true);
3200                   self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
3201                 });
3202               });
3203           });
3204         }
3205       }
3206
3207       var styleWatchDereg = $scope.$watch(function () { return self.grid.styleComputations; }, function() {
3208         self.grid.refreshCanvas(true);
3209       });
3210
3211       $scope.$on('$destroy', function() {
3212         deregFunctions.forEach( function( deregFn ){ deregFn(); });
3213         styleWatchDereg();
3214       });
3215
3216       self.fireEvent = function(eventName, args) {
3217         // Add the grid to the event arguments if it's not there
3218         if (typeof(args) === 'undefined' || args === undefined) {
3219           args = {};
3220         }
3221
3222         if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3223           args.grid = self.grid;
3224         }
3225
3226         $scope.$broadcast(eventName, args);
3227       };
3228
3229       self.innerCompile = function innerCompile(elm) {
3230         $compile(elm)($scope);
3231       };
3232
3233     }]);
3234
3235 /**
3236  *  @ngdoc directive
3237  *  @name ui.grid.directive:uiGrid
3238  *  @element div
3239  *  @restrict EA
3240  *  @param {Object} uiGrid Options for the grid to use
3241  *
3242  *  @description Create a very basic grid.
3243  *
3244  *  @example
3245     <example module="app">
3246       <file name="app.js">
3247         var app = angular.module('app', ['ui.grid']);
3248
3249         app.controller('MainCtrl', ['$scope', function ($scope) {
3250           $scope.data = [
3251             { name: 'Bob', title: 'CEO' },
3252             { name: 'Frank', title: 'Lowly Developer' }
3253           ];
3254         }]);
3255       </file>
3256       <file name="index.html">
3257         <div ng-controller="MainCtrl">
3258           <div ui-grid="{ data: data }"></div>
3259         </div>
3260       </file>
3261     </example>
3262  */
3263 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
3264
3265 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3266 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3267   return {
3268     templateUrl: 'ui-grid/ui-grid',
3269     scope: {
3270       uiGrid: '='
3271     },
3272     replace: true,
3273     transclude: true,
3274     controller: 'uiGridController',
3275     compile: function () {
3276       return {
3277         post: function ($scope, $elm, $attrs, uiGridCtrl) {
3278           var grid = uiGridCtrl.grid;
3279           // Initialize scrollbars (TODO: move to controller??)
3280           uiGridCtrl.scrollbars = [];
3281           grid.element = $elm;
3282
3283
3284           // See if the grid has a rendered width, if not, wait a bit and try again
3285           var sizeCheckInterval = 100; // ms
3286           var maxSizeChecks = 20; // 2 seconds total
3287           var sizeChecks = 0;
3288
3289           // Setup (event listeners) the grid
3290           setup();
3291
3292           // And initialize it
3293           init();
3294
3295           // Mark rendering complete so API events can happen
3296           grid.renderingComplete();
3297
3298           // If the grid doesn't have size currently, wait for a bit to see if it gets size
3299           checkSize();
3300
3301           /*-- Methods --*/
3302
3303           function checkSize() {
3304             // If the grid has no width and we haven't checked more than <maxSizeChecks> times, check again in <sizeCheckInterval> milliseconds
3305             if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
3306               setTimeout(checkSize, sizeCheckInterval);
3307               sizeChecks++;
3308             }
3309             else {
3310               $timeout(init);
3311             }
3312           }
3313
3314           // Setup event listeners and watchers
3315           function setup() {
3316             // Bind to window resize events
3317             angular.element($window).on('resize', gridResize);
3318
3319             // Unbind from window resize events when the grid is destroyed
3320             $elm.on('$destroy', function () {
3321               angular.element($window).off('resize', gridResize);
3322             });
3323
3324             // If we add a left container after render, we need to watch and react
3325             $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3326               if (newValue === oldValue) {
3327                 return;
3328               }
3329               grid.refreshCanvas(true);
3330             });
3331
3332             // If we add a right container after render, we need to watch and react
3333             $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3334               if (newValue === oldValue) {
3335                 return;
3336               }
3337               grid.refreshCanvas(true);
3338             });
3339           }
3340
3341           // Initialize the directive
3342           function init() {
3343             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3344
3345             // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3346             grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3347
3348             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3349
3350             // If the grid isn't tall enough to fit a single row, it's kind of useless. Resize it to fit a minimum number of rows
3351             if (grid.gridHeight < grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3352               autoAdjustHeight();
3353             }
3354
3355             // Run initial canvas refresh
3356             grid.refreshCanvas(true);
3357           }
3358
3359           // Set the grid's height ourselves in the case that its height would be unusably small
3360           function autoAdjustHeight() {
3361             // Figure out the new height
3362             var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3363             var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
3364             var footerHeight = grid.calcFooterHeight();
3365             
3366             var scrollbarHeight = 0;
3367             if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3368               scrollbarHeight = gridUtil.getScrollbarWidth();
3369             }
3370
3371             var maxNumberOfFilters = 0;
3372             // Calculates the maximum number of filters in the columns
3373             angular.forEach(grid.options.columnDefs, function(col) {
3374               if (col.hasOwnProperty('filter')) {
3375                 if (maxNumberOfFilters < 1) {
3376                     maxNumberOfFilters = 1;
3377                 }
3378               }
3379               else if (col.hasOwnProperty('filters')) {
3380                 if (maxNumberOfFilters < col.filters.length) {
3381                     maxNumberOfFilters = col.filters.length;
3382                 }
3383               }
3384             });
3385
3386             if (grid.options.enableFiltering) {
3387               var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.every(function(col) {
3388                 return col.enableFiltering === false;
3389               });
3390
3391               if (!allColumnsHaveFilteringTurnedOff) {
3392                 maxNumberOfFilters++;
3393               }
3394             }
3395
3396             var filterHeight = maxNumberOfFilters * headerHeight;
3397
3398             var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3399
3400             $elm.css('height', newHeight + 'px');
3401
3402             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3403           }
3404
3405           // Resize the grid on window resize events
3406           function gridResize($event) {
3407             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3408             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3409
3410             grid.refreshCanvas(true);
3411           }
3412         }
3413       };
3414     }
3415   };
3416 }
3417
3418 })();
3419
3420 (function(){
3421   'use strict';
3422
3423   // TODO: rename this file to ui-grid-pinned-container.js
3424
3425   angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3426     return {
3427       restrict: 'EA',
3428       replace: true,
3429       template: '<div class="ui-grid-pinned-container"><div ui-grid-render-container container-id="side" row-container-name="\'body\'" col-container-name="side" bind-scroll-vertical="true" class="{{ side }} ui-grid-render-container-{{ side }}"></div></div>',
3430       scope: {
3431         side: '=uiGridPinnedContainer'
3432       },
3433       require: '^uiGrid',
3434       compile: function compile() {
3435         return {
3436           post: function ($scope, $elm, $attrs, uiGridCtrl) {
3437             // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3438
3439             var grid = uiGridCtrl.grid;
3440
3441             var myWidth = 0;
3442
3443             $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3444
3445             // Monkey-patch the viewport width function
3446             if ($scope.side === 'left' || $scope.side === 'right') {
3447               grid.renderContainers[$scope.side].getViewportWidth = monkeyPatchedGetViewportWidth;
3448             }
3449
3450             function monkeyPatchedGetViewportWidth() {
3451               /*jshint validthis: true */
3452               var self = this;
3453
3454               var viewportWidth = 0;
3455               self.visibleColumnCache.forEach(function (column) {
3456                 viewportWidth += column.drawnWidth;
3457               });
3458
3459               var adjustment = self.getViewportAdjustment();
3460
3461               viewportWidth = viewportWidth + adjustment.width;
3462
3463               return viewportWidth;
3464             }
3465
3466             function updateContainerWidth() {
3467               if ($scope.side === 'left' || $scope.side === 'right') {
3468                 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3469                 var width = 0;
3470                 for (var i = 0; i < cols.length; i++) {
3471                   var col = cols[i];
3472                   width += col.drawnWidth || col.width || 0;
3473                 }
3474
3475                 return width;
3476               }
3477             }
3478
3479             function updateContainerDimensions() {
3480               var ret = '';
3481
3482               // Column containers
3483               if ($scope.side === 'left' || $scope.side === 'right') {
3484                 myWidth = updateContainerWidth();
3485
3486                 // gridUtil.logDebug('myWidth', myWidth);
3487
3488                 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3489                 $elm.attr('style', null);
3490
3491              //   var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3492
3493                 ret += '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ', .grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ' .ui-grid-render-container-' + $scope.side + ' .ui-grid-viewport { width: ' + myWidth + 'px; } ';
3494               }
3495
3496               return ret;
3497             }
3498
3499             grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3500               myWidth = updateContainerWidth();
3501
3502               // Subtract our own width
3503               adjustment.width -= myWidth;
3504               adjustment.side = $scope.side;
3505
3506               return adjustment;
3507             });
3508
3509             // Register style computation to adjust for columns in `side`'s render container
3510             grid.registerStyleComputation({
3511               priority: 15,
3512               func: updateContainerDimensions
3513             });
3514           }
3515         };
3516       }
3517     };
3518   }]);
3519 })();
3520
3521 (function(){
3522
3523 angular.module('ui.grid')
3524 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
3525     function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout, ScrollEvent) {
3526
3527   /**
3528    * @ngdoc object
3529    * @name ui.grid.core.api:PublicApi
3530    * @description Public Api for the core grid features
3531    *
3532    */
3533
3534   /**
3535    * @ngdoc function
3536    * @name ui.grid.class:Grid
3537    * @description Grid is the main viewModel.  Any properties or methods needed to maintain state are defined in
3538    * this prototype.  One instance of Grid is created per Grid directive instance.
3539    * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3540    */
3541   var Grid = function Grid(options) {
3542     var self = this;
3543     // Get the id out of the options, then remove it
3544     if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3545       if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3546         throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3547       }
3548     }
3549     else {
3550       throw new Error('No ID provided. An ID must be given when creating a grid.');
3551     }
3552
3553     self.id = options.id;
3554     delete options.id;
3555
3556     // Get default options
3557     self.options = GridOptions.initialize( options );
3558
3559     /**
3560      * @ngdoc object
3561      * @name appScope
3562      * @propertyOf ui.grid.class:Grid
3563      * @description reference to the application scope (the parent scope of the ui-grid element).  Assigned in ui-grid controller
3564      * <br/>
3565      * use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
3566      */
3567     self.appScope = self.options.appScopeProvider;
3568
3569     self.headerHeight = self.options.headerRowHeight;
3570
3571
3572     /**
3573      * @ngdoc object
3574      * @name footerHeight
3575      * @propertyOf ui.grid.class:Grid
3576      * @description returns the total footer height gridFooter + columnFooter
3577      */
3578     self.footerHeight = self.calcFooterHeight();
3579
3580
3581     /**
3582      * @ngdoc object
3583      * @name columnFooterHeight
3584      * @propertyOf ui.grid.class:Grid
3585      * @description returns the total column footer height
3586      */
3587     self.columnFooterHeight = self.calcColumnFooterHeight();
3588
3589     self.rtl = false;
3590     self.gridHeight = 0;
3591     self.gridWidth = 0;
3592     self.columnBuilders = [];
3593     self.rowBuilders = [];
3594     self.rowsProcessors = [];
3595     self.columnsProcessors = [];
3596     self.styleComputations = [];
3597     self.viewportAdjusters = [];
3598     self.rowHeaderColumns = [];
3599     self.dataChangeCallbacks = {};
3600     self.verticalScrollSyncCallBackFns = {};
3601     self.horizontalScrollSyncCallBackFns = {};
3602
3603     // self.visibleRowCache = [];
3604
3605     // Set of 'render' containers for self grid, which can render sets of rows
3606     self.renderContainers = {};
3607
3608     // Create a
3609     self.renderContainers.body = new GridRenderContainer('body', self);
3610
3611     self.cellValueGetterCache = {};
3612
3613     // Cached function to use with custom row templates
3614     self.getRowTemplateFn = null;
3615
3616
3617     //representation of the rows on the grid.
3618     //these are wrapped references to the actual data rows (options.data)
3619     self.rows = [];
3620
3621     //represents the columns on the grid
3622     self.columns = [];
3623
3624     /**
3625      * @ngdoc boolean
3626      * @name isScrollingVertically
3627      * @propertyOf ui.grid.class:Grid
3628      * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3629      */
3630     self.isScrollingVertically = false;
3631
3632     /**
3633      * @ngdoc boolean
3634      * @name isScrollingHorizontally
3635      * @propertyOf ui.grid.class:Grid
3636      * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3637      */
3638     self.isScrollingHorizontally = false;
3639
3640     /**
3641      * @ngdoc property
3642      * @name scrollDirection
3643      * @propertyOf ui.grid.class:Grid
3644      * @description set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells
3645      * us which direction we are scrolling. Set to NONE via debounced method
3646      */
3647     self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3648
3649     //if true, grid will not respond to any scroll events
3650     self.disableScrolling = false;
3651
3652
3653     function vertical (scrollEvent) {
3654       self.isScrollingVertically = false;
3655       self.api.core.raise.scrollEnd(scrollEvent);
3656       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3657     }
3658
3659     var debouncedVertical = gridUtil.debounce(vertical, self.options.scrollDebounce);
3660     var debouncedVerticalMinDelay = gridUtil.debounce(vertical, 0);
3661
3662     function horizontal (scrollEvent) {
3663       self.isScrollingHorizontally = false;
3664       self.api.core.raise.scrollEnd(scrollEvent);
3665       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3666     }
3667
3668     var debouncedHorizontal = gridUtil.debounce(horizontal, self.options.scrollDebounce);
3669     var debouncedHorizontalMinDelay = gridUtil.debounce(horizontal, 0);
3670
3671
3672     /**
3673      * @ngdoc function
3674      * @name flagScrollingVertically
3675      * @methodOf ui.grid.class:Grid
3676      * @description sets isScrollingVertically to true and sets it to false in a debounced function
3677      */
3678     self.flagScrollingVertically = function(scrollEvent) {
3679       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3680         self.api.core.raise.scrollBegin(scrollEvent);
3681       }
3682       self.isScrollingVertically = true;
3683       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3684         debouncedVerticalMinDelay(scrollEvent);
3685       }
3686       else {
3687         debouncedVertical(scrollEvent);
3688       }
3689     };
3690
3691     /**
3692      * @ngdoc function
3693      * @name flagScrollingHorizontally
3694      * @methodOf ui.grid.class:Grid
3695      * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3696      */
3697     self.flagScrollingHorizontally = function(scrollEvent) {
3698       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3699         self.api.core.raise.scrollBegin(scrollEvent);
3700       }
3701       self.isScrollingHorizontally = true;
3702       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3703         debouncedHorizontalMinDelay(scrollEvent);
3704       }
3705       else {
3706         debouncedHorizontal(scrollEvent);
3707       }
3708     };
3709
3710     self.scrollbarHeight = 0;
3711     self.scrollbarWidth = 0;
3712     if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3713       self.scrollbarHeight = gridUtil.getScrollbarWidth();
3714     }
3715
3716     if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3717       self.scrollbarWidth = gridUtil.getScrollbarWidth();
3718     }
3719
3720
3721
3722     self.api = new GridApi(self);
3723
3724     /**
3725      * @ngdoc function
3726      * @name refresh
3727      * @methodOf ui.grid.core.api:PublicApi
3728      * @description Refresh the rendered grid on screen.
3729      * The refresh method re-runs both the columnProcessors and the
3730      * rowProcessors, as well as calling refreshCanvas to update all
3731      * the grid sizing.  In general you should prefer to use queueGridRefresh
3732      * instead, which is basically a debounced version of refresh.
3733      *
3734      * If you only want to resize the grid, not regenerate all the rows
3735      * and columns, you should consider directly calling refreshCanvas instead.
3736      *
3737      */
3738     self.api.registerMethod( 'core', 'refresh', this.refresh );
3739
3740     /**
3741      * @ngdoc function
3742      * @name queueGridRefresh
3743      * @methodOf ui.grid.core.api:PublicApi
3744      * @description Request a refresh of the rendered grid on screen, if multiple
3745      * calls to queueGridRefresh are made within a digest cycle only one will execute.
3746      * The refresh method re-runs both the columnProcessors and the
3747      * rowProcessors, as well as calling refreshCanvas to update all
3748      * the grid sizing.  In general you should prefer to use queueGridRefresh
3749      * instead, which is basically a debounced version of refresh.
3750      *
3751      */
3752     self.api.registerMethod( 'core', 'queueGridRefresh', this.queueGridRefresh );
3753
3754     /**
3755      * @ngdoc function
3756      * @name refreshRows
3757      * @methodOf ui.grid.core.api:PublicApi
3758      * @description Runs only the rowProcessors, columns remain as they were.
3759      * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
3760      * @returns {promise} promise that is resolved when render completes?
3761      *
3762      */
3763     self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3764
3765     /**
3766      * @ngdoc function
3767      * @name queueRefresh
3768      * @methodOf ui.grid.core.api:PublicApi
3769      * @description Requests execution of refreshCanvas, if multiple requests are made
3770      * during a digest cycle only one will run.  RefreshCanvas updates the grid sizing.
3771      * @returns {promise} promise that is resolved when render completes?
3772      *
3773      */
3774     self.api.registerMethod( 'core', 'queueRefresh', this.queueRefresh );
3775
3776     /**
3777      * @ngdoc function
3778      * @name handleWindowResize
3779      * @methodOf ui.grid.core.api:PublicApi
3780      * @description Trigger a grid resize, normally this would be picked
3781      * up by a watch on window size, but in some circumstances it is necessary
3782      * to call this manually
3783      * @returns {promise} promise that is resolved when render completes?
3784      *
3785      */
3786     self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3787
3788
3789     /**
3790      * @ngdoc function
3791      * @name addRowHeaderColumn
3792      * @methodOf ui.grid.core.api:PublicApi
3793      * @description adds a row header column to the grid
3794      * @param {object} column def
3795      *
3796      */
3797     self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
3798
3799     /**
3800      * @ngdoc function
3801      * @name scrollToIfNecessary
3802      * @methodOf ui.grid.core.api:PublicApi
3803      * @description Scrolls the grid to make a certain row and column combo visible,
3804      *   in the case that it is not completely visible on the screen already.
3805      * @param {GridRow} gridRow row to make visible
3806      * @param {GridCol} gridCol column to make visible
3807      * @returns {promise} a promise that is resolved when scrolling is complete
3808      *
3809      */
3810     self.api.registerMethod( 'core', 'scrollToIfNecessary', function(gridRow, gridCol) { return self.scrollToIfNecessary(gridRow, gridCol);} );
3811
3812     /**
3813      * @ngdoc function
3814      * @name scrollTo
3815      * @methodOf ui.grid.core.api:PublicApi
3816      * @description Scroll the grid such that the specified
3817      * row and column is in view
3818      * @param {object} rowEntity gridOptions.data[] array instance to make visible
3819      * @param {object} colDef to make visible
3820      * @returns {promise} a promise that is resolved after any scrolling is finished
3821      */
3822     self.api.registerMethod( 'core', 'scrollTo', function (rowEntity, colDef) { return self.scrollTo(rowEntity, colDef);}  );
3823
3824     /**
3825      * @ngdoc function
3826      * @name registerRowsProcessor
3827      * @methodOf ui.grid.core.api:PublicApi
3828      * @description
3829      * Register a "rows processor" function. When the rows are updated,
3830      * the grid calls each registered "rows processor", which has a chance
3831      * to alter the set of rows (sorting, etc) as long as the count is not
3832      * modified.
3833      *
3834      * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
3835      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3836      * return the updated rows list, which is passed to the next processor in the chain
3837      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3838      * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
3839      *
3840      * At present allRowsVisible is running at 50, sort manipulations running at 60-65, filter is running at 100,
3841      * sort is at 200, grouping and treeview at 400-410, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
3842      */
3843     self.api.registerMethod( 'core', 'registerRowsProcessor', this.registerRowsProcessor  );
3844
3845     /**
3846      * @ngdoc function
3847      * @name registerColumnsProcessor
3848      * @methodOf ui.grid.core.api:PublicApi
3849      * @description
3850      * Register a "columns processor" function. When the columns are updated,
3851      * the grid calls each registered "columns processor", which has a chance
3852      * to alter the set of columns as long as the count is not
3853      * modified.
3854      *
3855      * @param {function(renderedColumnsToProcess, rows )} processorFunction columns processor function, which
3856      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3857      * return the updated columns list, which is passed to the next processor in the chain
3858      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3859      * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
3860      *
3861      * At present allRowsVisible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
3862      */
3863     self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor  );
3864
3865
3866
3867     /**
3868      * @ngdoc function
3869      * @name sortHandleNulls
3870      * @methodOf ui.grid.core.api:PublicApi
3871      * @description A null handling method that can be used when building custom sort
3872      * functions
3873      * @example
3874      * <pre>
3875      *   mySortFn = function(a, b) {
3876      *   var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3877      *   if ( nulls !== null ){
3878      *     return nulls;
3879      *   } else {
3880      *     // your code for sorting here
3881      *   };
3882      * </pre>
3883      * @param {object} a sort value a
3884      * @param {object} b sort value b
3885      * @returns {number} null if there were no nulls/undefineds, otherwise returns
3886      * a sort value that should be passed back from the sort function
3887      *
3888      */
3889     self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
3890
3891
3892     /**
3893      * @ngdoc function
3894      * @name sortChanged
3895      * @methodOf  ui.grid.core.api:PublicApi
3896      * @description The sort criteria on one or more columns has
3897      * changed.  Provides as parameters the grid and the output of
3898      * getColumnSorting, which is an array of gridColumns
3899      * that have sorting on them, sorted in priority order.
3900      *
3901      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3902      * @param {Function} callBack Will be called when the event is emited. The function passes back an array of columns with
3903      * sorts on them, in priority order.
3904      *
3905      * @example
3906      * <pre>
3907      *      gridApi.core.on.sortChanged( $scope, function(sortColumns){
3908      *        // do something
3909      *      });
3910      * </pre>
3911      */
3912     self.api.registerEvent( 'core', 'sortChanged' );
3913
3914       /**
3915      * @ngdoc function
3916      * @name columnVisibilityChanged
3917      * @methodOf  ui.grid.core.api:PublicApi
3918      * @description The visibility of a column has changed,
3919      * the column itself is passed out as a parameter of the event.
3920      * 
3921      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3922      * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
3923      *
3924      * @example
3925      * <pre>
3926      *      gridApi.core.on.columnVisibilityChanged( $scope, function (column) {
3927      *        // do something
3928      *      } );
3929      * </pre>
3930      */
3931     self.api.registerEvent( 'core', 'columnVisibilityChanged' );
3932
3933     /**
3934      * @ngdoc method
3935      * @name notifyDataChange
3936      * @methodOf ui.grid.core.api:PublicApi
3937      * @description Notify the grid that a data or config change has occurred,
3938      * where that change isn't something the grid was otherwise noticing.  This
3939      * might be particularly relevant where you've changed values within the data
3940      * and you'd like cell classes to be re-evaluated, or changed config within
3941      * the columnDef and you'd like headerCellClasses to be re-evaluated.
3942      * @param {string} type one of the
3943      * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3944      * us which refreshes to fire.
3945      *
3946      */
3947     self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
3948
3949     /**
3950      * @ngdoc method
3951      * @name clearAllFilters
3952      * @methodOf ui.grid.core.api:PublicApi
3953      * @description Clears all filters and optionally refreshes the visible rows.
3954      * @param {object} refreshRows Defaults to true.
3955      * @param {object} clearConditions Defaults to false.
3956      * @param {object} clearFlags Defaults to false.
3957      * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
3958      */
3959     self.api.registerMethod('core', 'clearAllFilters', this.clearAllFilters);
3960
3961     self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
3962     self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
3963     self.registerDataChangeCallback( self.updateFooterHeightCallback, [uiGridConstants.dataChange.OPTIONS]);
3964
3965     self.registerStyleComputation({
3966       priority: 10,
3967       func: self.getFooterStyles
3968     });
3969   };
3970
3971    Grid.prototype.calcFooterHeight = function () {
3972      if (!this.hasFooter()) {
3973        return 0;
3974      }
3975
3976      var height = 0;
3977      if (this.options.showGridFooter) {
3978        height += this.options.gridFooterHeight;
3979      }
3980
3981      height += this.calcColumnFooterHeight();
3982
3983      return height;
3984    };
3985
3986    Grid.prototype.calcColumnFooterHeight = function () {
3987      var height = 0;
3988
3989      if (this.options.showColumnFooter) {
3990        height += this.options.columnFooterHeight;
3991      }
3992
3993      return height;
3994    };
3995
3996    Grid.prototype.getFooterStyles = function () {
3997      var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }';
3998      style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }';
3999      return style;
4000    };
4001
4002   Grid.prototype.hasFooter = function () {
4003    return this.options.showGridFooter || this.options.showColumnFooter;
4004   };
4005
4006   /**
4007    * @ngdoc function
4008    * @name isRTL
4009    * @methodOf ui.grid.class:Grid
4010    * @description Returns true if grid is RightToLeft
4011    */
4012   Grid.prototype.isRTL = function () {
4013     return this.rtl;
4014   };
4015
4016
4017   /**
4018    * @ngdoc function
4019    * @name registerColumnBuilder
4020    * @methodOf ui.grid.class:Grid
4021    * @description When the build creates columns from column definitions, the columnbuilders will be called to add
4022    * additional properties to the column.
4023    * @param {function(colDef, col, gridOptions)} columnBuilder function to be called
4024    */
4025   Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
4026     this.columnBuilders.push(columnBuilder);
4027   };
4028
4029   /**
4030    * @ngdoc function
4031    * @name buildColumnDefsFromData
4032    * @methodOf ui.grid.class:Grid
4033    * @description Populates columnDefs from the provided data
4034    * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
4035    */
4036   Grid.prototype.buildColumnDefsFromData = function (dataRows){
4037     this.options.columnDefs =  gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
4038   };
4039
4040   /**
4041    * @ngdoc function
4042    * @name registerRowBuilder
4043    * @methodOf ui.grid.class:Grid
4044    * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
4045    * additional properties to the row.
4046    * @param {function(row, gridOptions)} rowBuilder function to be called
4047    */
4048   Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
4049     this.rowBuilders.push(rowBuilder);
4050   };
4051
4052
4053   /**
4054    * @ngdoc function
4055    * @name registerDataChangeCallback
4056    * @methodOf ui.grid.class:Grid
4057    * @description When a data change occurs, the data change callbacks of the specified type
4058    * will be called.  The rules are:
4059    *
4060    * - when the data watch fires, that is considered a ROW change (the data watch only notices
4061    *   added or removed rows)
4062    * - when the api is called to inform us of a change, the declared type of that change is used
4063    * - when a cell edit completes, the EDIT callbacks are triggered
4064    * - when the columnDef watch fires, the COLUMN callbacks are triggered
4065    * - when the options watch fires, the OPTIONS callbacks are triggered
4066    *
4067    * For a given event:
4068    * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks
4069    * - ROW calls ROW and ALL callbacks
4070    * - EDIT calls EDIT and ALL callbacks
4071    * - COLUMN calls COLUMN and ALL callbacks
4072    * - OPTIONS calls OPTIONS and ALL callbacks
4073    *
4074    * @param {function(grid)} callback function to be called
4075    * @param {array} types the types of data change you want to be informed of.  Values from
4076    * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ).  Optional and defaults to
4077    * ALL
4078    * @returns {function} deregister function - a function that can be called to deregister this callback
4079    */
4080   Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4081     var uid = gridUtil.nextUid();
4082     if ( !types ){
4083       types = [uiGridConstants.dataChange.ALL];
4084     }
4085     if ( !Array.isArray(types)){
4086       gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
4087     }
4088     this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4089
4090     var self = this;
4091     var deregisterFunction = function() {
4092       delete self.dataChangeCallbacks[uid];
4093     };
4094     return deregisterFunction;
4095   };
4096
4097   /**
4098    * @ngdoc function
4099    * @name callDataChangeCallbacks
4100    * @methodOf ui.grid.class:Grid
4101    * @description Calls the callbacks based on the type of data change that
4102    * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
4103    * event type is matching, or if the type is ALL.
4104    * @param {number} type the type of event that occurred - one of the
4105    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4106    */
4107   Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4108     angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4109       if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
4110            callback.types.indexOf( type ) !== -1 ||
4111            type === uiGridConstants.dataChange.ALL ) {
4112         if (callback._this) {
4113            callback.callback.apply(callback._this,this);
4114         }
4115         else {
4116           callback.callback( this );
4117         }
4118       }
4119     }, this);
4120   };
4121
4122   /**
4123    * @ngdoc function
4124    * @name notifyDataChange
4125    * @methodOf ui.grid.class:Grid
4126    * @description Notifies us that a data change has occurred, used in the public
4127    * api for users to tell us when they've changed data or some other event that
4128    * our watches cannot pick up
4129    * @param {string} type the type of event that occurred - one of the
4130    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4131    */
4132   Grid.prototype.notifyDataChange = function notifyDataChange(type) {
4133     var constants = uiGridConstants.dataChange;
4134     if ( type === constants.ALL ||
4135          type === constants.COLUMN ||
4136          type === constants.EDIT ||
4137          type === constants.ROW ||
4138          type === constants.OPTIONS ){
4139       this.callDataChangeCallbacks( type );
4140     } else {
4141       gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
4142     }
4143   };
4144
4145
4146   /**
4147    * @ngdoc function
4148    * @name columnRefreshCallback
4149    * @methodOf ui.grid.class:Grid
4150    * @description refreshes the grid when a column refresh
4151    * is notified, which triggers handling of the visible flag.
4152    * This is called on uiGridConstants.dataChange.COLUMN, and is
4153    * registered as a dataChangeCallback in grid.js
4154    * @param {string} name column name
4155    */
4156   Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4157     grid.buildColumns();
4158     grid.queueGridRefresh();
4159   };
4160
4161
4162   /**
4163    * @ngdoc function
4164    * @name processRowsCallback
4165    * @methodOf ui.grid.class:Grid
4166    * @description calls the row processors, specifically
4167    * intended to reset the sorting when an edit is called,
4168    * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4169    * @param {string} name column name
4170    */
4171   Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4172     grid.queueGridRefresh();
4173   };
4174
4175
4176   /**
4177    * @ngdoc function
4178    * @name updateFooterHeightCallback
4179    * @methodOf ui.grid.class:Grid
4180    * @description recalculates the footer height,
4181    * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4182    * @param {string} name column name
4183    */
4184   Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4185     grid.footerHeight = grid.calcFooterHeight();
4186     grid.columnFooterHeight = grid.calcColumnFooterHeight();
4187   };
4188
4189
4190   /**
4191    * @ngdoc function
4192    * @name getColumn
4193    * @methodOf ui.grid.class:Grid
4194    * @description returns a grid column for the column name
4195    * @param {string} name column name
4196    */
4197   Grid.prototype.getColumn = function getColumn(name) {
4198     var columns = this.columns.filter(function (column) {
4199       return column.colDef.name === name;
4200     });
4201     return columns.length > 0 ? columns[0] : null;
4202   };
4203
4204   /**
4205    * @ngdoc function
4206    * @name getColDef
4207    * @methodOf ui.grid.class:Grid
4208    * @description returns a grid colDef for the column name
4209    * @param {string} name column.field
4210    */
4211   Grid.prototype.getColDef = function getColDef(name) {
4212     var colDefs = this.options.columnDefs.filter(function (colDef) {
4213       return colDef.name === name;
4214     });
4215     return colDefs.length > 0 ? colDefs[0] : null;
4216   };
4217
4218   /**
4219    * @ngdoc function
4220    * @name assignTypes
4221    * @methodOf ui.grid.class:Grid
4222    * @description uses the first row of data to assign colDef.type for any types not defined.
4223    */
4224   /**
4225    * @ngdoc property
4226    * @name type
4227    * @propertyOf ui.grid.class:GridOptions.columnDef
4228    * @description the type of the column, used in sorting.  If not provided then the
4229    * grid will guess the type.  Add this only if the grid guessing is not to your
4230    * satisfaction.  One of:
4231    * - 'string'
4232    * - 'boolean'
4233    * - 'number'
4234    * - 'date'
4235    * - 'object'
4236    * - 'numberStr'
4237    * Note that if you choose date, your dates should be in a javascript date type
4238    *
4239    */
4240   Grid.prototype.assignTypes = function(){
4241     var self = this;
4242     self.options.columnDefs.forEach(function (colDef, index) {
4243
4244       //Assign colDef type if not specified
4245       if (!colDef.type) {
4246         var col = new GridColumn(colDef, index, self);
4247         var firstRow = self.rows.length > 0 ? self.rows[0] : null;
4248         if (firstRow) {
4249           colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
4250         }
4251         else {
4252           colDef.type = 'string';
4253         }
4254       }
4255     });
4256   };
4257
4258
4259   /**
4260    * @ngdoc function
4261    * @name isRowHeaderColumn
4262    * @methodOf ui.grid.class:Grid
4263    * @description returns true if the column is a row Header
4264    * @param {object} column column
4265    */
4266   Grid.prototype.isRowHeaderColumn = function isRowHeaderColumn(column) {
4267     return this.rowHeaderColumns.indexOf(column) !== -1;
4268   };
4269
4270   /**
4271   * @ngdoc function
4272   * @name addRowHeaderColumn
4273   * @methodOf ui.grid.class:Grid
4274   * @description adds a row header column to the grid
4275   * @param {object} column def
4276   */
4277   Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
4278     var self = this;
4279     var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
4280     rowHeaderCol.isRowHeader = true;
4281     if (self.isRTL()) {
4282       self.createRightContainer();
4283       rowHeaderCol.renderContainer = 'right';
4284     }
4285     else {
4286       self.createLeftContainer();
4287       rowHeaderCol.renderContainer = 'left';
4288     }
4289
4290     // relies on the default column builder being first in array, as it is instantiated
4291     // as part of grid creation
4292     self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4293       .then(function(){
4294         rowHeaderCol.enableFiltering = false;
4295         rowHeaderCol.enableSorting = false;
4296         rowHeaderCol.enableHiding = false;
4297         self.rowHeaderColumns.push(rowHeaderCol);
4298         self.buildColumns()
4299           .then( function() {
4300             self.preCompileCellTemplates();
4301             self.queueGridRefresh();
4302           });
4303       });
4304   };
4305
4306   /**
4307    * @ngdoc function
4308    * @name getOnlyDataColumns
4309    * @methodOf ui.grid.class:Grid
4310    * @description returns all columns except for rowHeader columns
4311    */
4312   Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4313     var self = this;
4314     var cols = [];
4315     self.columns.forEach(function (col) {
4316       if (self.rowHeaderColumns.indexOf(col) === -1) {
4317         cols.push(col);
4318       }
4319     });
4320     return cols;
4321   };
4322
4323   /**
4324    * @ngdoc function
4325    * @name buildColumns
4326    * @methodOf ui.grid.class:Grid
4327    * @description creates GridColumn objects from the columnDefinition.  Calls each registered
4328    * columnBuilder to further process the column
4329    * @param {object} options  An object contains options to use when building columns
4330    *
4331    * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
4332    *
4333    * @returns {Promise} a promise to load any needed column resources
4334    */
4335   Grid.prototype.buildColumns = function buildColumns(opts) {
4336     var options = {
4337       orderByColumnDefs: false
4338     };
4339
4340     angular.extend(options, opts);
4341
4342     // gridUtil.logDebug('buildColumns');
4343     var self = this;
4344     var builderPromises = [];
4345     var headerOffset = self.rowHeaderColumns.length;
4346     var i;
4347
4348     // Remove any columns for which a columnDef cannot be found
4349     // Deliberately don't use forEach, as it doesn't like splice being called in the middle
4350     // Also don't cache columns.length, as it will change during this operation
4351     for (i = 0; i < self.columns.length; i++){
4352       if (!self.getColDef(self.columns[i].name)) {
4353         self.columns.splice(i, 1);
4354         i--;
4355       }
4356     }
4357
4358     //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4359     self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
4360       self.columns.unshift(rowHeaderColumn);
4361     });
4362
4363
4364     // look at each column def, and update column properties to match.  If the column def
4365     // doesn't have a column, then splice in a new gridCol
4366     self.options.columnDefs.forEach(function (colDef, index) {
4367       self.preprocessColDef(colDef);
4368       var col = self.getColumn(colDef.name);
4369
4370       if (!col) {
4371         col = new GridColumn(colDef, gridUtil.nextUid(), self);
4372         self.columns.splice(index + headerOffset, 0, col);
4373       }
4374       else {
4375         // tell updateColumnDef that the column was pre-existing
4376         col.updateColumnDef(colDef, false);
4377       }
4378
4379       self.columnBuilders.forEach(function (builder) {
4380         builderPromises.push(builder.call(self, colDef, col, self.options));
4381       });
4382     });
4383
4384     /*** Reorder columns if necessary ***/
4385     if (!!options.orderByColumnDefs) {
4386       // Create a shallow copy of the columns as a cache
4387       var columnCache = self.columns.slice(0);
4388
4389       // We need to allow for the "row headers" when mapping from the column defs array to the columns array
4390       //   If we have a row header in columns[0] and don't account for it   we'll overwrite it with the column in columnDefs[0]
4391
4392       // Go through all the column defs, use the shorter of columns length and colDefs.length because if a user has given two columns the same name then
4393       // columns will be shorter than columnDefs.  In this situation we'll avoid an error, but the user will still get an unexpected result
4394       var len = Math.min(self.options.columnDefs.length, self.columns.length);
4395       for (i = 0; i < len; i++) {
4396         // If the column at this index has a different name than the column at the same index in the column defs...
4397         if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) {
4398           // Replace the one in the cache with the appropriate column
4399           columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name);
4400         }
4401         else {
4402           // Otherwise just copy over the one from the initial columns
4403           columnCache[i + headerOffset] = self.columns[i + headerOffset];
4404         }
4405       }
4406
4407       // Empty out the columns array, non-destructively
4408       self.columns.length = 0;
4409
4410       // And splice in the updated, ordered columns from the cache
4411       Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
4412     }
4413
4414     return $q.all(builderPromises).then(function(){
4415       if (self.rows.length > 0){
4416         self.assignTypes();
4417       }
4418     });
4419   };
4420
4421 /**
4422  * @ngdoc function
4423  * @name preCompileCellTemplates
4424  * @methodOf ui.grid.class:Grid
4425  * @description precompiles all cell templates
4426  */
4427   Grid.prototype.preCompileCellTemplates = function() {
4428     var self = this;
4429
4430     var preCompileTemplate = function( col ) {
4431       var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4432       html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4433
4434       var compiledElementFn = $compile(html);
4435       col.compiledElementFn = compiledElementFn;
4436
4437       if (col.compiledElementFnDefer) {
4438         col.compiledElementFnDefer.resolve(col.compiledElementFn);
4439       }
4440     };
4441
4442     this.columns.forEach(function (col) {
4443       if ( col.cellTemplate ){
4444         preCompileTemplate( col );
4445       } else if ( col.cellTemplatePromise ){
4446         col.cellTemplatePromise.then( function() {
4447           preCompileTemplate( col );
4448         });
4449       }
4450     });
4451   };
4452
4453   /**
4454    * @ngdoc function
4455    * @name getGridQualifiedColField
4456    * @methodOf ui.grid.class:Grid
4457    * @description Returns the $parse-able accessor for a column within its $scope
4458    * @param {GridColumn} col col object
4459    */
4460   Grid.prototype.getQualifiedColField = function (col) {
4461     return 'row.entity.' + gridUtil.preEval(col.field);
4462   };
4463
4464   /**
4465    * @ngdoc function
4466    * @name createLeftContainer
4467    * @methodOf ui.grid.class:Grid
4468    * @description creates the left render container if it doesn't already exist
4469    */
4470   Grid.prototype.createLeftContainer = function() {
4471     if (!this.hasLeftContainer()) {
4472       this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
4473     }
4474   };
4475
4476   /**
4477    * @ngdoc function
4478    * @name createRightContainer
4479    * @methodOf ui.grid.class:Grid
4480    * @description creates the right render container if it doesn't already exist
4481    */
4482   Grid.prototype.createRightContainer = function() {
4483     if (!this.hasRightContainer()) {
4484       this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
4485     }
4486   };
4487
4488   /**
4489    * @ngdoc function
4490    * @name hasLeftContainer
4491    * @methodOf ui.grid.class:Grid
4492    * @description returns true if leftContainer exists
4493    */
4494   Grid.prototype.hasLeftContainer = function() {
4495     return this.renderContainers.left !== undefined;
4496   };
4497
4498   /**
4499    * @ngdoc function
4500    * @name hasRightContainer
4501    * @methodOf ui.grid.class:Grid
4502    * @description returns true if rightContainer exists
4503    */
4504   Grid.prototype.hasRightContainer = function() {
4505     return this.renderContainers.right !== undefined;
4506   };
4507
4508
4509       /**
4510    * undocumented function
4511    * @name preprocessColDef
4512    * @methodOf ui.grid.class:Grid
4513    * @description defaults the name property from field to maintain backwards compatibility with 2.x
4514    * validates that name or field is present
4515    */
4516   Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
4517     var self = this;
4518
4519     if (!colDef.field && !colDef.name) {
4520       throw new Error('colDef.name or colDef.field property is required');
4521     }
4522
4523     //maintain backwards compatibility with 2.x
4524     //field was required in 2.x.  now name is required
4525     if (colDef.name === undefined && colDef.field !== undefined) {
4526       // See if the column name already exists:
4527       var newName = colDef.field,
4528         counter = 2;
4529       while (self.getColumn(newName)) {
4530         newName = colDef.field + counter.toString();
4531         counter++;
4532       }
4533       colDef.name = newName;
4534     }
4535   };
4536
4537   // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters
4538   Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
4539     var self = this;
4540
4541     var t = [];
4542     for (var i = 0; i < n.length; i++) {
4543       var nV = nAccessor ? n[i][nAccessor] : n[i];
4544
4545       var found = false;
4546       for (var j = 0; j < o.length; j++) {
4547         var oV = oAccessor ? o[j][oAccessor] : o[j];
4548         if (self.options.rowEquality(nV, oV)) {
4549           found = true;
4550           break;
4551         }
4552       }
4553       if (!found) {
4554         t.push(nV);
4555       }
4556     }
4557
4558     return t;
4559   };
4560
4561   /**
4562    * @ngdoc function
4563    * @name getRow
4564    * @methodOf ui.grid.class:Grid
4565    * @description returns the GridRow that contains the rowEntity
4566    * @param {object} rowEntity the gridOptions.data array element instance
4567    * @param {array} rows [optional] the rows to look in - if not provided then
4568    * looks in grid.rows
4569    */
4570   Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
4571     var self = this;
4572
4573     lookInRows = typeof(lookInRows) === 'undefined' ? self.rows : lookInRows;
4574
4575     var rows = lookInRows.filter(function (row) {
4576       return self.options.rowEquality(row.entity, rowEntity);
4577     });
4578     return rows.length > 0 ? rows[0] : null;
4579   };
4580
4581
4582   /**
4583    * @ngdoc function
4584    * @name modifyRows
4585    * @methodOf ui.grid.class:Grid
4586    * @description creates or removes GridRow objects from the newRawData array.  Calls each registered
4587    * rowBuilder to further process the row
4588    * @param {array} newRawData Modified set of data
4589    *
4590    * This method aims to achieve three things:
4591    * 1. the resulting rows array is in the same order as the newRawData, we'll call
4592    * rowsProcessors immediately after to sort the data anyway
4593    * 2. if we have row hashing available, we try to use the rowHash to find the row
4594    * 3. no memory leaks - rows that are no longer in newRawData need to be garbage collected
4595    *
4596    * The basic logic flow makes use of the newRawData, oldRows and oldHash, and creates
4597    * the newRows and newHash
4598    *
4599    * ```
4600    * newRawData.forEach newEntity
4601    *   if (hashing enabled)
4602    *     check oldHash for newEntity
4603    *   else
4604    *     look for old row directly in oldRows
4605    *   if !oldRowFound     // must be a new row
4606    *     create newRow
4607    *   append to the newRows and add to newHash
4608    *   run the processors
4609    * ```
4610    * 
4611    * Rows are identified using the hashKey if configured.  If not configured, then rows
4612    * are identified using the gridOptions.rowEquality function
4613    * 
4614    * This method is useful when trying to select rows immediately after loading data without
4615    * using a $timeout/$interval, e.g.:
4616    * 
4617    *   $scope.gridOptions.data =  someData;
4618    *   $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
4619    *   $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4620    * 
4621    * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
4622    * originally selected rows to be re-selected))
4623    */
4624   Grid.prototype.modifyRows = function modifyRows(newRawData) {
4625     var self = this;
4626     var oldRows = self.rows.slice(0);
4627     var oldRowHash = self.rowHashMap || self.createRowHashMap();
4628     self.rowHashMap = self.createRowHashMap();
4629     self.rows.length = 0;
4630
4631     newRawData.forEach( function( newEntity, i ) {
4632       var newRow;
4633       if ( self.options.enableRowHashing ){
4634         // if hashing is enabled, then this row will be in the hash if we already know about it
4635         newRow = oldRowHash.get( newEntity );
4636       } else {
4637         // otherwise, manually search the oldRows to see if we can find this row
4638         newRow = self.getRow(newEntity, oldRows);
4639       }
4640
4641       // if we didn't find the row, it must be new, so create it
4642       if ( !newRow ){
4643         newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
4644       }
4645
4646       self.rows.push( newRow );
4647       self.rowHashMap.put( newEntity, newRow );
4648     });
4649
4650     self.assignTypes();
4651
4652     var p1 = $q.when(self.processRowsProcessors(self.rows))
4653       .then(function (renderableRows) {
4654         return self.setVisibleRows(renderableRows);
4655       });
4656
4657     var p2 = $q.when(self.processColumnsProcessors(self.columns))
4658       .then(function (renderableColumns) {
4659         return self.setVisibleColumns(renderableColumns);
4660       });
4661
4662     return $q.all([p1, p2]);
4663   };
4664
4665
4666   /**
4667    * Private Undocumented Method
4668    * @name addRows
4669    * @methodOf ui.grid.class:Grid
4670    * @description adds the newRawData array of rows to the grid and calls all registered
4671    * rowBuilders. this keyword will reference the grid
4672    */
4673   Grid.prototype.addRows = function addRows(newRawData) {
4674     var self = this;
4675
4676     var existingRowCount = self.rows.length;
4677     for (var i = 0; i < newRawData.length; i++) {
4678       var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4679
4680       if (self.options.enableRowHashing) {
4681         var found = self.rowHashMap.get(newRow.entity);
4682         if (found) {
4683           found.row = newRow;
4684         }
4685       }
4686
4687       self.rows.push(newRow);
4688     }
4689   };
4690
4691   /**
4692    * @ngdoc function
4693    * @name processRowBuilders
4694    * @methodOf ui.grid.class:Grid
4695    * @description processes all RowBuilders for the gridRow
4696    * @param {GridRow} gridRow reference to gridRow
4697    * @returns {GridRow} the gridRow with all additional behavior added
4698    */
4699   Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4700     var self = this;
4701
4702     self.rowBuilders.forEach(function (builder) {
4703       builder.call(self, gridRow, self.options);
4704     });
4705
4706     return gridRow;
4707   };
4708
4709   /**
4710    * @ngdoc function
4711    * @name registerStyleComputation
4712    * @methodOf ui.grid.class:Grid
4713    * @description registered a styleComputation function
4714    *
4715    * If the function returns a value it will be appended into the grid's `<style>` block
4716    * @param {function($scope)} styleComputation function
4717    */
4718   Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4719     this.styleComputations.push(styleComputationInfo);
4720   };
4721
4722
4723   // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4724   // Grid.prototype.registerRowFilter = function(filter) {
4725   //   // TODO(c0bra): validate filter?
4726
4727   //   this.rowFilters.push(filter);
4728   // };
4729
4730   // Grid.prototype.removeRowFilter = function(filter) {
4731   //   var idx = this.rowFilters.indexOf(filter);
4732
4733   //   if (typeof(idx) !== 'undefined' && idx !== undefined) {
4734   //     this.rowFilters.slice(idx, 1);
4735   //   }
4736   // };
4737
4738   // Grid.prototype.processRowFilters = function(rows) {
4739   //   var self = this;
4740   //   self.rowFilters.forEach(function (filter) {
4741   //     filter.call(self, rows);
4742   //   });
4743   // };
4744
4745
4746   /**
4747    * @ngdoc function
4748    * @name registerRowsProcessor
4749    * @methodOf ui.grid.class:Grid
4750    * @description
4751    *
4752    * Register a "rows processor" function. When the rows are updated,
4753    * the grid calls each registered "rows processor", which has a chance
4754    * to alter the set of rows (sorting, etc) as long as the count is not
4755    * modified.
4756    *
4757    * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4758    * is run in the context of the grid (i.e. this for the function will be the grid), and must
4759    * return the updated rows list, which is passed to the next processor in the chain
4760    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4761    * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
4762    *
4763    * At present all rows visible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
4764    *
4765    */
4766   Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor, priority) {
4767     if (!angular.isFunction(processor)) {
4768       throw 'Attempt to register non-function rows processor: ' + processor;
4769     }
4770
4771     this.rowsProcessors.push({processor: processor, priority: priority});
4772     this.rowsProcessors.sort(function sortByPriority( a, b ){
4773       return a.priority - b.priority;
4774     });
4775   };
4776
4777   /**
4778    * @ngdoc function
4779    * @name removeRowsProcessor
4780    * @methodOf ui.grid.class:Grid
4781    * @param {function(renderableRows)} rows processor function
4782    * @description Remove a registered rows processor
4783    */
4784   Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
4785     var idx = -1;
4786     this.rowsProcessors.forEach(function(rowsProcessor, index){
4787       if ( rowsProcessor.processor === processor ){
4788         idx = index;
4789       }
4790     });
4791
4792     if ( idx !== -1 ) {
4793       this.rowsProcessors.splice(idx, 1);
4794     }
4795   };
4796
4797   /**
4798    * Private Undocumented Method
4799    * @name processRowsProcessors
4800    * @methodOf ui.grid.class:Grid
4801    * @param {Array[GridRow]} The array of "renderable" rows
4802    * @param {Array[GridColumn]} The array of columns
4803    * @description Run all the registered rows processors on the array of renderable rows
4804    */
4805   Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
4806     var self = this;
4807
4808     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4809     var myRenderableRows = renderableRows.slice(0);
4810
4811     // Return myRenderableRows with no processing if we have no rows processors
4812     if (self.rowsProcessors.length === 0) {
4813       return $q.when(myRenderableRows);
4814     }
4815
4816     // Counter for iterating through rows processors
4817     var i = 0;
4818
4819     // Promise for when we're done with all the processors
4820     var finished = $q.defer();
4821
4822     // This function will call the processor in self.rowsProcessors at index 'i', and then
4823     //   when done will call the next processor in the list, using the output from the processor
4824     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4825     //
4826     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4827     //   the result.
4828     function startProcessor(i, renderedRowsToProcess) {
4829       // Get the processor at 'i'
4830       var processor = self.rowsProcessors[i].processor;
4831
4832       // Call the processor, passing in the rows to process and the current columns
4833       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4834       return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
4835         .then(function handleProcessedRows(processedRows) {
4836           // Check for errors
4837           if (!processedRows) {
4838             throw "Processor at index " + i + " did not return a set of renderable rows";
4839           }
4840
4841           if (!angular.isArray(processedRows)) {
4842             throw "Processor at index " + i + " did not return an array";
4843           }
4844
4845           // Processor is done, increment the counter
4846           i++;
4847
4848           // If we're not done with the processors, call the next one
4849           if (i <= self.rowsProcessors.length - 1) {
4850             return startProcessor(i, processedRows);
4851           }
4852           // We're done! Resolve the 'finished' promise
4853           else {
4854             finished.resolve(processedRows);
4855           }
4856         });
4857     }
4858
4859     // Start on the first processor
4860     startProcessor(0, myRenderableRows);
4861
4862     return finished.promise;
4863   };
4864
4865   Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
4866     var self = this;
4867
4868     // Reset all the render container row caches
4869     for (var i in self.renderContainers) {
4870       var container = self.renderContainers[i];
4871
4872       container.canvasHeightShouldUpdate = true;
4873
4874       if ( typeof(container.visibleRowCache) === 'undefined' ){
4875         container.visibleRowCache = [];
4876       } else {
4877         container.visibleRowCache.length = 0;
4878       }
4879     }
4880
4881     // rows.forEach(function (row) {
4882     for (var ri = 0; ri < rows.length; ri++) {
4883       var row = rows[ri];
4884
4885       var targetContainer = (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) ? row.renderContainer : 'body';
4886
4887       // If the row is visible
4888       if (row.visible) {
4889         self.renderContainers[targetContainer].visibleRowCache.push(row);
4890       }
4891     }
4892     self.api.core.raise.rowsRendered(this.api);
4893   };
4894
4895   /**
4896    * @ngdoc function
4897    * @name registerColumnsProcessor
4898    * @methodOf ui.grid.class:Grid
4899    * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
4900    * is run in the context of the grid (i.e. this for the function will be the grid), and
4901    * which must return an updated renderedColumnsToProcess which can be passed to the next processor
4902    * in the chain
4903    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4904    * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
4905    *
4906    * At present all rows visible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
4907    * @description
4908
4909      Register a "columns processor" function. When the columns are updated,
4910      the grid calls each registered "columns processor", which has a chance
4911      to alter the set of columns, as long as the count is not modified.
4912    */
4913   Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor, priority) {
4914     if (!angular.isFunction(processor)) {
4915       throw 'Attempt to register non-function rows processor: ' + processor;
4916     }
4917
4918     this.columnsProcessors.push({processor: processor, priority: priority});
4919     this.columnsProcessors.sort(function sortByPriority( a, b ){
4920       return a.priority - b.priority;
4921     });
4922   };
4923
4924   Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
4925     var idx = this.columnsProcessors.indexOf(processor);
4926
4927     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4928       this.columnsProcessors.splice(idx, 1);
4929     }
4930   };
4931
4932   Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
4933     var self = this;
4934
4935     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4936     var myRenderableColumns = renderableColumns.slice(0);
4937
4938     // Return myRenderableRows with no processing if we have no rows processors
4939     if (self.columnsProcessors.length === 0) {
4940       return $q.when(myRenderableColumns);
4941     }
4942
4943     // Counter for iterating through rows processors
4944     var i = 0;
4945
4946     // Promise for when we're done with all the processors
4947     var finished = $q.defer();
4948
4949     // This function will call the processor in self.rowsProcessors at index 'i', and then
4950     //   when done will call the next processor in the list, using the output from the processor
4951     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4952     //
4953     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4954     //   the result.
4955     function startProcessor(i, renderedColumnsToProcess) {
4956       // Get the processor at 'i'
4957       var processor = self.columnsProcessors[i].processor;
4958
4959       // Call the processor, passing in the rows to process and the current columns
4960       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4961       return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
4962         .then(function handleProcessedRows(processedColumns) {
4963           // Check for errors
4964           if (!processedColumns) {
4965             throw "Processor at index " + i + " did not return a set of renderable rows";
4966           }
4967
4968           if (!angular.isArray(processedColumns)) {
4969             throw "Processor at index " + i + " did not return an array";
4970           }
4971
4972           // Processor is done, increment the counter
4973           i++;
4974
4975           // If we're not done with the processors, call the next one
4976           if (i <= self.columnsProcessors.length - 1) {
4977             return startProcessor(i, myRenderableColumns);
4978           }
4979           // We're done! Resolve the 'finished' promise
4980           else {
4981             finished.resolve(myRenderableColumns);
4982           }
4983         });
4984     }
4985
4986     // Start on the first processor
4987     startProcessor(0, myRenderableColumns);
4988
4989     return finished.promise;
4990   };
4991
4992   Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
4993     // gridUtil.logDebug('setVisibleColumns');
4994
4995     var self = this;
4996
4997     // Reset all the render container row caches
4998     for (var i in self.renderContainers) {
4999       var container = self.renderContainers[i];
5000
5001       container.visibleColumnCache.length = 0;
5002     }
5003
5004     for (var ci = 0; ci < columns.length; ci++) {
5005       var column = columns[ci];
5006
5007       // If the column is visible
5008       if (column.visible) {
5009         // If the column has a container specified
5010         if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
5011           self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
5012         }
5013         // If not, put it into the body container
5014         else {
5015           self.renderContainers.body.visibleColumnCache.push(column);
5016         }
5017       }
5018     }
5019   };
5020
5021   /**
5022    * @ngdoc function
5023    * @name handleWindowResize
5024    * @methodOf ui.grid.class:Grid
5025    * @description Triggered when the browser window resizes; automatically resizes the grid
5026    */
5027   Grid.prototype.handleWindowResize = function handleWindowResize($event) {
5028     var self = this;
5029
5030     self.gridWidth = gridUtil.elementWidth(self.element);
5031     self.gridHeight = gridUtil.elementHeight(self.element);
5032
5033     self.queueRefresh();
5034   };
5035
5036   /**
5037    * @ngdoc function
5038    * @name queueRefresh
5039    * @methodOf ui.grid.class:Grid
5040    * @description queues a grid refreshCanvas, a way of debouncing all the refreshes we might otherwise issue
5041    */
5042   Grid.prototype.queueRefresh = function queueRefresh() {
5043     var self = this;
5044
5045     if (self.refreshCanceller) {
5046       $timeout.cancel(self.refreshCanceller);
5047     }
5048
5049     self.refreshCanceller = $timeout(function () {
5050       self.refreshCanvas(true);
5051     });
5052
5053     self.refreshCanceller.then(function () {
5054       self.refreshCanceller = null;
5055     });
5056
5057     return self.refreshCanceller;
5058   };
5059
5060
5061   /**
5062    * @ngdoc function
5063    * @name queueGridRefresh
5064    * @methodOf ui.grid.class:Grid
5065    * @description queues a grid refresh, a way of debouncing all the refreshes we might otherwise issue
5066    */
5067   Grid.prototype.queueGridRefresh = function queueGridRefresh() {
5068     var self = this;
5069
5070     if (self.gridRefreshCanceller) {
5071       $timeout.cancel(self.gridRefreshCanceller);
5072     }
5073
5074     self.gridRefreshCanceller = $timeout(function () {
5075       self.refresh(true);
5076     });
5077
5078     self.gridRefreshCanceller.then(function () {
5079       self.gridRefreshCanceller = null;
5080     });
5081
5082     return self.gridRefreshCanceller;
5083   };
5084
5085
5086   /**
5087    * @ngdoc function
5088    * @name updateCanvasHeight
5089    * @methodOf ui.grid.class:Grid
5090    * @description flags all render containers to update their canvas height
5091    */
5092   Grid.prototype.updateCanvasHeight = function updateCanvasHeight() {
5093     var self = this;
5094
5095     for (var containerId in self.renderContainers) {
5096       if (self.renderContainers.hasOwnProperty(containerId)) {
5097         var container = self.renderContainers[containerId];
5098         container.canvasHeightShouldUpdate = true;
5099       }
5100     }
5101   };
5102
5103   /**
5104    * @ngdoc function
5105    * @name buildStyles
5106    * @methodOf ui.grid.class:Grid
5107    * @description calls each styleComputation function
5108    */
5109   // TODO: this used to take $scope, but couldn't see that it was used
5110   Grid.prototype.buildStyles = function buildStyles() {
5111     // gridUtil.logDebug('buildStyles');
5112
5113     var self = this;
5114
5115     self.customStyles = '';
5116
5117     self.styleComputations
5118       .sort(function(a, b) {
5119         if (a.priority === null) { return 1; }
5120         if (b.priority === null) { return -1; }
5121         if (a.priority === null && b.priority === null) { return 0; }
5122         return a.priority - b.priority;
5123       })
5124       .forEach(function (compInfo) {
5125         // this used to provide $scope as a second parameter, but I couldn't find any
5126         // style builders that used it, so removed it as part of moving to grid from controller
5127         var ret = compInfo.func.call(self);
5128
5129         if (angular.isString(ret)) {
5130           self.customStyles += '\n' + ret;
5131         }
5132       });
5133   };
5134
5135
5136   Grid.prototype.minColumnsToRender = function minColumnsToRender() {
5137     var self = this;
5138     var viewport = this.getViewportWidth();
5139
5140     var min = 0;
5141     var totalWidth = 0;
5142     self.columns.forEach(function(col, i) {
5143       if (totalWidth < viewport) {
5144         totalWidth += col.drawnWidth;
5145         min++;
5146       }
5147       else {
5148         var currWidth = 0;
5149         for (var j = i; j >= i - min; j--) {
5150           currWidth += self.columns[j].drawnWidth;
5151         }
5152         if (currWidth < viewport) {
5153           min++;
5154         }
5155       }
5156     });
5157
5158     return min;
5159   };
5160
5161   Grid.prototype.getBodyHeight = function getBodyHeight() {
5162     // Start with the viewportHeight
5163     var bodyHeight = this.getViewportHeight();
5164
5165     // Add the horizontal scrollbar height if there is one
5166     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5167     //  bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5168     //}
5169
5170     return bodyHeight;
5171   };
5172
5173   // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
5174   // TODO(c0bra): account for footer height
5175   Grid.prototype.getViewportHeight = function getViewportHeight() {
5176     var self = this;
5177
5178     var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
5179
5180     // Account for native horizontal scrollbar, if present
5181     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5182     //  viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5183     //}
5184
5185     var adjustment = self.getViewportAdjustment();
5186
5187     viewPortHeight = viewPortHeight + adjustment.height;
5188
5189     //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5190
5191     return viewPortHeight;
5192   };
5193
5194   Grid.prototype.getViewportWidth = function getViewportWidth() {
5195     var self = this;
5196
5197     var viewPortWidth = this.gridWidth;
5198
5199     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5200     //  viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5201     //}
5202
5203     var adjustment = self.getViewportAdjustment();
5204
5205     viewPortWidth = viewPortWidth + adjustment.width;
5206
5207     //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5208
5209     return viewPortWidth;
5210   };
5211
5212   Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5213     var viewPortWidth = this.getViewportWidth();
5214
5215     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5216     //  viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5217     //}
5218
5219     return viewPortWidth;
5220   };
5221
5222   Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
5223     this.verticalScrollSyncCallBackFns[containerId] = callBackFn;
5224   };
5225
5226   Grid.prototype.addHorizontalScrollSync = function (containerId, callBackFn) {
5227     this.horizontalScrollSyncCallBackFns[containerId] = callBackFn;
5228   };
5229
5230 /**
5231  * Scroll needed containers by calling their ScrollSyncs
5232  * @param sourceContainerId the containerId that has already set it's top/left.
5233  *         can be empty string which means all containers need to set top/left
5234  * @param scrollEvent
5235  */
5236   Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5237
5238     if (scrollEvent.y) {
5239       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5240       var verts = ['body','left', 'right'];
5241
5242       this.flagScrollingVertically(scrollEvent);
5243
5244       if (sourceContainerId === 'body') {
5245         verts = ['left', 'right'];
5246       }
5247       else if (sourceContainerId === 'left') {
5248         verts = ['body', 'right'];
5249       }
5250       else if (sourceContainerId === 'right') {
5251         verts = ['body', 'left'];
5252       }
5253
5254       for (var i = 0; i < verts.length; i++) {
5255         var id = verts[i];
5256         if (this.verticalScrollSyncCallBackFns[id]) {
5257           this.verticalScrollSyncCallBackFns[id](scrollEvent);
5258         }
5259       }
5260
5261     }
5262
5263     if (scrollEvent.x) {
5264       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5265       var horizs = ['body','bodyheader', 'bodyfooter'];
5266
5267       this.flagScrollingHorizontally(scrollEvent);
5268       if (sourceContainerId === 'body') {
5269         horizs = ['bodyheader', 'bodyfooter'];
5270       }
5271
5272       for (var j = 0; j < horizs.length; j++) {
5273         var idh = horizs[j];
5274         if (this.horizontalScrollSyncCallBackFns[idh]) {
5275           this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
5276         }
5277       }
5278
5279     }
5280
5281   };
5282
5283   Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5284     this.viewportAdjusters.push(func);
5285   };
5286
5287   Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5288     var idx = this.viewportAdjusters.indexOf(func);
5289
5290     if (typeof(idx) !== 'undefined' && idx !== undefined) {
5291       this.viewportAdjusters.splice(idx, 1);
5292     }
5293   };
5294
5295   Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
5296     var self = this;
5297
5298     var adjustment = { height: 0, width: 0 };
5299
5300     self.viewportAdjusters.forEach(function (func) {
5301       adjustment = func.call(this, adjustment);
5302     });
5303
5304     return adjustment;
5305   };
5306
5307   Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5308     // var count = 0;
5309
5310     // this.rows.forEach(function (row) {
5311     //   if (row.visible) {
5312     //     count++;
5313     //   }
5314     // });
5315
5316     // return this.visibleRowCache.length;
5317     return this.renderContainers.body.visibleRowCache.length;
5318   };
5319
5320    Grid.prototype.getVisibleRows = function getVisibleRows() {
5321     return this.renderContainers.body.visibleRowCache;
5322    };
5323
5324   Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5325     // var count = 0;
5326
5327     // this.rows.forEach(function (row) {
5328     //   if (row.visible) {
5329     //     count++;
5330     //   }
5331     // });
5332
5333     // return this.visibleRowCache.length;
5334     return this.renderContainers.body.visibleColumnCache.length;
5335   };
5336
5337
5338   Grid.prototype.searchRows = function searchRows(renderableRows) {
5339     return rowSearcher.search(this, renderableRows, this.columns);
5340   };
5341
5342   Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
5343     return rowSorter.sort(this, renderableRows, this.columns);
5344   };
5345
5346   /**
5347    * @ngdoc function
5348    * @name getCellValue
5349    * @methodOf ui.grid.class:Grid
5350    * @description Gets the value of a cell for a particular row and column
5351    * @param {GridRow} row Row to access
5352    * @param {GridColumn} col Column to access
5353    */
5354   Grid.prototype.getCellValue = function getCellValue(row, col){
5355     if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
5356       return row.entity[ '$$' + col.uid].rendered;
5357     } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5358       return row.entity[col.field];
5359     } else {
5360       if (!col.cellValueGetterCache) {
5361         col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
5362       }
5363
5364       return col.cellValueGetterCache(row);
5365     }
5366   };
5367
5368   /**
5369    * @ngdoc function
5370    * @name getCellDisplayValue
5371    * @methodOf ui.grid.class:Grid
5372    * @description Gets the displayed value of a cell after applying any the `cellFilter`
5373    * @param {GridRow} row Row to access
5374    * @param {GridColumn} col Column to access
5375    */
5376   Grid.prototype.getCellDisplayValue = function getCellDisplayValue(row, col) {
5377     if ( !col.cellDisplayGetterCache ) {
5378       var custom_filter = col.cellFilter ? " | " + col.cellFilter : "";
5379
5380       if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
5381         col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5382       } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5383         col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5384       } else {
5385         col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
5386       }
5387     }
5388
5389     return col.cellDisplayGetterCache(row);
5390   };
5391
5392
5393   Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
5394     var self = this,
5395         p = 0;
5396
5397     self.columns.forEach(function (col) {
5398       if (col.sort && col.sort.priority && col.sort.priority > p) {
5399         p = col.sort.priority;
5400       }
5401     });
5402
5403     return p + 1;
5404   };
5405
5406   /**
5407    * @ngdoc function
5408    * @name resetColumnSorting
5409    * @methodOf ui.grid.class:Grid
5410    * @description Return the columns that the grid is currently being sorted by
5411    * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
5412    */
5413   Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
5414     var self = this;
5415
5416     self.columns.forEach(function (col) {
5417       if (col !== excludeCol && !col.suppressRemoveSort) {
5418         col.sort = {};
5419       }
5420     });
5421   };
5422
5423   /**
5424    * @ngdoc function
5425    * @name getColumnSorting
5426    * @methodOf ui.grid.class:Grid
5427    * @description Return the columns that the grid is currently being sorted by
5428    * @returns {Array[GridColumn]} An array of GridColumn objects
5429    */
5430   Grid.prototype.getColumnSorting = function getColumnSorting() {
5431     var self = this;
5432
5433     var sortedCols = [], myCols;
5434
5435     // Iterate through all the columns, sorted by priority
5436     // Make local copy of column list, because sorting is in-place and we do not want to
5437     // change the original sequence of columns
5438     myCols = self.columns.slice(0);
5439     myCols.sort(rowSorter.prioritySort).forEach(function (col) {
5440       if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
5441         sortedCols.push(col);
5442       }
5443     });
5444
5445     return sortedCols;
5446   };
5447
5448   /**
5449    * @ngdoc function
5450    * @name sortColumn
5451    * @methodOf ui.grid.class:Grid
5452    * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
5453    * Emits the sortChanged event whenever the sort criteria are changed.
5454    * @param {GridColumn} column Column to set the sorting on
5455    * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
5456    *   If not provided, the column will iterate through the sort directions
5457    *   specified in the {@link ui.grid.class:GridOptions.columnDef#sortDirectionCycle sortDirectionCycle} attribute.
5458    * @param {boolean} [add] Add this column to the sorting. If not provided or set to `false`, the Grid will reset any existing sorting and sort
5459    *   by this column only
5460    * @returns {Promise} A resolved promise that supplies the column.
5461    */
5462
5463   Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
5464     var self = this,
5465         direction = null;
5466
5467     if (typeof(column) === 'undefined' || !column) {
5468       throw new Error('No column parameter provided');
5469     }
5470
5471     // Second argument can either be a direction or whether to add this column to the existing sort.
5472     //   If it's a boolean, it's an add, otherwise, it's a direction
5473     if (typeof(directionOrAdd) === 'boolean') {
5474       add = directionOrAdd;
5475     }
5476     else {
5477       direction = directionOrAdd;
5478     }
5479
5480     if (!add) {
5481       self.resetColumnSorting(column);
5482       column.sort.priority = 0;
5483       // Get the actual priority since there may be columns which have suppressRemoveSort set
5484       column.sort.priority = self.getNextColumnSortPriority();
5485     }
5486     else if (!column.sort.priority){
5487       column.sort.priority = self.getNextColumnSortPriority();
5488     }
5489
5490     if (!direction) {
5491       // Find the current position in the cycle (or -1).
5492       var i = column.sortDirectionCycle.indexOf(column.sort.direction ? column.sort.direction : null);
5493       // Proceed to the next position in the cycle (or start at the beginning).
5494       i = (i+1) % column.sortDirectionCycle.length;
5495       // If suppressRemoveSort is set, and the next position in the cycle would
5496       // remove the sort, skip it.
5497       if (column.colDef && column.suppressRemoveSort && !column.sortDirectionCycle[i]) {
5498         i = (i+1) % column.sortDirectionCycle.length;
5499       }
5500
5501       if (column.sortDirectionCycle[i]) {
5502         column.sort.direction = column.sortDirectionCycle[i];
5503       } else {
5504         column.sort = {};
5505       }
5506     }
5507     else {
5508       column.sort.direction = direction;
5509     }
5510
5511     self.api.core.raise.sortChanged( self, self.getColumnSorting() );
5512
5513     return $q.when(column);
5514   };
5515
5516   /**
5517    * communicate to outside world that we are done with initial rendering
5518    */
5519   Grid.prototype.renderingComplete = function(){
5520     if (angular.isFunction(this.options.onRegisterApi)) {
5521       this.options.onRegisterApi(this.api);
5522     }
5523     this.api.core.raise.renderingComplete( this.api );
5524   };
5525
5526   Grid.prototype.createRowHashMap = function createRowHashMap() {
5527     var self = this;
5528
5529     var hashMap = new RowHashMap();
5530     hashMap.grid = self;
5531
5532     return hashMap;
5533   };
5534
5535
5536   /**
5537    * @ngdoc function
5538    * @name refresh
5539    * @methodOf ui.grid.class:Grid
5540    * @description Refresh the rendered grid on screen.
5541    * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed.
5542    */
5543   Grid.prototype.refresh = function refresh(rowsAltered) {
5544     var self = this;
5545
5546     var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
5547       self.setVisibleRows(renderableRows);
5548     });
5549
5550     var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
5551       self.setVisibleColumns(renderableColumns);
5552     });
5553
5554     return $q.all([p1, p2]).then(function () {
5555       self.redrawInPlace(rowsAltered);
5556
5557       self.refreshCanvas(true);
5558     });
5559   };
5560
5561   /**
5562    * @ngdoc function
5563    * @name refreshRows
5564    * @methodOf ui.grid.class:Grid
5565    * @description Refresh the rendered rows on screen?  Note: not functional at present
5566    * @returns {promise} promise that is resolved when render completes?
5567    *
5568    */
5569   Grid.prototype.refreshRows = function refreshRows() {
5570     var self = this;
5571
5572     return self.processRowsProcessors(self.rows)
5573       .then(function (renderableRows) {
5574         self.setVisibleRows(renderableRows);
5575
5576         self.redrawInPlace();
5577
5578         self.refreshCanvas( true );
5579       });
5580   };
5581
5582   /**
5583    * @ngdoc function
5584    * @name refreshCanvas
5585    * @methodOf ui.grid.class:Grid
5586    * @description Builds all styles and recalculates much of the grid sizing
5587    * @param {object} buildStyles optional parameter.  Use TBD
5588    * @returns {promise} promise that is resolved when the canvas
5589    * has been refreshed
5590    *
5591    */
5592   Grid.prototype.refreshCanvas = function(buildStyles) {
5593     var self = this;
5594
5595     if (buildStyles) {
5596       self.buildStyles();
5597     }
5598
5599     var p = $q.defer();
5600
5601     // Get all the header heights
5602     var containerHeadersToRecalc = [];
5603     for (var containerId in self.renderContainers) {
5604       if (self.renderContainers.hasOwnProperty(containerId)) {
5605         var container = self.renderContainers[containerId];
5606
5607         // Skip containers that have no canvasWidth set yet
5608         if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5609           continue;
5610         }
5611
5612         if (container.header || container.headerCanvas) {
5613           container.explicitHeaderHeight = container.explicitHeaderHeight || null;
5614           container.explicitHeaderCanvasHeight = container.explicitHeaderCanvasHeight || null;
5615
5616           containerHeadersToRecalc.push(container);
5617         }
5618       }
5619     }
5620
5621     /*
5622      *
5623      * Here we loop through the headers, measuring each element as well as any header "canvas" it has within it.
5624      *
5625      * If any header is less than the largest header height, it will be resized to that so that we don't have headers
5626      * with different heights, which looks like a rendering problem
5627      *
5628      * We'll do the same thing with the header canvases, and give the header CELLS an explicit height if their canvas
5629      * is smaller than the largest canvas height. That was header cells without extra controls like filtering don't
5630      * appear shorter than other cells.
5631      *
5632      */
5633     if (containerHeadersToRecalc.length > 0) {
5634       // Build the styles without the explicit header heights
5635       if (buildStyles) {
5636         self.buildStyles();
5637       }
5638
5639       // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
5640       $timeout(function() {
5641         // var oldHeaderHeight = self.grid.headerHeight;
5642         // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
5643
5644         var rebuildStyles = false;
5645
5646         // Get all the header heights
5647         var maxHeaderHeight = 0;
5648         var maxHeaderCanvasHeight = 0;
5649         var i, container;
5650         var getHeight = function(oldVal, newVal){
5651           if ( oldVal !== newVal){
5652             rebuildStyles = true;
5653           }
5654           return newVal;
5655         };
5656         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5657           container = containerHeadersToRecalc[i];
5658
5659           // Skip containers that have no canvasWidth set yet
5660           if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5661             continue;
5662           }
5663
5664           if (container.header) {
5665             var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5666
5667             // Get the "inner" header height, that is the height minus the top and bottom borders, if present. We'll use it to make sure all the headers have a consistent height
5668             var topBorder = gridUtil.getBorderSize(container.header, 'top');
5669             var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
5670             var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
5671
5672             innerHeaderHeight  = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
5673
5674             container.innerHeaderHeight = innerHeaderHeight;
5675
5676             // If the header doesn't have an explicit height set, save the largest header height for use later
5677             //   Explicit header heights are based off of the max we are calculating here. We never want to base the max on something we're setting explicitly
5678             if (!container.explicitHeaderHeight && innerHeaderHeight > maxHeaderHeight) {
5679               maxHeaderHeight = innerHeaderHeight;
5680             }
5681           }
5682
5683           if (container.headerCanvas) {
5684             var headerCanvasHeight = container.headerCanvasHeight = getHeight(container.headerCanvasHeight, parseInt(gridUtil.outerElementHeight(container.headerCanvas), 10));
5685
5686
5687             // If the header doesn't have an explicit canvas height, save the largest header canvas height for use later
5688             //   Explicit header heights are based off of the max we are calculating here. We never want to base the max on something we're setting explicitly
5689             if (!container.explicitHeaderCanvasHeight && headerCanvasHeight > maxHeaderCanvasHeight) {
5690               maxHeaderCanvasHeight = headerCanvasHeight;
5691             }
5692           }
5693         }
5694
5695         // Go through all the headers
5696         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5697           container = containerHeadersToRecalc[i];
5698
5699           /* If:
5700               1. We have a max header height
5701               2. This container has a header height defined
5702               3. And either this container has an explicit header height set, OR its header height is less than the max
5703
5704               then:
5705
5706               Give this container's header an explicit height so it will line up with the tallest header
5707           */
5708           if (
5709             maxHeaderHeight > 0 && typeof(container.headerHeight) !== 'undefined' && container.headerHeight !== null &&
5710             (container.explicitHeaderHeight || container.headerHeight < maxHeaderHeight)
5711           ) {
5712             container.explicitHeaderHeight = getHeight(container.explicitHeaderHeight, maxHeaderHeight);
5713           }
5714
5715           // Do the same as above except for the header canvas
5716           if (
5717             maxHeaderCanvasHeight > 0 && typeof(container.headerCanvasHeight) !== 'undefined' && container.headerCanvasHeight !== null &&
5718             (container.explicitHeaderCanvasHeight || container.headerCanvasHeight < maxHeaderCanvasHeight)
5719           ) {
5720             container.explicitHeaderCanvasHeight = getHeight(container.explicitHeaderCanvasHeight, maxHeaderCanvasHeight);
5721           }
5722         }
5723
5724         // Rebuild styles if the header height has changed
5725         //   The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
5726         if (buildStyles && rebuildStyles) {
5727           self.buildStyles();
5728         }
5729
5730         p.resolve();
5731       });
5732     }
5733     else {
5734       // Timeout still needs to be here to trigger digest after styles have been rebuilt
5735       $timeout(function() {
5736         p.resolve();
5737       });
5738     }
5739
5740     return p.promise;
5741   };
5742
5743
5744   /**
5745    * @ngdoc function
5746    * @name redrawCanvas
5747    * @methodOf ui.grid.class:Grid
5748    * @description Redraw the rows and columns based on our current scroll position
5749    * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
5750    *
5751    */
5752   Grid.prototype.redrawInPlace = function redrawInPlace(rowsAdded) {
5753     // gridUtil.logDebug('redrawInPlace');
5754
5755     var self = this;
5756
5757     for (var i in self.renderContainers) {
5758       var container = self.renderContainers[i];
5759
5760       // gridUtil.logDebug('redrawing container', i);
5761
5762       if (rowsAdded) {
5763         container.adjustRows(container.prevScrollTop, null);
5764         container.adjustColumns(container.prevScrollLeft, null);
5765       }
5766       else {
5767         container.adjustRows(null, container.prevScrolltopPercentage);
5768         container.adjustColumns(null, container.prevScrollleftPercentage);
5769       }
5770     }
5771   };
5772
5773     /**
5774      * @ngdoc function
5775      * @name hasLeftContainerColumns
5776      * @methodOf ui.grid.class:Grid
5777      * @description returns true if leftContainer has columns
5778      */
5779     Grid.prototype.hasLeftContainerColumns = function () {
5780       return this.hasLeftContainer() && this.renderContainers.left.renderedColumns.length > 0;
5781     };
5782
5783     /**
5784      * @ngdoc function
5785      * @name hasRightContainerColumns
5786      * @methodOf ui.grid.class:Grid
5787      * @description returns true if rightContainer has columns
5788      */
5789     Grid.prototype.hasRightContainerColumns = function () {
5790       return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
5791     };
5792
5793     /**
5794      * @ngdoc method
5795      * @methodOf  ui.grid.class:Grid
5796      * @name scrollToIfNecessary
5797      * @description Scrolls the grid to make a certain row and column combo visible,
5798      *   in the case that it is not completely visible on the screen already.
5799      * @param {GridRow} gridRow row to make visible
5800      * @param {GridCol} gridCol column to make visible
5801      * @returns {promise} a promise that is resolved when scrolling is complete
5802      */
5803     Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
5804       var self = this;
5805
5806       var scrollEvent = new ScrollEvent(self, 'uiGrid.scrollToIfNecessary');
5807
5808       // Alias the visible row and column caches
5809       var visRowCache = self.renderContainers.body.visibleRowCache;
5810       var visColCache = self.renderContainers.body.visibleColumnCache;
5811
5812       /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
5813
5814       // The top boundary is the current Y scroll position PLUS the header height, because the header can obscure rows when the grid is scrolled downwards
5815       var topBound = self.renderContainers.body.prevScrollTop + self.headerHeight;
5816
5817       // Don't the let top boundary be less than 0
5818       topBound = (topBound < 0) ? 0 : topBound;
5819
5820       // The left boundary is the current X scroll position
5821       var leftBound = self.renderContainers.body.prevScrollLeft;
5822
5823       // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
5824       //   Basically this is the viewport height added on to the scroll position
5825       var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight -  self.scrollbarWidth;
5826
5827       // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
5828       //if (self.horizontalScrollbarHeight) {
5829       //  bottomBound = bottomBound - self.horizontalScrollbarHeight;
5830       //}
5831
5832       // The right position is the current X scroll position minus the grid width
5833       var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
5834
5835       // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
5836       //if (self.verticalScrollbarWidth) {
5837       //  rightBound = rightBound - self.verticalScrollbarWidth;
5838       //}
5839
5840       // We were given a row to scroll to
5841       if (gridRow !== null) {
5842         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5843         var seekRowIndex = visRowCache.indexOf(gridRow);
5844
5845         // Total vertical scroll length of the grid
5846         var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
5847
5848         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5849         //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
5850         //  scrollLength = scrollLength + self.horizontalScrollbarHeight;
5851         //}
5852
5853         // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
5854         var pixelsToSeeRow = ((seekRowIndex + 1) * self.options.rowHeight);
5855
5856         // Don't let the pixels required to see the row be less than zero
5857         pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
5858
5859         var scrollPixels, percentage;
5860
5861         // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the self...
5862         if (pixelsToSeeRow < topBound) {
5863           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5864           //   to get the full position we need
5865           scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
5866
5867           // Turn the scroll position into a percentage and make it an argument for a scroll event
5868           percentage = scrollPixels / scrollLength;
5869           scrollEvent.y = { percentage: percentage  };
5870         }
5871         // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the self...
5872         else if (pixelsToSeeRow > bottomBound) {
5873           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5874           //   to get the full position we need
5875           scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
5876
5877           // Turn the scroll position into a percentage and make it an argument for a scroll event
5878           percentage = scrollPixels / scrollLength;
5879           scrollEvent.y = { percentage: percentage  };
5880         }
5881       }
5882
5883       // We were given a column to scroll to
5884       if (gridCol !== null) {
5885         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5886         var seekColumnIndex = visColCache.indexOf(gridCol);
5887
5888         // Total vertical scroll length of the grid
5889         var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
5890
5891         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5892         // if (self.verticalScrollbarWidth && self.verticalScrollbarWidth > 0) {
5893         //   horizScrollLength = horizScrollLength + self.verticalScrollbarWidth;
5894         // }
5895
5896         // This is the minimum amount of pixels we need to scroll vertical in order to see this column
5897         var columnLeftEdge = 0;
5898         for (var i = 0; i < seekColumnIndex; i++) {
5899           var col = visColCache[i];
5900           columnLeftEdge += col.drawnWidth;
5901         }
5902         columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
5903
5904         var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
5905
5906         // Don't let the pixels required to see the column be less than zero
5907         columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
5908
5909         var horizScrollPixels, horizPercentage;
5910
5911         // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the self...
5912         if (columnLeftEdge < leftBound) {
5913           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5914           //   to get the full position we need
5915           horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
5916
5917           // Turn the scroll position into a percentage and make it an argument for a scroll event
5918           horizPercentage = horizScrollPixels / horizScrollLength;
5919           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5920           scrollEvent.x = { percentage: horizPercentage  };
5921         }
5922         // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the self...
5923         else if (columnRightEdge > rightBound) {
5924           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5925           //   to get the full position we need
5926           horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
5927
5928           // Turn the scroll position into a percentage and make it an argument for a scroll event
5929           horizPercentage = horizScrollPixels / horizScrollLength;
5930           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5931           scrollEvent.x = { percentage: horizPercentage  };
5932         }
5933       }
5934
5935       var deferred = $q.defer();
5936
5937       // If we need to scroll on either the x or y axes, fire a scroll event
5938       if (scrollEvent.y || scrollEvent.x) {
5939         scrollEvent.withDelay = false;
5940         self.scrollContainers('',scrollEvent);
5941         var dereg = self.api.core.on.scrollEnd(null,function() {
5942           deferred.resolve(scrollEvent);
5943           dereg();
5944         });
5945       }
5946       else {
5947         deferred.resolve();
5948       }
5949
5950       return deferred.promise;
5951     };
5952
5953     /**
5954      * @ngdoc method
5955      * @methodOf ui.grid.class:Grid
5956      * @name scrollTo
5957      * @description Scroll the grid such that the specified
5958      * row and column is in view
5959      * @param {object} rowEntity gridOptions.data[] array instance to make visible
5960      * @param {object} colDef to make visible
5961      * @returns {promise} a promise that is resolved after any scrolling is finished
5962      */
5963     Grid.prototype.scrollTo = function (rowEntity, colDef) {
5964       var gridRow = null, gridCol = null;
5965
5966       if (rowEntity !== null && typeof(rowEntity) !== 'undefined' ) {
5967         gridRow = this.getRow(rowEntity);
5968       }
5969
5970       if (colDef !== null && typeof(colDef) !== 'undefined' ) {
5971         gridCol = this.getColumn(colDef.name ? colDef.name : colDef.field);
5972       }
5973       return this.scrollToIfNecessary(gridRow, gridCol);
5974     };
5975
5976   /**
5977    * @ngdoc function
5978    * @name clearAllFilters
5979    * @methodOf ui.grid.class:Grid
5980    * @description Clears all filters and optionally refreshes the visible rows.
5981    * @param {object} refreshRows Defaults to true.
5982    * @param {object} clearConditions Defaults to false.
5983    * @param {object} clearFlags Defaults to false.
5984    * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
5985    */
5986   Grid.prototype.clearAllFilters = function clearAllFilters(refreshRows, clearConditions, clearFlags) {
5987     // Default `refreshRows` to true because it will be the most commonly desired behaviour.
5988     if (refreshRows === undefined) {
5989       refreshRows = true;
5990     }
5991     if (clearConditions === undefined) {
5992       clearConditions = false;
5993     }
5994     if (clearFlags === undefined) {
5995       clearFlags = false;
5996     }
5997
5998     this.columns.forEach(function(column) {
5999       column.filters.forEach(function(filter) {
6000         filter.term = undefined;
6001
6002         if (clearConditions) {
6003           filter.condition = undefined;
6004         }
6005
6006         if (clearFlags) {
6007           filter.flags = undefined;
6008         }
6009       });
6010     });
6011
6012     if (refreshRows) {
6013       return this.refreshRows();
6014     }
6015   };
6016
6017
6018       // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
6019   function RowHashMap() {}
6020
6021   RowHashMap.prototype = {
6022     /**
6023      * Store key value pair
6024      * @param key key to store can be any type
6025      * @param value value to store can be any type
6026      */
6027     put: function(key, value) {
6028       this[this.grid.options.rowIdentity(key)] = value;
6029     },
6030
6031     /**
6032      * @param key
6033      * @returns {Object} the value for the key
6034      */
6035     get: function(key) {
6036       return this[this.grid.options.rowIdentity(key)];
6037     },
6038
6039     /**
6040      * Remove the key/value pair
6041      * @param key
6042      */
6043     remove: function(key) {
6044       var value = this[key = this.grid.options.rowIdentity(key)];
6045       delete this[key];
6046       return value;
6047     }
6048   };
6049
6050
6051
6052   return Grid;
6053
6054 }]);
6055
6056 })();
6057
6058 (function () {
6059
6060   angular.module('ui.grid')
6061     .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6062       function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6063         /**
6064          * @ngdoc function
6065          * @name ui.grid.class:GridApi
6066          * @description GridApi provides the ability to register public methods events inside the grid and allow
6067          * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6068          * <br/>
6069          * To listen to events, you must add a callback to gridOptions.onRegisterApi
6070          * <pre>
6071          *   $scope.gridOptions.onRegisterApi = function(gridApi){
6072          *      gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6073          *          $log.log('navigation event');
6074          *      });
6075          *   };
6076          * </pre>
6077          * @param {object} grid grid that owns api
6078          */
6079         var GridApi = function GridApi(grid) {
6080           this.grid = grid;
6081           this.listeners = [];
6082           
6083           /**
6084            * @ngdoc function
6085            * @name renderingComplete
6086            * @methodOf  ui.grid.core.api:PublicApi
6087            * @description Rendering is complete, called at the same
6088            * time as `onRegisterApi`, but provides a way to obtain
6089            * that same event within features without stopping end
6090            * users from getting at the onRegisterApi method.
6091            * 
6092            * Included in gridApi so that it's always there - otherwise
6093            * there is still a timing problem with when a feature can
6094            * call this. 
6095            * 
6096            * @param {GridApi} gridApi the grid api, as normally 
6097            * returned in the onRegisterApi method
6098            * 
6099            * @example
6100            * <pre>
6101            *      gridApi.core.on.renderingComplete( grid );
6102            * </pre>
6103            */
6104           this.registerEvent( 'core', 'renderingComplete' );
6105
6106           /**
6107            * @ngdoc event
6108            * @name filterChanged
6109            * @eventOf  ui.grid.core.api:PublicApi
6110            * @description  is raised after the filter is changed.  The nature
6111            * of the watch expression doesn't allow notification of what changed,
6112            * so the receiver of this event will need to re-extract the filter 
6113            * conditions from the columns.
6114            * 
6115            */
6116           this.registerEvent( 'core', 'filterChanged' );
6117
6118           /**
6119            * @ngdoc function
6120            * @name setRowInvisible
6121            * @methodOf  ui.grid.core.api:PublicApi
6122            * @description Sets an override on the row to make it always invisible,
6123            * which will override any filtering or other visibility calculations.  
6124            * If the row is currently visible then sets it to invisible and calls
6125            * both grid refresh and emits the rowsVisibleChanged event
6126            * @param {object} rowEntity gridOptions.data[] array instance
6127            */
6128           this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6129       
6130           /**
6131            * @ngdoc function
6132            * @name clearRowInvisible
6133            * @methodOf  ui.grid.core.api:PublicApi
6134            * @description Clears any override on visibility for the row so that it returns to 
6135            * using normal filtering and other visibility calculations.  
6136            * If the row is currently invisible then sets it to visible and calls
6137            * both grid refresh and emits the rowsVisibleChanged event
6138            * TODO: if a filter is active then we can't just set it to visible?
6139            * @param {object} rowEntity gridOptions.data[] array instance
6140            */
6141           this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6142       
6143           /**
6144            * @ngdoc function
6145            * @name getVisibleRows
6146            * @methodOf  ui.grid.core.api:PublicApi
6147            * @description Returns all visible rows
6148            * @param {Grid} grid the grid you want to get visible rows from
6149            * @returns {array} an array of gridRow
6150            */
6151           this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6152           
6153           /**
6154            * @ngdoc event
6155            * @name rowsVisibleChanged
6156            * @eventOf  ui.grid.core.api:PublicApi
6157            * @description  is raised after the rows that are visible
6158            * change.  The filtering is zero-based, so it isn't possible
6159            * to say which rows changed (unlike in the selection feature).
6160            * We can plausibly know which row was changed when setRowInvisible
6161            * is called, but in that situation the user already knows which row
6162            * they changed.  When a filter runs we don't know what changed,
6163            * and that is the one that would have been useful.
6164            *
6165            */
6166           this.registerEvent( 'core', 'rowsVisibleChanged' );
6167
6168           /**
6169            * @ngdoc event
6170            * @name rowsRendered
6171            * @eventOf  ui.grid.core.api:PublicApi
6172            * @description  is raised after the cache of visible rows is changed.
6173            */
6174           this.registerEvent( 'core', 'rowsRendered' );
6175
6176
6177           /**
6178            * @ngdoc event
6179            * @name scrollBegin
6180            * @eventOf  ui.grid.core.api:PublicApi
6181            * @description  is raised when scroll begins.  Is throttled, so won't be raised too frequently
6182            */
6183           this.registerEvent( 'core', 'scrollBegin' );
6184
6185           /**
6186            * @ngdoc event
6187            * @name scrollEnd
6188            * @eventOf  ui.grid.core.api:PublicApi
6189            * @description  is raised when scroll has finished.  Is throttled, so won't be raised too frequently
6190            */
6191           this.registerEvent( 'core', 'scrollEnd' );
6192
6193           /**
6194            * @ngdoc event
6195            * @name canvasHeightChanged
6196            * @eventOf  ui.grid.core.api:PublicApi
6197            * @description  is raised when the canvas height has changed
6198            * <br/>
6199            * arguments: oldHeight, newHeight
6200            */
6201           this.registerEvent( 'core', 'canvasHeightChanged');
6202         };
6203
6204         /**
6205          * @ngdoc function
6206          * @name ui.grid.class:suppressEvents
6207          * @methodOf ui.grid.class:GridApi
6208          * @description Used to execute a function while disabling the specified event listeners.
6209          * Disables the listenerFunctions, executes the callbackFn, and then enables
6210          * the listenerFunctions again
6211          * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
6212          * functions that were used in the .on.eventName method
6213          * @param {object} callBackFn function to execute
6214          * @example
6215          * <pre>
6216          *    var navigate = function (newRowCol, oldRowCol){
6217          *       //do something on navigate
6218          *    }
6219          *
6220          *    gridApi.cellNav.on.navigate(scope,navigate);
6221          *
6222          *
6223          *    //call the scrollTo event and suppress our navigate listener
6224          *    //scrollTo will still raise the event for other listeners
6225          *    gridApi.suppressEvents(navigate, function(){
6226          *       gridApi.cellNav.scrollTo(aRow, aCol);
6227          *    });
6228          *
6229          * </pre>
6230          */
6231         GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6232           var self = this;
6233           var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6234
6235           //find all registered listeners
6236           var foundListeners = self.listeners.filter(function(listener) {
6237             return listeners.some(function(l) {
6238               return listener.handler === l;
6239             });
6240           });
6241
6242           //deregister all the listeners
6243           foundListeners.forEach(function(l){
6244             l.dereg();
6245           });
6246
6247           callBackFn();
6248
6249           //reregister all the listeners
6250           foundListeners.forEach(function(l){
6251               l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
6252           });
6253
6254         };
6255
6256         /**
6257          * @ngdoc function
6258          * @name registerEvent
6259          * @methodOf ui.grid.class:GridApi
6260          * @description Registers a new event for the given feature.  The event will get a
6261          * .raise and .on prepended to it
6262          * <br>
6263          * .raise.eventName() - takes no arguments
6264          * <br/>
6265          * <br/>
6266          * .on.eventName(scope, callBackFn, _this)
6267          * <br/>
6268          * scope - a scope reference to add a deregister call to the scopes .$on('destroy').  Scope is optional and can be a null value,
6269          * but in this case you must deregister it yourself via the returned deregister function
6270          * <br/>
6271          * callBackFn - The function to call
6272          * <br/>
6273          * _this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
6274          * <br/>
6275          * .on.eventName returns a dereg funtion that will remove the listener.  It's not necessary to use it as the listener
6276          * will be removed when the scope is destroyed.
6277          * @param {string} featureName name of the feature that raises the event
6278          * @param {string} eventName  name of the event
6279          */
6280         GridApi.prototype.registerEvent = function (featureName, eventName) {
6281           var self = this;
6282           if (!self[featureName]) {
6283             self[featureName] = {};
6284           }
6285
6286           var feature = self[featureName];
6287           if (!feature.on) {
6288             feature.on = {};
6289             feature.raise = {};
6290           }
6291
6292           var eventId = self.grid.id + featureName + eventName;
6293
6294           // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
6295           feature.raise[eventName] = function () {
6296             $rootScope.$emit.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
6297           };
6298
6299           // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
6300           feature.on[eventName] = function (scope, handler, _this) {
6301             if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6302               gridUtil.logError('asked to listen on ' + featureName + '.on.' + eventName + ' but scope wasn\'t passed in the input parameters.  It is legitimate to pass null, but you\'ve passed something else, so you probably forgot to provide scope rather than did it deliberately, not registering');
6303               return;
6304             }
6305             var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
6306
6307             //track our listener so we can turn off and on
6308             var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6309             self.listeners.push(listener);
6310
6311             var removeListener = function(){
6312               listener.dereg();
6313               var index = self.listeners.indexOf(listener);
6314               self.listeners.splice(index,1);
6315             };
6316
6317             //destroy tracking when scope is destroyed
6318             if (scope) {
6319               scope.$on('$destroy', function() {
6320                 removeListener();
6321               });
6322             }
6323
6324
6325             return removeListener;
6326           };
6327         };
6328
6329         function registerEventWithAngular(eventId, handler, grid, _this) {
6330           return $rootScope.$on(eventId, function (event) {
6331             var args = Array.prototype.slice.call(arguments);
6332             args.splice(0, 1); //remove evt argument
6333             handler.apply(_this ? _this : grid.api, args);
6334           });
6335         }
6336
6337         /**
6338          * @ngdoc function
6339          * @name registerEventsFromObject
6340          * @methodOf ui.grid.class:GridApi
6341          * @description Registers features and events from a simple objectMap.
6342          * eventObjectMap must be in this format (multiple features allowed)
6343          * <pre>
6344          * {featureName:
6345          *        {
6346          *          eventNameOne:function(args){},
6347          *          eventNameTwo:function(args){}
6348          *        }
6349          *  }
6350          * </pre>
6351          * @param {object} eventObjectMap map of feature/event names
6352          */
6353         GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6354           var self = this;
6355           var features = [];
6356           angular.forEach(eventObjectMap, function (featProp, featPropName) {
6357             var feature = {name: featPropName, events: []};
6358             angular.forEach(featProp, function (prop, propName) {
6359               feature.events.push(propName);
6360             });
6361             features.push(feature);
6362           });
6363
6364           features.forEach(function (feature) {
6365             feature.events.forEach(function (event) {
6366               self.registerEvent(feature.name, event);
6367             });
6368           });
6369
6370         };
6371
6372         /**
6373          * @ngdoc function
6374          * @name registerMethod
6375          * @methodOf ui.grid.class:GridApi
6376          * @description Registers a new event for the given feature
6377          * @param {string} featureName name of the feature
6378          * @param {string} methodName  name of the method
6379          * @param {object} callBackFn function to execute
6380          * @param {object} _this binds callBackFn 'this' to _this.  Defaults to gridApi.grid
6381          */
6382         GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, _this) {
6383           if (!this[featureName]) {
6384             this[featureName] = {};
6385           }
6386
6387           var feature = this[featureName];
6388
6389           feature[methodName] = gridUtil.createBoundedWrapper(_this || this.grid, callBackFn);
6390         };
6391
6392         /**
6393          * @ngdoc function
6394          * @name registerMethodsFromObject
6395          * @methodOf ui.grid.class:GridApi
6396          * @description Registers features and methods from a simple objectMap.
6397          * eventObjectMap must be in this format (multiple features allowed)
6398          * <br>
6399          * {featureName:
6400          *        {
6401          *          methodNameOne:function(args){},
6402          *          methodNameTwo:function(args){}
6403          *        }
6404          * @param {object} eventObjectMap map of feature/event names
6405          * @param {object} _this binds this to _this for all functions.  Defaults to gridApi.grid
6406          */
6407         GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
6408           var self = this;
6409           var features = [];
6410           angular.forEach(methodMap, function (featProp, featPropName) {
6411             var feature = {name: featPropName, methods: []};
6412             angular.forEach(featProp, function (prop, propName) {
6413               feature.methods.push({name: propName, fn: prop});
6414             });
6415             features.push(feature);
6416           });
6417
6418           features.forEach(function (feature) {
6419             feature.methods.forEach(function (method) {
6420               self.registerMethod(feature.name, method.name, method.fn, _this);
6421             });
6422           });
6423
6424         };
6425         
6426         return GridApi;
6427
6428       }]);
6429
6430 })();
6431
6432 (function(){
6433
6434 angular.module('ui.grid')
6435 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
6436
6437   /**
6438    * ******************************************************************************************
6439    * PaulL1: Ugly hack here in documentation.  These properties are clearly properties of GridColumn,
6440    * and need to be noted as such for those extending and building ui-grid itself.
6441    * However, from an end-developer perspective, they interact with all these through columnDefs,
6442    * and they really need to be documented there.  I feel like they're relatively static, and
6443    * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
6444    * comment block.  Ugh.
6445    *
6446    */
6447
6448   /**
6449    * @ngdoc property
6450    * @name name
6451    * @propertyOf ui.grid.class:GridColumn
6452    * @description (mandatory) each column should have a name, although for backward
6453    * compatibility with 2.x name can be omitted if field is present
6454    *
6455    */
6456
6457   /**
6458    * @ngdoc property
6459    * @name name
6460    * @propertyOf ui.grid.class:GridOptions.columnDef
6461    * @description (mandatory) each column should have a name, although for backward
6462    * compatibility with 2.x name can be omitted if field is present
6463    *
6464    */
6465
6466   /**
6467    * @ngdoc property
6468    * @name displayName
6469    * @propertyOf ui.grid.class:GridColumn
6470    * @description Column name that will be shown in the header.  If displayName is not
6471    * provided then one is generated using the name.
6472    *
6473    */
6474
6475   /**
6476    * @ngdoc property
6477    * @name displayName
6478    * @propertyOf ui.grid.class:GridOptions.columnDef
6479    * @description Column name that will be shown in the header.  If displayName is not
6480    * provided then one is generated using the name.
6481    *
6482    */
6483
6484   /**
6485    * @ngdoc property
6486    * @name field
6487    * @propertyOf ui.grid.class:GridColumn
6488    * @description field must be provided if you wish to bind to a
6489    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6490    * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
6491    * See the angular docs on binding expressions.
6492    *
6493    */
6494
6495   /**
6496    * @ngdoc property
6497    * @name field
6498    * @propertyOf ui.grid.class:GridOptions.columnDef
6499    * @description field must be provided if you wish to bind to a
6500    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6501    * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.    * See the angular docs on binding expressions.    *
6502    */
6503
6504   /**
6505    * @ngdoc property
6506    * @name filter
6507    * @propertyOf ui.grid.class:GridColumn
6508    * @description Filter on this column.
6509    * @example
6510    * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...', ariaLabel: 'Filter for text', flags: { caseSensitive: false }, type: uiGridConstants.filter.SELECT, [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ] }</pre>
6511    *
6512    */
6513
6514   /**
6515    * @ngdoc object
6516    * @name ui.grid.class:GridColumn
6517    * @description Represents the viewModel for each column.  Any state or methods needed for a Grid Column
6518    * are defined on this prototype
6519    * @param {ColumnDef} colDef the column def to associate with this column
6520    * @param {number} uid the unique and immutable uid we'd like to allocate to this column
6521    * @param {Grid} grid the grid we'd like to create this column in
6522    */
6523   function GridColumn(colDef, uid, grid) {
6524     var self = this;
6525
6526     self.grid = grid;
6527     self.uid = uid;
6528
6529     self.updateColumnDef(colDef, true);
6530
6531     /**
6532      * @ngdoc function
6533      * @name hideColumn
6534      * @methodOf ui.grid.class:GridColumn
6535      * @description Hides the column by setting colDef.visible = false
6536      */
6537     GridColumn.prototype.hideColumn = function() {
6538       this.colDef.visible = false;
6539     };
6540
6541     self.aggregationValue = undefined;
6542
6543     // The footer cell registers to listen for the rowsRendered event, and calls this.  Needed to be
6544     // in something with a scope so that the dereg would get called
6545     self.updateAggregationValue = function() {
6546
6547      // gridUtil.logDebug('getAggregationValue for Column ' + self.colDef.name);
6548
6549       /**
6550        * @ngdoc property
6551        * @name aggregationType
6552        * @propertyOf ui.grid.class:GridOptions.columnDef
6553        * @description The aggregation that you'd like to show in the columnFooter for this
6554        * column.  Valid values are in uiGridConstants, and currently include `uiGridConstants.aggregationTypes.count`,
6555        * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
6556        * `uiGridConstants.aggregationTypes.max`.
6557        *
6558        * You can also provide a function as the aggregation type, in this case your function needs to accept the full
6559        * set of visible rows, and return a value that should be shown
6560        */
6561       if (!self.aggregationType) {
6562         self.aggregationValue = undefined;
6563         return;
6564       }
6565
6566       var result = 0;
6567       var visibleRows = self.grid.getVisibleRows();
6568
6569       var cellValues = function(){
6570         var values = [];
6571         visibleRows.forEach(function (row) {
6572           var cellValue = self.grid.getCellValue(row, self);
6573           var cellNumber = Number(cellValue);
6574           if (!isNaN(cellNumber)) {
6575             values.push(cellNumber);
6576           }
6577         });
6578         return values;
6579       };
6580
6581       if (angular.isFunction(self.aggregationType)) {
6582         self.aggregationValue = self.aggregationType(visibleRows, self);
6583       }
6584       else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6585         self.aggregationValue = self.grid.getVisibleRowCount();
6586       }
6587       else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6588         cellValues().forEach(function (value) {
6589           result += value;
6590         });
6591         self.aggregationValue = result;
6592       }
6593       else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6594         cellValues().forEach(function (value) {
6595           result += value;
6596         });
6597         result = result / cellValues().length;
6598         self.aggregationValue = result;
6599       }
6600       else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6601         self.aggregationValue = Math.min.apply(null, cellValues());
6602       }
6603       else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6604         self.aggregationValue = Math.max.apply(null, cellValues());
6605       }
6606       else {
6607         self.aggregationValue = '\u00A0';
6608       }
6609     };
6610
6611 //     var throttledUpdateAggregationValue = gridUtil.throttle(updateAggregationValue, self.grid.options.aggregationCalcThrottle, { trailing: true, context: self.name });
6612
6613     /**
6614      * @ngdoc function
6615      * @name getAggregationValue
6616      * @methodOf ui.grid.class:GridColumn
6617      * @description gets the aggregation value based on the aggregation type for this column.
6618      * Debounced using scrollDebounce option setting
6619      */
6620     this.getAggregationValue =  function() {
6621 //      if (!self.grid.isScrollingVertically && !self.grid.isScrollingHorizontally) {
6622 //        throttledUpdateAggregationValue();
6623 //      }
6624
6625       return self.aggregationValue;
6626     };
6627   }
6628
6629
6630   /**
6631    * @ngdoc method
6632    * @methodOf ui.grid.class:GridColumn
6633    * @name setPropertyOrDefault
6634    * @description Sets a property on the column using the passed in columnDef, and
6635    * setting the defaultValue if the value cannot be found on the colDef
6636    * @param {ColumnDef} colDef the column def to look in for the property value
6637    * @param {string} propName the property name we'd like to set
6638    * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
6639    */
6640   GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
6641     var self = this;
6642
6643     // Use the column definition filter if we were passed it
6644     if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
6645       self[propName] = colDef[propName];
6646     }
6647     // Otherwise use our own if it's set
6648     else if (typeof(self[propName]) !== 'undefined') {
6649       self[propName] = self[propName];
6650     }
6651     // Default to empty object for the filter
6652     else {
6653       self[propName] = defaultValue ? defaultValue : {};
6654     }
6655   };
6656
6657
6658
6659   /**
6660    * @ngdoc property
6661    * @name width
6662    * @propertyOf ui.grid.class:GridOptions.columnDef
6663    * @description sets the column width.  Can be either
6664    * a number or a percentage, or an * for auto.
6665    * @example
6666    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
6667    *                                          { field: 'field2', width: '20%'},
6668    *                                          { field: 'field3', width: '*' }]; </pre>
6669    *
6670    */
6671
6672   /**
6673    * @ngdoc property
6674    * @name minWidth
6675    * @propertyOf ui.grid.class:GridOptions.columnDef
6676    * @description sets the minimum column width.  Should be a number.
6677    * @example
6678    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
6679    *
6680    */
6681
6682   /**
6683    * @ngdoc property
6684    * @name maxWidth
6685    * @propertyOf ui.grid.class:GridOptions.columnDef
6686    * @description sets the maximum column width.  Should be a number.
6687    * @example
6688    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
6689    *
6690    */
6691
6692   /**
6693    * @ngdoc property
6694    * @name visible
6695    * @propertyOf ui.grid.class:GridOptions.columnDef
6696    * @description sets whether or not the column is visible
6697    * </br>Default is true
6698    * @example
6699    * <pre>  $scope.gridOptions.columnDefs = [
6700    *     { field: 'field1', visible: true},
6701    *     { field: 'field2', visible: false }
6702    *   ]; </pre>
6703    *
6704    */
6705
6706  /**
6707   * @ngdoc property
6708   * @name sort
6709   * @propertyOf ui.grid.class:GridOptions.columnDef
6710   * @description An object of sort information, attributes are:
6711   *
6712   * - direction: values are uiGridConstants.ASC or uiGridConstants.DESC
6713   * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6714   * - priority: says what order to sort the columns in (lower priority gets sorted first).
6715   * @example
6716   * <pre>
6717   *   $scope.gridOptions.columnDefs = [{
6718   *     field: 'field1',
6719   *     sort: {
6720   *       direction: uiGridConstants.ASC,
6721   *       ignoreSort: true,
6722   *       priority: 0
6723   *      }
6724   *   }];
6725   * </pre>
6726   */
6727
6728
6729   /**
6730    * @ngdoc property
6731    * @name sortingAlgorithm
6732    * @propertyOf ui.grid.class:GridOptions.columnDef
6733    * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
6734    * like any normal sorting function with additional 'rowA', 'rowB', and 'direction' parameters
6735    * that are the row objects and the current direction of the sort respectively.
6736    *
6737    */
6738
6739   /**
6740    * @ngdoc array
6741    * @name filters
6742    * @propertyOf ui.grid.class:GridOptions.columnDef
6743    * @description Specify multiple filter fields.
6744    * @example
6745    * <pre>$scope.gridOptions.columnDefs = [
6746    *   {
6747    *     field: 'field1', filters: [
6748    *       {
6749    *         term: 'aa',
6750    *         condition: uiGridConstants.filter.STARTS_WITH,
6751    *         placeholder: 'starts with...',
6752    *         ariaLabel: 'Filter for field1',
6753    *         flags: { caseSensitive: false },
6754    *         type: uiGridConstants.filter.SELECT,
6755    *         selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6756    *       },
6757    *       {
6758    *         condition: uiGridConstants.filter.ENDS_WITH,
6759    *         placeholder: 'ends with...'
6760    *       }
6761    *     ]
6762    *   }
6763    * ]; </pre>
6764    *
6765    *
6766    */
6767
6768   /**
6769    * @ngdoc array
6770    * @name filters
6771    * @propertyOf ui.grid.class:GridColumn
6772    * @description Filters for this column. Includes 'term' property bound to filter input elements.
6773    * @example
6774    * <pre>[
6775    *   {
6776    *     term: 'foo', // ngModel for <input>
6777    *     condition: uiGridConstants.filter.STARTS_WITH,
6778    *     placeholder: 'starts with...',
6779    *     ariaLabel: 'Filter for foo',
6780    *     flags: { caseSensitive: false },
6781    *     type: uiGridConstants.filter.SELECT,
6782    *     selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6783    *   },
6784    *   {
6785    *     term: 'baz',
6786    *     condition: uiGridConstants.filter.ENDS_WITH,
6787    *     placeholder: 'ends with...'
6788    *   }
6789    * ] </pre>
6790    *
6791    *
6792    */
6793
6794   /**
6795    * @ngdoc array
6796    * @name menuItems
6797    * @propertyOf ui.grid.class:GridOptions.columnDef
6798    * @description used to add menu items to a column.  Refer to the tutorial on this
6799    * functionality.  A number of settings are supported:
6800    *
6801    * - title: controls the title that is displayed in the menu
6802    * - icon: the icon shown alongside that title
6803    * - action: the method to call when the menu is clicked
6804    * - shown: a function to evaluate to determine whether or not to show the item
6805    * - active: a function to evaluate to determine whether or not the item is currently selected
6806    * - context: context to pass to the action function, available in this.context in your handler
6807    * - leaveOpen: if set to true, the menu should stay open after the action, defaults to false
6808    * @example
6809    * <pre>  $scope.gridOptions.columnDefs = [
6810    *   { field: 'field1', menuItems: [
6811    *     {
6812    *       title: 'Outer Scope Alert',
6813    *       icon: 'ui-grid-icon-info-circled',
6814    *       action: function($event) {
6815    *         this.context.blargh(); // $scope.blargh() would work too, this is just an example
6816    *       },
6817    *       shown: function() { return true; },
6818    *       active: function() { return true; },
6819    *       context: $scope
6820    *     },
6821    *     {
6822    *       title: 'Grid ID',
6823    *       action: function() {
6824    *         alert('Grid ID: ' + this.grid.id);
6825    *       }
6826    *     }
6827    *   ] }]; </pre>
6828    *
6829    */
6830
6831   /**
6832    * @ngdoc method
6833    * @methodOf ui.grid.class:GridColumn
6834    * @name updateColumnDef
6835    * @description Moves settings from the columnDef down onto the column,
6836    * and sets properties as appropriate
6837    * @param {ColumnDef} colDef the column def to look in for the property value
6838    * @param {boolean} isNew whether the column is being newly created, if not
6839    * we're updating an existing column, and some items such as the sort shouldn't
6840    * be copied down
6841    */
6842   GridColumn.prototype.updateColumnDef = function(colDef, isNew) {
6843     var self = this;
6844
6845     self.colDef = colDef;
6846
6847     if (colDef.name === undefined) {
6848       throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
6849     }
6850
6851     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6852
6853     if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
6854       var colDefWidth = colDef.width;
6855       var parseErrorMsg = "Cannot parse column width '" + colDefWidth + "' for column named '" + colDef.name + "'";
6856       self.hasCustomWidth = false;
6857
6858       if (!angular.isString(colDefWidth) && !angular.isNumber(colDefWidth)) {
6859         self.width = '*';
6860       } else if (angular.isString(colDefWidth)) {
6861         // See if it ends with a percent
6862         if (gridUtil.endsWith(colDefWidth, '%')) {
6863           // If so we should be able to parse the non-percent-sign part to a number
6864           var percentStr = colDefWidth.replace(/%/g, '');
6865           var percent = parseInt(percentStr, 10);
6866           if (isNaN(percent)) {
6867             throw new Error(parseErrorMsg);
6868           }
6869           self.width = colDefWidth;
6870         }
6871         // And see if it's a number string
6872         else if (colDefWidth.match(/^(\d+)$/)) {
6873           self.width = parseInt(colDefWidth.match(/^(\d+)$/)[1], 10);
6874         }
6875         // Otherwise it should be a string of asterisks
6876         else if (colDefWidth.match(/^\*+$/)) {
6877           self.width = colDefWidth;
6878         }
6879         // No idea, throw an Error
6880         else {
6881           throw new Error(parseErrorMsg);
6882         }
6883       }
6884       // Is a number, use it as the width
6885       else {
6886         self.width = colDefWidth;
6887       }
6888     }
6889
6890     ['minWidth', 'maxWidth'].forEach(function (name) {
6891       var minOrMaxWidth = colDef[name];
6892       var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
6893
6894       if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
6895         //Sets default minWidth and maxWidth values
6896         self[name] = ((name === 'minWidth') ? 30 : 9000);
6897       } else if (angular.isString(minOrMaxWidth)) {
6898         if (minOrMaxWidth.match(/^(\d+)$/)) {
6899           self[name] = parseInt(minOrMaxWidth.match(/^(\d+)$/)[1], 10);
6900         } else {
6901           throw new Error(parseErrorMsg);
6902         }
6903       } else {
6904         self[name] = minOrMaxWidth;
6905       }
6906     });
6907
6908     //use field if it is defined; name if it is not
6909     self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
6910
6911     if ( typeof( self.field ) !== 'string' ){
6912       gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
6913     }
6914
6915     self.name = colDef.name;
6916
6917     // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
6918     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6919
6920     //self.originalIndex = index;
6921
6922     self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
6923     self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
6924
6925     /**
6926      * @ngdoc property
6927      * @name cellTooltip
6928      * @propertyOf ui.grid.class:GridOptions.columnDef
6929      * @description Whether or not to show a tooltip when a user hovers over the cell.
6930      * If set to false, no tooltip.  If true, the cell value is shown in the tooltip (useful
6931      * if you have long values in your cells), if a function then that function is called
6932      * passing in the row and the col `cellTooltip( row, col )`, and the return value is shown in the tooltip,
6933      * if it is a static string then displays that static string.
6934      *
6935      * Defaults to false
6936      *
6937      */
6938     if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
6939       self.cellTooltip = false;
6940     } else if ( colDef.cellTooltip === true ){
6941       self.cellTooltip = function(row, col) {
6942         return self.grid.getCellValue( row, col );
6943       };
6944     } else if (typeof(colDef.cellTooltip) === 'function' ){
6945       self.cellTooltip = colDef.cellTooltip;
6946     } else {
6947       self.cellTooltip = function ( row, col ){
6948         return col.colDef.cellTooltip;
6949       };
6950     }
6951
6952     /**
6953      * @ngdoc property
6954      * @name headerTooltip
6955      * @propertyOf ui.grid.class:GridOptions.columnDef
6956      * @description Whether or not to show a tooltip when a user hovers over the header cell.
6957      * If set to false, no tooltip.  If true, the displayName is shown in the tooltip (useful
6958      * if you have long values in your headers), if a function then that function is called
6959      * passing in the row and the col `headerTooltip( col )`, and the return value is shown in the tooltip,
6960      * if a static string then shows that static string.
6961      *
6962      * Defaults to false
6963      *
6964      */
6965     if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
6966       self.headerTooltip = false;
6967     } else if ( colDef.headerTooltip === true ){
6968       self.headerTooltip = function(col) {
6969         return col.displayName;
6970       };
6971     } else if (typeof(colDef.headerTooltip) === 'function' ){
6972       self.headerTooltip = colDef.headerTooltip;
6973     } else {
6974       self.headerTooltip = function ( col ) {
6975         return col.colDef.headerTooltip;
6976       };
6977     }
6978
6979
6980     /**
6981      * @ngdoc property
6982      * @name footerCellClass
6983      * @propertyOf ui.grid.class:GridOptions.columnDef
6984      * @description footerCellClass can be a string specifying the class to append to a cell
6985      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
6986      *
6987      */
6988     self.footerCellClass = colDef.footerCellClass;
6989
6990     /**
6991      * @ngdoc property
6992      * @name cellClass
6993      * @propertyOf ui.grid.class:GridOptions.columnDef
6994      * @description cellClass can be a string specifying the class to append to a cell
6995      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
6996      *
6997      */
6998     self.cellClass = colDef.cellClass;
6999
7000     /**
7001      * @ngdoc property
7002      * @name headerCellClass
7003      * @propertyOf ui.grid.class:GridOptions.columnDef
7004      * @description headerCellClass can be a string specifying the class to append to a cell
7005      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
7006      *
7007      */
7008     self.headerCellClass = colDef.headerCellClass;
7009
7010     /**
7011      * @ngdoc property
7012      * @name cellFilter
7013      * @propertyOf ui.grid.class:GridOptions.columnDef
7014      * @description cellFilter is a filter to apply to the content of each cell
7015      * @example
7016      * <pre>
7017      *   gridOptions.columnDefs[0].cellFilter = 'date'
7018      *
7019      */
7020     self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
7021
7022     /**
7023      * @ngdoc boolean
7024      * @name sortCellFiltered
7025      * @propertyOf ui.grid.class:GridOptions.columnDef
7026      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7027      * sorting the data. Note that when using this option uiGrid will assume that the displayed value is
7028      * a string, and use the {@link ui.grid.class:RowSorter#sortAlpha sortAlpha} `sortFn`. It is possible
7029      * to return a non-string value from an angularjs filter, in which case you should define a {@link ui.grid.class:GridOptions.columnDef#sortingAlgorithm sortingAlgorithm}
7030      * for the column which hanldes the returned type. You may specify one of the `sortingAlgorithms`
7031      * found in the {@link ui.grid.RowSorter rowSorter} service.
7032      */
7033     self.sortCellFiltered = colDef.sortCellFiltered ? true : false;
7034
7035     /**
7036      * @ngdoc boolean
7037      * @name filterCellFiltered
7038      * @propertyOf ui.grid.class:GridOptions.columnDef
7039      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7040      * applying "search" `filters`.
7041      */
7042     self.filterCellFiltered = colDef.filterCellFiltered ? true : false;
7043
7044     /**
7045      * @ngdoc property
7046      * @name headerCellFilter
7047      * @propertyOf ui.grid.class:GridOptions.columnDef
7048      * @description headerCellFilter is a filter to apply to the content of the column header
7049      * @example
7050      * <pre>
7051      *   gridOptions.columnDefs[0].headerCellFilter = 'translate'
7052      *
7053      */
7054     self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
7055
7056     /**
7057      * @ngdoc property
7058      * @name footerCellFilter
7059      * @propertyOf ui.grid.class:GridOptions.columnDef
7060      * @description footerCellFilter is a filter to apply to the content of the column footer
7061      * @example
7062      * <pre>
7063      *   gridOptions.columnDefs[0].footerCellFilter = 'date'
7064      *
7065      */
7066     self.footerCellFilter = colDef.footerCellFilter ? colDef.footerCellFilter : "";
7067
7068     self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
7069
7070     self.headerClass = colDef.headerClass;
7071     //self.cursor = self.sortable ? 'pointer' : 'default';
7072
7073     // Turn on sorting by default
7074     self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
7075     self.sortingAlgorithm = colDef.sortingAlgorithm;
7076
7077     /**
7078      * @ngdoc property
7079      * @name sortDirectionCycle
7080      * @propertyOf ui.grid.class:GridOptions.columnDef
7081      * @description (optional) An array of sort directions, specifying the order that they
7082      * should cycle through as the user repeatedly clicks on the column heading.
7083      * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
7084      * refers to the unsorted state. This does not affect the initial sort
7085      * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
7086      * property for that. If
7087      * {@link ui.grid.class:GridOptions.columnDef#suppressRemoveSort suppressRemoveSort}
7088      * is also set, the unsorted state will be skipped even if it is listed here.
7089      * Each direction may not appear in the list more than once (e.g. `[ASC,
7090      * DESC, DESC]` is not allowed), and the list may not be empty.
7091      */
7092     self.sortDirectionCycle = typeof(colDef.sortDirectionCycle) !== 'undefined' ?
7093       colDef.sortDirectionCycle :
7094       [null, uiGridConstants.ASC, uiGridConstants.DESC];
7095
7096     /**
7097      * @ngdoc boolean
7098      * @name suppressRemoveSort
7099      * @propertyOf ui.grid.class:GridOptions.columnDef
7100      * @description (optional) False by default. When enabled, this setting hides the removeSort option
7101      * in the menu, and prevents users from manually removing the sort
7102      */
7103     if ( typeof(self.suppressRemoveSort) === 'undefined'){
7104       self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
7105     }
7106
7107     /**
7108      * @ngdoc property
7109      * @name enableFiltering
7110      * @propertyOf ui.grid.class:GridOptions.columnDef
7111      * @description turn off filtering for an individual column, where
7112      * you've turned on filtering for the overall grid
7113      * @example
7114      * <pre>
7115      *   gridOptions.columnDefs[0].enableFiltering = false;
7116      *
7117      */
7118     // Turn on filtering by default (it's disabled by default at the Grid level)
7119     self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
7120
7121     // self.menuItems = colDef.menuItems;
7122     self.setPropertyOrDefault(colDef, 'menuItems', []);
7123
7124     // Use the column definition sort if we were passed it, but only if this is a newly added column
7125     if ( isNew ){
7126       self.setPropertyOrDefault(colDef, 'sort');
7127     }
7128
7129     // Set up default filters array for when one is not provided.
7130     //   In other words, this (in column def):
7131     //
7132     //       filter: { term: 'something', flags: {}, condition: [CONDITION] }
7133     //
7134     //   is just shorthand for this:
7135     //
7136     //       filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
7137     //
7138     var defaultFilters = [];
7139     if (colDef.filter) {
7140       defaultFilters.push(colDef.filter);
7141     }
7142     else if ( colDef.filters ){
7143       defaultFilters = colDef.filters;
7144     } else {
7145       // Add an empty filter definition object, which will
7146       // translate to a guessed condition and no pre-populated
7147       // value for the filter <input>.
7148       defaultFilters.push({});
7149     }
7150
7151     /**
7152      * @ngdoc property
7153      * @name filter
7154      * @propertyOf ui.grid.class:GridOptions.columnDef
7155      * @description Specify a single filter field on this column.
7156      *
7157      * A filter consists of a condition, a term, and a placeholder:
7158      *
7159      * - condition defines how rows are chosen as matching the filter term. This can be set to
7160      * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
7161      * that gets passed the following arguments: [searchTerm, cellValue, row, column].
7162      * - term: If set, the filter field will be pre-populated
7163      * with this value.
7164      * - placeholder: String that will be set to the `<input>.placeholder` attribute.
7165      * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
7166      * - noTerm: set this to true if you have defined a custom function in condition, and
7167      * your custom function doesn't require a term (so it can run even when the term is null)
7168      * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
7169      * case sensitive matching
7170      * - type: defaults to uiGridConstants.filter.INPUT, which gives a text box.  If set to uiGridConstants.filter.SELECT
7171      * then a select box will be shown with options selectOptions
7172      * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`.  No i18n filter is provided, you need
7173      * to perform the i18n on the values before you provide them
7174      * - disableCancelFilterButton: defaults to false. If set to true then the 'x' button that cancels/clears the filter
7175      * will not be shown.
7176      * @example
7177      * <pre>$scope.gridOptions.columnDefs = [
7178      *   {
7179      *     field: 'field1',
7180      *     filter: {
7181      *       term: 'xx',
7182      *       condition: uiGridConstants.filter.STARTS_WITH,
7183      *       placeholder: 'starts with...',
7184      *       ariaLabel: 'Starts with filter for field1',
7185      *       flags: { caseSensitive: false },
7186      *       type: uiGridConstants.filter.SELECT,
7187      *       selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ],
7188      *       disableCancelFilterButton: true
7189      *     }
7190      *   }
7191      * ]; </pre>
7192      *
7193      */
7194
7195     /*
7196
7197
7198     /*
7199
7200       self.filters = [
7201         {
7202           term: 'search term'
7203           condition: uiGridConstants.filter.CONTAINS,
7204           placeholder: 'my placeholder',
7205           ariaLabel: 'Starts with filter for field1',
7206           flags: {
7207             caseSensitive: true
7208           }
7209         }
7210       ]
7211
7212     */
7213
7214     // Only set filter if this is a newly added column, if we're updating an existing
7215     // column then we don't want to put the default filter back if the user may have already
7216     // removed it.
7217     // However, we do want to keep the settings if they change, just not the term
7218     if ( isNew ) {
7219       self.setPropertyOrDefault(colDef, 'filter');
7220       self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
7221     } else if ( self.filters.length === defaultFilters.length ) {
7222       self.filters.forEach( function( filter, index ){
7223         if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
7224           filter.placeholder = defaultFilters[index].placeholder;
7225         }
7226         if (typeof(defaultFilters[index].ariaLabel) !== 'undefined') {
7227           filter.ariaLabel = defaultFilters[index].ariaLabel;
7228         }
7229         if (typeof(defaultFilters[index].flags) !== 'undefined') {
7230           filter.flags = defaultFilters[index].flags;
7231         }
7232         if (typeof(defaultFilters[index].type) !== 'undefined') {
7233           filter.type = defaultFilters[index].type;
7234         }
7235         if (typeof(defaultFilters[index].selectOptions) !== 'undefined') {
7236           filter.selectOptions = defaultFilters[index].selectOptions;
7237         }
7238       });
7239     }
7240
7241     // Remove this column from the grid sorting, include inside build columns so has
7242     // access to self - all seems a bit dodgy but doesn't work otherwise so have left
7243     // as is
7244     GridColumn.prototype.unsort = function () {
7245       this.sort = {};
7246       self.grid.api.core.raise.sortChanged( self.grid, self.grid.getColumnSorting() );
7247     };
7248
7249   };
7250
7251
7252   /**
7253    * @ngdoc function
7254    * @name getColClass
7255    * @methodOf ui.grid.class:GridColumn
7256    * @description Returns the class name for the column
7257    * @param {bool} prefixDot  if true, will return .className instead of className
7258    */
7259   GridColumn.prototype.getColClass = function (prefixDot) {
7260     var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
7261
7262     return prefixDot ? '.' + cls : cls;
7263   };
7264
7265     /**
7266      * @ngdoc function
7267      * @name isPinnedLeft
7268      * @methodOf ui.grid.class:GridColumn
7269      * @description Returns true if column is in the left render container
7270      */
7271     GridColumn.prototype.isPinnedLeft = function () {
7272       return this.renderContainer === 'left';
7273     };
7274
7275     /**
7276      * @ngdoc function
7277      * @name isPinnedRight
7278      * @methodOf ui.grid.class:GridColumn
7279      * @description Returns true if column is in the right render container
7280      */
7281     GridColumn.prototype.isPinnedRight = function () {
7282       return this.renderContainer === 'right';
7283     };
7284
7285
7286     /**
7287    * @ngdoc function
7288    * @name getColClassDefinition
7289    * @methodOf ui.grid.class:GridColumn
7290    * @description Returns the class definition for th column
7291    */
7292   GridColumn.prototype.getColClassDefinition = function () {
7293     return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { min-width: ' + this.drawnWidth + 'px; max-width: ' + this.drawnWidth + 'px; }';
7294   };
7295
7296   /**
7297    * @ngdoc function
7298    * @name getRenderContainer
7299    * @methodOf ui.grid.class:GridColumn
7300    * @description Returns the render container object that this column belongs to.
7301    *
7302    * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
7303    */
7304   GridColumn.prototype.getRenderContainer = function getRenderContainer() {
7305     var self = this;
7306
7307     var containerId = self.renderContainer;
7308
7309     if (containerId === null || containerId === '' || containerId === undefined) {
7310       containerId = 'body';
7311     }
7312
7313     return self.grid.renderContainers[containerId];
7314   };
7315
7316   /**
7317    * @ngdoc function
7318    * @name showColumn
7319    * @methodOf ui.grid.class:GridColumn
7320    * @description Makes the column visible by setting colDef.visible = true
7321    */
7322   GridColumn.prototype.showColumn = function() {
7323       this.colDef.visible = true;
7324   };
7325
7326
7327   /**
7328    * @ngdoc property
7329    * @name aggregationHideLabel
7330    * @propertyOf ui.grid.class:GridOptions.columnDef
7331    * @description defaults to false, if set to true hides the label text
7332    * in the aggregation footer, so only the value is displayed.
7333    *
7334    */
7335   /**
7336    * @ngdoc function
7337    * @name getAggregationText
7338    * @methodOf ui.grid.class:GridColumn
7339    * @description Gets the aggregation label from colDef.aggregationLabel if
7340    * specified or by using i18n, including deciding whether or not to display
7341    * based on colDef.aggregationHideLabel.
7342    *
7343    * @param {string} label the i18n lookup value to use for the column label
7344    *
7345    */
7346   GridColumn.prototype.getAggregationText = function () {
7347     var self = this;
7348     if ( self.colDef.aggregationHideLabel ){
7349       return '';
7350     }
7351     else if ( self.colDef.aggregationLabel ) {
7352       return self.colDef.aggregationLabel;
7353     }
7354     else {
7355       switch ( self.colDef.aggregationType ){
7356         case uiGridConstants.aggregationTypes.count:
7357           return i18nService.getSafeText('aggregation.count');
7358         case uiGridConstants.aggregationTypes.sum:
7359           return i18nService.getSafeText('aggregation.sum');
7360         case uiGridConstants.aggregationTypes.avg:
7361           return i18nService.getSafeText('aggregation.avg');
7362         case uiGridConstants.aggregationTypes.min:
7363           return i18nService.getSafeText('aggregation.min');
7364         case uiGridConstants.aggregationTypes.max:
7365           return i18nService.getSafeText('aggregation.max');
7366         default:
7367           return '';
7368       }
7369     }
7370   };
7371
7372   GridColumn.prototype.getCellTemplate = function () {
7373     var self = this;
7374
7375     return self.cellTemplatePromise;
7376   };
7377
7378   GridColumn.prototype.getCompiledElementFn = function () {
7379     var self = this;
7380
7381     return self.compiledElementFnDefer.promise;
7382   };
7383
7384   return GridColumn;
7385 }]);
7386
7387 })();
7388
7389   (function(){
7390
7391 angular.module('ui.grid')
7392 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7393
7394   /**
7395    * @ngdoc function
7396    * @name ui.grid.class:GridOptions
7397    * @description Default GridOptions class.  GridOptions are defined by the application developer and overlaid
7398    * over this object.  Setting gridOptions within your controller is the most common method for an application
7399    * developer to configure the behaviour of their ui-grid
7400    *
7401    * @example To define your gridOptions within your controller:
7402    * <pre>$scope.gridOptions = {
7403    *   data: $scope.myData,
7404    *   columnDefs: [
7405    *     { name: 'field1', displayName: 'pretty display name' },
7406    *     { name: 'field2', visible: false }
7407    *  ]
7408    * };</pre>
7409    *
7410    * You can then use this within your html template, when you define your grid:
7411    * <pre>&lt;div ui-grid="gridOptions"&gt;&lt;/div&gt;</pre>
7412    *
7413    * To provide default options for all of the grids within your application, use an angular
7414    * decorator to modify the GridOptions factory.
7415    * <pre>
7416    * app.config(function($provide){
7417    *   $provide.decorator('GridOptions',function($delegate){
7418    *     var gridOptions;
7419    *     gridOptions = angular.copy($delegate);
7420    *     gridOptions.initialize = function(options) {
7421    *       var initOptions;
7422    *       initOptions = $delegate.initialize(options);
7423    *       initOptions.enableColumnMenus = false;
7424    *       return initOptions;
7425    *     };
7426    *     return gridOptions;
7427    *   });
7428    * });
7429    * </pre>
7430    */
7431   return {
7432     initialize: function( baseOptions ){
7433       /**
7434        * @ngdoc function
7435        * @name onRegisterApi
7436        * @propertyOf ui.grid.class:GridOptions
7437        * @description A callback that returns the gridApi once the grid is instantiated, which is
7438        * then used to interact with the grid programatically.
7439        *
7440        * Note that the gridApi.core.renderingComplete event is identical to this
7441        * callback, but has the advantage that it can be called from multiple places
7442        * if needed
7443        *
7444        * @example
7445        * <pre>
7446        *   $scope.gridOptions.onRegisterApi = function ( gridApi ) {
7447        *     $scope.gridApi = gridApi;
7448        *     $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid );
7449        *   };
7450        * </pre>
7451        *
7452        */
7453       baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
7454
7455       /**
7456        * @ngdoc object
7457        * @name data
7458        * @propertyOf ui.grid.class:GridOptions
7459        * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
7460        * the grid.
7461        *
7462        * Most commonly the data is an array of objects, where each object has a number of attributes.
7463        * Each attribute automatically becomes a column in your grid.  This array could, for example, be sourced from
7464        * an angularJS $resource query request.  The array can also contain complex objects, refer the binding tutorial
7465        * for examples of that.
7466        *
7467        * The most flexible usage is to set your data on $scope:
7468        *
7469        * `$scope.data = data;`
7470        *
7471        * And then direct the grid to resolve whatever is in $scope.data:
7472        *
7473        * `$scope.gridOptions.data = 'data';`
7474        *
7475        * This is the most flexible approach as it allows you to replace $scope.data whenever you feel like it without
7476        * getting pointer issues.
7477        *
7478        * Alternatively you can directly set the data array:
7479        *
7480        * `$scope.gridOptions.data = [ ];`
7481        * or
7482        *
7483        * `$http.get('/data/100.json')
7484        * .success(function(data) {
7485        *   $scope.myData = data;
7486        *   $scope.gridOptions.data = $scope.myData;
7487        *  });`
7488        *
7489        * Where you do this, you need to take care in updating the data - you can't just update `$scope.myData` to some other
7490        * array, you need to update $scope.gridOptions.data to point to that new array as well.
7491        *
7492        */
7493       baseOptions.data = baseOptions.data || [];
7494
7495       /**
7496        * @ngdoc array
7497        * @name columnDefs
7498        * @propertyOf  ui.grid.class:GridOptions
7499        * @description Array of columnDef objects.  Only required property is name.
7500        * The individual options available in columnDefs are documented in the
7501        * {@link ui.grid.class:GridOptions.columnDef columnDef} section
7502        * </br>_field property can be used in place of name for backwards compatibility with 2.x_
7503        *  @example
7504        *
7505        * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7506        *
7507        */
7508       baseOptions.columnDefs = baseOptions.columnDefs || [];
7509
7510       /**
7511        * @ngdoc object
7512        * @name ui.grid.class:GridOptions.columnDef
7513        * @description Definition / configuration of an individual column, which would typically be
7514        * one of many column definitions within the gridOptions.columnDefs array
7515        * @example
7516        * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7517        *
7518        */
7519
7520
7521       /**
7522        * @ngdoc array
7523        * @name excludeProperties
7524        * @propertyOf  ui.grid.class:GridOptions
7525        * @description Array of property names in data to ignore when auto-generating column names.  Provides the
7526        * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
7527        * to exclude.
7528        *
7529        * If columnDefs is defined, this will be ignored.
7530        *
7531        * Defaults to ['$$hashKey']
7532        */
7533
7534       baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
7535
7536       /**
7537        * @ngdoc boolean
7538        * @name enableRowHashing
7539        * @propertyOf ui.grid.class:GridOptions
7540        * @description True by default. When enabled, this setting allows uiGrid to add
7541        * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
7542        * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
7543        *
7544        * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
7545        * you do not want this at all you can disable this setting but you will take a performance hit if you are using large numbers of rows
7546        * and are altering the data set often.
7547        */
7548       baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
7549
7550       /**
7551        * @ngdoc function
7552        * @name rowIdentity
7553        * @methodOf ui.grid.class:GridOptions
7554        * @description This function is used to get and, if necessary, set the value uniquely identifying this row (i.e. if an identity is not present it will set one).
7555        *
7556        * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
7557        */
7558       baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
7559         return gridUtil.hashKey(row);
7560       };
7561
7562       /**
7563        * @ngdoc function
7564        * @name getRowIdentity
7565        * @methodOf ui.grid.class:GridOptions
7566        * @description This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
7567        *
7568        * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
7569        */
7570       baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
7571         return row.$$hashKey;
7572       };
7573
7574       /**
7575        * @ngdoc property
7576        * @name flatEntityAccess
7577        * @propertyOf ui.grid.class:GridOptions
7578        * @description Set to true if your columns are all related directly to fields in a flat object structure - i.e.
7579        * each of your columns associate directly with a property on each of the entities in your data array.
7580        *
7581        * In that situation we can avoid all the logic associated with complex binding to functions or to properties of sub-objects,
7582        * which can provide a significant speed improvement with large data sets when filtering or sorting.
7583        *
7584        * By default false
7585        */
7586       baseOptions.flatEntityAccess = baseOptions.flatEntityAccess === true;
7587
7588       /**
7589        * @ngdoc property
7590        * @name showHeader
7591        * @propertyOf ui.grid.class:GridOptions
7592        * @description True by default. When set to false, this setting will replace the
7593        * standard header template with '<div></div>', resulting in no header being shown.
7594        */
7595       baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
7596
7597       /* (NOTE): Don't show this in the docs. We only use it internally
7598        * @ngdoc property
7599        * @name headerRowHeight
7600        * @propertyOf ui.grid.class:GridOptions
7601        * @description The height of the header in pixels, defaults to 30
7602        *
7603        */
7604       if (!baseOptions.showHeader) {
7605         baseOptions.headerRowHeight = 0;
7606       }
7607       else {
7608         baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
7609       }
7610
7611       /**
7612        * @ngdoc property
7613        * @name rowHeight
7614        * @propertyOf ui.grid.class:GridOptions
7615        * @description The height of the row in pixels, defaults to 30
7616        *
7617        */
7618       baseOptions.rowHeight = baseOptions.rowHeight || 30;
7619
7620       /**
7621        * @ngdoc integer
7622        * @name minRowsToShow
7623        * @propertyOf ui.grid.class:GridOptions
7624        * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
7625        */
7626       baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
7627
7628       /**
7629        * @ngdoc property
7630        * @name showGridFooter
7631        * @propertyOf ui.grid.class:GridOptions
7632        * @description Whether or not to show the footer, defaults to false
7633        * The footer display Total Rows and Visible Rows (filtered rows)
7634        */
7635       baseOptions.showGridFooter = baseOptions.showGridFooter === true;
7636
7637       /**
7638        * @ngdoc property
7639        * @name showColumnFooter
7640        * @propertyOf ui.grid.class:GridOptions
7641        * @description Whether or not to show the column footer, defaults to false
7642        * The column footer displays column aggregates
7643        */
7644       baseOptions.showColumnFooter = baseOptions.showColumnFooter === true;
7645
7646       /**
7647        * @ngdoc property
7648        * @name columnFooterHeight
7649        * @propertyOf ui.grid.class:GridOptions
7650        * @description The height of the footer rows (column footer and grid footer) in pixels
7651        *
7652        */
7653       baseOptions.columnFooterHeight = typeof(baseOptions.columnFooterHeight) !== "undefined" ? baseOptions.columnFooterHeight : 30;
7654       baseOptions.gridFooterHeight = typeof(baseOptions.gridFooterHeight) !== "undefined" ? baseOptions.gridFooterHeight : 30;
7655
7656       baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
7657
7658       /**
7659        * @ngdoc property
7660        * @name maxVisibleColumnCount
7661        * @propertyOf ui.grid.class:GridOptions
7662        * @description Defaults to 200
7663        *
7664        */
7665       baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
7666
7667       /**
7668        * @ngdoc property
7669        * @name virtualizationThreshold
7670        * @propertyOf ui.grid.class:GridOptions
7671        * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
7672        */
7673       baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
7674
7675       /**
7676        * @ngdoc property
7677        * @name columnVirtualizationThreshold
7678        * @propertyOf ui.grid.class:GridOptions
7679        * @description Turn virtualization on when number of columns goes over this number, defaults to 10
7680        */
7681       baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
7682
7683       /**
7684        * @ngdoc property
7685        * @name excessRows
7686        * @propertyOf ui.grid.class:GridOptions
7687        * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
7688        * Defaults to 4
7689        */
7690       baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7691       /**
7692        * @ngdoc property
7693        * @name scrollThreshold
7694        * @propertyOf ui.grid.class:GridOptions
7695        * @description Defaults to 4
7696        */
7697       baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
7698
7699       /**
7700        * @ngdoc property
7701        * @name excessColumns
7702        * @propertyOf ui.grid.class:GridOptions
7703        * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
7704        * Defaults to 4
7705        */
7706       baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7707       /**
7708        * @ngdoc property
7709        * @name horizontalScrollThreshold
7710        * @propertyOf ui.grid.class:GridOptions
7711        * @description Defaults to 4
7712        */
7713       baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
7714
7715
7716       /**
7717        * @ngdoc property
7718        * @name aggregationCalcThrottle
7719        * @propertyOf ui.grid.class:GridOptions
7720        * @description Default time in milliseconds to throttle aggregation calcuations, defaults to 500ms
7721        */
7722       baseOptions.aggregationCalcThrottle = typeof(baseOptions.aggregationCalcThrottle) !== "undefined" ? baseOptions.aggregationCalcThrottle : 500;
7723
7724       /**
7725        * @ngdoc property
7726        * @name wheelScrollThrottle
7727        * @propertyOf ui.grid.class:GridOptions
7728        * @description Default time in milliseconds to throttle scroll events to, defaults to 70ms
7729        */
7730       baseOptions.wheelScrollThrottle = typeof(baseOptions.wheelScrollThrottle) !== "undefined" ? baseOptions.wheelScrollThrottle : 70;
7731
7732
7733       /**
7734        * @ngdoc property
7735        * @name scrollDebounce
7736        * @propertyOf ui.grid.class:GridOptions
7737        * @description Default time in milliseconds to debounce scroll events, defaults to 300ms
7738        */
7739       baseOptions.scrollDebounce = typeof(baseOptions.scrollDebounce) !== "undefined" ? baseOptions.scrollDebounce : 300;
7740
7741       /**
7742        * @ngdoc boolean
7743        * @name enableSorting
7744        * @propertyOf ui.grid.class:GridOptions
7745        * @description True by default. When enabled, this setting adds sort
7746        * widgets to the column headers, allowing sorting of the data for the entire grid.
7747        * Sorting can then be disabled on individual columns using the columnDefs.
7748        */
7749       baseOptions.enableSorting = baseOptions.enableSorting !== false;
7750
7751       /**
7752        * @ngdoc boolean
7753        * @name enableFiltering
7754        * @propertyOf ui.grid.class:GridOptions
7755        * @description False by default. When enabled, this setting adds filter
7756        * boxes to each column header, allowing filtering within the column for the entire grid.
7757        * Filtering can then be disabled on individual columns using the columnDefs.
7758        */
7759       baseOptions.enableFiltering = baseOptions.enableFiltering === true;
7760
7761       /**
7762        * @ngdoc boolean
7763        * @name enableColumnMenus
7764        * @propertyOf ui.grid.class:GridOptions
7765        * @description True by default. When enabled, this setting displays a column
7766        * menu within each column.
7767        */
7768       baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
7769
7770       /**
7771        * @ngdoc boolean
7772        * @name enableVerticalScrollbar
7773        * @propertyOf ui.grid.class:GridOptions
7774        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
7775        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7776        */
7777       baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7778
7779       /**
7780        * @ngdoc boolean
7781        * @name enableHorizontalScrollbar
7782        * @propertyOf ui.grid.class:GridOptions
7783        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
7784        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7785        */
7786       baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7787
7788       /**
7789        * @ngdoc boolean
7790        * @name enableMinHeightCheck
7791        * @propertyOf ui.grid.class:GridOptions
7792        * @description True by default. When enabled, a newly initialized grid will check to see if it is tall enough to display
7793        * at least one row of data.  If the grid is not tall enough, it will resize the DOM element to display minRowsToShow number
7794        * of rows.
7795        */
7796        baseOptions.enableMinHeightCheck = baseOptions.enableMinHeightCheck !== false;
7797
7798       /**
7799        * @ngdoc boolean
7800        * @name minimumColumnSize
7801        * @propertyOf ui.grid.class:GridOptions
7802        * @description Columns can't be smaller than this, defaults to 10 pixels
7803        */
7804       baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
7805
7806       /**
7807        * @ngdoc function
7808        * @name rowEquality
7809        * @methodOf ui.grid.class:GridOptions
7810        * @description By default, rows are compared using object equality.  This option can be overridden
7811        * to compare on any data item property or function
7812        * @param {object} entityA First Data Item to compare
7813        * @param {object} entityB Second Data Item to compare
7814        */
7815       baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
7816         return entityA === entityB;
7817       };
7818
7819       /**
7820        * @ngdoc string
7821        * @name headerTemplate
7822        * @propertyOf ui.grid.class:GridOptions
7823        * @description Null by default. When provided, this setting uses a custom header
7824        * template, rather than the default template. Can be set to either the name of a template file:
7825        * <pre>  $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
7826        * inline html
7827        * <pre>  $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
7828        * or the id of a precompiled template (TBD how to use this).
7829        * </br>Refer to the custom header tutorial for more information.
7830        * If you want no header at all, you can set to an empty div:
7831        * <pre>  $scope.gridOptions.headerTemplate = '<div></div>';</pre>
7832        *
7833        * If you want to only have a static header, then you can set to static content.  If
7834        * you want to tailor the existing column headers, then you should look at the
7835        * current 'ui-grid-header.html' template in github as your starting point.
7836        *
7837        */
7838       baseOptions.headerTemplate = baseOptions.headerTemplate || null;
7839
7840       /**
7841        * @ngdoc string
7842        * @name footerTemplate
7843        * @propertyOf ui.grid.class:GridOptions
7844        * @description (optional) ui-grid/ui-grid-footer by default.  This footer shows the per-column
7845        * aggregation totals.
7846        * When provided, this setting uses a custom footer template. Can be set to either the name of a template file 'footer_template.html', inline html
7847        * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
7848        * of a precompiled template (TBD how to use this).  Refer to the custom footer tutorial for more information.
7849        */
7850       baseOptions.footerTemplate = baseOptions.footerTemplate || 'ui-grid/ui-grid-footer';
7851
7852       /**
7853        * @ngdoc string
7854        * @name gridFooterTemplate
7855        * @propertyOf ui.grid.class:GridOptions
7856        * @description (optional) ui-grid/ui-grid-grid-footer by default. This template by default shows the
7857        * total items at the bottom of the grid, and the selected items if selection is enabled.
7858        */
7859       baseOptions.gridFooterTemplate = baseOptions.gridFooterTemplate || 'ui-grid/ui-grid-grid-footer';
7860
7861       /**
7862        * @ngdoc string
7863        * @name rowTemplate
7864        * @propertyOf ui.grid.class:GridOptions
7865        * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
7866        * custom row template.  Can be set to either the name of a template file:
7867        * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
7868        * inline html
7869        * <pre>  $scope.gridOptions.rowTemplate = '<div style="background-color: aquamarine" ng-click="grid.appScope.fnOne(row)" ng-repeat="col in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ui-grid-cell></div>';</pre>
7870        * or the id of a precompiled template (TBD how to use this) can be provided.
7871        * </br>Refer to the custom row template tutorial for more information.
7872        */
7873       baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
7874
7875       /**
7876        * @ngdoc object
7877        * @name appScopeProvider
7878        * @propertyOf ui.grid.class:GridOptions
7879        * @description by default, the parent scope of the ui-grid element will be assigned to grid.appScope
7880        * this property allows you to assign any reference you want to grid.appScope
7881        */
7882       baseOptions.appScopeProvider = baseOptions.appScopeProvider || null;
7883
7884       return baseOptions;
7885     }
7886   };
7887
7888
7889 }]);
7890
7891 })();
7892
7893 (function(){
7894
7895 angular.module('ui.grid')
7896
7897   /**
7898    * @ngdoc function
7899    * @name ui.grid.class:GridRenderContainer
7900    * @description The grid has render containers, allowing the ability to have pinned columns.  If the grid
7901    * is right-to-left then there may be a right render container, if left-to-right then there may
7902    * be a left render container.  There is always a body render container.
7903    * @param {string} name The name of the render container ('body', 'left', or 'right')
7904    * @param {Grid} grid the grid the render container is in
7905    * @param {object} options the render container options
7906    */
7907 .factory('GridRenderContainer', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
7908   function GridRenderContainer(name, grid, options) {
7909     var self = this;
7910
7911     // if (gridUtil.type(grid) !== 'Grid') {
7912     //   throw new Error('Grid argument is not a Grid object');
7913     // }
7914
7915     self.name = name;
7916
7917     self.grid = grid;
7918
7919     // self.rowCache = [];
7920     // self.columnCache = [];
7921
7922     self.visibleRowCache = [];
7923     self.visibleColumnCache = [];
7924
7925     self.renderedRows = [];
7926     self.renderedColumns = [];
7927
7928     self.prevScrollTop = 0;
7929     self.prevScrolltopPercentage = 0;
7930     self.prevRowScrollIndex = 0;
7931
7932     self.prevScrollLeft = 0;
7933     self.prevScrollleftPercentage = 0;
7934     self.prevColumnScrollIndex = 0;
7935
7936     self.columnStyles = "";
7937
7938     self.viewportAdjusters = [];
7939
7940     /**
7941      *  @ngdoc boolean
7942      *  @name hasHScrollbar
7943      *  @propertyOf  ui.grid.class:GridRenderContainer
7944      *  @description flag to signal that container has a horizontal scrollbar
7945      */
7946     self.hasHScrollbar = false;
7947
7948     /**
7949      *  @ngdoc boolean
7950      *  @name hasVScrollbar
7951      *  @propertyOf  ui.grid.class:GridRenderContainer
7952      *  @description flag to signal that container has a vertical scrollbar
7953      */
7954     self.hasVScrollbar = false;
7955
7956     /**
7957      *  @ngdoc boolean
7958      *  @name canvasHeightShouldUpdate
7959      *  @propertyOf  ui.grid.class:GridRenderContainer
7960      *  @description flag to signal that container should recalculate the canvas size
7961      */
7962     self.canvasHeightShouldUpdate = true;
7963
7964     /**
7965      *  @ngdoc boolean
7966      *  @name canvasHeight
7967      *  @propertyOf  ui.grid.class:GridRenderContainer
7968      *  @description last calculated canvas height value
7969      */
7970     self.$$canvasHeight = 0;
7971
7972     if (options && angular.isObject(options)) {
7973       angular.extend(self, options);
7974     }
7975
7976     grid.registerStyleComputation({
7977       priority: 5,
7978       func: function () {
7979         self.updateColumnWidths();
7980         return self.columnStyles;
7981       }
7982     });
7983   }
7984
7985
7986   GridRenderContainer.prototype.reset = function reset() {
7987     // this.rowCache.length = 0;
7988     // this.columnCache.length = 0;
7989
7990     this.visibleColumnCache.length = 0;
7991     this.visibleRowCache.length = 0;
7992
7993     this.renderedRows.length = 0;
7994     this.renderedColumns.length = 0;
7995   };
7996
7997   // TODO(c0bra): calculate size?? Should this be in a stackable directive?
7998
7999
8000   GridRenderContainer.prototype.containsColumn = function (col) {
8001      return this.visibleColumnCache.indexOf(col) !== -1;
8002   };
8003
8004   GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
8005     var self = this;
8006     var minRows = 0;
8007     var rowAddedHeight = 0;
8008     var viewPortHeight = self.getViewportHeight();
8009     for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
8010       rowAddedHeight += self.visibleRowCache[i].height;
8011       minRows++;
8012     }
8013     return minRows;
8014   };
8015
8016   GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
8017     var self = this;
8018     var viewportWidth = this.getViewportWidth();
8019
8020     var min = 0;
8021     var totalWidth = 0;
8022     // self.columns.forEach(function(col, i) {
8023     for (var i = 0; i < self.visibleColumnCache.length; i++) {
8024       var col = self.visibleColumnCache[i];
8025
8026       if (totalWidth < viewportWidth) {
8027         totalWidth += col.drawnWidth ? col.drawnWidth : 0;
8028         min++;
8029       }
8030       else {
8031         var currWidth = 0;
8032         for (var j = i; j >= i - min; j--) {
8033           currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
8034         }
8035         if (currWidth < viewportWidth) {
8036           min++;
8037         }
8038       }
8039     }
8040
8041     return min;
8042   };
8043
8044   GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
8045     return this.visibleRowCache.length;
8046   };
8047
8048   /**
8049    * @ngdoc function
8050    * @name registerViewportAdjuster
8051    * @methodOf ui.grid.class:GridRenderContainer
8052    * @description Registers an adjuster to the render container's available width or height.  Adjusters are used
8053    * to tell the render container that there is something else consuming space, and to adjust it's size
8054    * appropriately.
8055    * @param {function} func the adjuster function we want to register
8056    */
8057
8058   GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
8059     this.viewportAdjusters.push(func);
8060   };
8061
8062   /**
8063    * @ngdoc function
8064    * @name removeViewportAdjuster
8065    * @methodOf ui.grid.class:GridRenderContainer
8066    * @description Removes an adjuster, should be used when your element is destroyed
8067    * @param {function} func the adjuster function we want to remove
8068    */
8069   GridRenderContainer.prototype.removeViewportAdjuster = function removeViewportAdjuster(func) {
8070     var idx = this.viewportAdjusters.indexOf(func);
8071
8072     if (idx > -1) {
8073       this.viewportAdjusters.splice(idx, 1);
8074     }
8075   };
8076
8077   /**
8078    * @ngdoc function
8079    * @name getViewportAdjustment
8080    * @methodOf ui.grid.class:GridRenderContainer
8081    * @description Gets the adjustment based on the viewportAdjusters.
8082    * @returns {object} a hash of { height: x, width: y }.  Usually the values will be negative
8083    */
8084   GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
8085     var self = this;
8086
8087     var adjustment = { height: 0, width: 0 };
8088
8089     self.viewportAdjusters.forEach(function (func) {
8090       adjustment = func.call(this, adjustment);
8091     });
8092
8093     return adjustment;
8094   };
8095
8096   GridRenderContainer.prototype.getMargin = function getMargin(side) {
8097     var self = this;
8098
8099     var amount = 0;
8100
8101     self.viewportAdjusters.forEach(function (func) {
8102       var adjustment = func.call(this, { height: 0, width: 0 });
8103
8104       if (adjustment.side && adjustment.side === side) {
8105         amount += adjustment.width * -1;
8106       }
8107     });
8108
8109     return amount;
8110   };
8111
8112   GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
8113     var self = this;
8114
8115     var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
8116
8117     var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
8118
8119
8120     var adjustment = self.getViewportAdjustment();
8121
8122     viewPortHeight = viewPortHeight + adjustment.height;
8123
8124     return viewPortHeight;
8125   };
8126
8127   GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
8128     var self = this;
8129
8130     var viewportWidth = self.grid.gridWidth;
8131
8132     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8133     //  viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8134     //}
8135
8136     // var viewportWidth = 0;\
8137     // self.visibleColumnCache.forEach(function (column) {
8138     //   viewportWidth += column.drawnWidth;
8139     // });
8140
8141     var adjustment = self.getViewportAdjustment();
8142
8143     viewportWidth = viewportWidth + adjustment.width;
8144
8145     return viewportWidth;
8146   };
8147
8148   GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8149     var self = this;
8150
8151     var viewportWidth = this.getViewportWidth();
8152
8153     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8154     //  viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8155     //}
8156
8157     // var adjustment = self.getViewportAdjustment();
8158     // viewPortWidth = viewPortWidth + adjustment.width;
8159
8160     return viewportWidth;
8161   };
8162
8163
8164   /**
8165    * @ngdoc function
8166    * @name getCanvasHeight
8167    * @methodOf ui.grid.class:GridRenderContainer
8168    * @description Returns the total canvas height.   Only recalculates if canvasHeightShouldUpdate = false
8169    * @returns {number} total height of all the visible rows in the container
8170    */
8171   GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
8172     var self = this;
8173
8174     if (!self.canvasHeightShouldUpdate) {
8175       return self.$$canvasHeight;
8176     }
8177
8178     var oldCanvasHeight = self.$$canvasHeight;
8179
8180     self.$$canvasHeight =  0;
8181
8182     self.visibleRowCache.forEach(function(row){
8183       self.$$canvasHeight += row.height;
8184     });
8185
8186
8187     self.canvasHeightShouldUpdate = false;
8188
8189     self.grid.api.core.raise.canvasHeightChanged(oldCanvasHeight, self.$$canvasHeight);
8190
8191     return self.$$canvasHeight;
8192   };
8193
8194   GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8195     return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight;
8196   };
8197
8198   GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
8199     var self = this;
8200
8201     var ret = self.canvasWidth;
8202
8203     return ret;
8204   };
8205
8206   GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
8207     this.renderedRows.length = newRows.length;
8208     for (var i = 0; i < newRows.length; i++) {
8209       this.renderedRows[i] = newRows[i];
8210     }
8211   };
8212
8213   GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8214     var self = this;
8215
8216     // OLD:
8217     this.renderedColumns.length = newColumns.length;
8218     for (var i = 0; i < newColumns.length; i++) {
8219       this.renderedColumns[i] = newColumns[i];
8220     }
8221
8222     this.updateColumnOffset();
8223   };
8224
8225   GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
8226     // Calculate the width of the columns on the left side that are no longer rendered.
8227     //  That will be the offset for the columns as we scroll horizontally.
8228     var hiddenColumnsWidth = 0;
8229     for (var i = 0; i < this.currentFirstColumn; i++) {
8230       hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
8231     }
8232
8233     this.columnOffset = hiddenColumnsWidth;
8234   };
8235
8236   GridRenderContainer.prototype.scrollVertical = function (newScrollTop) {
8237     var vertScrollPercentage = -1;
8238
8239     if (newScrollTop !== this.prevScrollTop) {
8240       var yDiff = newScrollTop - this.prevScrollTop;
8241
8242       if (yDiff > 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; }
8243       if (yDiff < 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.UP; }
8244
8245       var vertScrollLength = this.getVerticalScrollLength();
8246
8247       vertScrollPercentage = newScrollTop / vertScrollLength;
8248
8249       // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8250
8251       if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
8252       if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
8253
8254       this.adjustScrollVertical(newScrollTop, vertScrollPercentage);
8255       return vertScrollPercentage;
8256     }
8257   };
8258
8259   GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8260     var horizScrollPercentage = -1;
8261
8262     // Handle RTL here
8263
8264     if (newScrollLeft !== this.prevScrollLeft) {
8265       var xDiff = newScrollLeft - this.prevScrollLeft;
8266
8267       if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
8268       if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
8269
8270       var horizScrollLength = (this.canvasWidth - this.getViewportWidth());
8271       if (horizScrollLength !== 0) {
8272         horizScrollPercentage = newScrollLeft / horizScrollLength;
8273       }
8274       else {
8275         horizScrollPercentage = 0;
8276       }
8277
8278       this.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
8279       return horizScrollPercentage;
8280     }
8281   };
8282
8283   GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
8284     if (this.prevScrollTop === scrollTop && !force) {
8285       return;
8286     }
8287
8288     if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
8289       scrollTop = (this.getCanvasHeight() - this.getViewportHeight()) * scrollPercentage;
8290     }
8291
8292     this.adjustRows(scrollTop, scrollPercentage, false);
8293
8294     this.prevScrollTop = scrollTop;
8295     this.prevScrolltopPercentage = scrollPercentage;
8296
8297     this.grid.queueRefresh();
8298   };
8299
8300   GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
8301     if (this.prevScrollLeft === scrollLeft && !force) {
8302       return;
8303     }
8304
8305     if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
8306       scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
8307     }
8308
8309     this.adjustColumns(scrollLeft, scrollPercentage);
8310
8311     this.prevScrollLeft = scrollLeft;
8312     this.prevScrollleftPercentage = scrollPercentage;
8313
8314     this.grid.queueRefresh();
8315   };
8316
8317   GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) {
8318     var self = this;
8319
8320     var minRows = self.minRowsToRender();
8321
8322     var rowCache = self.visibleRowCache;
8323
8324     var maxRowIndex = rowCache.length - minRows;
8325
8326     // console.log('scroll%1', scrollPercentage);
8327
8328     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
8329     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
8330       scrollPercentage = scrollTop / self.getVerticalScrollLength();
8331     }
8332
8333     var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8334
8335     // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
8336
8337     // Define a max row index that we can't scroll past
8338     if (rowIndex > maxRowIndex) {
8339       rowIndex = maxRowIndex;
8340     }
8341
8342     var newRange = [];
8343     if (rowCache.length > self.grid.options.virtualizationThreshold) {
8344       if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) {
8345         // Have we hit the threshold going down?
8346         if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8347           return;
8348         }
8349         //Have we hit the threshold going up?
8350         if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8351           return;
8352         }
8353       }
8354       var rangeStart = {};
8355       var rangeEnd = {};
8356
8357       rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8358       rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8359
8360       newRange = [rangeStart, rangeEnd];
8361     }
8362     else {
8363       var maxLen = self.visibleRowCache.length;
8364       newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
8365     }
8366
8367     self.updateViewableRowRange(newRange);
8368
8369     self.prevRowScrollIndex = rowIndex;
8370   };
8371
8372   GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
8373     var self = this;
8374
8375     var minCols = self.minColumnsToRender();
8376
8377     var columnCache = self.visibleColumnCache;
8378     var maxColumnIndex = columnCache.length - minCols;
8379
8380     // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
8381     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8382       var horizScrollLength = (self.getCanvasWidth() - self.getViewportWidth());
8383       scrollPercentage = scrollLeft / horizScrollLength;
8384     }
8385
8386     var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
8387
8388     // Define a max row index that we can't scroll past
8389     if (colIndex > maxColumnIndex) {
8390       colIndex = maxColumnIndex;
8391     }
8392
8393     var newRange = [];
8394     if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8395       /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8396        * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8397       // Have we hit the threshold going down?
8398       if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8399         return;
8400       }
8401       //Have we hit the threshold going up?
8402       if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8403         return;
8404       }*/
8405
8406       var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
8407       var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
8408
8409       newRange = [rangeStart, rangeEnd];
8410     }
8411     else {
8412       var maxLen = self.visibleColumnCache.length;
8413
8414       newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
8415     }
8416
8417     self.updateViewableColumnRange(newRange);
8418
8419     self.prevColumnScrollIndex = colIndex;
8420   };
8421
8422   // Method for updating the visible rows
8423   GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
8424     // Slice out the range of rows from the data
8425     // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
8426     var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
8427
8428     // Define the top-most rendered row
8429     this.currentTopRow = renderedRange[0];
8430
8431     this.setRenderedRows(rowArr);
8432   };
8433
8434   // Method for updating the visible columns
8435   GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
8436     // Slice out the range of rows from the data
8437     // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
8438     var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
8439
8440     // Define the left-most rendered columns
8441     this.currentFirstColumn = renderedRange[0];
8442
8443     this.setRenderedColumns(columnArr);
8444   };
8445
8446   GridRenderContainer.prototype.headerCellWrapperStyle = function () {
8447     var self = this;
8448
8449     if (self.currentFirstColumn !== 0) {
8450       var offset = self.columnOffset;
8451
8452       if (self.grid.isRTL()) {
8453         return { 'margin-right': offset + 'px' };
8454       }
8455       else {
8456         return { 'margin-left': offset + 'px' };
8457       }
8458     }
8459
8460     return null;
8461   };
8462
8463     /**
8464      *  @ngdoc boolean
8465      *  @name updateColumnWidths
8466      *  @propertyOf  ui.grid.class:GridRenderContainer
8467      *  @description Determine the appropriate column width of each column across all render containers.
8468      *
8469      *  Column width is easy when each column has a specified width.  When columns are variable width (i.e.
8470      *  have an * or % of the viewport) then we try to calculate so that things fit in.  The problem is that
8471      *  we have multiple render containers, and we don't want one render container to just take the whole viewport
8472      *  when it doesn't need to - we want things to balance out across the render containers.
8473      *
8474      *  To do this, we use this method to calculate all the renderContainers, recognising that in a given render
8475      *  cycle it'll get called once per render container, so it needs to return the same values each time.
8476      *
8477      *  The constraints on this method are therefore:
8478      *  - must return the same value when called multiple times, to do this it needs to rely on properties of the
8479      *    columns, but not properties that change when this is called (so it shouldn't rely on drawnWidth)
8480      *
8481      *  The general logic of this method is:
8482      *  - calculate our total available width
8483      *  - look at all the columns across all render containers, and work out which have widths and which have
8484      *    constraints such as % or * or something else
8485      *  - for those with *, count the total number of * we see and add it onto a running total, add this column to an * array
8486      *  - for those with a %, allocate the % as a percentage of the viewport, having consideration of min and max
8487      *  - for those with manual width (in pixels) we set the drawnWidth to the specified width
8488      *  - we end up with an asterisks array still to process
8489      *  - we look at our remaining width.  If it's greater than zero, we divide it up among the asterisk columns, then process
8490      *    them for min and max width constraints
8491      *  - if it's zero or less, we set the asterisk columns to their minimum widths
8492      *  - we use parseInt quite a bit, as we try to make all our column widths integers
8493      */
8494   GridRenderContainer.prototype.updateColumnWidths = function () {
8495     var self = this;
8496
8497     var asterisksArray = [],
8498         asteriskNum = 0,
8499         usedWidthSum = 0,
8500         ret = '';
8501
8502     // Get the width of the viewport
8503     var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
8504
8505     // get all the columns across all render containers, we have to calculate them all or one render container
8506     // could consume the whole viewport
8507     var columnCache = [];
8508     angular.forEach(self.grid.renderContainers, function( container, name){
8509       columnCache = columnCache.concat(container.visibleColumnCache);
8510     });
8511
8512     // look at each column, process any manual values or %, put the * into an array to look at later
8513     columnCache.forEach(function(column, i) {
8514       var width = 0;
8515       // Skip hidden columns
8516       if (!column.visible) { return; }
8517
8518       if (angular.isNumber(column.width)) {
8519         // pixel width, set to this value
8520         width = parseInt(column.width, 10);
8521         usedWidthSum = usedWidthSum + width;
8522         column.drawnWidth = width;
8523
8524       } else if (gridUtil.endsWith(column.width, "%")) {
8525         // percentage width, set to percentage of the viewport
8526         width = parseInt(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8527
8528         if ( width > column.maxWidth ){
8529           width = column.maxWidth;
8530         }
8531
8532         if ( width < column.minWidth ){
8533           width = column.minWidth;
8534         }
8535
8536         usedWidthSum = usedWidthSum + width;
8537         column.drawnWidth = width;
8538       } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
8539         // is an asterisk column, the gridColumn already checked the string consists only of '****'
8540         asteriskNum = asteriskNum + column.width.length;
8541         asterisksArray.push(column);
8542       }
8543     });
8544
8545     // Get the remaining width (available width subtracted by the used widths sum)
8546     var remainingWidth = availableWidth - usedWidthSum;
8547
8548     var i, column, colWidth;
8549
8550     if (asterisksArray.length > 0) {
8551       // the width that each asterisk value would be assigned (this can be negative)
8552       var asteriskVal = remainingWidth / asteriskNum;
8553
8554       asterisksArray.forEach(function( column ){
8555         var width = parseInt(column.width.length * asteriskVal, 10);
8556
8557         if ( width > column.maxWidth ){
8558           width = column.maxWidth;
8559         }
8560
8561         if ( width < column.minWidth ){
8562           width = column.minWidth;
8563         }
8564
8565         usedWidthSum = usedWidthSum + width;
8566         column.drawnWidth = width;
8567       });
8568     }
8569
8570     // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8571     // calculated widths would have the grid narrower than the available space,
8572     // dole the remainder out one by one to make everything fit
8573     var processColumnUpwards = function(column){
8574       if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8575         column.drawnWidth++;
8576         usedWidthSum++;
8577         leftoverWidth--;
8578         columnsToChange = true;
8579       }
8580     };
8581
8582     var leftoverWidth = availableWidth - usedWidthSum;
8583     var columnsToChange = true;
8584
8585     while (leftoverWidth > 0 && columnsToChange) {
8586       columnsToChange = false;
8587       asterisksArray.forEach(processColumnUpwards);
8588     }
8589
8590     // We can end up with too much width even though some columns aren't at their max width, in this situation
8591     // we can trim the columns a little
8592     var processColumnDownwards = function(column){
8593       if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8594         column.drawnWidth--;
8595         usedWidthSum--;
8596         excessWidth--;
8597         columnsToChange = true;
8598       }
8599     };
8600
8601     var excessWidth =  usedWidthSum - availableWidth;
8602     columnsToChange = true;
8603
8604     while (excessWidth > 0 && columnsToChange) {
8605       columnsToChange = false;
8606       asterisksArray.forEach(processColumnDownwards);
8607     }
8608
8609
8610     // all that was across all the renderContainers, now we need to work out what that calculation decided for
8611     // our renderContainer
8612     var canvasWidth = 0;
8613     self.visibleColumnCache.forEach(function(column){
8614       if ( column.visible ){
8615         canvasWidth = canvasWidth + column.drawnWidth;
8616       }
8617     });
8618
8619     // Build the CSS
8620     columnCache.forEach(function (column) {
8621       ret = ret + column.getColClassDefinition();
8622     });
8623
8624     self.canvasWidth = canvasWidth;
8625
8626     // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
8627     // return ret;
8628
8629     // Set this render container's column styles so they can be used in style computation
8630     this.columnStyles = ret;
8631   };
8632
8633   GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8634     return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8635   };
8636
8637   GridRenderContainer.prototype.getViewportStyle = function () {
8638     var self = this;
8639     var styles = {};
8640
8641     self.hasHScrollbar = false;
8642     self.hasVScrollbar = false;
8643
8644     if (self.grid.disableScrolling) {
8645       styles['overflow-x'] = 'hidden';
8646       styles['overflow-y'] = 'hidden';
8647       return styles;
8648     }
8649
8650     if (self.name === 'body') {
8651       self.hasHScrollbar = self.grid.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER;
8652       if (!self.grid.isRTL()) {
8653         if (!self.grid.hasRightContainerColumns()) {
8654           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8655         }
8656       }
8657       else {
8658         if (!self.grid.hasLeftContainerColumns()) {
8659           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8660         }
8661       }
8662     }
8663     else if (self.name === 'left') {
8664       self.hasVScrollbar = self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8665     }
8666     else {
8667       self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8668     }
8669
8670     styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8671     styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8672
8673
8674     return styles;
8675
8676
8677   };
8678
8679   return GridRenderContainer;
8680 }]);
8681
8682 })();
8683
8684 (function(){
8685
8686 angular.module('ui.grid')
8687 .factory('GridRow', ['gridUtil', function(gridUtil) {
8688
8689    /**
8690    * @ngdoc function
8691    * @name ui.grid.class:GridRow
8692    * @description GridRow is the viewModel for one logical row on the grid.  A grid Row is not necessarily a one-to-one
8693    * relation to gridOptions.data.
8694    * @param {object} entity the array item from GridOptions.data
8695    * @param {number} index the current position of the row in the array
8696    * @param {Grid} reference to the parent grid
8697    */
8698   function GridRow(entity, index, grid) {
8699
8700      /**
8701       *  @ngdoc object
8702       *  @name grid
8703       *  @propertyOf  ui.grid.class:GridRow
8704       *  @description A reference back to the grid
8705       */
8706      this.grid = grid;
8707
8708      /**
8709       *  @ngdoc object
8710       *  @name entity
8711       *  @propertyOf  ui.grid.class:GridRow
8712       *  @description A reference to an item in gridOptions.data[]
8713       */
8714     this.entity = entity;
8715
8716      /**
8717       *  @ngdoc object
8718       *  @name uid
8719       *  @propertyOf  ui.grid.class:GridRow
8720       *  @description  UniqueId of row
8721       */
8722      this.uid = gridUtil.nextUid();
8723
8724      /**
8725       *  @ngdoc object
8726       *  @name visible
8727       *  @propertyOf  ui.grid.class:GridRow
8728       *  @description If true, the row will be rendered
8729       */
8730     // Default to true
8731     this.visible = true;
8732
8733
8734     this.$$height = grid.options.rowHeight;
8735
8736   }
8737
8738     /**
8739      *  @ngdoc object
8740      *  @name height
8741      *  @propertyOf  ui.grid.class:GridRow
8742      *  @description height of each individual row. changing the height will flag all
8743      *  row renderContainers to recalculate their canvas height
8744      */
8745     Object.defineProperty(GridRow.prototype, 'height', {
8746       get: function() {
8747         return this.$$height;
8748       },
8749       set: function(height) {
8750         if (height !== this.$$height) {
8751           this.grid.updateCanvasHeight();
8752           this.$$height = height;
8753         }
8754       }
8755     });
8756
8757   /**
8758    * @ngdoc function
8759    * @name getQualifiedColField
8760    * @methodOf ui.grid.class:GridRow
8761    * @description returns the qualified field name as it exists on scope
8762    * ie: row.entity.fieldA
8763    * @param {GridCol} col column instance
8764    * @returns {string} resulting name that can be evaluated on scope
8765    */
8766     GridRow.prototype.getQualifiedColField = function(col) {
8767       return 'row.' + this.getEntityQualifiedColField(col);
8768     };
8769
8770     /**
8771      * @ngdoc function
8772      * @name getEntityQualifiedColField
8773      * @methodOf ui.grid.class:GridRow
8774      * @description returns the qualified field name minus the row path
8775      * ie: entity.fieldA
8776      * @param {GridCol} col column instance
8777      * @returns {string} resulting name that can be evaluated against a row
8778      */
8779   GridRow.prototype.getEntityQualifiedColField = function(col) {
8780     return gridUtil.preEval('entity.' + col.field);
8781   };
8782   
8783   
8784   /**
8785    * @ngdoc function
8786    * @name setRowInvisible
8787    * @methodOf  ui.grid.class:GridRow
8788    * @description Sets an override on the row that forces it to always
8789    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8790    * 
8791    * This method can be called from the api, passing in the gridRow we want
8792    * altered.  It should really work by calling gridRow.setRowInvisible, but that's
8793    * not the way I coded it, and too late to change now.  Changed to just call
8794    * the internal function row.setThisRowInvisible().
8795    * 
8796    * @param {GridRow} row the row we want to set to invisible
8797    * 
8798    */
8799   GridRow.prototype.setRowInvisible = function ( row ) {
8800     if (row && row.setThisRowInvisible){
8801       row.setThisRowInvisible( 'user' );
8802     }
8803   };
8804   
8805   
8806   /**
8807    * @ngdoc function
8808    * @name clearRowInvisible
8809    * @methodOf  ui.grid.class:GridRow
8810    * @description Clears an override on the row that forces it to always
8811    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8812    * 
8813    * This method can be called from the api, passing in the gridRow we want
8814    * altered.  It should really work by calling gridRow.clearRowInvisible, but that's
8815    * not the way I coded it, and too late to change now.  Changed to just call
8816    * the internal function row.clearThisRowInvisible().
8817    * 
8818    * @param {GridRow} row the row we want to clear the invisible flag
8819    * 
8820    */
8821   GridRow.prototype.clearRowInvisible = function ( row ) {
8822     if (row && row.clearThisRowInvisible){
8823       row.clearThisRowInvisible( 'user' );
8824     }
8825   };
8826   
8827   
8828   /**
8829    * @ngdoc function
8830    * @name setThisRowInvisible
8831    * @methodOf  ui.grid.class:GridRow
8832    * @description Sets an override on the row that forces it to always
8833    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility
8834    *
8835    * @param {string} reason the reason (usually the module) for the row to be invisible.
8836    * E.g. grouping, user, filter
8837    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8838    */
8839   GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
8840     if ( !this.invisibleReason ){
8841       this.invisibleReason = {};
8842     }
8843     this.invisibleReason[reason] = true;
8844     this.evaluateRowVisibility( fromRowsProcessor);
8845   };
8846
8847
8848   /**
8849    * @ngdoc function
8850    * @name clearRowInvisible
8851    * @methodOf ui.grid.class:GridRow
8852    * @description Clears any override on the row visibility, returning it 
8853    * to normal visibility calculations.  Emits the rowsVisibleChanged
8854    * event
8855    * 
8856    * @param {string} reason the reason (usually the module) for the row to be invisible.
8857    * E.g. grouping, user, filter
8858    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8859    */
8860   GridRow.prototype.clearThisRowInvisible = function ( reason, fromRowsProcessor ) {
8861     if (typeof(this.invisibleReason) !== 'undefined' ) {
8862       delete this.invisibleReason[reason];
8863     }
8864     this.evaluateRowVisibility( fromRowsProcessor );
8865   };
8866
8867
8868   /**
8869    * @ngdoc function
8870    * @name evaluateRowVisibility
8871    * @methodOf ui.grid.class:GridRow
8872    * @description Determines whether the row should be visible based on invisibleReason, 
8873    * and if it changes the row visibility, then emits the rowsVisibleChanged event.
8874    * 
8875    * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
8876    * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
8877    * row processor does that already
8878    */
8879   GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
8880     var newVisibility = true;
8881     if ( typeof(this.invisibleReason) !== 'undefined' ){
8882       angular.forEach(this.invisibleReason, function( value, key ){
8883         if ( value ){
8884           newVisibility = false;
8885         }
8886       });
8887     }
8888     
8889     if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
8890       this.visible = newVisibility;
8891       if ( !fromRowProcessor ){
8892         this.grid.queueGridRefresh();
8893         this.grid.api.core.raise.rowsVisibleChanged(this);
8894       }
8895     }
8896   };
8897   
8898
8899   return GridRow;
8900 }]);
8901
8902 })();
8903
8904 (function(){
8905   'use strict';
8906   /**
8907    * @ngdoc object
8908    * @name ui.grid.class:GridRowColumn
8909    * @param {GridRow} row The row for this pair
8910    * @param {GridColumn} column The column for this pair
8911    * @description A row and column pair that represents the intersection of these two entities.
8912    * Must be instantiated as a constructor using the `new` keyword.
8913    */
8914   angular.module('ui.grid')
8915   .factory('GridRowColumn', ['$parse', '$filter',
8916     function GridRowColumnFactory($parse, $filter){
8917       var GridRowColumn = function GridRowColumn(row, col) {
8918         if ( !(this instanceof GridRowColumn)){
8919           throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
8920         }
8921
8922         /**
8923          * @ngdoc object
8924          * @name row
8925          * @propertyOf ui.grid.class:GridRowColumn
8926          * @description {@link ui.grid.class:GridRow }
8927          */
8928         this.row = row;
8929         /**
8930          * @ngdoc object
8931          * @name col
8932          * @propertyOf ui.grid.class:GridRowColumn
8933          * @description {@link ui.grid.class:GridColumn }
8934          */
8935         this.col = col;
8936       };
8937
8938       /**
8939        * @ngdoc function
8940        * @name getIntersectionValueRaw
8941        * @methodOf ui.grid.class:GridRowColumn
8942        * @description Gets the intersection of where the row and column meet.
8943        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8944        *          If the column has a cellFilter this will NOT return the filtered value.
8945        */
8946       GridRowColumn.prototype.getIntersectionValueRaw = function(){
8947         var getter = $parse(this.row.getEntityQualifiedColField(this.col));
8948         var context = this.row;
8949         return getter(context);
8950       };
8951       /**
8952        * @ngdoc function
8953        * @name getIntersectionValueFiltered
8954        * @methodOf ui.grid.class:GridRowColumn
8955        * @description Gets the intersection of where the row and column meet.
8956        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8957        *          If the column has a cellFilter this will also apply the filter to it and return the value that the filter displays.
8958        */
8959       GridRowColumn.prototype.getIntersectionValueFiltered = function(){
8960         var value = this.getIntersectionValueRaw();
8961         if (this.col.cellFilter && this.col.cellFilter !== ''){
8962           var getFilterIfExists = function(filterName){
8963             try {
8964               return $filter(filterName);
8965             } catch (e){
8966               return null;
8967             }
8968           };
8969           var filter = getFilterIfExists(this.col.cellFilter);
8970           if (filter) { // Check if this is filter name or a filter string
8971             value = filter(value);
8972           } else { // We have the template version of a filter so we need to parse it apart
8973             // Get the filter params out using a regex
8974             // Test out this regex here https://regex101.com/r/rC5eR5/2
8975             var re = /([^:]*):([^:]*):?([\s\S]+)?/;
8976             var matches;
8977             if ((matches = re.exec(this.col.cellFilter)) !== null) {
8978                 // View your result using the matches-variable.
8979                 // eg matches[0] etc.
8980                 value = $filter(matches[1])(value, matches[2], matches[3]);
8981             }
8982           }
8983         }
8984         return value;
8985       };
8986       return GridRowColumn;
8987     }
8988   ]);
8989 })();
8990
8991 (function () {
8992   angular.module('ui.grid')
8993     .factory('ScrollEvent', ['gridUtil', function (gridUtil) {
8994
8995       /**
8996        * @ngdoc function
8997        * @name ui.grid.class:ScrollEvent
8998        * @description Model for all scrollEvents
8999        * @param {Grid} grid that owns the scroll event
9000        * @param {GridRenderContainer} sourceRowContainer that owns the scroll event. Can be null
9001        * @param {GridRenderContainer} sourceColContainer that owns the scroll event. Can be null
9002        * @param {string} source the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
9003        */
9004       function ScrollEvent(grid, sourceRowContainer, sourceColContainer, source) {
9005         var self = this;
9006         if (!grid) {
9007           throw new Error("grid argument is required");
9008         }
9009
9010         /**
9011          *  @ngdoc object
9012          *  @name grid
9013          *  @propertyOf  ui.grid.class:ScrollEvent
9014          *  @description A reference back to the grid
9015          */
9016          self.grid = grid;
9017
9018
9019
9020         /**
9021          *  @ngdoc object
9022          *  @name source
9023          *  @propertyOf  ui.grid.class:ScrollEvent
9024          *  @description the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
9025          */
9026         self.source = source;
9027
9028
9029         /**
9030          *  @ngdoc object
9031          *  @name noDelay
9032          *  @propertyOf  ui.grid.class:ScrollEvent
9033          *  @description most scroll events from the mouse or trackpad require delay to operate properly
9034          *  set to false to eliminate delay.  Useful for scroll events that the grid causes, such as scrolling to make a row visible.
9035          */
9036         self.withDelay = true;
9037
9038         self.sourceRowContainer = sourceRowContainer;
9039         self.sourceColContainer = sourceColContainer;
9040
9041         self.newScrollLeft = null;
9042         self.newScrollTop = null;
9043         self.x = null;
9044         self.y = null;
9045
9046         self.verticalScrollLength = -9999999;
9047         self.horizontalScrollLength = -999999;
9048
9049
9050         /**
9051          *  @ngdoc function
9052          *  @name fireThrottledScrollingEvent
9053          *  @methodOf  ui.grid.class:ScrollEvent
9054          *  @description fires a throttled event using grid.api.core.raise.scrollEvent
9055          */
9056         self.fireThrottledScrollingEvent = gridUtil.throttle(function(sourceContainerId) {
9057           self.grid.scrollContainers(sourceContainerId, self);
9058         }, self.grid.options.wheelScrollThrottle, {trailing: true});
9059
9060       }
9061
9062
9063       /**
9064        *  @ngdoc function
9065        *  @name getNewScrollLeft
9066        *  @methodOf  ui.grid.class:ScrollEvent
9067        *  @description returns newScrollLeft property if available; calculates a new value if it isn't
9068        */
9069       ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9070         var self = this;
9071
9072         if (!self.newScrollLeft){
9073           var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
9074
9075           var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
9076
9077           var scrollXPercentage;
9078           if (typeof(self.x.percentage) !== 'undefined' && self.x.percentage !== undefined) {
9079             scrollXPercentage = self.x.percentage;
9080           }
9081           else if (typeof(self.x.pixels) !== 'undefined' && self.x.pixels !== undefined) {
9082             scrollXPercentage = self.x.percentage = (oldScrollLeft + self.x.pixels) / scrollWidth;
9083           }
9084           else {
9085             throw new Error("No percentage or pixel value provided for scroll event X axis");
9086           }
9087
9088           return Math.max(0, scrollXPercentage * scrollWidth);
9089         }
9090
9091         return self.newScrollLeft;
9092       };
9093
9094
9095       /**
9096        *  @ngdoc function
9097        *  @name getNewScrollTop
9098        *  @methodOf  ui.grid.class:ScrollEvent
9099        *  @description returns newScrollTop property if available; calculates a new value if it isn't
9100        */
9101       ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9102         var self = this;
9103
9104
9105         if (!self.newScrollTop){
9106           var scrollLength = rowContainer.getVerticalScrollLength();
9107
9108           var oldScrollTop = viewport[0].scrollTop;
9109
9110           var scrollYPercentage;
9111           if (typeof(self.y.percentage) !== 'undefined' && self.y.percentage !== undefined) {
9112             scrollYPercentage = self.y.percentage;
9113           }
9114           else if (typeof(self.y.pixels) !== 'undefined' && self.y.pixels !== undefined) {
9115             scrollYPercentage = self.y.percentage = (oldScrollTop + self.y.pixels) / scrollLength;
9116           }
9117           else {
9118             throw new Error("No percentage or pixel value provided for scroll event Y axis");
9119           }
9120
9121           return Math.max(0, scrollYPercentage * scrollLength);
9122         }
9123
9124         return self.newScrollTop;
9125       };
9126
9127       ScrollEvent.prototype.atTop = function(scrollTop) {
9128         return (this.y && (this.y.percentage === 0 || this.verticalScrollLength < 0) && scrollTop === 0);
9129       };
9130
9131       ScrollEvent.prototype.atBottom = function(scrollTop) {
9132         return (this.y && (this.y.percentage === 1 || this.verticalScrollLength === 0) && scrollTop > 0);
9133       };
9134
9135       ScrollEvent.prototype.atLeft = function(scrollLeft) {
9136         return (this.x && (this.x.percentage === 0 || this.horizontalScrollLength < 0) && scrollLeft === 0);
9137       };
9138
9139       ScrollEvent.prototype.atRight = function(scrollLeft) {
9140         return (this.x && (this.x.percentage === 1 || this.horizontalScrollLength ===0) && scrollLeft > 0);
9141       };
9142
9143
9144       ScrollEvent.Sources = {
9145         ViewPortScroll: 'ViewPortScroll',
9146         RenderContainerMouseWheel: 'RenderContainerMouseWheel',
9147         RenderContainerTouchMove: 'RenderContainerTouchMove',
9148         Other: 99
9149       };
9150
9151       return ScrollEvent;
9152     }]);
9153
9154
9155
9156 })();
9157
9158 (function () {
9159   'use strict';
9160   /**
9161    *  @ngdoc object
9162    *  @name ui.grid.service:gridClassFactory
9163    *
9164    *  @description factory to return dom specific instances of a grid
9165    *
9166    */
9167   angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
9168     function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
9169
9170       var service = {
9171         /**
9172          * @ngdoc method
9173          * @name createGrid
9174          * @methodOf ui.grid.service:gridClassFactory
9175          * @description Creates a new grid instance. Each instance will have a unique id
9176          * @param {object} options An object map of options to pass into the created grid instance.
9177          * @returns {Grid} grid
9178          */
9179         createGrid : function(options) {
9180           options = (typeof(options) !== 'undefined') ? options : {};
9181           options.id = gridUtil.newId();
9182           var grid = new Grid(options);
9183
9184           // NOTE/TODO: rowTemplate should always be defined...
9185           if (grid.options.rowTemplate) {
9186             var rowTemplateFnPromise = $q.defer();
9187             grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9188             
9189             gridUtil.getTemplate(grid.options.rowTemplate)
9190               .then(
9191                 function (template) {
9192                   var rowTemplateFn = $compile(template);
9193                   rowTemplateFnPromise.resolve(rowTemplateFn);
9194                 },
9195                 function (res) {
9196                   // Todo handle response error here?
9197                   throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9198                 });
9199           }
9200
9201           grid.registerColumnBuilder(service.defaultColumnBuilder);
9202
9203           // Row builder for custom row templates
9204           grid.registerRowBuilder(service.rowTemplateAssigner);
9205
9206           // Reset all rows to visible initially
9207           grid.registerRowsProcessor(function allRowsVisible(rows) {
9208             rows.forEach(function (row) {
9209               row.evaluateRowVisibility( true );
9210             }, 50);
9211
9212             return rows;
9213           });
9214
9215           grid.registerColumnsProcessor(function allColumnsVisible(columns) {
9216             columns.forEach(function (column) {
9217               column.visible = true;
9218             });
9219
9220             return columns;
9221           }, 50);
9222
9223           grid.registerColumnsProcessor(function(renderableColumns) {
9224               renderableColumns.forEach(function (column) {
9225                   if (column.colDef.visible === false) {
9226                       column.visible = false;
9227                   }
9228               });
9229
9230               return renderableColumns;
9231           }, 50);
9232
9233
9234           grid.registerRowsProcessor(grid.searchRows, 100);
9235
9236           // Register the default row processor, it sorts rows by selected columns
9237           if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
9238             grid.registerRowsProcessor(grid.options.externalSort, 200);
9239           }
9240           else {
9241             grid.registerRowsProcessor(grid.sortByColumn, 200);
9242           }
9243
9244           return grid;
9245         },
9246
9247         /**
9248          * @ngdoc function
9249          * @name defaultColumnBuilder
9250          * @methodOf ui.grid.service:gridClassFactory
9251          * @description Processes designTime column definitions and applies them to col for the
9252          *              core grid features
9253          * @param {object} colDef reference to column definition
9254          * @param {GridColumn} col reference to gridCol
9255          * @param {object} gridOptions reference to grid options
9256          */
9257         defaultColumnBuilder: function (colDef, col, gridOptions) {
9258
9259           var templateGetPromises = [];
9260
9261           // Abstracts the standard template processing we do for every template type.
9262           var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9263             if ( !colDef[templateType] ){
9264               col[providedType] = defaultTemplate;
9265             } else {
9266               col[providedType] = colDef[templateType];
9267             }
9268  
9269              templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9270                 .then(
9271                 function (template) {
9272                   if ( angular.isFunction(template) ) { template = template(); }
9273                   var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9274                   if ( tooltipType && col[tooltipType] === false ){
9275                     template = template.replace(uiGridConstants.TOOLTIP, '');
9276                   } else if ( tooltipType && col[tooltipType] ){
9277                     template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
9278                   }
9279
9280                   if ( filterType ){
9281                     col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
9282                       return col[filterType] ? "|" + col[filterType] : "";
9283                     });
9284                   } else {
9285                     col[templateType] = template;
9286                   }
9287                 },
9288                 function (res) {
9289                   throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9290                 })
9291             );
9292
9293           };
9294
9295
9296           /**
9297            * @ngdoc property
9298            * @name cellTemplate
9299            * @propertyOf ui.grid.class:GridOptions.columnDef
9300            * @description a custom template for each cell in this column.  The default
9301            * is ui-grid/uiGridCell.  If you are using the cellNav feature, this template
9302            * must contain a div that can receive focus.
9303            *
9304            */
9305           processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9306           col.cellTemplatePromise = templateGetPromises[0];
9307
9308           /**
9309            * @ngdoc property
9310            * @name headerCellTemplate
9311            * @propertyOf ui.grid.class:GridOptions.columnDef
9312            * @description a custom template for the header for this column.  The default
9313            * is ui-grid/uiGridHeaderCell
9314            *
9315            */
9316           processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9317
9318           /**
9319            * @ngdoc property
9320            * @name footerCellTemplate
9321            * @propertyOf ui.grid.class:GridOptions.columnDef
9322            * @description a custom template for the footer for this column.  The default
9323            * is ui-grid/uiGridFooterCell
9324            *
9325            */
9326           processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9327
9328           /**
9329            * @ngdoc property
9330            * @name filterHeaderTemplate
9331            * @propertyOf ui.grid.class:GridOptions.columnDef
9332            * @description a custom template for the filter input.  The default is ui-grid/ui-grid-filter
9333            *
9334            */
9335           processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9336
9337           // Create a promise for the compiled element function
9338           col.compiledElementFnDefer = $q.defer();
9339
9340           return $q.all(templateGetPromises);
9341         },
9342         
9343
9344         rowTemplateAssigner: function rowTemplateAssigner(row) {
9345           var grid = this;
9346
9347           // Row has no template assigned to it
9348           if (!row.rowTemplate) {
9349             // Use the default row template from the grid
9350             row.rowTemplate = grid.options.rowTemplate;
9351
9352             // Use the grid's function for fetching the compiled row template function
9353             row.getRowTemplateFn = grid.getRowTemplateFn;
9354           }
9355           // Row has its own template assigned
9356           else {
9357             // Create a promise for the compiled row template function
9358             var perRowTemplateFnPromise = $q.defer();
9359             row.getRowTemplateFn = perRowTemplateFnPromise.promise;
9360
9361             // Get the row template
9362             gridUtil.getTemplate(row.rowTemplate)
9363               .then(function (template) {
9364                 // Compile the template
9365                 var rowTemplateFn = $compile(template);
9366                 
9367                 // Resolve the compiled template function promise
9368                 perRowTemplateFnPromise.resolve(rowTemplateFn);
9369               },
9370               function (res) {
9371                 // Todo handle response error here?
9372                 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
9373               });
9374           }
9375
9376           return row.getRowTemplateFn;
9377         }
9378       };
9379
9380       //class definitions (moved to separate factories)
9381
9382       return service;
9383     }]);
9384
9385 })();
9386
9387 (function() {
9388
9389 var module = angular.module('ui.grid');
9390
9391 function escapeRegExp(str) {
9392   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
9393 }
9394
9395
9396 /**
9397  *  @ngdoc service
9398  *  @name ui.grid.service:rowSearcher
9399  *
9400  *  @description Service for searching/filtering rows based on column value conditions.
9401  */
9402 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
9403   var defaultCondition = uiGridConstants.filter.CONTAINS;
9404
9405   var rowSearcher = {};
9406
9407   /**
9408    * @ngdoc function
9409    * @name getTerm
9410    * @methodOf ui.grid.service:rowSearcher
9411    * @description Get the term from a filter
9412    * Trims leading and trailing whitespace
9413    * @param {object} filter object to use
9414    * @returns {object} Parsed term
9415    */
9416   rowSearcher.getTerm = function getTerm(filter) {
9417     if (typeof(filter.term) === 'undefined') { return filter.term; }
9418     
9419     var term = filter.term;
9420
9421     // Strip leading and trailing whitespace if the term is a string
9422     if (typeof(term) === 'string') {
9423       term = term.trim();
9424     }
9425
9426     return term;
9427   };
9428
9429   /**
9430    * @ngdoc function
9431    * @name stripTerm
9432    * @methodOf ui.grid.service:rowSearcher
9433    * @description Remove leading and trailing asterisk (*) from the filter's term
9434    * @param {object} filter object to use
9435    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9436    */
9437   rowSearcher.stripTerm = function stripTerm(filter) {
9438     var term = rowSearcher.getTerm(filter);
9439
9440     if (typeof(term) === 'string') {
9441       return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
9442     }
9443     else {
9444       return term;
9445     }
9446   };
9447   
9448
9449   /**
9450    * @ngdoc function
9451    * @name guessCondition
9452    * @methodOf ui.grid.service:rowSearcher
9453    * @description Guess the condition for a filter based on its term
9454    * <br>
9455    * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
9456    * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
9457    * @param {object} filter object to use
9458    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9459    */
9460   rowSearcher.guessCondition = function guessCondition(filter) {
9461     if (typeof(filter.term) === 'undefined' || !filter.term) {
9462       return defaultCondition;
9463     }
9464
9465     var term = rowSearcher.getTerm(filter);
9466     
9467     if (/\*/.test(term)) {
9468       var regexpFlags = '';
9469       if (!filter.flags || !filter.flags.caseSensitive) {
9470         regexpFlags += 'i';
9471       }
9472
9473       var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
9474       return new RegExp('^' + reText + '$', regexpFlags);
9475     }
9476     // Otherwise default to default condition
9477     else {
9478       return defaultCondition;
9479     }
9480   };
9481   
9482   
9483   /**
9484    * @ngdoc function
9485    * @name setupFilters
9486    * @methodOf ui.grid.service:rowSearcher
9487    * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
9488    * do all the parsing and pre-processing and store that data into a new filters object.  The object
9489    * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9490    * 
9491    * We could use a forEach in here, since it's much less performance sensitive, but since we're using 
9492    * for loops everywhere else in this module...
9493    * 
9494    * @param {array} filters the filters from the column (col.filters or [col.filter])
9495    * @returns {array} An array of parsed/preprocessed filters
9496    */
9497   rowSearcher.setupFilters = function setupFilters( filters ){
9498     var newFilters = [];
9499     
9500     var filtersLength = filters.length;
9501     for ( var i = 0; i < filtersLength; i++ ){
9502       var filter = filters[i];
9503       
9504       if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9505         var newFilter = {};
9506         
9507         var regexpFlags = '';
9508         if (!filter.flags || !filter.flags.caseSensitive) {
9509           regexpFlags += 'i';
9510         }
9511     
9512         if ( !gridUtil.isNullOrUndefined(filter.term) ){
9513           // it is possible to have noTerm.  We don't need to copy that across, it was just a flag to avoid
9514           // getting the filter ignored if the filter was a function that didn't use a term
9515           newFilter.term = rowSearcher.stripTerm(filter);
9516         }
9517         
9518         if ( filter.condition ){
9519           newFilter.condition = filter.condition;
9520         } else {
9521           newFilter.condition = rowSearcher.guessCondition(filter);
9522         }
9523
9524         newFilter.flags = angular.extend( { caseSensitive: false, date: false }, filter.flags );
9525
9526         if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
9527           newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
9528         }
9529         
9530          if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
9531           newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
9532         }
9533
9534         if (newFilter.condition === uiGridConstants.filter.CONTAINS) {
9535           newFilter.containsRE = new RegExp(newFilter.term, regexpFlags);
9536         }
9537
9538         if (newFilter.condition === uiGridConstants.filter.EXACT) {
9539           newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
9540         }
9541         
9542         newFilters.push(newFilter);
9543       }
9544     }
9545     return newFilters;
9546   };
9547   
9548
9549   /**
9550    * @ngdoc function
9551    * @name runColumnFilter
9552    * @methodOf ui.grid.service:rowSearcher
9553    * @description Runs a single pre-parsed filter against a cell, returning true
9554    * if the cell matches that one filter.
9555    * 
9556    * @param {Grid} grid the grid we're working against
9557    * @param {GridRow} row the row we're matching against
9558    * @param {GridCol} column the column that we're working against
9559    * @param {object} filter the specific, preparsed, filter that we want to test
9560    * @returns {boolean} true if we match (row stays visible)
9561    */
9562   rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, filter) {
9563     // Cache typeof condition
9564     var conditionType = typeof(filter.condition);
9565
9566     // Term to search for.
9567     var term = filter.term;
9568
9569     // Get the column value for this row
9570     var value;
9571     if ( column.filterCellFiltered ){
9572       value = grid.getCellDisplayValue(row, column);
9573     } else {
9574       value = grid.getCellValue(row, column);
9575     }
9576
9577
9578     // If the filter's condition is a RegExp, then use it
9579     if (filter.condition instanceof RegExp) {
9580       return filter.condition.test(value);
9581     }
9582
9583     // If the filter's condition is a function, run it
9584     if (conditionType === 'function') {
9585       return filter.condition(term, value, row, column);
9586     }
9587
9588     if (filter.startswithRE) {
9589       return filter.startswithRE.test(value);
9590     }
9591
9592     if (filter.endswithRE) {
9593       return filter.endswithRE.test(value);
9594     }
9595
9596     if (filter.containsRE) {
9597       return filter.containsRE.test(value);
9598     }
9599
9600     if (filter.exactRE) {
9601       return filter.exactRE.test(value);
9602     }
9603
9604     if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
9605       var regex = new RegExp('^' + term + '$');
9606       return !regex.exec(value);
9607     }
9608
9609     if (typeof(value) === 'number' && typeof(term) === 'string' ){
9610       // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
9611       // the same for negative numbers
9612       // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
9613       var tempFloat = parseFloat(term.replace(/\\\./,'.').replace(/\\\-/,'-'));
9614       if (!isNaN(tempFloat)) {
9615         term = tempFloat;
9616       }
9617     }
9618
9619     if (filter.flags.date === true) {
9620       value = new Date(value);
9621       // If the term has a dash in it, it comes through as '\-' -- we need to take out the '\'.
9622       term = new Date(term.replace(/\\/g, ''));
9623     }
9624
9625     if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
9626       return (value > term);
9627     }
9628
9629     if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
9630       return (value >= term);
9631     }
9632
9633     if (filter.condition === uiGridConstants.filter.LESS_THAN) {
9634       return (value < term);
9635     }
9636
9637     if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
9638       return (value <= term);
9639     }
9640
9641     return true;
9642   };
9643
9644
9645   /**
9646    * @ngdoc boolean
9647    * @name useExternalFiltering
9648    * @propertyOf ui.grid.class:GridOptions
9649    * @description False by default. When enabled, this setting suppresses the internal filtering.
9650    * All UI logic will still operate, allowing filter conditions to be set and modified.
9651    * 
9652    * The external filter logic can listen for the `filterChange` event, which fires whenever
9653    * a filter has been adjusted.
9654    */
9655   /**
9656    * @ngdoc function
9657    * @name searchColumn
9658    * @methodOf ui.grid.service:rowSearcher
9659    * @description Process provided filters on provided column against a given row. If the row meets 
9660    * the conditions on all the filters, return true.
9661    * @param {Grid} grid Grid to search in
9662    * @param {GridRow} row Row to search on
9663    * @param {GridCol} column Column with the filters to use
9664    * @param {array} filters array of pre-parsed/preprocessed filters to apply
9665    * @returns {boolean} Whether the column matches or not.
9666    */
9667   rowSearcher.searchColumn = function searchColumn(grid, row, column, filters) {
9668     if (grid.options.useExternalFiltering) {
9669       return true;
9670     }
9671
9672     var filtersLength = filters.length;
9673     for (var i = 0; i < filtersLength; i++) {
9674       var filter = filters[i];
9675
9676       var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9677       if (!ret) {
9678         return false;
9679       }
9680     }
9681
9682     return true;
9683   };
9684
9685
9686   /**
9687    * @ngdoc function
9688    * @name search
9689    * @methodOf ui.grid.service:rowSearcher
9690    * @description Run a search across the given rows and columns, marking any rows that don't 
9691    * match the stored col.filters or col.filter as invisible.
9692    * @param {Grid} grid Grid instance to search inside
9693    * @param {Array[GridRow]} rows GridRows to filter
9694    * @param {Array[GridColumn]} columns GridColumns with filters to process
9695    */
9696   rowSearcher.search = function search(grid, rows, columns) {
9697     /*
9698      * Added performance optimisations into this code base, as this logic creates deeply nested
9699      * loops and is therefore very performance sensitive.  In particular, avoiding forEach as
9700      * this impacts some browser optimisers (particularly Chrome), using iterators instead
9701      */
9702
9703     // Don't do anything if we weren't passed any rows
9704     if (!rows) {
9705       return;
9706     }
9707
9708     // don't filter if filtering currently disabled
9709     if (!grid.options.enableFiltering){
9710       return rows;
9711     }
9712
9713     // Build list of filters to apply
9714     var filterData = [];
9715
9716     var colsLength = columns.length;
9717
9718     var hasTerm = function( filters ) {
9719       var hasTerm = false;
9720
9721       filters.forEach( function (filter) {
9722         if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9723           hasTerm = true;
9724         }
9725       });
9726
9727       return hasTerm;
9728     };
9729
9730     for (var i = 0; i < colsLength; i++) {
9731       var col = columns[i];
9732
9733       if (typeof(col.filters) !== 'undefined' && hasTerm(col.filters) ) {
9734         filterData.push( { col: col, filters: rowSearcher.setupFilters(col.filters) } );
9735       }
9736     }
9737
9738     if (filterData.length > 0) {
9739       // define functions outside the loop, performance optimisation
9740       var foreachRow = function(grid, row, col, filters){
9741         if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
9742           row.visible = false;
9743         }
9744       };
9745
9746       var foreachFilterCol = function(grid, filterData){
9747         var rowsLength = rows.length;
9748         for ( var i = 0; i < rowsLength; i++){
9749           foreachRow(grid, rows[i], filterData.col, filterData.filters);  
9750         }
9751       };
9752
9753       // nested loop itself - foreachFilterCol, which in turn calls foreachRow
9754       var filterDataLength = filterData.length;
9755       for ( var j = 0; j < filterDataLength; j++){
9756         foreachFilterCol( grid, filterData[j] );  
9757       }
9758
9759       if (grid.api.core.raise.rowsVisibleChanged) {
9760         grid.api.core.raise.rowsVisibleChanged();
9761       }
9762
9763       // drop any invisible rows
9764       // keeping these, as needed with filtering for trees - we have to come back and make parent nodes visible if child nodes are selected in the filter
9765       // rows = rows.filter(function(row){ return row.visible; });
9766
9767     }
9768
9769     return rows;
9770   };
9771
9772   return rowSearcher;
9773 }]);
9774
9775 })();
9776
9777 (function() {
9778
9779 var module = angular.module('ui.grid');
9780
9781 /**
9782  * @ngdoc object
9783  * @name ui.grid.class:RowSorter
9784  * @description RowSorter provides the default sorting mechanisms, 
9785  * including guessing column types and applying appropriate sort 
9786  * algorithms
9787  * 
9788  */ 
9789
9790 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
9791   var currencyRegexStr = 
9792     '(' +
9793     uiGridConstants.CURRENCY_SYMBOLS
9794       .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
9795       .join('|') + // Join all the symbols together with |s
9796     ')?';
9797
9798   // /^[-+]?[£$¤¥]?[\d,.]+%?$/
9799   var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
9800
9801   var rowSorter = {
9802     // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
9803     //   this takes a piece of data from the cell and tries to determine its type and what sorting
9804     //   function to use for it
9805     colSortFnCache: {}
9806   };
9807
9808
9809   /**
9810    * @ngdoc method
9811    * @methodOf ui.grid.class:RowSorter
9812    * @name guessSortFn
9813    * @description Assigns a sort function to use based on the itemType in the column
9814    * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'.  And
9815    * error will be thrown for any other type.
9816    * @returns {function} a sort function that will sort that type
9817    */
9818   rowSorter.guessSortFn = function guessSortFn(itemType) {
9819     switch (itemType) {
9820       case "number":
9821         return rowSorter.sortNumber;
9822       case "numberStr":
9823         return rowSorter.sortNumberStr;
9824       case "boolean":
9825         return rowSorter.sortBool;
9826       case "string":
9827         return rowSorter.sortAlpha;
9828       case "date":
9829         return rowSorter.sortDate;
9830       case "object":
9831         return rowSorter.basicSort;
9832       default:
9833         throw new Error('No sorting function found for type:' + itemType);
9834     }
9835   };
9836
9837
9838   /**
9839    * @ngdoc method
9840    * @methodOf ui.grid.class:RowSorter
9841    * @name handleNulls
9842    * @description Sorts nulls and undefined to the bottom (top when
9843    * descending).  Called by each of the internal sorters before
9844    * attempting to sort.  Note that this method is available on the core api
9845    * via gridApi.core.sortHandleNulls
9846    * @param {object} a sort value a
9847    * @param {object} b sort value b
9848    * @returns {number} null if there were no nulls/undefineds, otherwise returns
9849    * a sort value that should be passed back from the sort function
9850    */
9851   rowSorter.handleNulls = function handleNulls(a, b) {
9852     // We want to allow zero values and false values to be evaluated in the sort function
9853     if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
9854       // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
9855       if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
9856         return 0;
9857       }
9858       else if (!a && a !== 0 && a !== false) {
9859         return 1;
9860       }
9861       else if (!b && b !== 0 && b !== false) {
9862         return -1;
9863       }
9864     }
9865     return null;
9866   };
9867
9868
9869   /**
9870    * @ngdoc method
9871    * @methodOf ui.grid.class:RowSorter
9872    * @name basicSort
9873    * @description Sorts any values that provide the < method, including strings
9874    * or numbers.  Handles nulls and undefined through calling handleNulls 
9875    * @param {object} a sort value a
9876    * @param {object} b sort value b
9877    * @returns {number} normal sort function, returns -ve, 0, +ve
9878    */
9879   rowSorter.basicSort = function basicSort(a, b) {
9880     var nulls = rowSorter.handleNulls(a, b);
9881     if ( nulls !== null ){
9882       return nulls;
9883     } else {
9884       if (a === b) {
9885         return 0;
9886       }
9887       if (a < b) {
9888         return -1;
9889       }
9890       return 1;
9891     }
9892   };
9893
9894
9895   /**
9896    * @ngdoc method
9897    * @methodOf ui.grid.class:RowSorter
9898    * @name sortNumber
9899    * @description Sorts numerical values.  Handles nulls and undefined through calling handleNulls 
9900    * @param {object} a sort value a
9901    * @param {object} b sort value b
9902    * @returns {number} normal sort function, returns -ve, 0, +ve
9903    */
9904   rowSorter.sortNumber = function sortNumber(a, b) {
9905     var nulls = rowSorter.handleNulls(a, b);
9906     if ( nulls !== null ){
9907       return nulls;
9908     } else {
9909       return a - b;
9910     }
9911   };
9912
9913
9914   /**
9915    * @ngdoc method
9916    * @methodOf ui.grid.class:RowSorter
9917    * @name sortNumberStr
9918    * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).  
9919    * Handles nulls and undefined through calling handleNulls 
9920    * @param {object} a sort value a
9921    * @param {object} b sort value b
9922    * @returns {number} normal sort function, returns -ve, 0, +ve
9923    */
9924   rowSorter.sortNumberStr = function sortNumberStr(a, b) {
9925     var nulls = rowSorter.handleNulls(a, b);
9926     if ( nulls !== null ){
9927       return nulls;
9928     } else {
9929       var numA, // The parsed number form of 'a'
9930           numB, // The parsed number form of 'b'
9931           badA = false,
9932           badB = false;
9933   
9934       // Try to parse 'a' to a float
9935       numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
9936   
9937       // If 'a' couldn't be parsed to float, flag it as bad
9938       if (isNaN(numA)) {
9939           badA = true;
9940       }
9941   
9942       // Try to parse 'b' to a float
9943       numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
9944   
9945       // If 'b' couldn't be parsed to float, flag it as bad
9946       if (isNaN(numB)) {
9947           badB = true;
9948       }
9949   
9950       // We want bad ones to get pushed to the bottom... which effectively is "greater than"
9951       if (badA && badB) {
9952           return 0;
9953       }
9954   
9955       if (badA) {
9956           return 1;
9957       }
9958   
9959       if (badB) {
9960           return -1;
9961       }
9962   
9963       return numA - numB;
9964     }
9965   };
9966
9967
9968   /**
9969    * @ngdoc method
9970    * @methodOf ui.grid.class:RowSorter
9971    * @name sortAlpha
9972    * @description Sorts string values. Handles nulls and undefined through calling handleNulls 
9973    * @param {object} a sort value a
9974    * @param {object} b sort value b
9975    * @returns {number} normal sort function, returns -ve, 0, +ve
9976    */
9977   rowSorter.sortAlpha = function sortAlpha(a, b) {
9978     var nulls = rowSorter.handleNulls(a, b);
9979     if ( nulls !== null ){
9980       return nulls;
9981     } else {
9982       var strA = a.toString().toLowerCase(),
9983           strB = b.toString().toLowerCase();
9984   
9985       return strA === strB ? 0 : (strA < strB ? -1 : 1);
9986     }
9987   };
9988
9989
9990   /**
9991    * @ngdoc method
9992    * @methodOf ui.grid.class:RowSorter
9993    * @name sortDate
9994    * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
9995    * Handles date strings by converting to Date object if not already an instance of Date
9996    * @param {object} a sort value a
9997    * @param {object} b sort value b
9998    * @returns {number} normal sort function, returns -ve, 0, +ve
9999    */
10000   rowSorter.sortDate = function sortDate(a, b) {
10001     var nulls = rowSorter.handleNulls(a, b);
10002     if ( nulls !== null ){
10003       return nulls;
10004     } else {
10005       if (!(a instanceof Date)) {
10006         a = new Date(a);
10007       }
10008       if (!(b instanceof Date)){
10009         b = new Date(b);
10010       }
10011       var timeA = a.getTime(),
10012           timeB = b.getTime();
10013   
10014       return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
10015     }
10016   };
10017
10018
10019   /**
10020    * @ngdoc method
10021    * @methodOf ui.grid.class:RowSorter
10022    * @name sortBool
10023    * @description Sorts boolean values, true is considered larger than false. 
10024    * Handles nulls and undefined through calling handleNulls 
10025    * @param {object} a sort value a
10026    * @param {object} b sort value b
10027    * @returns {number} normal sort function, returns -ve, 0, +ve
10028    */
10029   rowSorter.sortBool = function sortBool(a, b) {
10030     var nulls = rowSorter.handleNulls(a, b);
10031     if ( nulls !== null ){
10032       return nulls;
10033     } else {
10034       if (a && b) {
10035         return 0;
10036       }
10037   
10038       if (!a && !b) {
10039         return 0;
10040       }
10041       else {
10042         return a ? 1 : -1;
10043       }
10044     }
10045   };
10046
10047
10048   /**
10049    * @ngdoc method
10050    * @methodOf ui.grid.class:RowSorter
10051    * @name getSortFn
10052    * @description Get the sort function for the column.  Looks first in 
10053    * rowSorter.colSortFnCache using the column name, failing that it
10054    * looks at col.sortingAlgorithm (and puts it in the cache), failing that
10055    * it guesses the sort algorithm based on the data type.
10056    * 
10057    * The cache currently seems a bit pointless, as none of the work we do is
10058    * processor intensive enough to need caching.  Presumably in future we might
10059    * inspect the row data itself to guess the sort function, and in that case
10060    * it would make sense to have a cache, the infrastructure is in place to allow
10061    * that.
10062    * 
10063    * @param {Grid} grid the grid to consider
10064    * @param {GridCol} col the column to find a function for
10065    * @param {array} rows an array of grid rows.  Currently unused, but presumably in future
10066    * we might inspect the rows themselves to decide what sort of data might be there
10067    * @returns {function} the sort function chosen for the column
10068    */
10069   rowSorter.getSortFn = function getSortFn(grid, col, rows) {
10070     var sortFn, item;
10071
10072     // See if we already figured out what to use to sort the column and have it in the cache
10073     if (rowSorter.colSortFnCache[col.colDef.name]) {
10074       sortFn = rowSorter.colSortFnCache[col.colDef.name];
10075     }
10076     // If the column has its OWN sorting algorithm, use that
10077     else if (col.sortingAlgorithm !== undefined) {
10078       sortFn = col.sortingAlgorithm;
10079       rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
10080     }
10081     // Always default to sortAlpha when sorting after a cellFilter
10082     else if ( col.sortCellFiltered && col.cellFilter ){
10083       sortFn = rowSorter.sortAlpha;
10084       rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10085     }
10086     // Try and guess what sort function to use
10087     else {
10088       // Guess the sort function
10089       sortFn = rowSorter.guessSortFn(col.colDef.type);
10090
10091       // If we found a sort function, cache it
10092       if (sortFn) {
10093         rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10094       }
10095       else {
10096         // We assign the alpha sort because anything that is null/undefined will never get passed to
10097         // the actual sorting function. It will get caught in our null check and returned to be sorted
10098         // down to the bottom
10099         sortFn = rowSorter.sortAlpha;
10100       }
10101     }
10102
10103     return sortFn;
10104   };
10105
10106
10107
10108   /**
10109    * @ngdoc method
10110    * @methodOf ui.grid.class:RowSorter
10111    * @name prioritySort
10112    * @description Used where multiple columns are present in the sort criteria,
10113    * we determine which column should take precedence in the sort by sorting
10114    * the columns based on their sort.priority
10115    * 
10116    * @param {gridColumn} a column a
10117    * @param {gridColumn} b column b
10118    * @returns {number} normal sort function, returns -ve, 0, +ve
10119    */
10120   rowSorter.prioritySort = function (a, b) {
10121     // Both columns have a sort priority
10122     if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
10123       // A is higher priority
10124       if (a.sort.priority < b.sort.priority) {
10125         return -1;
10126       }
10127       // Equal
10128       else if (a.sort.priority === b.sort.priority) {
10129         return 0;
10130       }
10131       // B is higher
10132       else {
10133         return 1;
10134       }
10135     }
10136     // Only A has a priority
10137     else if (a.sort.priority || a.sort.priority === 0) {
10138       return -1;
10139     }
10140     // Only B has a priority
10141     else if (b.sort.priority || b.sort.priority === 0) {
10142       return 1;
10143     }
10144     // Neither has a priority
10145     else {
10146       return 0;
10147     }
10148   };
10149
10150
10151   /**
10152    * @ngdoc object
10153    * @name useExternalSorting
10154    * @propertyOf ui.grid.class:GridOptions
10155    * @description Prevents the internal sorting from executing.  Events will
10156    * still be fired when the sort changes, and the sort information on
10157    * the columns will be updated, allowing an external sorter (for example,
10158    * server sorting) to be implemented.  Defaults to false. 
10159    * 
10160    */
10161   /**
10162    * @ngdoc method
10163    * @methodOf ui.grid.class:RowSorter
10164    * @name sort
10165    * @description sorts the grid 
10166    * @param {Object} grid the grid itself
10167    * @param {array} rows the rows to be sorted
10168    * @param {array} columns the columns in which to look
10169    * for sort criteria
10170    * @returns {array} sorted rows
10171    */
10172   rowSorter.sort = function rowSorterSort(grid, rows, columns) {
10173     // first make sure we are even supposed to do work
10174     if (!rows) {
10175       return;
10176     }
10177     
10178     if (grid.options.useExternalSorting){
10179       return rows;
10180     }
10181
10182     // Build the list of columns to sort by
10183     var sortCols = [];
10184     columns.forEach(function (col) {
10185       if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10186         sortCols.push(col);
10187       }
10188     });
10189
10190     // Sort the "sort columns" by their sort priority
10191     sortCols = sortCols.sort(rowSorter.prioritySort);
10192
10193     // Now rows to sort by, maintain original order
10194     if (sortCols.length === 0) {
10195       return rows;
10196     }
10197
10198     // Re-usable variables
10199     var col, direction;
10200
10201     // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10202     var setIndex = function( row, idx ){
10203       row.entity.$$uiGridIndex = idx;
10204     };
10205     rows.forEach(setIndex);
10206
10207     // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
10208     // var d = data.slice(0);
10209     var r = rows.slice(0);
10210
10211     // Now actually sort the data
10212     var rowSortFn = function (rowA, rowB) {
10213       var tem = 0,
10214           idx = 0,
10215           sortFn;
10216
10217       while (tem === 0 && idx < sortCols.length) {
10218         // grab the metadata for the rest of the logic
10219         col = sortCols[idx];
10220         direction = sortCols[idx].sort.direction;
10221
10222         sortFn = rowSorter.getSortFn(grid, col, r);
10223
10224         var propA, propB;
10225
10226         if ( col.sortCellFiltered ){
10227           propA = grid.getCellDisplayValue(rowA, col);
10228           propB = grid.getCellDisplayValue(rowB, col);
10229         } else {
10230           propA = grid.getCellValue(rowA, col);
10231           propB = grid.getCellValue(rowB, col);
10232         }
10233
10234         tem = sortFn(propA, propB, rowA, rowB, direction);
10235
10236         idx++;
10237       }
10238
10239       // Chrome doesn't implement a stable sort function.  If our sort returns 0 
10240       // (i.e. the items are equal), and we're at the last sort column in the list,
10241       // then return the previous order using our custom
10242       // index variable
10243       if (tem === 0 ) {
10244         return rowA.entity.$$uiGridIndex - rowB.entity.$$uiGridIndex;
10245       }
10246
10247       // Made it this far, we don't have to worry about null & undefined
10248       if (direction === uiGridConstants.ASC) {
10249         return tem;
10250       } else {
10251         return 0 - tem;
10252       }
10253     };
10254
10255     var newRows = rows.sort(rowSortFn);
10256     
10257     // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10258     var clearIndex = function( row, idx ){
10259        delete row.entity.$$uiGridIndex;
10260     };
10261     rows.forEach(clearIndex);
10262     
10263     return newRows;
10264   };
10265
10266   return rowSorter;
10267 }]);
10268
10269 })();
10270
10271 (function() {
10272
10273 var module = angular.module('ui.grid');
10274
10275 var bindPolyfill;
10276 if (typeof Function.prototype.bind !== "function") {
10277   bindPolyfill = function() {
10278     var slice = Array.prototype.slice;
10279     return function(context) {
10280       var fn = this,
10281         args = slice.call(arguments, 1);
10282       if (args.length) {
10283         return function() {
10284           return arguments.length ? fn.apply(context, args.concat(slice.call(arguments))) : fn.apply(context, args);
10285         };
10286       }
10287       return function() {
10288         return arguments.length ? fn.apply(context, arguments) : fn.call(context);
10289       };
10290     };
10291   };
10292 }
10293
10294 function getStyles (elem) {
10295   var e = elem;
10296   if (typeof(e.length) !== 'undefined' && e.length) {
10297     e = elem[0];
10298   }
10299
10300   return e.ownerDocument.defaultView.getComputedStyle(e, null);
10301 }
10302
10303 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
10304     // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
10305     // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
10306     rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
10307     cssShow = { position: "absolute", visibility: "hidden", display: "block" };
10308
10309 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
10310   var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
10311           // If we already have the right measurement, avoid augmentation
10312           4 :
10313           // Otherwise initialize for horizontal or vertical properties
10314           name === 'width' ? 1 : 0,
10315
10316           val = 0;
10317
10318   var sides = ['Top', 'Right', 'Bottom', 'Left'];
10319
10320   for ( ; i < 4; i += 2 ) {
10321     var side = sides[i];
10322     // dump('side', side);
10323
10324     // both box models exclude margin, so add it if we want it
10325     if ( extra === 'margin' ) {
10326       var marg = parseFloat(styles[extra + side]);
10327       if (!isNaN(marg)) {
10328         val += marg;
10329       }
10330     }
10331     // dump('val1', val);
10332
10333     if ( isBorderBox ) {
10334       // border-box includes padding, so remove it if we want content
10335       if ( extra === 'content' ) {
10336         var padd = parseFloat(styles['padding' + side]);
10337         if (!isNaN(padd)) {
10338           val -= padd;
10339           // dump('val2', val);
10340         }
10341       }
10342
10343       // at this point, extra isn't border nor margin, so remove border
10344       if ( extra !== 'margin' ) {
10345         var bordermarg = parseFloat(styles['border' + side + 'Width']);
10346         if (!isNaN(bordermarg)) {
10347           val -= bordermarg;
10348           // dump('val3', val);
10349         }
10350       }
10351     }
10352     else {
10353       // at this point, extra isn't content, so add padding
10354       var nocontentPad = parseFloat(styles['padding' + side]);
10355       if (!isNaN(nocontentPad)) {
10356         val += nocontentPad;
10357         // dump('val4', val);
10358       }
10359
10360       // at this point, extra isn't content nor padding, so add border
10361       if ( extra !== 'padding') {
10362         var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
10363         if (!isNaN(nocontentnopad)) {
10364           val += nocontentnopad;
10365           // dump('val5', val);
10366         }
10367       }
10368     }
10369   }
10370
10371   // dump('augVal', val);
10372
10373   return val;
10374 }
10375
10376 function getWidthOrHeight( elem, name, extra ) {
10377   // Start with offset property, which is equivalent to the border-box value
10378   var valueIsBorderBox = true,
10379           val, // = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
10380           styles = getStyles(elem),
10381           isBorderBox = styles['boxSizing'] === 'border-box';
10382
10383   // some non-html elements return undefined for offsetWidth, so check for null/undefined
10384   // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
10385   // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
10386   if ( val <= 0 || val == null ) {
10387     // Fall back to computed then uncomputed css if necessary
10388     val = styles[name];
10389     if ( val < 0 || val == null ) {
10390       val = elem.style[ name ];
10391     }
10392
10393     // Computed unit is not pixels. Stop here and return.
10394     if ( rnumnonpx.test(val) ) {
10395       return val;
10396     }
10397
10398     // we need the check for style in case a browser which returns unreliable values
10399     // for getComputedStyle silently falls back to the reliable elem.style
10400     valueIsBorderBox = isBorderBox &&
10401             ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
10402
10403     // Normalize "", auto, and prepare for extra
10404     val = parseFloat( val ) || 0;
10405   }
10406
10407   // use the active box-sizing model to add/subtract irrelevant styles
10408   var ret = ( val +
10409     augmentWidthOrHeight(
10410       elem,
10411       name,
10412       extra || ( isBorderBox ? "border" : "content" ),
10413       valueIsBorderBox,
10414       styles
10415     )
10416   );
10417
10418   // dump('ret', ret, val);
10419   return ret;
10420 }
10421
10422 function getLineHeight(elm) {
10423   elm = angular.element(elm)[0];
10424   var parent = elm.parentElement;
10425
10426   if (!parent) {
10427     parent = document.getElementsByTagName('body')[0];
10428   }
10429
10430   return parseInt( getStyles(parent).fontSize ) || parseInt( getStyles(elm).fontSize ) || 16;
10431 }
10432
10433 var uid = ['0', '0', '0', '0'];
10434 var uidPrefix = 'uiGrid-';
10435
10436 /**
10437  *  @ngdoc service
10438  *  @name ui.grid.service:GridUtil
10439  *
10440  *  @description Grid utility functions
10441  */
10442 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$interval', '$injector', '$q', '$interpolate', 'uiGridConstants',
10443   function ($log, $window, $document, $http, $templateCache, $timeout, $interval, $injector, $q, $interpolate, uiGridConstants) {
10444   var s = {
10445
10446     augmentWidthOrHeight: augmentWidthOrHeight,
10447
10448     getStyles: getStyles,
10449
10450     /**
10451      * @ngdoc method
10452      * @name createBoundedWrapper
10453      * @methodOf ui.grid.service:GridUtil
10454      *
10455      * @param {object} Object to bind 'this' to
10456      * @param {method} Method to bind
10457      * @returns {Function} The wrapper that performs the binding
10458      *
10459      * @description
10460      * Binds given method to given object.
10461      *
10462      * By means of a wrapper, ensures that ``method`` is always bound to
10463      * ``object`` regardless of its calling environment.
10464      * Iow, inside ``method``, ``this`` always points to ``object``.
10465      *
10466      * See http://alistapart.com/article/getoutbindingsituations
10467      *
10468      */
10469     createBoundedWrapper: function(object, method) {
10470         return function() {
10471             return method.apply(object, arguments);
10472         };
10473     },
10474
10475
10476     /**
10477      * @ngdoc method
10478      * @name readableColumnName
10479      * @methodOf ui.grid.service:GridUtil
10480      *
10481      * @param {string} columnName Column name as a string
10482      * @returns {string} Column name appropriately capitalized and split apart
10483      *
10484        @example
10485        <example module="app">
10486         <file name="app.js">
10487           var app = angular.module('app', ['ui.grid']);
10488
10489           app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
10490             $scope.name = 'firstName';
10491             $scope.columnName = function(name) {
10492               return gridUtil.readableColumnName(name);
10493             };
10494           }]);
10495         </file>
10496         <file name="index.html">
10497           <div ng-controller="MainCtrl">
10498             <strong>Column name:</strong> <input ng-model="name" />
10499             <br>
10500             <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
10501           </div>
10502         </file>
10503       </example>
10504      */
10505     readableColumnName: function (columnName) {
10506       // Convert underscores to spaces
10507       if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
10508
10509       if (typeof(columnName) !== 'string') {
10510         columnName = String(columnName);
10511       }
10512
10513       return columnName.replace(/_+/g, ' ')
10514         // Replace a completely all-capsed word with a first-letter-capitalized version
10515         .replace(/^[A-Z]+$/, function (match) {
10516           return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10517         })
10518         // Capitalize the first letter of words
10519         .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10520           return angular.uppercase(match.charAt(0)) + match.slice(1);
10521         })
10522         // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
10523         // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
10524         // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
10525         .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
10526     },
10527
10528     /**
10529      * @ngdoc method
10530      * @name getColumnsFromData
10531      * @methodOf ui.grid.service:GridUtil
10532      * @description Return a list of column names, given a data set
10533      *
10534      * @param {string} data Data array for grid
10535      * @returns {Object} Column definitions with field accessor and column name
10536      *
10537      * @example
10538        <pre>
10539          var data = [
10540            { firstName: 'Bob', lastName: 'Jones' },
10541            { firstName: 'Frank', lastName: 'Smith' }
10542          ];
10543
10544          var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
10545
10546          columnDefs == [
10547           {
10548             field: 'firstName',
10549             name: 'First Name'
10550           },
10551           {
10552             field: 'lastName',
10553             name: 'Last Name'
10554           }
10555          ];
10556        </pre>
10557      */
10558     getColumnsFromData: function (data, excludeProperties) {
10559       var columnDefs = [];
10560
10561       if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
10562       if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
10563
10564       var item = data[0];
10565
10566       angular.forEach(item,function (prop, propName) {
10567         if ( excludeProperties.indexOf(propName) === -1){
10568           columnDefs.push({
10569             name: propName
10570           });
10571         }
10572       });
10573
10574       return columnDefs;
10575     },
10576
10577     /**
10578      * @ngdoc method
10579      * @name newId
10580      * @methodOf ui.grid.service:GridUtil
10581      * @description Return a unique ID string
10582      *
10583      * @returns {string} Unique string
10584      *
10585      * @example
10586        <pre>
10587         var id = GridUtil.newId();
10588
10589         # 1387305700482;
10590        </pre>
10591      */
10592     newId: (function() {
10593       var seedId = new Date().getTime();
10594       return function() {
10595           return seedId += 1;
10596       };
10597     })(),
10598
10599
10600     /**
10601      * @ngdoc method
10602      * @name getTemplate
10603      * @methodOf ui.grid.service:GridUtil
10604      * @description Get's template from cache / element / url
10605      *
10606      * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10607      *   an jQuery/Angualr element, or a promise that returns the template contents to use.
10608      * @returns {object} a promise resolving to template contents
10609      *
10610      * @example
10611      <pre>
10612      GridUtil.getTemplate(url).then(function (contents) {
10613           alert(contents);
10614         })
10615      </pre>
10616      */
10617     getTemplate: function (template) {
10618       // Try to fetch the template out of the templateCache
10619       if ($templateCache.get(template)) {
10620         return s.postProcessTemplate($templateCache.get(template));
10621       }
10622
10623       // See if the template is itself a promise
10624       if (template.hasOwnProperty('then')) {
10625         return template.then(s.postProcessTemplate);
10626       }
10627
10628       // If the template is an element, return the element
10629       try {
10630         if (angular.element(template).length > 0) {
10631           return $q.when(template).then(s.postProcessTemplate);
10632         }
10633       }
10634       catch (err){
10635         //do nothing; not valid html
10636       }
10637
10638       s.logDebug('fetching url', template);
10639
10640       // Default to trying to fetch the template as a url with $http
10641       return $http({ method: 'GET', url: template})
10642         .then(
10643           function (result) {
10644             var templateHtml = result.data.trim();
10645             //put in templateCache for next call
10646             $templateCache.put(template, templateHtml);
10647             return templateHtml;
10648           },
10649           function (err) {
10650             throw new Error("Could not get template " + template + ": " + err);
10651           }
10652         )
10653         .then(s.postProcessTemplate);
10654     },
10655
10656     //
10657     postProcessTemplate: function (template) {
10658       var startSym = $interpolate.startSymbol(),
10659           endSym = $interpolate.endSymbol();
10660
10661       // If either of the interpolation symbols have been changed, we need to alter this template
10662       if (startSym !== '{{' || endSym !== '}}') {
10663         template = template.replace(/\{\{/g, startSym);
10664         template = template.replace(/\}\}/g, endSym);
10665       }
10666
10667       return $q.when(template);
10668     },
10669
10670     /**
10671      * @ngdoc method
10672      * @name guessType
10673      * @methodOf ui.grid.service:GridUtil
10674      * @description guesses the type of an argument
10675      *
10676      * @param {string/number/bool/object} item variable to examine
10677      * @returns {string} one of the following
10678      * - 'string'
10679      * - 'boolean'
10680      * - 'number'
10681      * - 'date'
10682      * - 'object'
10683      */
10684     guessType : function (item) {
10685       var itemType = typeof(item);
10686
10687       // Check for numbers and booleans
10688       switch (itemType) {
10689         case "number":
10690         case "boolean":
10691         case "string":
10692           return itemType;
10693         default:
10694           if (angular.isDate(item)) {
10695             return "date";
10696           }
10697           return "object";
10698       }
10699     },
10700
10701
10702   /**
10703     * @ngdoc method
10704     * @name elementWidth
10705     * @methodOf ui.grid.service:GridUtil
10706     *
10707     * @param {element} element DOM element
10708     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10709     *
10710     * @returns {number} Element width in pixels, accounting for any borders, etc.
10711     */
10712     elementWidth: function (elem) {
10713
10714     },
10715
10716     /**
10717     * @ngdoc method
10718     * @name elementHeight
10719     * @methodOf ui.grid.service:GridUtil
10720     *
10721     * @param {element} element DOM element
10722     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10723     *
10724     * @returns {number} Element height in pixels, accounting for any borders, etc.
10725     */
10726     elementHeight: function (elem) {
10727
10728     },
10729
10730     // Thanks to http://stackoverflow.com/a/13382873/888165
10731     getScrollbarWidth: function() {
10732         var outer = document.createElement("div");
10733         outer.style.visibility = "hidden";
10734         outer.style.width = "100px";
10735         outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
10736
10737         document.body.appendChild(outer);
10738
10739         var widthNoScroll = outer.offsetWidth;
10740         // force scrollbars
10741         outer.style.overflow = "scroll";
10742
10743         // add innerdiv
10744         var inner = document.createElement("div");
10745         inner.style.width = "100%";
10746         outer.appendChild(inner);
10747
10748         var widthWithScroll = inner.offsetWidth;
10749
10750         // remove divs
10751         outer.parentNode.removeChild(outer);
10752
10753         return widthNoScroll - widthWithScroll;
10754     },
10755
10756     swap: function( elem, options, callback, args ) {
10757       var ret, name,
10758               old = {};
10759
10760       // Remember the old values, and insert the new ones
10761       for ( name in options ) {
10762         old[ name ] = elem.style[ name ];
10763         elem.style[ name ] = options[ name ];
10764       }
10765
10766       ret = callback.apply( elem, args || [] );
10767
10768       // Revert the old values
10769       for ( name in options ) {
10770         elem.style[ name ] = old[ name ];
10771       }
10772
10773       return ret;
10774     },
10775
10776     fakeElement: function( elem, options, callback, args ) {
10777       var ret, name,
10778           newElement = angular.element(elem).clone()[0];
10779
10780       for ( name in options ) {
10781         newElement.style[ name ] = options[ name ];
10782       }
10783
10784       angular.element(document.body).append(newElement);
10785
10786       ret = callback.call( newElement, newElement );
10787
10788       angular.element(newElement).remove();
10789
10790       return ret;
10791     },
10792
10793     /**
10794     * @ngdoc method
10795     * @name normalizeWheelEvent
10796     * @methodOf ui.grid.service:GridUtil
10797     *
10798     * @param {event} event A mouse wheel event
10799     *
10800     * @returns {event} A normalized event
10801     *
10802     * @description
10803     * Given an event from this list:
10804     *
10805     * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
10806     *
10807     * "normalize" it
10808     * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
10809     */
10810     normalizeWheelEvent: function (event) {
10811       // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
10812       // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
10813       var lowestDelta, lowestDeltaXY;
10814
10815       var orgEvent   = event || window.event,
10816           args       = [].slice.call(arguments, 1),
10817           delta      = 0,
10818           deltaX     = 0,
10819           deltaY     = 0,
10820           absDelta   = 0,
10821           absDeltaXY = 0,
10822           fn;
10823
10824       // event = $.event.fix(orgEvent);
10825       // event.type = 'mousewheel';
10826
10827       // NOTE: jQuery masks the event and stores it in the event as originalEvent
10828       if (orgEvent.originalEvent) {
10829         orgEvent = orgEvent.originalEvent;
10830       }
10831
10832       // Old school scrollwheel delta
10833       if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
10834       if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
10835
10836       // At a minimum, setup the deltaY to be delta
10837       deltaY = delta;
10838
10839       // Firefox < 17 related to DOMMouseScroll event
10840       if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
10841           deltaY = 0;
10842           deltaX = delta * -1;
10843       }
10844
10845       // New school wheel delta (wheel event)
10846       if ( orgEvent.deltaY ) {
10847           deltaY = orgEvent.deltaY * -1;
10848           delta  = deltaY;
10849       }
10850       if ( orgEvent.deltaX ) {
10851           deltaX = orgEvent.deltaX;
10852           delta  = deltaX * -1;
10853       }
10854
10855       // Webkit
10856       if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
10857       if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
10858
10859       // Look for lowest delta to normalize the delta values
10860       absDelta = Math.abs(delta);
10861       if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
10862       absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
10863       if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
10864
10865       // Get a whole value for the deltas
10866       fn     = delta > 0 ? 'floor' : 'ceil';
10867       delta  = Math[fn](delta  / lowestDelta);
10868       deltaX = Math[fn](deltaX / lowestDeltaXY);
10869       deltaY = Math[fn](deltaY / lowestDeltaXY);
10870
10871       return {
10872         delta: delta,
10873         deltaX: deltaX,
10874         deltaY: deltaY
10875       };
10876     },
10877
10878     // Stolen from Modernizr
10879     // TODO: make this, and everythign that flows from it, robust
10880     //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
10881     isTouchEnabled: function() {
10882       var bool;
10883
10884       if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
10885         bool = true;
10886       }
10887
10888       return bool;
10889     },
10890
10891     isNullOrUndefined: function(obj) {
10892       if (obj === undefined || obj === null) {
10893         return true;
10894       }
10895       return false;
10896     },
10897
10898     endsWith: function(str, suffix) {
10899       if (!str || !suffix || typeof str !== "string") {
10900         return false;
10901       }
10902       return str.indexOf(suffix, str.length - suffix.length) !== -1;
10903     },
10904
10905     arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
10906         var found = false;
10907         angular.forEach(array, function (object) {
10908             if (object[propertyName] === propertyValue) {
10909                 found = true;
10910             }
10911         });
10912         return found;
10913     },
10914
10915     //// Shim requestAnimationFrame
10916     //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
10917     //                       $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
10918     //                       function(fn) {
10919     //                         return $timeout(fn, 10, false);
10920     //                       },
10921
10922     numericAndNullSort: function (a, b) {
10923       if (a === null) { return 1; }
10924       if (b === null) { return -1; }
10925       if (a === null && b === null) { return 0; }
10926       return a - b;
10927     },
10928
10929     // Disable ngAnimate animations on an element
10930     disableAnimations: function (element) {
10931       var $animate;
10932       try {
10933         $animate = $injector.get('$animate');
10934         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10935         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10936           $animate.enabled(element, false);
10937         } else {
10938           $animate.enabled(false, element);
10939         }
10940       }
10941       catch (e) {}
10942     },
10943
10944     enableAnimations: function (element) {
10945       var $animate;
10946       try {
10947         $animate = $injector.get('$animate');
10948         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10949         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10950           $animate.enabled(element, true);
10951         } else {
10952           $animate.enabled(true, element);
10953         }
10954         return $animate;
10955       }
10956       catch (e) {}
10957     },
10958
10959     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
10960     nextUid: function nextUid() {
10961       var index = uid.length;
10962       var digit;
10963
10964       while (index) {
10965         index--;
10966         digit = uid[index].charCodeAt(0);
10967         if (digit === 57 /*'9'*/) {
10968           uid[index] = 'A';
10969           return uidPrefix + uid.join('');
10970         }
10971         if (digit === 90  /*'Z'*/) {
10972           uid[index] = '0';
10973         } else {
10974           uid[index] = String.fromCharCode(digit + 1);
10975           return uidPrefix + uid.join('');
10976         }
10977       }
10978       uid.unshift('0');
10979
10980       return uidPrefix + uid.join('');
10981     },
10982
10983     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
10984     hashKey: function hashKey(obj) {
10985       var objType = typeof obj,
10986           key;
10987
10988       if (objType === 'object' && obj !== null) {
10989         if (typeof (key = obj.$$hashKey) === 'function') {
10990           // must invoke on object to keep the right this
10991           key = obj.$$hashKey();
10992         }
10993         else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
10994           key = obj.$$hashKey;
10995         }
10996         else if (key === undefined) {
10997           key = obj.$$hashKey = s.nextUid();
10998         }
10999       }
11000       else {
11001         key = obj;
11002       }
11003
11004       return objType + ':' + key;
11005     },
11006
11007     resetUids: function () {
11008       uid = ['0', '0', '0'];
11009     },
11010
11011     /**
11012      * @ngdoc method
11013      * @methodOf ui.grid.service:GridUtil
11014      * @name logError
11015      * @description wraps the $log method, allowing us to choose different
11016      * treatment within ui-grid if we so desired.  At present we only log
11017      * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
11018      * @param {string} logMessage message to be logged to the console
11019      *
11020      */
11021     logError: function( logMessage ){
11022       if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11023         $log.error( logMessage );
11024       }
11025     },
11026
11027     /**
11028      * @ngdoc method
11029      * @methodOf ui.grid.service:GridUtil
11030      * @name logWarn
11031      * @description wraps the $log method, allowing us to choose different
11032      * treatment within ui-grid if we so desired.  At present we only log
11033      * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
11034      * @param {string} logMessage message to be logged to the console
11035      *
11036      */
11037     logWarn: function( logMessage ){
11038       if ( uiGridConstants.LOG_WARN_MESSAGES ){
11039         $log.warn( logMessage );
11040       }
11041     },
11042
11043     /**
11044      * @ngdoc method
11045      * @methodOf ui.grid.service:GridUtil
11046      * @name logDebug
11047      * @description wraps the $log method, allowing us to choose different
11048      * treatment within ui-grid if we so desired.  At present we only log
11049      * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
11050      *
11051      */
11052     logDebug: function() {
11053       if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11054         $log.debug.apply($log, arguments);
11055       }
11056     }
11057
11058   };
11059
11060   /**
11061    * @ngdoc object
11062    * @name focus
11063    * @propertyOf ui.grid.service:GridUtil
11064    * @description Provies a set of methods to set the document focus inside the grid.
11065    * See {@link ui.grid.service:GridUtil.focus} for more information.
11066    */
11067
11068   /**
11069    * @ngdoc object
11070    * @name ui.grid.service:GridUtil.focus
11071    * @description Provies a set of methods to set the document focus inside the grid.
11072    * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
11073    * e.g. click events that need to run before the focus or
11074    * inputs elements that are in a disabled state but are enabled when those events
11075    * are triggered.
11076    */
11077   s.focus = {
11078     queue: [],
11079     //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11080     /**
11081      * @ngdoc method
11082      * @methodOf ui.grid.service:GridUtil.focus
11083      * @name byId
11084      * @description Sets the focus of the document to the given id value.
11085      * If provided with the grid object it will automatically append the grid id.
11086      * This is done to encourage unique dom id's as it allows for multiple grids on a
11087      * page.
11088      * @param {String} id the id of the dom element to set the focus on
11089      * @param {Object=} Grid the grid object for this grid instance. See: {@link ui.grid.class:Grid}
11090      * @param {Number} Grid.id the unique id for this grid. Already set on an initialized grid object.
11091      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11092      * then the promise will fail with the `'canceled'` reason.
11093      */
11094     byId: function (id, Grid) {
11095       this._purgeQueue();
11096       var promise = $timeout(function() {
11097         var elementID = (Grid && Grid.id ? Grid.id + '-' : '') + id;
11098         var element = $window.document.getElementById(elementID);
11099         if (element) {
11100           element.focus();
11101         } else {
11102           s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
11103         }
11104       });
11105       this.queue.push(promise);
11106       return promise;
11107     },
11108
11109     /**
11110      * @ngdoc method
11111      * @methodOf ui.grid.service:GridUtil.focus
11112      * @name byElement
11113      * @description Sets the focus of the document to the given dom element.
11114      * @param {(element|angular.element)} element the DOM element to set the focus on
11115      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11116      * then the promise will fail with the `'canceled'` reason.
11117      */
11118     byElement: function(element){
11119       if (!angular.isElement(element)){
11120         s.logWarn("Trying to focus on an element that isn\'t an element.");
11121         return $q.reject('not-element');
11122       }
11123       element = angular.element(element);
11124       this._purgeQueue();
11125       var promise = $timeout(function(){
11126         if (element){
11127           element[0].focus();
11128         }
11129       });
11130       this.queue.push(promise);
11131       return promise;
11132     },
11133     /**
11134      * @ngdoc method
11135      * @methodOf ui.grid.service:GridUtil.focus
11136      * @name bySelector
11137      * @description Sets the focus of the document to the given dom element.
11138      * @param {(element|angular.element)} parentElement the parent/ancestor of the dom element that you are selecting using the query selector
11139      * @param {String} querySelector finds the dom element using the {@link http://www.w3schools.com/jsref/met_document_queryselector.asp querySelector}
11140      * @param {boolean} [aSync=false] If true then the selector will be querried inside of a timeout. Otherwise the selector will be querried imidately
11141      * then the focus will be called.
11142      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11143      * then the promise will fail with the `'canceled'` reason.
11144      */
11145     bySelector: function(parentElement, querySelector, aSync){
11146       var self = this;
11147       if (!angular.isElement(parentElement)){
11148         throw new Error("The parent element is not an element.");
11149       }
11150       // Ensure that this is an angular element.
11151       // It is fine if this is already an angular element.
11152       parentElement = angular.element(parentElement);
11153       var focusBySelector = function(){
11154         var element = parentElement[0].querySelector(querySelector);
11155         return self.byElement(element);
11156       };
11157       this._purgeQueue();
11158       if (aSync){ //Do this asynchronysly
11159         var promise = $timeout(focusBySelector);
11160         this.queue.push($timeout(focusBySelector));
11161         return promise;
11162       } else {
11163         return focusBySelector();
11164       }
11165     },
11166     _purgeQueue: function(){
11167       this.queue.forEach(function(element){
11168         $timeout.cancel(element);
11169       });
11170       this.queue = [];
11171     }
11172   };
11173
11174
11175   ['width', 'height'].forEach(function (name) {
11176     var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11177     s['element' + capsName] = function (elem, extra) {
11178       var e = elem;
11179       if (e && typeof(e.length) !== 'undefined' && e.length) {
11180         e = elem[0];
11181       }
11182
11183       if (e) {
11184         var styles = getStyles(e);
11185         return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11186                   s.swap(e, cssShow, function() {
11187                     return getWidthOrHeight(e, name, extra );
11188                   }) :
11189                   getWidthOrHeight( e, name, extra );
11190       }
11191       else {
11192         return null;
11193       }
11194     };
11195
11196     s['outerElement' + capsName] = function (elem, margin) {
11197       return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
11198     };
11199   });
11200
11201   // http://stackoverflow.com/a/24107550/888165
11202   s.closestElm = function closestElm(el, selector) {
11203     if (typeof(el.length) !== 'undefined' && el.length) {
11204       el = el[0];
11205     }
11206
11207     var matchesFn;
11208
11209     // find vendor prefix
11210     ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
11211         if (typeof document.body[fn] === 'function') {
11212             matchesFn = fn;
11213             return true;
11214         }
11215         return false;
11216     });
11217
11218     // traverse parents
11219     var parent;
11220     while (el !== null) {
11221       parent = el.parentElement;
11222       if (parent !== null && parent[matchesFn](selector)) {
11223           return parent;
11224       }
11225       el = parent;
11226     }
11227
11228     return null;
11229   };
11230
11231   s.type = function (obj) {
11232     var text = Function.prototype.toString.call(obj.constructor);
11233     return text.match(/function (.*?)\(/)[1];
11234   };
11235
11236   s.getBorderSize = function getBorderSize(elem, borderType) {
11237     if (typeof(elem.length) !== 'undefined' && elem.length) {
11238       elem = elem[0];
11239     }
11240
11241     var styles = getStyles(elem);
11242
11243     // If a specific border is supplied, like 'top', read the 'borderTop' style property
11244     if (borderType) {
11245       borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
11246     }
11247     else {
11248       borderType = 'border';
11249     }
11250
11251     borderType += 'Width';
11252
11253     var val = parseInt(styles[borderType], 10);
11254
11255     if (isNaN(val)) {
11256       return 0;
11257     }
11258     else {
11259       return val;
11260     }
11261   };
11262
11263   // http://stackoverflow.com/a/22948274/888165
11264   // TODO: Opera? Mobile?
11265   s.detectBrowser = function detectBrowser() {
11266     var userAgent = $window.navigator.userAgent;
11267
11268     var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
11269
11270     for (var key in browsers) {
11271       if (browsers[key].test(userAgent)) {
11272         return key;
11273       }
11274     }
11275
11276     return 'unknown';
11277   };
11278
11279   // Borrowed from https://github.com/othree/jquery.rtl-scroll-type
11280   // Determine the scroll "type" this browser is using for RTL
11281   s.rtlScrollType = function rtlScrollType() {
11282     if (rtlScrollType.type) {
11283       return rtlScrollType.type;
11284     }
11285
11286     var definer = angular.element('<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>')[0],
11287         type = 'reverse';
11288
11289     document.body.appendChild(definer);
11290
11291     if (definer.scrollLeft > 0) {
11292       type = 'default';
11293     }
11294     else {
11295       definer.scrollLeft = 1;
11296       if (definer.scrollLeft === 0) {
11297         type = 'negative';
11298       }
11299     }
11300
11301     angular.element(definer).remove();
11302     rtlScrollType.type = type;
11303
11304     return type;
11305   };
11306
11307     /**
11308      * @ngdoc method
11309      * @name normalizeScrollLeft
11310      * @methodOf ui.grid.service:GridUtil
11311      *
11312      * @param {element} element The element to get the `scrollLeft` from.
11313      * @param {grid} grid -  grid used to normalize (uses the rtl property)
11314      *
11315      * @returns {number} A normalized scrollLeft value for the current browser.
11316      *
11317      * @description
11318      * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
11319      */
11320   s.normalizeScrollLeft = function normalizeScrollLeft(element, grid) {
11321     if (typeof(element.length) !== 'undefined' && element.length) {
11322       element = element[0];
11323     }
11324
11325     var scrollLeft = element.scrollLeft;
11326
11327     if (grid.isRTL()) {
11328       switch (s.rtlScrollType()) {
11329         case 'default':
11330           return element.scrollWidth - scrollLeft - element.clientWidth;
11331         case 'negative':
11332           return Math.abs(scrollLeft);
11333         case 'reverse':
11334           return scrollLeft;
11335       }
11336     }
11337
11338     return scrollLeft;
11339   };
11340
11341   /**
11342   * @ngdoc method
11343   * @name denormalizeScrollLeft
11344   * @methodOf ui.grid.service:GridUtil
11345   *
11346   * @param {element} element The element to normalize the `scrollLeft` value for
11347   * @param {number} scrollLeft The `scrollLeft` value to denormalize.
11348   * @param {grid} grid The grid that owns the scroll event.
11349   *
11350   * @returns {number} A normalized scrollLeft value for the current browser.
11351   *
11352   * @description
11353   * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
11354   */
11355   s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft, grid) {
11356     if (typeof(element.length) !== 'undefined' && element.length) {
11357       element = element[0];
11358     }
11359
11360     if (grid.isRTL()) {
11361       switch (s.rtlScrollType()) {
11362         case 'default':
11363           // Get the max scroll for the element
11364           var maxScrollLeft = element.scrollWidth - element.clientWidth;
11365
11366           // Subtract the current scroll amount from the max scroll
11367           return maxScrollLeft - scrollLeft;
11368         case 'negative':
11369           return scrollLeft * -1;
11370         case 'reverse':
11371           return scrollLeft;
11372       }
11373     }
11374
11375     return scrollLeft;
11376   };
11377
11378     /**
11379      * @ngdoc method
11380      * @name preEval
11381      * @methodOf ui.grid.service:GridUtil
11382      *
11383      * @param {string} path Path to evaluate
11384      *
11385      * @returns {string} A path that is normalized.
11386      *
11387      * @description
11388      * Takes a field path and converts it to bracket notation to allow for special characters in path
11389      * @example
11390      * <pre>
11391      * gridUtil.preEval('property') == 'property'
11392      * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
11393      * </pre>
11394      */
11395   s.preEval = function (path) {
11396     var m = uiGridConstants.BRACKET_REGEXP.exec(path);
11397     if (m) {
11398       return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
11399     } else {
11400       path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
11401       var parts = path.split(uiGridConstants.DOT_REGEXP);
11402       var preparsed = [parts.shift()];    // first item must be var notation, thus skip
11403       angular.forEach(parts, function (part) {
11404         preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
11405       });
11406       return preparsed.join('[\'');
11407     }
11408   };
11409
11410   /**
11411    * @ngdoc method
11412    * @name debounce
11413    * @methodOf ui.grid.service:GridUtil
11414    *
11415    * @param {function} func function to debounce
11416    * @param {number} wait milliseconds to delay
11417    * @param {boolean} immediate execute before delay
11418    *
11419    * @returns {function} A function that can be executed as debounced function
11420    *
11421    * @description
11422    * Copied from https://github.com/shahata/angular-debounce
11423    * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
11424    * @example
11425    * <pre>
11426    * var debouncedFunc =  gridUtil.debounce(function(){alert('debounced');}, 500);
11427    * debouncedFunc();
11428    * debouncedFunc();
11429    * debouncedFunc();
11430    * </pre>
11431    */
11432   s.debounce =  function (func, wait, immediate) {
11433     var timeout, args, context, result;
11434     function debounce() {
11435       /* jshint validthis:true */
11436       context = this;
11437       args = arguments;
11438       var later = function () {
11439         timeout = null;
11440         if (!immediate) {
11441           result = func.apply(context, args);
11442         }
11443       };
11444       var callNow = immediate && !timeout;
11445       if (timeout) {
11446         $timeout.cancel(timeout);
11447       }
11448       timeout = $timeout(later, wait);
11449       if (callNow) {
11450         result = func.apply(context, args);
11451       }
11452       return result;
11453     }
11454     debounce.cancel = function () {
11455       $timeout.cancel(timeout);
11456       timeout = null;
11457     };
11458     return debounce;
11459   };
11460
11461   /**
11462    * @ngdoc method
11463    * @name throttle
11464    * @methodOf ui.grid.service:GridUtil
11465    *
11466    * @param {function} func function to throttle
11467    * @param {number} wait milliseconds to delay after first trigger
11468    * @param {Object} params to use in throttle.
11469    *
11470    * @returns {function} A function that can be executed as throttled function
11471    *
11472    * @description
11473    * Adapted from debounce function (above)
11474    * Potential keys for Params Object are:
11475    *    trailing (bool) - whether to trigger after throttle time ends if called multiple times
11476    * Updated to use $interval rather than $timeout, as protractor (e2e tests) is able to work with $interval,
11477    * but not with $timeout
11478    *
11479    * Note that when using throttle, you need to use throttle to create a new function upfront, then use the function
11480    * return from that call each time you need to call throttle.  If you call throttle itself repeatedly, the lastCall
11481    * variable will get overwritten and the throttling won't work
11482    *
11483    * @example
11484    * <pre>
11485    * var throttledFunc =  gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11486    * throttledFunc(); //=> logs throttled
11487    * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
11488    * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
11489    * </pre>
11490    */
11491   s.throttle = function(func, wait, options){
11492     options = options || {};
11493     var lastCall = 0, queued = null, context, args;
11494
11495     function runFunc(endDate){
11496       lastCall = +new Date();
11497       func.apply(context, args);
11498       $interval(function(){ queued = null; }, 0, 1);
11499     }
11500
11501     return function(){
11502       /* jshint validthis:true */
11503       context = this;
11504       args = arguments;
11505       if (queued === null){
11506         var sinceLast = +new Date() - lastCall;
11507         if (sinceLast > wait){
11508           runFunc();
11509         }
11510         else if (options.trailing){
11511           queued = $interval(runFunc, wait - sinceLast, 1);
11512         }
11513       }
11514     };
11515   };
11516
11517   s.on = {};
11518   s.off = {};
11519   s._events = {};
11520
11521   s.addOff = function (eventName) {
11522     s.off[eventName] = function (elm, fn) {
11523       var idx = s._events[eventName].indexOf(fn);
11524       if (idx > 0) {
11525         s._events[eventName].removeAt(idx);
11526       }
11527     };
11528   };
11529
11530   var mouseWheeltoBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
11531       nullLowestDeltaTimeout,
11532       lowestDelta;
11533
11534   s.on.mousewheel = function (elm, fn) {
11535     if (!elm || !fn) { return; }
11536
11537     var $elm = angular.element(elm);
11538
11539     // Store the line height and page height for this particular element
11540     $elm.data('mousewheel-line-height', getLineHeight($elm));
11541     $elm.data('mousewheel-page-height', s.elementHeight($elm));
11542     if (!$elm.data('mousewheel-callbacks')) { $elm.data('mousewheel-callbacks', {}); }
11543
11544     var cbs = $elm.data('mousewheel-callbacks');
11545     cbs[fn] = (Function.prototype.bind || bindPolyfill).call(mousewheelHandler, $elm[0], fn);
11546
11547     // Bind all the mousew heel events
11548     for ( var i = mouseWheeltoBind.length; i; ) {
11549       $elm.on(mouseWheeltoBind[--i], cbs[fn]);
11550     }
11551   };
11552   s.off.mousewheel = function (elm, fn) {
11553     var $elm = angular.element(elm);
11554
11555     var cbs = $elm.data('mousewheel-callbacks');
11556     var handler = cbs[fn];
11557
11558     if (handler) {
11559       for ( var i = mouseWheeltoBind.length; i; ) {
11560         $elm.off(mouseWheeltoBind[--i], handler);
11561       }
11562     }
11563
11564     delete cbs[fn];
11565
11566     if (Object.keys(cbs).length === 0) {
11567       $elm.removeData('mousewheel-line-height');
11568       $elm.removeData('mousewheel-page-height');
11569       $elm.removeData('mousewheel-callbacks');
11570     }
11571   };
11572
11573   function mousewheelHandler(fn, event) {
11574     var $elm = angular.element(this);
11575
11576     var delta      = 0,
11577         deltaX     = 0,
11578         deltaY     = 0,
11579         absDelta   = 0,
11580         offsetX    = 0,
11581         offsetY    = 0;
11582
11583     // jQuery masks events
11584     if (event.originalEvent) { event = event.originalEvent; }
11585
11586     if ( 'detail'      in event ) { deltaY = event.detail * -1;      }
11587     if ( 'wheelDelta'  in event ) { deltaY = event.wheelDelta;       }
11588     if ( 'wheelDeltaY' in event ) { deltaY = event.wheelDeltaY;      }
11589     if ( 'wheelDeltaX' in event ) { deltaX = event.wheelDeltaX * -1; }
11590
11591     // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
11592     if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
11593       deltaX = deltaY * -1;
11594       deltaY = 0;
11595     }
11596
11597     // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
11598     delta = deltaY === 0 ? deltaX : deltaY;
11599
11600     // New school wheel delta (wheel event)
11601     if ( 'deltaY' in event ) {
11602       deltaY = event.deltaY * -1;
11603       delta  = deltaY;
11604     }
11605     if ( 'deltaX' in event ) {
11606       deltaX = event.deltaX;
11607       if ( deltaY === 0 ) { delta  = deltaX * -1; }
11608     }
11609
11610     // No change actually happened, no reason to go any further
11611     if ( deltaY === 0 && deltaX === 0 ) { return; }
11612
11613     // Need to convert lines and pages to pixels if we aren't already in pixels
11614     // There are three delta modes:
11615     //   * deltaMode 0 is by pixels, nothing to do
11616     //   * deltaMode 1 is by lines
11617     //   * deltaMode 2 is by pages
11618     if ( event.deltaMode === 1 ) {
11619         var lineHeight = $elm.data('mousewheel-line-height');
11620         delta  *= lineHeight;
11621         deltaY *= lineHeight;
11622         deltaX *= lineHeight;
11623     }
11624     else if ( event.deltaMode === 2 ) {
11625         var pageHeight = $elm.data('mousewheel-page-height');
11626         delta  *= pageHeight;
11627         deltaY *= pageHeight;
11628         deltaX *= pageHeight;
11629     }
11630
11631     // Store lowest absolute delta to normalize the delta values
11632     absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
11633
11634     if ( !lowestDelta || absDelta < lowestDelta ) {
11635       lowestDelta = absDelta;
11636
11637       // Adjust older deltas if necessary
11638       if ( shouldAdjustOldDeltas(event, absDelta) ) {
11639         lowestDelta /= 40;
11640       }
11641     }
11642
11643     // Get a whole, normalized value for the deltas
11644     delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
11645     deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
11646     deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
11647
11648     event.deltaMode = 0;
11649
11650     // Normalise offsetX and offsetY properties
11651     // if ($elm[0].getBoundingClientRect ) {
11652     //   var boundingRect = $(elm)[0].getBoundingClientRect();
11653     //   offsetX = event.clientX - boundingRect.left;
11654     //   offsetY = event.clientY - boundingRect.top;
11655     // }
11656
11657     // event.deltaX = deltaX;
11658     // event.deltaY = deltaY;
11659     // event.deltaFactor = lowestDelta;
11660
11661     var newEvent = {
11662       originalEvent: event,
11663       deltaX: deltaX,
11664       deltaY: deltaY,
11665       deltaFactor: lowestDelta,
11666       preventDefault: function () { event.preventDefault(); },
11667       stopPropagation: function () { event.stopPropagation(); }
11668     };
11669
11670     // Clearout lowestDelta after sometime to better
11671     // handle multiple device types that give
11672     // a different lowestDelta
11673     // Ex: trackpad = 3 and mouse wheel = 120
11674     if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
11675     nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
11676
11677     fn.call($elm[0], newEvent);
11678   }
11679
11680   function nullLowestDelta() {
11681     lowestDelta = null;
11682   }
11683
11684   function shouldAdjustOldDeltas(orgEvent, absDelta) {
11685     // If this is an older event and the delta is divisable by 120,
11686     // then we are assuming that the browser is treating this as an
11687     // older mouse wheel event and that we should divide the deltas
11688     // by 40 to try and get a more usable deltaFactor.
11689     // Side note, this actually impacts the reported scroll distance
11690     // in older browsers and can cause scrolling to be slower than native.
11691     // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
11692     return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
11693   }
11694
11695   return s;
11696 }]);
11697
11698 // Add 'px' to the end of a number string if it doesn't have it already
11699 module.filter('px', function() {
11700   return function(str) {
11701     if (str.match(/^[\d\.]+$/)) {
11702       return str + 'px';
11703     }
11704     else {
11705       return str;
11706     }
11707   };
11708 });
11709
11710 })();
11711
11712 (function () {
11713   angular.module('ui.grid').config(['$provide', function($provide) {
11714     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11715       var lang = {
11716               aggregate: {
11717                   label: 'položky'
11718               },
11719               groupPanel: {
11720                   description: 'Přesuňte záhlaví zde pro vytvoření skupiny dle sloupce.'
11721               },
11722               search: {
11723                   placeholder: 'Hledat...',
11724                   showingItems: 'Zobrazuji položky:',
11725                   selectedItems: 'Vybrané položky:',
11726                   totalItems: 'Celkem položek:',
11727                   size: 'Velikost strany:',
11728                   first: 'První strana',
11729                   next: 'Další strana',
11730                   previous: 'Předchozí strana',
11731                   last: 'Poslední strana'
11732               },
11733               menu: {
11734                   text: 'Vyberte sloupec:'
11735               },
11736               sort: {
11737                   ascending: 'Seřadit od A-Z',
11738                   descending: 'Seřadit od Z-A',
11739                   remove: 'Odebrat seřazení'
11740               },
11741               column: {
11742                   hide: 'Schovat sloupec'
11743               },
11744               aggregation: {
11745                   count: 'celkem řádků: ',
11746                   sum: 'celkem: ',
11747                   avg: 'avg: ',
11748                   min: 'min.: ',
11749                   max: 'max.: '
11750               },
11751               pinning: {
11752                   pinLeft: 'Zamknout vlevo',
11753                   pinRight: 'Zamknout vpravo',
11754                   unpin: 'Odemknout'
11755               },
11756               gridMenu: {
11757                   columns: 'Sloupce:',
11758                   importerTitle: 'Importovat soubor',
11759                   exporterAllAsCsv: 'Exportovat všechna data do csv',
11760                   exporterVisibleAsCsv: 'Exportovat viditelná data do csv',
11761                   exporterSelectedAsCsv: 'Exportovat vybraná data do csv',
11762                   exporterAllAsPdf: 'Exportovat všechna data do pdf',
11763                   exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
11764                   exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
11765                   clearAllFilters: 'Odstranit všechny filtry'
11766               },
11767               importer: {
11768                   noHeaders: 'Názvy sloupců se nepodařilo získat, obsahuje soubor záhlaví?',
11769                   noObjects: 'Data se nepodařilo zpracovat, obsahuje soubor řádky mimo záhlaví?',
11770                   invalidCsv: 'Soubor nelze zpracovat, jedná se o CSV?',
11771                   invalidJson: 'Soubor nelze zpracovat, je to JSON?',
11772                   jsonNotArray: 'Soubor musí obsahovat json. Ukončuji..'
11773               },
11774               pagination: {
11775                   sizes: 'položek na stránku',
11776                   totalItems: 'položek'
11777               },
11778               grouping: {
11779                   group: 'Seskupit',
11780                   ungroup: 'Odebrat seskupení',
11781                   aggregate_count: 'Agregace: Count',
11782                   aggregate_sum: 'Agregace: Sum',
11783                   aggregate_max: 'Agregace: Max',
11784                   aggregate_min: 'Agregace: Min',
11785                   aggregate_avg: 'Agregace: Avg',
11786                   aggregate_remove: 'Agregace: Odebrat'
11787               }
11788           };
11789
11790           // support varianty of different czech keys.
11791           $delegate.add('cs', lang);
11792           $delegate.add('cz', lang);
11793           $delegate.add('cs-cz', lang);
11794           $delegate.add('cs-CZ', lang);
11795       return $delegate;
11796     }]);
11797   }]);
11798 })();
11799
11800 (function(){
11801   angular.module('ui.grid').config(['$provide', function($provide) {
11802     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11803       $delegate.add('da', {
11804         aggregate:{
11805           label: 'artikler'
11806         },
11807         groupPanel:{
11808           description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
11809         },
11810         search:{
11811           placeholder: 'Søg...',
11812           showingItems: 'Viste rækker:',
11813           selectedItems: 'Valgte rækker:',
11814           totalItems: 'Rækker totalt:',
11815           size: 'Side størrelse:',
11816           first: 'Første side',
11817           next: 'Næste side',
11818           previous: 'Forrige side',
11819           last: 'Sidste side'
11820         },
11821         menu:{
11822           text: 'Vælg kolonner:'
11823         },
11824         column: {
11825           hide: 'Skjul kolonne'
11826         },
11827         aggregation: {
11828           count: 'samlede rækker: ',
11829           sum: 'smalede: ',
11830           avg: 'gns: ',
11831           min: 'min: ',
11832           max: 'max: '
11833         },
11834         gridMenu: {
11835           columns: 'Columns:',
11836           importerTitle: 'Import file',
11837           exporterAllAsCsv: 'Export all data as csv',
11838           exporterVisibleAsCsv: 'Export visible data as csv',
11839           exporterSelectedAsCsv: 'Export selected data as csv',
11840           exporterAllAsPdf: 'Export all data as pdf',
11841           exporterVisibleAsPdf: 'Export visible data as pdf',
11842           exporterSelectedAsPdf: 'Export selected data as pdf',
11843           clearAllFilters: 'Clear all filters'
11844         },
11845         importer: {
11846           noHeaders: 'Column names were unable to be derived, does the file have a header?',
11847           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
11848           invalidCsv: 'File was unable to be processed, is it valid CSV?',
11849           invalidJson: 'File was unable to be processed, is it valid Json?',
11850           jsonNotArray: 'Imported json file must contain an array, aborting.'
11851         }
11852       });
11853       return $delegate;
11854     }]);
11855   }]);
11856 })();
11857
11858 (function () {
11859   angular.module('ui.grid').config(['$provide', function ($provide) {
11860     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
11861       $delegate.add('de', {
11862         aggregate: {
11863           label: 'Eintrag'
11864         },
11865         groupPanel: {
11866           description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
11867         },
11868         search: {
11869           placeholder: 'Suche...',
11870           showingItems: 'Zeige Einträge:',
11871           selectedItems: 'Ausgewählte Einträge:',
11872           totalItems: 'Einträge gesamt:',
11873           size: 'Einträge pro Seite:',
11874           first: 'Erste Seite',
11875           next: 'Nächste Seite',
11876           previous: 'Vorherige Seite',
11877           last: 'Letzte Seite'
11878         },
11879         menu: {
11880           text: 'Spalten auswählen:'
11881         },
11882         sort: {
11883           ascending: 'aufsteigend sortieren',
11884           descending: 'absteigend sortieren',
11885           remove: 'Sortierung zurücksetzen'
11886         },
11887         column: {
11888           hide: 'Spalte ausblenden'
11889         },
11890         aggregation: {
11891           count: 'Zeilen insgesamt: ',
11892           sum: 'gesamt: ',
11893           avg: 'Durchschnitt: ',
11894           min: 'min: ',
11895           max: 'max: '
11896         },
11897         pinning: {
11898             pinLeft: 'Links anheften',
11899             pinRight: 'Rechts anheften',
11900             unpin: 'Lösen'
11901         },
11902         gridMenu: {
11903           columns: 'Spalten:',
11904           importerTitle: 'Datei importieren',
11905           exporterAllAsCsv: 'Alle Daten als CSV exportieren',
11906           exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
11907           exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
11908           exporterAllAsPdf: 'Alle Daten als PDF exportieren',
11909           exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
11910           exporterSelectedAsPdf: 'markierte Daten als CSV exportieren',
11911           clearAllFilters: 'Alle filter reinigen'
11912         },
11913         importer: {
11914           noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
11915           noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
11916           invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
11917           invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
11918           jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
11919         },
11920         pagination: {
11921             sizes: 'Einträge pro Seite',
11922             totalItems: 'Einträge'
11923         },
11924         grouping: {
11925             group: 'Gruppieren',
11926             ungroup: 'Gruppierung aufheben',
11927             aggregate_count: 'Agg: Anzahl',
11928             aggregate_sum: 'Agg: Summe',
11929             aggregate_max: 'Agg: Maximum',
11930             aggregate_min: 'Agg: Minimum',
11931             aggregate_avg: 'Agg: Mittelwert',
11932             aggregate_remove: 'Aggregation entfernen'
11933         }
11934       });
11935       return $delegate;
11936     }]);
11937   }]);
11938 })();
11939
11940 (function () {
11941   angular.module('ui.grid').config(['$provide', function($provide) {
11942     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11943       $delegate.add('en', {
11944         headerCell: {
11945           aria: {
11946             defaultFilterLabel: 'Filter for column',
11947             removeFilter: 'Remove Filter',
11948             columnMenuButtonLabel: 'Column Menu'
11949           },
11950           priority: 'Priority:',
11951           filterLabel: "Filter for column: "
11952         },
11953         aggregate: {
11954           label: 'items'
11955         },
11956         groupPanel: {
11957           description: 'Drag a column header here and drop it to group by that column.'
11958         },
11959         search: {
11960           placeholder: 'Search...',
11961           showingItems: 'Showing Items:',
11962           selectedItems: 'Selected Items:',
11963           totalItems: 'Total Items:',
11964           size: 'Page Size:',
11965           first: 'First Page',
11966           next: 'Next Page',
11967           previous: 'Previous Page',
11968           last: 'Last Page'
11969         },
11970         menu: {
11971           text: 'Choose Columns:'
11972         },
11973         sort: {
11974           ascending: 'Sort Ascending',
11975           descending: 'Sort Descending',
11976           none: 'Sort None',
11977           remove: 'Remove Sort'
11978         },
11979         column: {
11980           hide: 'Hide Column'
11981         },
11982         aggregation: {
11983           count: 'total rows: ',
11984           sum: 'total: ',
11985           avg: 'avg: ',
11986           min: 'min: ',
11987           max: 'max: '
11988         },
11989         pinning: {
11990           pinLeft: 'Pin Left',
11991           pinRight: 'Pin Right',
11992           unpin: 'Unpin'
11993         },
11994         columnMenu: {
11995           close: 'Close'
11996         },
11997         gridMenu: {
11998           aria: {
11999             buttonLabel: 'Grid Menu'
12000           },
12001           columns: 'Columns:',
12002           importerTitle: 'Import file',
12003           exporterAllAsCsv: 'Export all data as csv',
12004           exporterVisibleAsCsv: 'Export visible data as csv',
12005           exporterSelectedAsCsv: 'Export selected data as csv',
12006           exporterAllAsPdf: 'Export all data as pdf',
12007           exporterVisibleAsPdf: 'Export visible data as pdf',
12008           exporterSelectedAsPdf: 'Export selected data as pdf',
12009           clearAllFilters: 'Clear all filters'
12010         },
12011         importer: {
12012           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12013           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12014           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12015           invalidJson: 'File was unable to be processed, is it valid Json?',
12016           jsonNotArray: 'Imported json file must contain an array, aborting.'
12017         },
12018         pagination: {
12019           aria: {
12020             pageToFirst: 'Page to first',
12021             pageBack: 'Page back',
12022             pageSelected: 'Selected page',
12023             pageForward: 'Page forward',
12024             pageToLast: 'Page to last'
12025           },
12026           sizes: 'items per page',
12027           totalItems: 'items',
12028           through: 'through',
12029           of: 'of'
12030         },
12031         grouping: {
12032           group: 'Group',
12033           ungroup: 'Ungroup',
12034           aggregate_count: 'Agg: Count',
12035           aggregate_sum: 'Agg: Sum',
12036           aggregate_max: 'Agg: Max',
12037           aggregate_min: 'Agg: Min',
12038           aggregate_avg: 'Agg: Avg',
12039           aggregate_remove: 'Agg: Remove'
12040         }
12041       });
12042       return $delegate;
12043     }]);
12044   }]);
12045 })();
12046
12047 (function () {
12048   angular.module('ui.grid').config(['$provide', function($provide) {
12049     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12050       $delegate.add('es', {
12051         aggregate: {
12052           label: 'Artículos'
12053         },
12054         groupPanel: {
12055           description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
12056         },
12057         search: {
12058           placeholder: 'Buscar...',
12059           showingItems: 'Artículos Mostrados:',
12060           selectedItems: 'Artículos Seleccionados:',
12061           totalItems: 'Artículos Totales:',
12062           size: 'Tamaño de Página:',
12063           first: 'Primera Página',
12064           next: 'Página Siguiente',
12065           previous: 'Página Anterior',
12066           last: 'Última Página'
12067         },
12068         menu: {
12069           text: 'Elegir columnas:'
12070         },
12071         sort: {
12072           ascending: 'Orden Ascendente',
12073           descending: 'Orden Descendente',
12074           remove: 'Sin Ordenar'
12075         },
12076         column: {
12077           hide: 'Ocultar la columna'
12078         },
12079         aggregation: {
12080           count: 'filas totales: ',
12081           sum: 'total: ',
12082           avg: 'media: ',
12083           min: 'min: ',
12084           max: 'max: '
12085         },
12086         pinning: {
12087           pinLeft: 'Fijar a la Izquierda',
12088           pinRight: 'Fijar a la Derecha',
12089           unpin: 'Quitar Fijación'
12090         },
12091         gridMenu: {
12092           columns: 'Columnas:',
12093           importerTitle: 'Importar archivo',
12094           exporterAllAsCsv: 'Exportar todo como csv',
12095           exporterVisibleAsCsv: 'Exportar vista como csv',
12096           exporterSelectedAsCsv: 'Exportar selección como csv',
12097           exporterAllAsPdf: 'Exportar todo como pdf',
12098           exporterVisibleAsPdf: 'Exportar vista como pdf',
12099           exporterSelectedAsPdf: 'Exportar selección como pdf',
12100           clearAllFilters: 'Limpiar todos los filtros'
12101         },
12102         importer: {
12103           noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
12104           noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
12105           invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
12106           invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
12107           jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
12108         },
12109         pagination: {
12110           sizes: 'registros por página',
12111           totalItems: 'registros',
12112           of: 'de'
12113         },
12114         grouping: {
12115           group: 'Agrupar',
12116           ungroup: 'Desagrupar',
12117           aggregate_count: 'Agr: Cont',
12118           aggregate_sum: 'Agr: Sum',
12119           aggregate_max: 'Agr: Máx',
12120           aggregate_min: 'Agr: Min',
12121           aggregate_avg: 'Agr: Prom',
12122           aggregate_remove: 'Agr: Quitar'
12123         }
12124       });
12125       return $delegate;
12126     }]);
12127 }]);
12128 })();
12129
12130 /**
12131  * Translated by: R. Salarmehr
12132  *                M. Hosseynzade
12133  *                Using Vajje.com online dictionary.
12134  */
12135 (function () {
12136   angular.module('ui.grid').config(['$provide', function ($provide) {
12137     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12138       $delegate.add('fa', {
12139         aggregate: {
12140           label: 'قلم'
12141         },
12142         groupPanel: {
12143           description: 'عنوان یک ستون را بگیر و به گروهی از آن ستون رها کن.'
12144         },
12145         search: {
12146           placeholder: 'جستجو...',
12147           showingItems: 'نمایش اقلام:',
12148           selectedItems: 'قلم\u200cهای انتخاب شده:',
12149           totalItems: 'مجموع اقلام:',
12150           size: 'اندازه\u200cی صفحه:',
12151           first: 'اولین صفحه',
12152           next: 'صفحه\u200cی\u200cبعدی',
12153           previous: 'صفحه\u200cی\u200c قبلی',
12154           last: 'آخرین صفحه'
12155         },
12156         menu: {
12157           text: 'ستون\u200cهای انتخابی:'
12158         },
12159         sort: {
12160           ascending: 'ترتیب صعودی',
12161           descending: 'ترتیب نزولی',
12162           remove: 'حذف مرتب کردن'
12163         },
12164         column: {
12165           hide: 'پنهان\u200cکردن ستون'
12166         },
12167         aggregation: {
12168           count: 'تعداد: ',
12169           sum: 'مجموع: ',
12170           avg: 'میانگین: ',
12171           min: 'کمترین: ',
12172           max: 'بیشترین: '
12173         },
12174         pinning: {
12175           pinLeft: 'پین کردن سمت چپ',
12176           pinRight: 'پین کردن سمت راست',
12177           unpin: 'حذف پین'
12178         },
12179         gridMenu: {
12180           columns: 'ستون\u200cها:',
12181           importerTitle: 'وارد کردن فایل',
12182           exporterAllAsCsv: 'خروجی تمام داده\u200cها در فایل csv',
12183           exporterVisibleAsCsv: 'خروجی داده\u200cهای قابل مشاهده در فایل csv',
12184           exporterSelectedAsCsv: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل csv',
12185           exporterAllAsPdf: 'خروجی تمام داده\u200cها در فایل pdf',
12186           exporterVisibleAsPdf: 'خروجی داده\u200cهای قابل مشاهده در فایل pdf',
12187           exporterSelectedAsPdf: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل pdf',
12188           clearAllFilters: 'پاک کردن تمام فیلتر'
12189         },
12190         importer: {
12191           noHeaders: 'نام ستون قابل استخراج نیست. آیا فایل عنوان دارد؟',
12192           noObjects: 'اشیا قابل استخراج نیستند. آیا به جز عنوان\u200cها در فایل داده وجود دارد؟',
12193           invalidCsv: 'فایل قابل پردازش نیست. آیا فرمت  csv  معتبر است؟',
12194           invalidJson: 'فایل قابل پردازش نیست. آیا فرمت json   معتبر است؟',
12195           jsonNotArray: 'فایل json وارد شده باید حاوی آرایه باشد. عملیات ساقط شد.'
12196         },
12197         pagination: {
12198           sizes: 'اقلام در هر صفحه',
12199           totalItems: 'اقلام',
12200           of: 'از'
12201         },
12202         grouping: {
12203           group: 'گروه\u200cبندی',
12204           ungroup: 'حذف گروه\u200cبندی',
12205           aggregate_count: 'Agg: تعداد',
12206           aggregate_sum: 'Agg: جمع',
12207           aggregate_max: 'Agg: بیشینه',
12208           aggregate_min: 'Agg: کمینه',
12209           aggregate_avg: 'Agg: میانگین',
12210           aggregate_remove: 'Agg: حذف'
12211         }
12212       });
12213       return $delegate;
12214     }]);
12215   }]);
12216 })();
12217
12218 (function () {
12219   angular.module('ui.grid').config(['$provide', function($provide) {
12220     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12221       $delegate.add('fi', {
12222         aggregate: {
12223           label: 'rivit'
12224         },
12225         groupPanel: {
12226           description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
12227         },
12228         search: {
12229           placeholder: 'Hae...',
12230           showingItems: 'Näytetään rivejä:',
12231           selectedItems: 'Valitut rivit:',
12232           totalItems: 'Rivejä yht.:',
12233           size: 'Näytä:',
12234           first: 'Ensimmäinen sivu',
12235           next: 'Seuraava sivu',
12236           previous: 'Edellinen sivu',
12237           last: 'Viimeinen sivu'
12238         },
12239         menu: {
12240           text: 'Valitse sarakkeet:'
12241         },
12242         sort: {
12243           ascending: 'Järjestä nouseva',
12244           descending: 'Järjestä laskeva',
12245           remove: 'Poista järjestys'
12246         },
12247         column: {
12248           hide: 'Piilota sarake'
12249         },
12250         aggregation: {
12251           count: 'Rivejä yht.: ',
12252           sum: 'Summa: ',
12253           avg: 'K.a.: ',
12254           min: 'Min: ',
12255           max: 'Max: '
12256         },
12257         pinning: {
12258          pinLeft: 'Lukitse vasemmalle',
12259           pinRight: 'Lukitse oikealle',
12260           unpin: 'Poista lukitus'
12261         },
12262         gridMenu: {
12263           columns: 'Sarakkeet:',
12264           importerTitle: 'Tuo tiedosto',
12265           exporterAllAsCsv: 'Vie tiedot csv-muodossa',
12266           exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
12267           exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
12268           exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
12269           exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
12270           exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12271           clearAllFilters: 'Puhdista kaikki suodattimet'
12272         },
12273         importer: {
12274           noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
12275           noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
12276           invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
12277           invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
12278           jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
12279         }
12280       });
12281       return $delegate;
12282     }]);
12283   }]);
12284 })();
12285
12286 (function () {
12287   angular.module('ui.grid').config(['$provide', function($provide) {
12288     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12289       $delegate.add('fr', {
12290         aggregate: {
12291           label: 'éléments'
12292         },
12293         groupPanel: {
12294           description: 'Faites glisser une en-tête de colonne ici pour créer un groupe de colonnes.'
12295         },
12296         search: {
12297           placeholder: 'Recherche...',
12298           showingItems: 'Affichage des éléments :',
12299           selectedItems: 'Éléments sélectionnés :',
12300           totalItems: 'Nombre total d\'éléments:',
12301           size: 'Taille de page:',
12302           first: 'Première page',
12303           next: 'Page Suivante',
12304           previous: 'Page précédente',
12305           last: 'Dernière page'
12306         },
12307         menu: {
12308           text: 'Choisir des colonnes :'
12309         },
12310         sort: {
12311           ascending: 'Trier par ordre croissant',
12312           descending: 'Trier par ordre décroissant',
12313           remove: 'Enlever le tri'
12314         },
12315         column: {
12316           hide: 'Cacher la colonne'
12317         },
12318         aggregation: {
12319           count: 'lignes totales: ',
12320           sum: 'total: ',
12321           avg: 'moy: ',
12322           min: 'min: ',
12323           max: 'max: '
12324         },
12325         pinning: {
12326           pinLeft: 'Épingler à gauche',
12327           pinRight: 'Épingler à droite',
12328           unpin: 'Détacher'
12329         },
12330         gridMenu: {
12331           columns: 'Colonnes:',
12332           importerTitle: 'Importer un fichier',
12333           exporterAllAsCsv: 'Exporter toutes les données en CSV',
12334           exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
12335           exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
12336           exporterAllAsPdf: 'Exporter toutes les données en PDF',
12337           exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
12338           exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
12339           clearAllFilters: 'Nettoyez tous les filtres'
12340         },
12341         importer: {
12342           noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il une en-tête ?',
12343           noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
12344           invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
12345           invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
12346           jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
12347         },
12348         pagination: {
12349           sizes: 'éléments par page',
12350           totalItems: 'éléments',
12351           of: 'sur'
12352         },
12353         grouping: {
12354           group: 'Grouper',
12355           ungroup: 'Dégrouper',
12356           aggregate_count: 'Agg: Compte',
12357           aggregate_sum: 'Agg: Somme',
12358           aggregate_max: 'Agg: Max',
12359           aggregate_min: 'Agg: Min',
12360           aggregate_avg: 'Agg: Moy',
12361           aggregate_remove: 'Agg: Retirer'
12362         }
12363       });
12364       return $delegate;
12365     }]);
12366   }]);
12367 })();
12368
12369 (function () {
12370   angular.module('ui.grid').config(['$provide', function ($provide) {
12371     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12372       $delegate.add('he', {
12373         aggregate: {
12374           label: 'items'
12375         },
12376         groupPanel: {
12377           description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
12378         },
12379         search: {
12380           placeholder: 'חפש...',
12381           showingItems: 'מציג:',
12382           selectedItems: 'סה"כ נבחרו:',
12383           totalItems: 'סה"כ רשומות:',
12384           size: 'תוצאות בדף:',
12385           first: 'דף ראשון',
12386           next: 'דף הבא',
12387           previous: 'דף קודם',
12388           last: 'דף אחרון'
12389         },
12390         menu: {
12391           text: 'בחר עמודות:'
12392         },
12393         sort: {
12394           ascending: 'סדר עולה',
12395           descending: 'סדר יורד',
12396           remove: 'בטל'
12397         },
12398         column: {
12399           hide: 'טור הסתר'
12400         },
12401         aggregation: {
12402           count: 'total rows: ',
12403           sum: 'total: ',
12404           avg: 'avg: ',
12405           min: 'min: ',
12406           max: 'max: '
12407         },
12408         gridMenu: {
12409           columns: 'Columns:',
12410           importerTitle: 'Import file',
12411           exporterAllAsCsv: 'Export all data as csv',
12412           exporterVisibleAsCsv: 'Export visible data as csv',
12413           exporterSelectedAsCsv: 'Export selected data as csv',
12414           exporterAllAsPdf: 'Export all data as pdf',
12415           exporterVisibleAsPdf: 'Export visible data as pdf',
12416           exporterSelectedAsPdf: 'Export selected data as pdf',
12417           clearAllFilters: 'Clean all filters'
12418         },
12419         importer: {
12420           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12421           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12422           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12423           invalidJson: 'File was unable to be processed, is it valid Json?',
12424           jsonNotArray: 'Imported json file must contain an array, aborting.'
12425         }
12426       });
12427       return $delegate;
12428     }]);
12429   }]);
12430 })();
12431
12432 (function () {
12433   angular.module('ui.grid').config(['$provide', function($provide) {
12434     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12435       $delegate.add('hy', {
12436         aggregate: {
12437           label: 'տվյալներ'
12438         },
12439         groupPanel: {
12440           description: 'Ըստ սյան խմբավորելու համար քաշեք և գցեք վերնագիրն այստեղ։'
12441         },
12442         search: {
12443           placeholder: 'Փնտրում...',
12444           showingItems: 'Ցուցադրված տվյալներ՝',
12445           selectedItems: 'Ընտրված:',
12446           totalItems: 'Ընդամենը՝',
12447           size: 'Տողերի քանակը էջում՝',
12448           first: 'Առաջին էջ',
12449           next: 'Հաջորդ էջ',
12450           previous: 'Նախորդ էջ',
12451           last: 'Վերջին էջ'
12452         },
12453         menu: {
12454           text: 'Ընտրել սյուները:'
12455         },
12456         sort: {
12457           ascending: 'Աճման կարգով',
12458           descending: 'Նվազման կարգով',
12459           remove: 'Հանել '
12460         },
12461         column: {
12462           hide: 'Թաքցնել սյունը'
12463         },
12464         aggregation: {
12465           count: 'ընդամենը տող՝ ',
12466           sum: 'ընդամենը՝ ',
12467           avg: 'միջին՝ ',
12468           min: 'մին՝ ',
12469           max: 'մաքս՝ '
12470         },
12471         pinning: {
12472           pinLeft: 'Կպցնել ձախ կողմում',
12473           pinRight: 'Կպցնել աջ կողմում',
12474           unpin: 'Արձակել'
12475         },
12476         gridMenu: {
12477           columns: 'Սյուներ:',
12478           importerTitle: 'Ներմուծել ֆայլ',
12479           exporterAllAsCsv: 'Արտահանել ամբողջը CSV',
12480           exporterVisibleAsCsv: 'Արտահանել երևացող տվյալները CSV',
12481           exporterSelectedAsCsv: 'Արտահանել ընտրված տվյալները CSV',
12482           exporterAllAsPdf: 'Արտահանել PDF',
12483           exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
12484           exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
12485           clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
12486         },
12487         importer: {
12488           noHeaders: 'Հնարավոր չեղավ որոշել սյան վերնագրերը։ Արդյո՞ք ֆայլը ունի վերնագրեր։',
12489           noObjects: 'Հնարավոր չեղավ կարդալ տվյալները։ Արդյո՞ք ֆայլում կան տվյալներ։',
12490           invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
12491           invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
12492           jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
12493         }
12494       });
12495       return $delegate;
12496     }]);
12497   }]);
12498 })();
12499
12500 (function () {
12501   angular.module('ui.grid').config(['$provide', function($provide) {
12502     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12503       $delegate.add('it', {
12504         aggregate: {
12505           label: 'elementi'
12506         },
12507         groupPanel: {
12508           description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
12509         },
12510         search: {
12511           placeholder: 'Ricerca...',
12512           showingItems: 'Mostra:',
12513           selectedItems: 'Selezionati:',
12514           totalItems: 'Totali:',
12515           size: 'Tot Pagine:',
12516           first: 'Prima',
12517           next: 'Prossima',
12518           previous: 'Precedente',
12519           last: 'Ultima'
12520         },
12521         menu: {
12522           text: 'Scegli le colonne:'
12523         },
12524         sort: {
12525           ascending: 'Asc.',
12526           descending: 'Desc.',
12527           remove: 'Annulla ordinamento'
12528         },
12529         column: {
12530           hide: 'Nascondi'
12531         },
12532         aggregation: {
12533           count: 'righe totali: ',
12534           sum: 'tot: ',
12535           avg: 'media: ',
12536           min: 'minimo: ',
12537           max: 'massimo: '
12538         },
12539         pinning: {
12540          pinLeft: 'Blocca a sx',
12541           pinRight: 'Blocca a dx',
12542           unpin: 'Blocca in alto'
12543         },
12544         gridMenu: {
12545           columns: 'Colonne:',
12546           importerTitle: 'Importa',
12547           exporterAllAsCsv: 'Esporta tutti i dati in CSV',
12548           exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
12549           exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
12550           exporterAllAsPdf: 'Esporta tutti i dati in PDF',
12551           exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
12552           exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
12553           clearAllFilters: 'Pulire tutti i filtri'
12554         },
12555         importer: {
12556           noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
12557           noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
12558           invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
12559           invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
12560           jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
12561         },
12562         grouping: {
12563           group: 'Raggruppa',
12564           ungroup: 'Separa',
12565           aggregate_count: 'Agg: N. Elem.',
12566           aggregate_sum: 'Agg: Somma',
12567           aggregate_max: 'Agg: Massimo',
12568           aggregate_min: 'Agg: Minimo',
12569           aggregate_avg: 'Agg: Media',
12570           aggregate_remove: 'Agg: Rimuovi'
12571         }
12572       });
12573       return $delegate;
12574     }]);
12575   }]);
12576 })();
12577
12578 (function() {
12579   angular.module('ui.grid').config(['$provide', function($provide) {
12580     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12581       $delegate.add('ja', {
12582         aggregate: {
12583           label: '項目'
12584         },
12585         groupPanel: {
12586           description: 'ここに列ヘッダをドラッグアンドドロップして、その列でグループ化します。'
12587         },
12588         search: {
12589           placeholder: '検索...',
12590           showingItems: '表示中の項目:',
12591           selectedItems: '選択した項目:',
12592           totalItems: '項目の総数:',
12593           size: 'ページサイズ:',
12594           first: '最初のページ',
12595           next: '次のページ',
12596           previous: '前のページ',
12597           last: '前のページ'
12598         },
12599         menu: {
12600           text: '列の選択:'
12601         },
12602         sort: {
12603           ascending: '昇順に並べ替え',
12604           descending: '降順に並べ替え',
12605           remove: '並べ替えの解除'
12606         },
12607         column: {
12608           hide: '列の非表示'
12609         },
12610         aggregation: {
12611           count: '合計行数: ',
12612           sum: '合計: ',
12613           avg: '平均: ',
12614           min: '最小: ',
12615           max: '最大: '
12616         },
12617         pinning: {
12618           pinLeft: '左に固定',
12619           pinRight: '右に固定',
12620           unpin: '固定解除'
12621         },
12622         gridMenu: {
12623           columns: '列:',
12624           importerTitle: 'ファイルのインポート',
12625           exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
12626           exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
12627           exporterSelectedAsCsv: '選択したデータをCSV形式でエクスポート',
12628           exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
12629           exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
12630           exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12631           clearAllFilters: 'すべてのフィルタを清掃してください'
12632         },
12633         importer: {
12634           noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
12635           noObjects: 'オブジェクトを取得できません。ファイルにヘッダ以外のデータが含まれていることを確認してください。',
12636           invalidCsv: 'ファイルを処理できません。ファイルが有効なCSV形式であることを確認してください。',
12637           invalidJson: 'ファイルを処理できません。ファイルが有効なJSON形式であることを確認してください。',
12638           jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
12639         },
12640         pagination: {
12641           sizes: '項目/ページ',
12642           totalItems: '項目'
12643         }
12644       });
12645       return $delegate;
12646     }]);
12647   }]);
12648 })();
12649
12650 (function () {
12651   angular.module('ui.grid').config(['$provide', function($provide) {
12652     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12653       $delegate.add('ko', {
12654         aggregate: {
12655           label: '아이템'
12656         },
12657         groupPanel: {
12658           description: '컬럼으로 그룹핑하기 위해서는 컬럼 헤더를 끌어 떨어뜨려 주세요.'
12659         },
12660         search: {
12661           placeholder: '검색...',
12662           showingItems: '항목 보여주기:',
12663           selectedItems: '선택 항목:',
12664           totalItems: '전체 항목:',
12665           size: '페이지 크기:',
12666           first: '첫번째 페이지',
12667           next: '다음 페이지',
12668           previous: '이전 페이지',
12669           last: '마지막 페이지'
12670         },
12671         menu: {
12672           text: '컬럼을 선택하세요:'
12673         },
12674         sort: {
12675           ascending: '오름차순 정렬',
12676           descending: '내림차순 정렬',
12677           remove: '소팅 제거'
12678         },
12679         column: {
12680           hide: '컬럼 제거'
12681         },
12682         aggregation: {
12683           count: '전체 갯수: ',
12684           sum: '전체: ',
12685           avg: '평균: ',
12686           min: '최소: ',
12687           max: '최대: '
12688         },
12689         pinning: {
12690          pinLeft: '왼쪽 핀',
12691           pinRight: '오른쪽 핀',
12692           unpin: '핀 제거'
12693         },
12694         gridMenu: {
12695           columns: '컬럼:',
12696           importerTitle: '파일 가져오기',
12697           exporterAllAsCsv: 'csv로 모든 데이터 내보내기',
12698           exporterVisibleAsCsv: 'csv로 보이는 데이터 내보내기',
12699           exporterSelectedAsCsv: 'csv로 선택된 데이터 내보내기',
12700           exporterAllAsPdf: 'pdf로 모든 데이터 내보내기',
12701           exporterVisibleAsPdf: 'pdf로 보이는 데이터 내보내기',
12702           exporterSelectedAsPdf: 'pdf로 선택 데이터 내보내기',
12703           clearAllFilters: '모든 필터를 청소'
12704         },
12705         importer: {
12706           noHeaders: '컬럼명이 지정되어 있지 않습니다. 파일에 헤더가 명시되어 있는지 확인해 주세요.',
12707           noObjects: '데이터가 지정되어 있지 않습니다. 데이터가 파일에 있는지 확인해 주세요.',
12708           invalidCsv: '파일을 처리할 수 없습니다. 올바른 csv인지 확인해 주세요.',
12709           invalidJson: '파일을 처리할 수 없습니다. 올바른 json인지 확인해 주세요.',
12710           jsonNotArray: 'json 파일은 배열을 포함해야 합니다.'
12711         },
12712         pagination: {
12713           sizes: '페이지당 항목',
12714           totalItems: '전체 항목'
12715         }
12716       });
12717       return $delegate;
12718     }]);
12719   }]);
12720 })();
12721
12722 (function () {
12723   angular.module('ui.grid').config(['$provide', function($provide) {
12724     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12725       $delegate.add('nl', {
12726         aggregate: {
12727           label: 'items'
12728         },
12729         groupPanel: {
12730           description: 'Sleep hier een kolomnaam heen om op te groeperen.'
12731         },
12732         search: {
12733           placeholder: 'Zoeken...',
12734           showingItems: 'Getoonde items:',
12735           selectedItems: 'Geselecteerde items:',
12736           totalItems: 'Totaal aantal items:',
12737           size: 'Items per pagina:',
12738           first: 'Eerste pagina',
12739           next: 'Volgende pagina',
12740           previous: 'Vorige pagina',
12741           last: 'Laatste pagina'
12742         },
12743         menu: {
12744           text: 'Kies kolommen:'
12745         },
12746         sort: {
12747           ascending: 'Sorteer oplopend',
12748           descending: 'Sorteer aflopend',
12749           remove: 'Verwijder sortering'
12750         },
12751         column: {
12752           hide: 'Verberg kolom'
12753         },
12754         aggregation: {
12755           count: 'Aantal rijen: ',
12756           sum: 'Som: ',
12757           avg: 'Gemiddelde: ',
12758           min: 'Min: ',
12759           max: 'Max: '
12760         },
12761         pinning: {
12762           pinLeft: 'Zet links vast',
12763           pinRight: 'Zet rechts vast',
12764           unpin: 'Maak los'
12765         },
12766         gridMenu: {
12767           columns: 'Kolommen:',
12768           importerTitle: 'Importeer bestand',
12769           exporterAllAsCsv: 'Exporteer alle data als csv',
12770           exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
12771           exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
12772           exporterAllAsPdf: 'Exporteer alle data als pdf',
12773           exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
12774           exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
12775           clearAllFilters: 'Reinig alle filters'
12776         },
12777         importer: {
12778           noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
12779           noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
12780           invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
12781           invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
12782           jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
12783         },
12784         pagination: {
12785             sizes: 'items per pagina',
12786             totalItems: 'items',
12787             of: 'van de'
12788         },
12789         grouping: {
12790             group: 'Groepeer',
12791             ungroup: 'Groepering opheffen',
12792             aggregate_count: 'Agg: Aantal',
12793             aggregate_sum: 'Agg: Som',
12794             aggregate_max: 'Agg: Max',
12795             aggregate_min: 'Agg: Min',
12796             aggregate_avg: 'Agg: Gem',
12797             aggregate_remove: 'Agg: Verwijder'
12798         }
12799       });
12800       return $delegate;
12801     }]);
12802   }]);
12803 })();
12804
12805 (function () {
12806   angular.module('ui.grid').config(['$provide', function($provide) {
12807     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12808       $delegate.add('pt-br', {
12809         aggregate: {
12810           label: 'itens'
12811         },
12812         groupPanel: {
12813           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
12814         },
12815         search: {
12816           placeholder: 'Procurar...',
12817           showingItems: 'Mostrando os Itens:',
12818           selectedItems: 'Items Selecionados:',
12819           totalItems: 'Total de Itens:',
12820           size: 'Tamanho da Página:',
12821           first: 'Primeira Página',
12822           next: 'Próxima Página',
12823           previous: 'Página Anterior',
12824           last: 'Última Página'
12825         },
12826         menu: {
12827           text: 'Selecione as colunas:'
12828         },
12829         sort: {
12830           ascending: 'Ordenar Ascendente',
12831           descending: 'Ordenar Descendente',
12832           remove: 'Remover Ordenação'
12833         },
12834         column: {
12835           hide: 'Esconder coluna'
12836         },
12837         aggregation: {
12838           count: 'total de linhas: ',
12839           sum: 'total: ',
12840           avg: 'med: ',
12841           min: 'min: ',
12842           max: 'max: '
12843         },
12844         pinning: {
12845           pinLeft: 'Fixar Esquerda',
12846           pinRight: 'Fixar Direita',
12847           unpin: 'Desprender'
12848         },
12849         gridMenu: {
12850           columns: 'Colunas:',
12851           importerTitle: 'Importar arquivo',
12852           exporterAllAsCsv: 'Exportar todos os dados como csv',
12853           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
12854           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
12855           exporterAllAsPdf: 'Exportar todos os dados como pdf',
12856           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
12857           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
12858           clearAllFilters: 'Limpar todos os filtros'
12859         },
12860         importer: {
12861           noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
12862           noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
12863           invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
12864           invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
12865           jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
12866         },
12867         pagination: {
12868           sizes: 'itens por página',
12869           totalItems: 'itens'
12870         },
12871         grouping: {
12872           group: 'Agrupar',
12873           ungroup: 'Desagrupar',
12874           aggregate_count: 'Agr: Contar',
12875           aggregate_sum: 'Agr: Soma',
12876           aggregate_max: 'Agr: Max',
12877           aggregate_min: 'Agr: Min',
12878           aggregate_avg: 'Agr: Med',
12879           aggregate_remove: 'Agr: Remover'
12880         }
12881       });
12882       return $delegate;
12883     }]);
12884 }]);
12885 })();
12886
12887 (function () {
12888   angular.module('ui.grid').config(['$provide', function($provide) {
12889     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12890       $delegate.add('pt', {
12891         aggregate: {
12892           label: 'itens'
12893         },
12894         groupPanel: {
12895           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
12896         },
12897         search: {
12898           placeholder: 'Procurar...',
12899           showingItems: 'Mostrando os Itens:',
12900           selectedItems: 'Itens Selecionados:',
12901           totalItems: 'Total de Itens:',
12902           size: 'Tamanho da Página:',
12903           first: 'Primeira Página',
12904           next: 'Próxima Página',
12905           previous: 'Página Anterior',
12906           last: 'Última Página'
12907         },
12908         menu: {
12909           text: 'Selecione as colunas:'
12910         },
12911         sort: {
12912           ascending: 'Ordenar Ascendente',
12913           descending: 'Ordenar Descendente',
12914           remove: 'Remover Ordenação'
12915         },
12916         column: {
12917           hide: 'Esconder coluna'
12918         },
12919         aggregation: {
12920           count: 'total de linhas: ',
12921           sum: 'total: ',
12922           avg: 'med: ',
12923           min: 'min: ',
12924           max: 'max: '
12925         },
12926         pinning: {
12927           pinLeft: 'Fixar Esquerda',
12928           pinRight: 'Fixar Direita',
12929           unpin: 'Desprender'
12930         },
12931         gridMenu: {
12932           columns: 'Colunas:',
12933           importerTitle: 'Importar ficheiro',
12934           exporterAllAsCsv: 'Exportar todos os dados como csv',
12935           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
12936           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
12937           exporterAllAsPdf: 'Exportar todos os dados como pdf',
12938           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
12939           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
12940           clearAllFilters: 'Limpar todos os filtros'
12941         },
12942         importer: {
12943           noHeaders: 'Nomes de colunas não puderam ser derivados. O ficheiro tem um cabeçalho?',
12944           noObjects: 'Objetos não puderam ser derivados. Havia dados no ficheiro, além dos cabeçalhos?',
12945           invalidCsv: 'Ficheiro não pode ser processado. É um CSV válido?',
12946           invalidJson: 'Ficheiro não pode ser processado. É um Json válido?',
12947           jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
12948         },
12949         pagination: {
12950           sizes: 'itens por página',
12951           totalItems: 'itens',
12952           of: 'de'
12953         },
12954         grouping: {
12955           group: 'Agrupar',
12956           ungroup: 'Desagrupar',
12957           aggregate_count: 'Agr: Contar',
12958           aggregate_sum: 'Agr: Soma',
12959           aggregate_max: 'Agr: Max',
12960           aggregate_min: 'Agr: Min',
12961           aggregate_avg: 'Agr: Med',
12962           aggregate_remove: 'Agr: Remover'
12963         }
12964       });
12965       return $delegate;
12966     }]);
12967 }]);
12968 })();
12969
12970 (function () {
12971   angular.module('ui.grid').config(['$provide', function($provide) {
12972     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12973       $delegate.add('ru', {
12974         aggregate: {
12975           label: 'элементы'
12976         },
12977         groupPanel: {
12978           description: 'Для группировки по столбцу перетащите сюда его название.'
12979         },
12980         search: {
12981           placeholder: 'Поиск...',
12982           showingItems: 'Показать элементы:',
12983           selectedItems: 'Выбранные элементы:',
12984           totalItems: 'Всего элементов:',
12985           size: 'Размер страницы:',
12986           first: 'Первая страница',
12987           next: 'Следующая страница',
12988           previous: 'Предыдущая страница',
12989           last: 'Последняя страница'
12990         },
12991         menu: {
12992           text: 'Выбрать столбцы:'
12993         },
12994         sort: {
12995           ascending: 'По возрастанию',
12996           descending: 'По убыванию',
12997           remove: 'Убрать сортировку'
12998         },
12999         column: {
13000           hide: 'Спрятать столбец'
13001         },
13002         aggregation: {
13003           count: 'всего строк: ',
13004           sum: 'итого: ',
13005           avg: 'среднее: ',
13006           min: 'мин: ',
13007           max: 'макс: '
13008         },
13009                                 pinning: {
13010                                         pinLeft: 'Закрепить слева',
13011                                         pinRight: 'Закрепить справа',
13012                                         unpin: 'Открепить'
13013                                 },
13014         gridMenu: {
13015           columns: 'Столбцы:',
13016           importerTitle: 'Import file',
13017           exporterAllAsCsv: 'Экспортировать всё в CSV',
13018           exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
13019           exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
13020           exporterAllAsPdf: 'Экспортировать всё в PDF',
13021           exporterVisibleAsPdf: 'Экспортировать видимые данные в PDF',
13022           exporterSelectedAsPdf: 'Экспортировать выбранные данные в PDF',
13023           clearAllFilters: 'Очистите все фильтры'
13024         },
13025         importer: {
13026           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13027           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13028           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13029           invalidJson: 'File was unable to be processed, is it valid Json?',
13030           jsonNotArray: 'Imported json file must contain an array, aborting.'
13031         }
13032       });
13033       return $delegate;
13034     }]);
13035   }]);
13036 })();
13037
13038 (function () {
13039   angular.module('ui.grid').config(['$provide', function($provide) {
13040     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13041       $delegate.add('sk', {
13042         aggregate: {
13043           label: 'items'
13044         },
13045         groupPanel: {
13046           description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13047         },
13048         search: {
13049           placeholder: 'Hľadaj...',
13050           showingItems: 'Zobrazujem položky:',
13051           selectedItems: 'Vybraté položky:',
13052           totalItems: 'Počet položiek:',
13053           size: 'Počet:',
13054           first: 'Prvá strana',
13055           next: 'Ďalšia strana',
13056           previous: 'Predchádzajúca strana',
13057           last: 'Posledná strana'
13058         },
13059         menu: {
13060           text: 'Vyberte stĺpce:'
13061         },
13062         sort: {
13063           ascending: 'Zotriediť vzostupne',
13064           descending: 'Zotriediť zostupne',
13065           remove: 'Vymazať triedenie'
13066         },
13067         aggregation: {
13068           count: 'total rows: ',
13069           sum: 'total: ',
13070           avg: 'avg: ',
13071           min: 'min: ',
13072           max: 'max: '
13073         },
13074         gridMenu: {
13075           columns: 'Columns:',
13076           importerTitle: 'Import file',
13077           exporterAllAsCsv: 'Export all data as csv',
13078           exporterVisibleAsCsv: 'Export visible data as csv',
13079           exporterSelectedAsCsv: 'Export selected data as csv',
13080           exporterAllAsPdf: 'Export all data as pdf',
13081           exporterVisibleAsPdf: 'Export visible data as pdf',
13082           exporterSelectedAsPdf: 'Export selected data as pdf',
13083           clearAllFilters: 'Clear all filters'
13084         },
13085         importer: {
13086           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13087           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13088           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13089           invalidJson: 'File was unable to be processed, is it valid Json?',
13090           jsonNotArray: 'Imported json file must contain an array, aborting.'
13091         }
13092       });
13093       return $delegate;
13094     }]);
13095   }]);
13096 })();
13097
13098 (function () {
13099   angular.module('ui.grid').config(['$provide', function($provide) {
13100     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13101       $delegate.add('sv', {
13102         aggregate: {
13103           label: 'Artiklar'
13104         },
13105         groupPanel: {
13106           description: 'Dra en kolumnrubrik hit och släpp den för gruppera efter den kolumnen.'
13107         },
13108         search: {
13109           placeholder: 'Sök...',
13110           showingItems: 'Visar artiklar:',
13111           selectedItems: 'Valda artiklar:',
13112           totalItems: 'Antal artiklar:',
13113           size: 'Sidstorlek:',
13114           first: 'Första sidan',
13115           next: 'Nästa sida',
13116           previous: 'Föregående sida',
13117           last: 'Sista sidan'
13118         },
13119         menu: {
13120           text: 'Välj kolumner:'
13121         },
13122         sort: {
13123           ascending: 'Sortera stigande',
13124           descending: 'Sortera fallande',
13125           remove: 'Inaktivera sortering'
13126         },
13127         column: {
13128           hide: 'Göm kolumn'
13129         },
13130         aggregation: {
13131           count: 'Antal rader: ',
13132           sum: 'Summa: ',
13133           avg: 'Genomsnitt: ',
13134           min: 'Min: ',
13135           max: 'Max: '
13136         },
13137         pinning: {
13138           pinLeft: 'Fäst vänster',
13139           pinRight: 'Fäst höger',
13140           unpin: 'Lösgör'
13141         },
13142         gridMenu: {
13143           columns: 'Kolumner:',
13144           importerTitle: 'Importera fil',
13145           exporterAllAsCsv: 'Exportera all data som CSV',
13146           exporterVisibleAsCsv: 'Exportera synlig data som CSV',
13147           exporterSelectedAsCsv: 'Exportera markerad data som CSV',
13148           exporterAllAsPdf: 'Exportera all data som PDF',
13149           exporterVisibleAsPdf: 'Exportera synlig data som PDF',
13150           exporterSelectedAsPdf: 'Exportera markerad data som PDF',
13151           clearAllFilters: 'Rengör alla filter'
13152         },
13153         importer: {
13154           noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
13155           noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
13156           invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
13157           invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
13158           jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
13159         },
13160         pagination: {
13161           sizes: 'Artiklar per sida',
13162           totalItems: 'Artiklar'
13163         }
13164       });
13165       return $delegate;
13166     }]);
13167   }]);
13168 })();
13169
13170 (function () {
13171   angular.module('ui.grid').config(['$provide', function($provide) {
13172     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13173       $delegate.add('ta', {
13174         aggregate: {
13175           label: 'உருப்படிகள்'
13176         },
13177         groupPanel: {
13178           description: 'ஒரு பத்தியை குழுவாக அமைக்க அப்பத்தியின் தலைப்பை இங்கே  இழுத்து வரவும் '
13179         },
13180         search: {
13181           placeholder: 'தேடல் ...',
13182           showingItems: 'உருப்படிகளை காண்பித்தல்:',
13183           selectedItems: 'தேர்ந்தெடுக்கப்பட்ட  உருப்படிகள்:',
13184           totalItems: 'மொத்த உருப்படிகள்:',
13185           size: 'பக்க அளவு: ',
13186           first: 'முதல் பக்கம்',
13187           next: 'அடுத்த பக்கம்',
13188           previous: 'முந்தைய பக்கம் ',
13189           last: 'இறுதி பக்கம்'
13190         },
13191         menu: {
13192           text: 'பத்திகளை தேர்ந்தெடு:'
13193         },
13194         sort: {
13195           ascending: 'மேலிருந்து கீழாக',
13196           descending: 'கீழிருந்து மேலாக',
13197           remove: 'வரிசையை நீக்கு'
13198         },
13199         column: {
13200           hide: 'பத்தியை மறைத்து வை '
13201         },
13202         aggregation: {
13203           count: 'மொத்த வரிகள்:',
13204           sum: 'மொத்தம்: ',
13205           avg: 'சராசரி: ',
13206           min: 'குறைந்தபட்ச: ',
13207           max: 'அதிகபட்ச: '
13208         },
13209         pinning: {
13210          pinLeft: 'இடதுபுறமாக தைக்க ',
13211           pinRight: 'வலதுபுறமாக தைக்க',
13212           unpin: 'பிரி'
13213         },
13214         gridMenu: {
13215           columns: 'பத்திகள்:',
13216           importerTitle: 'கோப்பு : படித்தல்',
13217           exporterAllAsCsv: 'எல்லா தரவுகளையும் கோப்பாக்கு: csv',
13218           exporterVisibleAsCsv: 'இருக்கும் தரவுகளை கோப்பாக்கு: csv',
13219           exporterSelectedAsCsv: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: csv',
13220           exporterAllAsPdf: 'எல்லா தரவுகளையும் கோப்பாக்கு: pdf',
13221           exporterVisibleAsPdf: 'இருக்கும் தரவுகளை கோப்பாக்கு: pdf',
13222           exporterSelectedAsPdf: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: pdf',
13223           clearAllFilters: 'Clear all filters'
13224         },
13225         importer: {
13226           noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
13227           noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
13228           invalidCsv:   'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
13229           invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
13230           jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
13231         },
13232         pagination: {
13233           sizes         : 'உருப்படிகள் / பக்கம்',
13234           totalItems    : 'உருப்படிகள் '
13235         },
13236         grouping: {
13237           group : 'குழு',
13238           ungroup : 'பிரி',
13239           aggregate_count       : 'மதிப்பீட்டு : எண்ணு',
13240           aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
13241           aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
13242           aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
13243           aggregate_avg : 'மதிப்பீட்டு : சராசரி',
13244           aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
13245         }
13246       });
13247       return $delegate;
13248     }]);
13249   }]);
13250 })();
13251
13252 /**
13253  * @ngdoc overview
13254  * @name ui.grid.i18n
13255  * @description
13256  *
13257  *  # ui.grid.i18n
13258  * This module provides i18n functions to ui.grid and any application that wants to use it
13259
13260  *
13261  * <div doc-module-components="ui.grid.i18n"></div>
13262  */
13263
13264 (function () {
13265   var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
13266   var FILTER_ALIASES = ['t', 'uiTranslate'];
13267
13268   var module = angular.module('ui.grid.i18n');
13269
13270
13271   /**
13272    *  @ngdoc object
13273    *  @name ui.grid.i18n.constant:i18nConstants
13274    *
13275    *  @description constants available in i18n module
13276    */
13277   module.constant('i18nConstants', {
13278     MISSING: '[MISSING]',
13279     UPDATE_EVENT: '$uiI18n',
13280
13281     LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
13282     // default to english
13283     DEFAULT_LANG: 'en'
13284   });
13285
13286 //    module.config(['$provide', function($provide) {
13287 //        $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
13288
13289   /**
13290    *  @ngdoc service
13291    *  @name ui.grid.i18n.service:i18nService
13292    *
13293    *  @description Services for i18n
13294    */
13295   module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
13296     function ($log, i18nConstants, $rootScope) {
13297
13298       var langCache = {
13299         _langs: {},
13300         current: null,
13301         get: function (lang) {
13302           return this._langs[lang.toLowerCase()];
13303         },
13304         add: function (lang, strings) {
13305           var lower = lang.toLowerCase();
13306           if (!this._langs[lower]) {
13307             this._langs[lower] = {};
13308           }
13309           angular.extend(this._langs[lower], strings);
13310         },
13311         getAllLangs: function () {
13312           var langs = [];
13313           if (!this._langs) {
13314             return langs;
13315           }
13316
13317           for (var key in this._langs) {
13318             langs.push(key);
13319           }
13320
13321           return langs;
13322         },
13323         setCurrent: function (lang) {
13324           this.current = lang.toLowerCase();
13325         },
13326         getCurrentLang: function () {
13327           return this.current;
13328         }
13329       };
13330
13331       var service = {
13332
13333         /**
13334          * @ngdoc service
13335          * @name add
13336          * @methodOf ui.grid.i18n.service:i18nService
13337          * @description  Adds the languages and strings to the cache. Decorate this service to
13338          * add more translation strings
13339          * @param {string} lang language to add
13340          * @param {object} stringMaps of strings to add grouped by property names
13341          * @example
13342          * <pre>
13343          *      i18nService.add('en', {
13344          *         aggregate: {
13345          *                 label1: 'items',
13346          *                 label2: 'some more items'
13347          *                 }
13348          *         },
13349          *         groupPanel: {
13350          *              description: 'Drag a column header here and drop it to group by that column.'
13351          *           }
13352          *      }
13353          * </pre>
13354          */
13355         add: function (langs, stringMaps) {
13356           if (typeof(langs) === 'object') {
13357             angular.forEach(langs, function (lang) {
13358               if (lang) {
13359                 langCache.add(lang, stringMaps);
13360               }
13361             });
13362           } else {
13363             langCache.add(langs, stringMaps);
13364           }
13365         },
13366
13367         /**
13368          * @ngdoc service
13369          * @name getAllLangs
13370          * @methodOf ui.grid.i18n.service:i18nService
13371          * @description  return all currently loaded languages
13372          * @returns {array} string
13373          */
13374         getAllLangs: function () {
13375           return langCache.getAllLangs();
13376         },
13377
13378         /**
13379          * @ngdoc service
13380          * @name get
13381          * @methodOf ui.grid.i18n.service:i18nService
13382          * @description  return all currently loaded languages
13383          * @param {string} lang to return.  If not specified, returns current language
13384          * @returns {object} the translation string maps for the language
13385          */
13386         get: function (lang) {
13387           var language = lang ? lang : service.getCurrentLang();
13388           return langCache.get(language);
13389         },
13390
13391         /**
13392          * @ngdoc service
13393          * @name getSafeText
13394          * @methodOf ui.grid.i18n.service:i18nService
13395          * @description  returns the text specified in the path or a Missing text if text is not found
13396          * @param {string} path property path to use for retrieving text from string map
13397          * @param {string} lang to return.  If not specified, returns current language
13398          * @returns {object} the translation for the path
13399          * @example
13400          * <pre>
13401          * i18nService.getSafeText('sort.ascending')
13402          * </pre>
13403          */
13404         getSafeText: function (path, lang) {
13405           var language = lang ? lang : service.getCurrentLang();
13406           var trans = langCache.get(language);
13407
13408           if (!trans) {
13409             return i18nConstants.MISSING;
13410           }
13411
13412           var paths = path.split('.');
13413           var current = trans;
13414
13415           for (var i = 0; i < paths.length; ++i) {
13416             if (current[paths[i]] === undefined || current[paths[i]] === null) {
13417               return i18nConstants.MISSING;
13418             } else {
13419               current = current[paths[i]];
13420             }
13421           }
13422
13423           return current;
13424
13425         },
13426
13427         /**
13428          * @ngdoc service
13429          * @name setCurrentLang
13430          * @methodOf ui.grid.i18n.service:i18nService
13431          * @description sets the current language to use in the application
13432          * $broadcasts the Update_Event on the $rootScope
13433          * @param {string} lang to set
13434          * @example
13435          * <pre>
13436          * i18nService.setCurrentLang('fr');
13437          * </pre>
13438          */
13439
13440         setCurrentLang: function (lang) {
13441           if (lang) {
13442             langCache.setCurrent(lang);
13443             $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
13444           }
13445         },
13446
13447         /**
13448          * @ngdoc service
13449          * @name getCurrentLang
13450          * @methodOf ui.grid.i18n.service:i18nService
13451          * @description returns the current language used in the application
13452          */
13453         getCurrentLang: function () {
13454           var lang = langCache.getCurrentLang();
13455           if (!lang) {
13456             lang = i18nConstants.DEFAULT_LANG;
13457             langCache.setCurrent(lang);
13458           }
13459           return lang;
13460         }
13461
13462       };
13463
13464       return service;
13465
13466     }]);
13467
13468   var localeDirective = function (i18nService, i18nConstants) {
13469     return {
13470       compile: function () {
13471         return {
13472           pre: function ($scope, $elm, $attrs) {
13473             var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
13474             // check for watchable property
13475             var lang = $scope.$eval($attrs[alias]);
13476             if (lang) {
13477               $scope.$watch($attrs[alias], function () {
13478                 i18nService.setCurrentLang(lang);
13479               });
13480             } else if ($attrs.$$observers) {
13481               $attrs.$observe(alias, function () {
13482                 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
13483               });
13484             }
13485           }
13486         };
13487       }
13488     };
13489   };
13490
13491   module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
13492
13493   // directive syntax
13494   var uitDirective = function ($parse, i18nService, i18nConstants) {
13495     return {
13496       restrict: 'EA',
13497       compile: function () {
13498         return {
13499           pre: function ($scope, $elm, $attrs) {
13500             var alias1 = DIRECTIVE_ALIASES[0],
13501               alias2 = DIRECTIVE_ALIASES[1];
13502             var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
13503             var missing = i18nConstants.MISSING + token;
13504             var observer;
13505             if ($attrs.$$observers) {
13506               var prop = $attrs[alias1] ? alias1 : alias2;
13507               observer = $attrs.$observe(prop, function (result) {
13508                 if (result) {
13509                   $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
13510                 }
13511               });
13512             }
13513             var getter = $parse(token);
13514             var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
13515               if (observer) {
13516                 observer($attrs[alias1] || $attrs[alias2]);
13517               } else {
13518                 // set text based on i18n current language
13519                 $elm.html(getter(i18nService.get()) || missing);
13520               }
13521             });
13522             $scope.$on('$destroy', listener);
13523
13524             $elm.html(getter(i18nService.get()) || missing);
13525           }
13526         };
13527       }
13528     };
13529   };
13530
13531   angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
13532     module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
13533   } );
13534
13535   // optional filter syntax
13536   var uitFilter = function ($parse, i18nService, i18nConstants) {
13537     return function (data) {
13538       var getter = $parse(data);
13539       // set text based on i18n current language
13540       return getter(i18nService.get()) || i18nConstants.MISSING + data;
13541     };
13542   };
13543
13544   angular.forEach( FILTER_ALIASES, function ( alias ) {
13545     module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
13546   } );
13547
13548
13549 })();
13550 (function() {
13551   angular.module('ui.grid').config(['$provide', function($provide) {
13552     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13553       $delegate.add('zh-cn', {
13554         headerCell: {
13555           aria: {
13556             defaultFilterLabel: '列过滤器',
13557             removeFilter: '移除过滤器',
13558             columnMenuButtonLabel: '列菜单'
13559           },
13560           priority: '优先级:',
13561           filterLabel: "列过滤器: "
13562         },
13563         aggregate: {
13564           label: '行'
13565         },
13566         groupPanel: {
13567           description: '拖曳表头到此处进行分组'
13568         },
13569         search: {
13570           placeholder: '查找',
13571           showingItems: '已显示行数:',
13572           selectedItems: '已选择行数:',
13573           totalItems: '总行数:',
13574           size: '每页显示行数:',
13575           first: '首页',
13576           next: '下一页',
13577           previous: '上一页',
13578           last: '末页'
13579         },
13580         menu: {
13581           text: '选择列:'
13582         },
13583         sort: {
13584           ascending: '升序',
13585           descending: '降序',
13586           none: '无序',
13587           remove: '取消排序'
13588         },
13589         column: {
13590           hide: '隐藏列'
13591         },
13592         aggregation: {
13593           count: '计数:',
13594           sum: '求和:',
13595           avg: '均值:',
13596           min: '最小值:',
13597           max: '最大值:'
13598         },
13599         pinning: {
13600           pinLeft: '左侧固定',
13601           pinRight: '右侧固定',
13602           unpin: '取消固定'
13603         },
13604         columnMenu: {
13605           close: '关闭'
13606         },
13607         gridMenu: {
13608           aria: {
13609             buttonLabel: '表格菜单'
13610           },
13611           columns: '列:',
13612           importerTitle: '导入文件',
13613           exporterAllAsCsv: '导出全部数据到CSV',
13614           exporterVisibleAsCsv: '导出可见数据到CSV',
13615           exporterSelectedAsCsv: '导出已选数据到CSV',
13616           exporterAllAsPdf: '导出全部数据到PDF',
13617           exporterVisibleAsPdf: '导出可见数据到PDF',
13618           exporterSelectedAsPdf: '导出已选数据到PDF',
13619           clearAllFilters: '清除所有过滤器'
13620         },
13621         importer: {
13622           noHeaders: '无法获取列名,确定文件包含表头?',
13623           noObjects: '无法获取数据,确定文件包含数据?',
13624           invalidCsv: '无法处理文件,确定是合法的CSV文件?',
13625           invalidJson: '无法处理文件,确定是合法的JSON文件?',
13626           jsonNotArray: '导入的文件不是JSON数组!'
13627         },
13628         pagination: {
13629           aria: {
13630             pageToFirst: '第一页',
13631             pageBack: '上一页',
13632             pageSelected: '当前页',
13633             pageForward: '下一页',
13634             pageToLast: '最后一页'
13635           },
13636           sizes: '行每页',
13637           totalItems: '行',
13638           through: '至',
13639           of: '共'
13640         },
13641         grouping: {
13642           group: '分组',
13643           ungroup: '取消分组',
13644           aggregate_count: '合计: 计数',
13645           aggregate_sum: '合计: 求和',
13646           aggregate_max: '合计: 最大',
13647           aggregate_min: '合计: 最小',
13648           aggregate_avg: '合计: 平均',
13649           aggregate_remove: '合计: 移除'
13650         }
13651       });
13652       return $delegate;
13653     }]);
13654   }]);
13655 })();
13656
13657 (function() {
13658   angular.module('ui.grid').config(['$provide', function($provide) {
13659     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13660       $delegate.add('zh-tw', {
13661         aggregate: {
13662           label: '行'
13663         },
13664         groupPanel: {
13665           description: '拖曳表頭到此處進行分組'
13666         },
13667         search: {
13668           placeholder: '查找',
13669           showingItems: '已顯示行數:',
13670           selectedItems: '已選擇行數:',
13671           totalItems: '總行數:',
13672           size: '每頁顯示行數:',
13673           first: '首頁',
13674           next: '下壹頁',
13675           previous: '上壹頁',
13676           last: '末頁'
13677         },
13678         menu: {
13679           text: '選擇列:'
13680         },
13681         sort: {
13682           ascending: '升序',
13683           descending: '降序',
13684           remove: '取消排序'
13685         },
13686         column: {
13687           hide: '隱藏列'
13688         },
13689         aggregation: {
13690           count: '計數:',
13691           sum: '求和:',
13692           avg: '均值:',
13693           min: '最小值:',
13694           max: '最大值:'
13695         },
13696         pinning: {
13697           pinLeft: '左側固定',
13698           pinRight: '右側固定',
13699           unpin: '取消固定'
13700         },
13701         gridMenu: {
13702           columns: '列:',
13703           importerTitle: '導入文件',
13704           exporterAllAsCsv: '導出全部數據到CSV',
13705           exporterVisibleAsCsv: '導出可見數據到CSV',
13706           exporterSelectedAsCsv: '導出已選數據到CSV',
13707           exporterAllAsPdf: '導出全部數據到PDF',
13708           exporterVisibleAsPdf: '導出可見數據到PDF',
13709           exporterSelectedAsPdf: '導出已選數據到PDF',
13710           clearAllFilters: '清除所有过滤器'
13711         },
13712         importer: {
13713           noHeaders: '無法獲取列名,確定文件包含表頭?',
13714           noObjects: '無法獲取數據,確定文件包含數據?',
13715           invalidCsv: '無法處理文件,確定是合法的CSV文件?',
13716           invalidJson: '無法處理文件,確定是合法的JSON文件?',
13717           jsonNotArray: '導入的文件不是JSON數組!'
13718         },
13719         pagination: {
13720           sizes: '行每頁',
13721           totalItems: '行'
13722         }
13723       });
13724       return $delegate;
13725     }]);
13726   }]);
13727 })();
13728
13729 (function() {
13730   'use strict';
13731   /**
13732    *  @ngdoc overview
13733    *  @name ui.grid.autoResize
13734    *
13735    *  @description
13736    *
13737    *  #ui.grid.autoResize
13738    *
13739    *  <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
13740    *
13741    *  This module provides auto-resizing functionality to UI-Grid.
13742    */
13743   var module = angular.module('ui.grid.autoResize', ['ui.grid']);
13744
13745
13746   module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
13747     return {
13748       require: 'uiGrid',
13749       scope: false,
13750       link: function ($scope, $elm, $attrs, uiGridCtrl) {
13751         var prevGridWidth, prevGridHeight;
13752
13753         function getDimensions() {
13754           prevGridHeight = gridUtil.elementHeight($elm);
13755           prevGridWidth = gridUtil.elementWidth($elm);
13756         }
13757
13758         // Initialize the dimensions
13759         getDimensions();
13760
13761         var resizeTimeoutId;
13762         function startTimeout() {
13763           clearTimeout(resizeTimeoutId);
13764
13765           resizeTimeoutId = setTimeout(function () {
13766             var newGridHeight = gridUtil.elementHeight($elm);
13767             var newGridWidth = gridUtil.elementWidth($elm);
13768
13769             if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
13770               uiGridCtrl.grid.gridHeight = newGridHeight;
13771               uiGridCtrl.grid.gridWidth = newGridWidth;
13772
13773               $scope.$apply(function () {
13774                 uiGridCtrl.grid.refresh()
13775                   .then(function () {
13776                     getDimensions();
13777
13778                     startTimeout();
13779                   });
13780               });
13781             }
13782             else {
13783               startTimeout();
13784             }
13785           }, 250);
13786         }
13787
13788         startTimeout();
13789
13790         $scope.$on('$destroy', function() {
13791           clearTimeout(resizeTimeoutId);
13792         });
13793       }
13794     };
13795   }]);
13796 })();
13797
13798 (function () {
13799   'use strict';
13800
13801   /**
13802    *  @ngdoc overview
13803    *  @name ui.grid.cellNav
13804    *
13805    *  @description
13806
13807       #ui.grid.cellNav
13808
13809       <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
13810
13811       This module provides auto-resizing functionality to UI-Grid.
13812    */
13813   var module = angular.module('ui.grid.cellNav', ['ui.grid']);
13814
13815   /**
13816    *  @ngdoc object
13817    *  @name ui.grid.cellNav.constant:uiGridCellNavConstants
13818    *
13819    *  @description constants available in cellNav
13820    */
13821   module.constant('uiGridCellNavConstants', {
13822     FEATURE_NAME: 'gridCellNav',
13823     CELL_NAV_EVENT: 'cellNav',
13824     direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
13825     EVENT_TYPE: {
13826       KEYDOWN: 0,
13827       CLICK: 1,
13828       CLEAR: 2
13829     }
13830   });
13831
13832
13833   module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
13834     function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
13835       /**
13836        *  @ngdoc object
13837        *  @name ui.grid.cellNav.object:CellNav
13838        *  @description returns a CellNav prototype function
13839        *  @param {object} rowContainer container for rows
13840        *  @param {object} colContainer parent column container
13841        *  @param {object} leftColContainer column container to the left of parent
13842        *  @param {object} rightColContainer column container to the right of parent
13843        */
13844       var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
13845         this.rows = rowContainer.visibleRowCache;
13846         this.columns = colContainer.visibleColumnCache;
13847         this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
13848         this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
13849         this.bodyContainer = rowContainer;
13850       };
13851
13852       /** returns focusable columns of all containers */
13853       UiGridCellNav.prototype.getFocusableCols = function () {
13854         var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
13855
13856         return allColumns.filter(function (col) {
13857           return col.colDef.allowCellFocus;
13858         });
13859       };
13860
13861       /**
13862        *  @ngdoc object
13863        *  @name ui.grid.cellNav.api:GridRow
13864        *
13865        *  @description GridRow settings for cellNav feature, these are available to be
13866        *  set only internally (for example, by other features)
13867        */
13868
13869       /**
13870        *  @ngdoc object
13871        *  @name allowCellFocus
13872        *  @propertyOf  ui.grid.cellNav.api:GridRow
13873        *  @description Enable focus on a cell within this row.  If set to false then no cells
13874        *  in this row can be focused - group header rows as an example would set this to false.
13875        *  <br/>Defaults to true
13876        */
13877       /** returns focusable rows */
13878       UiGridCellNav.prototype.getFocusableRows = function () {
13879         return this.rows.filter(function(row) {
13880           return row.allowCellFocus !== false;
13881         });
13882       };
13883
13884       UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
13885         switch (direction) {
13886           case uiGridCellNavConstants.direction.LEFT:
13887             return this.getRowColLeft(curRow, curCol);
13888           case uiGridCellNavConstants.direction.RIGHT:
13889             return this.getRowColRight(curRow, curCol);
13890           case uiGridCellNavConstants.direction.UP:
13891             return this.getRowColUp(curRow, curCol);
13892           case uiGridCellNavConstants.direction.DOWN:
13893             return this.getRowColDown(curRow, curCol);
13894           case uiGridCellNavConstants.direction.PG_UP:
13895             return this.getRowColPageUp(curRow, curCol);
13896           case uiGridCellNavConstants.direction.PG_DOWN:
13897             return this.getRowColPageDown(curRow, curCol);
13898         }
13899
13900       };
13901
13902       UiGridCellNav.prototype.initializeSelection = function () {
13903         var focusableCols = this.getFocusableCols();
13904         var focusableRows = this.getFocusableRows();
13905         if (focusableCols.length === 0 || focusableRows.length === 0) {
13906           return null;
13907         }
13908
13909         var curRowIndex = 0;
13910         var curColIndex = 0;
13911         return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
13912       };
13913
13914       UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
13915         var focusableCols = this.getFocusableCols();
13916         var focusableRows = this.getFocusableRows();
13917         var curColIndex = focusableCols.indexOf(curCol);
13918         var curRowIndex = focusableRows.indexOf(curRow);
13919
13920         //could not find column in focusable Columns so set it to 1
13921         if (curColIndex === -1) {
13922           curColIndex = 1;
13923         }
13924
13925         var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
13926
13927         //get column to left
13928         if (nextColIndex > curColIndex) {
13929           // On the first row
13930           // if (curRowIndex === 0 && curColIndex === 0) {
13931           //   return null;
13932           // }
13933           if (curRowIndex === 0) {
13934             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
13935           }
13936           else {
13937             //up one row and far right column
13938             return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
13939           }
13940         }
13941         else {
13942           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
13943         }
13944       };
13945
13946
13947
13948       UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
13949         var focusableCols = this.getFocusableCols();
13950         var focusableRows = this.getFocusableRows();
13951         var curColIndex = focusableCols.indexOf(curCol);
13952         var curRowIndex = focusableRows.indexOf(curRow);
13953
13954         //could not find column in focusable Columns so set it to 0
13955         if (curColIndex === -1) {
13956           curColIndex = 0;
13957         }
13958         var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
13959
13960         if (nextColIndex < curColIndex) {
13961           if (curRowIndex === focusableRows.length - 1) {
13962             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
13963           }
13964           else {
13965             //down one row and far left column
13966             return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
13967           }
13968         }
13969         else {
13970           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
13971         }
13972       };
13973
13974       UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
13975         var focusableCols = this.getFocusableCols();
13976         var focusableRows = this.getFocusableRows();
13977         var curColIndex = focusableCols.indexOf(curCol);
13978         var curRowIndex = focusableRows.indexOf(curRow);
13979
13980         //could not find column in focusable Columns so set it to 0
13981         if (curColIndex === -1) {
13982           curColIndex = 0;
13983         }
13984
13985         if (curRowIndex === focusableRows.length - 1) {
13986           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
13987         }
13988         else {
13989           //down one row
13990           return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
13991         }
13992       };
13993
13994       UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
13995         var focusableCols = this.getFocusableCols();
13996         var focusableRows = this.getFocusableRows();
13997         var curColIndex = focusableCols.indexOf(curCol);
13998         var curRowIndex = focusableRows.indexOf(curRow);
13999
14000         //could not find column in focusable Columns so set it to 0
14001         if (curColIndex === -1) {
14002           curColIndex = 0;
14003         }
14004
14005         var pageSize = this.bodyContainer.minRowsToRender();
14006         if (curRowIndex >= focusableRows.length - pageSize) {
14007           return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
14008         }
14009         else {
14010           //down one page
14011           return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
14012         }
14013       };
14014
14015       UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
14016         var focusableCols = this.getFocusableCols();
14017         var focusableRows = this.getFocusableRows();
14018         var curColIndex = focusableCols.indexOf(curCol);
14019         var curRowIndex = focusableRows.indexOf(curRow);
14020
14021         //could not find column in focusable Columns so set it to 0
14022         if (curColIndex === -1) {
14023           curColIndex = 0;
14024         }
14025
14026         if (curRowIndex === 0) {
14027           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14028         }
14029         else {
14030           //up one row
14031           return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
14032         }
14033       };
14034
14035       UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
14036         var focusableCols = this.getFocusableCols();
14037         var focusableRows = this.getFocusableRows();
14038         var curColIndex = focusableCols.indexOf(curCol);
14039         var curRowIndex = focusableRows.indexOf(curRow);
14040
14041         //could not find column in focusable Columns so set it to 0
14042         if (curColIndex === -1) {
14043           curColIndex = 0;
14044         }
14045
14046         var pageSize = this.bodyContainer.minRowsToRender();
14047         if (curRowIndex - pageSize < 0) {
14048           return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
14049         }
14050         else {
14051           //up one page
14052           return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
14053         }
14054       };
14055       return UiGridCellNav;
14056     }]);
14057
14058   /**
14059    *  @ngdoc service
14060    *  @name ui.grid.cellNav.service:uiGridCellNavService
14061    *
14062    *  @description Services for cell navigation features. If you don't like the key maps we use,
14063    *  or the direction cells navigation, override with a service decorator (see angular docs)
14064    */
14065   module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
14066     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
14067
14068       var service = {
14069
14070         initializeGrid: function (grid) {
14071           grid.registerColumnBuilder(service.cellNavColumnBuilder);
14072
14073
14074           /**
14075            *  @ngdoc object
14076            *  @name ui.grid.cellNav:Grid.cellNav
14077            * @description cellNav properties added to grid class
14078            */
14079           grid.cellNav = {};
14080           grid.cellNav.lastRowCol = null;
14081           grid.cellNav.focusedCells = [];
14082
14083           service.defaultGridOptions(grid.options);
14084
14085           /**
14086            *  @ngdoc object
14087            *  @name ui.grid.cellNav.api:PublicApi
14088            *
14089            *  @description Public Api for cellNav feature
14090            */
14091           var publicApi = {
14092             events: {
14093               cellNav: {
14094                 /**
14095                  * @ngdoc event
14096                  * @name navigate
14097                  * @eventOf  ui.grid.cellNav.api:PublicApi
14098                  * @description raised when the active cell is changed
14099                  * <pre>
14100                  *      gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
14101                  * </pre>
14102                  * @param {object} newRowCol new position
14103                  * @param {object} oldRowCol old position
14104                  */
14105                 navigate: function (newRowCol, oldRowCol) {},
14106                 /**
14107                  * @ngdoc event
14108                  * @name viewPortKeyDown
14109                  * @eventOf  ui.grid.cellNav.api:PublicApi
14110                  * @description  is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
14111                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14112                  * event whenever you need a keydown event on a cell
14113                  * <br/>
14114                  * @param {object} event keydown event
14115                  * @param {object} rowCol current rowCol position
14116                  */
14117                 viewPortKeyDown: function (event, rowCol) {},
14118
14119                 /**
14120                  * @ngdoc event
14121                  * @name viewPortKeyPress
14122                  * @eventOf  ui.grid.cellNav.api:PublicApi
14123                  * @description  is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
14124                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14125                  * event whenever you need a keypress event on a cell
14126                  * <br/>
14127                  * @param {object} event keypress event
14128                  * @param {object} rowCol current rowCol position
14129                  */
14130                 viewPortKeyPress: function (event, rowCol) {}
14131               }
14132             },
14133             methods: {
14134               cellNav: {
14135                 /**
14136                  * @ngdoc function
14137                  * @name scrollToFocus
14138                  * @methodOf  ui.grid.cellNav.api:PublicApi
14139                  * @description brings the specified row and column into view, and sets focus
14140                  * to that cell
14141                  * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
14142                  * @param {object} colDef to make visible and set focus
14143                  * @returns {promise} a promise that is resolved after any scrolling is finished
14144                  */
14145                 scrollToFocus: function (rowEntity, colDef) {
14146                   return service.scrollToFocus(grid, rowEntity, colDef);
14147                 },
14148
14149                 /**
14150                  * @ngdoc function
14151                  * @name getFocusedCell
14152                  * @methodOf  ui.grid.cellNav.api:PublicApi
14153                  * @description returns the current (or last if Grid does not have focus) focused row and column
14154                  * <br> value is null if no selection has occurred
14155                  */
14156                 getFocusedCell: function () {
14157                   return grid.cellNav.lastRowCol;
14158                 },
14159
14160                 /**
14161                  * @ngdoc function
14162                  * @name getCurrentSelection
14163                  * @methodOf  ui.grid.cellNav.api:PublicApi
14164                  * @description returns an array containing the current selection
14165                  * <br> array is empty if no selection has occurred
14166                  */
14167                 getCurrentSelection: function () {
14168                   return grid.cellNav.focusedCells;
14169                 },
14170
14171                 /**
14172                  * @ngdoc function
14173                  * @name rowColSelectIndex
14174                  * @methodOf  ui.grid.cellNav.api:PublicApi
14175                  * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
14176                  * isn't selected
14177                  * @param {object} rowCol the rowCol to evaluate
14178                  */
14179                 rowColSelectIndex: function (rowCol) {
14180                   //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
14181                   var index = -1;
14182                   for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
14183                     if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
14184                       grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
14185                       index = i;
14186                       break;
14187                     }
14188                   }
14189                   return index;
14190                 }
14191               }
14192             }
14193           };
14194
14195           grid.api.registerEventsFromObject(publicApi.events);
14196
14197           grid.api.registerMethodsFromObject(publicApi.methods);
14198
14199         },
14200
14201         defaultGridOptions: function (gridOptions) {
14202           /**
14203            *  @ngdoc object
14204            *  @name ui.grid.cellNav.api:GridOptions
14205            *
14206            *  @description GridOptions for cellNav feature, these are available to be
14207            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14208            */
14209
14210           /**
14211            *  @ngdoc object
14212            *  @name modifierKeysToMultiSelectCells
14213            *  @propertyOf  ui.grid.cellNav.api:GridOptions
14214            *  @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
14215            *  <br/>Defaults to false
14216            */
14217           gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
14218
14219         },
14220
14221         /**
14222          * @ngdoc service
14223          * @name decorateRenderContainers
14224          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14225          * @description  decorates grid renderContainers with cellNav functions
14226          */
14227         decorateRenderContainers: function (grid) {
14228
14229           var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
14230           var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
14231
14232           if (leftContainer !== null) {
14233             grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
14234           }
14235           if (rightContainer !== null) {
14236             grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
14237           }
14238
14239           grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
14240         },
14241
14242         /**
14243          * @ngdoc service
14244          * @name getDirection
14245          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14246          * @description  determines which direction to for a given keyDown event
14247          * @returns {uiGridCellNavConstants.direction} direction
14248          */
14249         getDirection: function (evt) {
14250           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
14251             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
14252             return uiGridCellNavConstants.direction.LEFT;
14253           }
14254           if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
14255             evt.keyCode === uiGridConstants.keymap.TAB) {
14256             return uiGridCellNavConstants.direction.RIGHT;
14257           }
14258
14259           if (evt.keyCode === uiGridConstants.keymap.UP ||
14260             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
14261             return uiGridCellNavConstants.direction.UP;
14262           }
14263
14264           if (evt.keyCode === uiGridConstants.keymap.PG_UP){
14265             return uiGridCellNavConstants.direction.PG_UP;
14266           }
14267
14268           if (evt.keyCode === uiGridConstants.keymap.DOWN ||
14269             evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
14270             return uiGridCellNavConstants.direction.DOWN;
14271           }
14272
14273           if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
14274             return uiGridCellNavConstants.direction.PG_DOWN;
14275           }
14276
14277           return null;
14278         },
14279
14280         /**
14281          * @ngdoc service
14282          * @name cellNavColumnBuilder
14283          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14284          * @description columnBuilder function that adds cell navigation properties to grid column
14285          * @returns {promise} promise that will load any needed templates when resolved
14286          */
14287         cellNavColumnBuilder: function (colDef, col, gridOptions) {
14288           var promises = [];
14289
14290           /**
14291            *  @ngdoc object
14292            *  @name ui.grid.cellNav.api:ColumnDef
14293            *
14294            *  @description Column Definitions for cellNav feature, these are available to be
14295            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
14296            */
14297
14298           /**
14299            *  @ngdoc object
14300            *  @name allowCellFocus
14301            *  @propertyOf  ui.grid.cellNav.api:ColumnDef
14302            *  @description Enable focus on a cell within this column.
14303            *  <br/>Defaults to true
14304            */
14305           colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
14306
14307           return $q.all(promises);
14308         },
14309
14310         /**
14311          * @ngdoc method
14312          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14313          * @name scrollToFocus
14314          * @description Scroll the grid such that the specified
14315          * row and column is in view, and set focus to the cell in that row and column
14316          * @param {Grid} grid the grid you'd like to act upon, usually available
14317          * from gridApi.grid
14318          * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
14319          * @param {object} colDef to make visible and set focus to
14320          * @returns {promise} a promise that is resolved after any scrolling is finished
14321          */
14322         scrollToFocus: function (grid, rowEntity, colDef) {
14323           var gridRow = null, gridCol = null;
14324
14325           if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
14326             gridRow = grid.getRow(rowEntity);
14327           }
14328
14329           if (typeof(colDef) !== 'undefined' && colDef !== null) {
14330             gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
14331           }
14332           return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
14333             var rowCol = { row: gridRow, col: gridCol };
14334
14335             // Broadcast the navigation
14336             if (gridRow !== null && gridCol !== null) {
14337               grid.cellNav.broadcastCellNav(rowCol);
14338             }
14339           });
14340
14341
14342
14343         },
14344
14345
14346         /**
14347          * @ngdoc method
14348          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14349          * @name getLeftWidth
14350          * @description Get the current drawn width of the columns in the
14351          * grid up to the numbered column, and add an apportionment for the
14352          * column that we're on.  So if we are on column 0, we want to scroll
14353          * 0% (i.e. exclude this column from calc).  If we're on the last column
14354          * we want to scroll to 100% (i.e. include this column in the calc). So
14355          * we include (thisColIndex / totalNumberCols) % of this column width
14356          * @param {Grid} grid the grid you'd like to act upon, usually available
14357          * from gridApi.grid
14358          * @param {gridCol} upToCol the column to total up to and including
14359          */
14360         getLeftWidth: function (grid, upToCol) {
14361           var width = 0;
14362
14363           if (!upToCol) {
14364             return width;
14365           }
14366
14367           var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
14368
14369           // total column widths up-to but not including the passed in column
14370           grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
14371             if ( index < lastIndex ){
14372               width += col.drawnWidth;
14373             }
14374           });
14375
14376           // pro-rata the final column based on % of total columns.
14377           var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
14378           width += upToCol.drawnWidth * percentage;
14379
14380           return width;
14381         }
14382       };
14383
14384       return service;
14385     }]);
14386
14387   /**
14388    *  @ngdoc directive
14389    *  @name ui.grid.cellNav.directive:uiCellNav
14390    *  @element div
14391    *  @restrict EA
14392    *
14393    *  @description Adds cell navigation features to the grid columns
14394    *
14395    *  @example
14396    <example module="app">
14397    <file name="app.js">
14398    var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
14399
14400    app.controller('MainCtrl', ['$scope', function ($scope) {
14401       $scope.data = [
14402         { name: 'Bob', title: 'CEO' },
14403             { name: 'Frank', title: 'Lowly Developer' }
14404       ];
14405
14406       $scope.columnDefs = [
14407         {name: 'name'},
14408         {name: 'title'}
14409       ];
14410     }]);
14411    </file>
14412    <file name="index.html">
14413    <div ng-controller="MainCtrl">
14414    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
14415    </div>
14416    </file>
14417    </example>
14418    */
14419   module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
14420     function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
14421       return {
14422         replace: true,
14423         priority: -150,
14424         require: '^uiGrid',
14425         scope: false,
14426         controller: function () {},
14427         compile: function () {
14428           return {
14429             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14430               var _scope = $scope;
14431
14432               var grid = uiGridCtrl.grid;
14433               uiGridCellNavService.initializeGrid(grid);
14434
14435               uiGridCtrl.cellNav = {};
14436
14437               //Ensure that the object has all of the methods we expect it to
14438               uiGridCtrl.cellNav.makeRowCol = function (obj) {
14439                 if (!(obj instanceof GridRowColumn)) {
14440                   obj = new GridRowColumn(obj.row, obj.col);
14441                 }
14442                 return obj;
14443               };
14444
14445               uiGridCtrl.cellNav.getActiveCell = function () {
14446                 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
14447                 if (elms.length > 0){
14448                   return elms[0];
14449                 }
14450
14451                 return undefined;
14452               };
14453
14454               uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
14455                 modifierDown = !(modifierDown === undefined || !modifierDown);
14456
14457                 newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
14458
14459                 uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
14460                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
14461               };
14462
14463               uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
14464                 grid.cellNav.focusedCells = [];
14465                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
14466               };
14467
14468               uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
14469                 modifierDown = !(modifierDown === undefined || !modifierDown);
14470
14471                 rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
14472
14473                 var row = rowCol.row,
14474                   col = rowCol.col;
14475
14476                 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
14477
14478                 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
14479                   var newRowCol = new GridRowColumn(row, col);
14480
14481                   grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
14482                   grid.cellNav.lastRowCol = newRowCol;
14483                   if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
14484                     grid.cellNav.focusedCells.push(rowCol);
14485                   } else {
14486                     grid.cellNav.focusedCells = [rowCol];
14487                   }
14488                 } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
14489                   rowColSelectIndex >= 0) {
14490
14491                   grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
14492                 }
14493               };
14494
14495               uiGridCtrl.cellNav.handleKeyDown = function (evt) {
14496                 var direction = uiGridCellNavService.getDirection(evt);
14497                 if (direction === null) {
14498                   return null;
14499                 }
14500
14501                 var containerId = 'body';
14502                 if (evt.uiGridTargetRenderContainerId) {
14503                   containerId = evt.uiGridTargetRenderContainerId;
14504                 }
14505
14506                 // Get the last-focused row+col combo
14507                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14508                 if (lastRowCol) {
14509                   // Figure out which new row+combo we're navigating to
14510                   var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
14511                   var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
14512                   var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
14513                   // Shift+tab on top-left cell should exit cellnav on render container
14514                   if (
14515                     // Navigating left
14516                     direction === uiGridCellNavConstants.direction.LEFT &&
14517                     // New col is last col (i.e. wrap around)
14518                     rowCol.col === focusableCols[focusableCols.length - 1] &&
14519                     // Staying on same row, which means we're at first row
14520                     rowCol.row === lastRowCol.row &&
14521                     evt.keyCode === uiGridConstants.keymap.TAB &&
14522                     evt.shiftKey
14523                   ) {
14524                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
14525                     uiGridCtrl.cellNav.clearFocus();
14526                     return true;
14527                   }
14528                   // Tab on bottom-right cell should exit cellnav on render container
14529                   else if (
14530                     direction === uiGridCellNavConstants.direction.RIGHT &&
14531                     // New col is first col (i.e. wrap around)
14532                     rowCol.col === focusableCols[0] &&
14533                     // Staying on same row, which means we're at first row
14534                     rowCol.row === lastRowCol.row &&
14535                     evt.keyCode === uiGridConstants.keymap.TAB &&
14536                     !evt.shiftKey
14537                   ) {
14538                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
14539                     uiGridCtrl.cellNav.clearFocus();
14540                     return true;
14541                   }
14542
14543                   // Scroll to the new cell, if it's not completely visible within the render container's viewport
14544                   grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
14545                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
14546                   });
14547
14548
14549                   evt.stopPropagation();
14550                   evt.preventDefault();
14551
14552                   return false;
14553                 }
14554               };
14555             },
14556             post: function ($scope, $elm, $attrs, uiGridCtrl) {
14557               var _scope = $scope;
14558               var grid = uiGridCtrl.grid;
14559
14560               function addAriaLiveRegion(){
14561                 // Thanks to google docs for the inspiration behind how to do this
14562                 // XXX: Why is this entire mess nessasary?
14563                 // Because browsers take a lot of coercing to get them to read out live regions
14564                 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
14565                 var ariaNotifierDomElt = '<div ' +
14566                                            'id="' + grid.id +'-aria-speakable" ' +
14567                                            'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
14568                                            'aria-live="assertive" ' +
14569                                            'role="region" ' +
14570                                            'aria-atomic="true" ' +
14571                                            'aria-hidden="false" ' +
14572                                            'aria-relevant="additions" ' +
14573                                            '>' +
14574                                            '&nbsp;' +
14575                                          '</div>';
14576
14577                 var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
14578                 $elm.prepend(ariaNotifier);
14579                 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
14580                   /*
14581                    * If the cell nav event was because of a focus event then we don't want to
14582                    * change the notifier text.
14583                    * Reasoning: Voice Over fires a focus events when moving arround the grid.
14584                    * If the screen reader is handing the grid nav properly then we don't need to
14585                    * use the alert to notify the user of the movement.
14586                    * In all other cases we do want a notification event.
14587                    */
14588                   if (originEvt && originEvt.type === 'focus'){return;}
14589
14590                   function setNotifyText(text){
14591                     if (text === ariaNotifier.text()){return;}
14592                     ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
14593                     /*
14594                      * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
14595                      */
14596                     ariaNotifier[0].innerHTML = "";
14597                     ariaNotifier[0].style.visibility = 'hidden';
14598                     ariaNotifier[0].style.visibility = 'visible';
14599                     if (text !== ''){
14600                       ariaNotifier[0].style.clip = 'auto';
14601                       /*
14602                        * The space after the text is something that google docs does.
14603                        */
14604                       ariaNotifier[0].appendChild(document.createTextNode(text + " "));
14605                       ariaNotifier[0].style.visibility = 'hidden';
14606                       ariaNotifier[0].style.visibility = 'visible';
14607                     }
14608                   }
14609
14610                   var values = [];
14611                   var currentSelection = grid.api.cellNav.getCurrentSelection();
14612                   for (var i = 0; i < currentSelection.length; i++) {
14613                     values.push(currentSelection[i].getIntersectionValueFiltered());
14614                   }
14615                   var cellText = values.toString();
14616                   setNotifyText(cellText);
14617
14618                 });
14619               }
14620               addAriaLiveRegion();
14621             }
14622           };
14623         }
14624       };
14625     }]);
14626
14627   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
14628     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
14629       return {
14630         replace: true,
14631         priority: -99999, //this needs to run very last
14632         require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
14633         scope: false,
14634         compile: function () {
14635           return {
14636             post: function ($scope, $elm, $attrs, controllers) {
14637               var uiGridCtrl = controllers[0],
14638                  renderContainerCtrl = controllers[1],
14639                  uiGridCellnavCtrl = controllers[2];
14640
14641               // Skip attaching cell-nav specific logic if the directive is not attached above us
14642               if (!uiGridCtrl.grid.api.cellNav) { return; }
14643
14644               var containerId = renderContainerCtrl.containerId;
14645
14646               var grid = uiGridCtrl.grid;
14647
14648               //run each time a render container is created
14649               uiGridCellNavService.decorateRenderContainers(grid);
14650
14651               // focusser only created for body
14652               if (containerId !== 'body') {
14653                 return;
14654               }
14655
14656
14657
14658               if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
14659                 $elm.attr('aria-multiselectable', true);
14660               } else {
14661                 $elm.attr('aria-multiselectable', false);
14662               }
14663
14664               //add an element with no dimensions that can be used to set focus and capture keystrokes
14665               var focuser = $compile('<div class="ui-grid-focuser" role="region" aria-live="assertive" aria-atomic="false" tabindex="0" aria-controls="' + grid.id +'-aria-speakable '+ grid.id + '-grid-container' +'" aria-owns="' + grid.id + '-grid-container' + '"></div>')($scope);
14666               $elm.append(focuser);
14667
14668               focuser.on('focus', function (evt) {
14669                 evt.uiGridTargetRenderContainerId = containerId;
14670                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14671                 if (rowCol === null) {
14672                   rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
14673                   if (rowCol.row && rowCol.col) {
14674                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
14675                   }
14676                 }
14677               });
14678
14679               uiGridCellnavCtrl.setAriaActivedescendant = function(id){
14680                 $elm.attr('aria-activedescendant', id);
14681               };
14682
14683               uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
14684                 if ($elm.attr('aria-activedescendant') === id){
14685                   $elm.attr('aria-activedescendant', '');
14686                 }
14687               };
14688
14689
14690               uiGridCtrl.focus = function () {
14691                 gridUtil.focus.byElement(focuser[0]);
14692                 //allow for first time grid focus
14693               };
14694
14695               var viewPortKeyDownWasRaisedForRowCol = null;
14696               // Bind to keydown events in the render container
14697               focuser.on('keydown', function (evt) {
14698                 evt.uiGridTargetRenderContainerId = containerId;
14699                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14700                 var result = uiGridCtrl.cellNav.handleKeyDown(evt);
14701                 if (result === null) {
14702                   uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
14703                   viewPortKeyDownWasRaisedForRowCol = rowCol;
14704                 }
14705               });
14706               //Bind to keypress events in the render container
14707               //keypress events are needed by edit function so the key press
14708               //that initiated an edit is not lost
14709               //must fire the event in a timeout so the editor can
14710               //initialize and subscribe to the event on another event loop
14711               focuser.on('keypress', function (evt) {
14712                 if (viewPortKeyDownWasRaisedForRowCol) {
14713                   $timeout(function () {
14714                     uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
14715                   },4);
14716
14717                   viewPortKeyDownWasRaisedForRowCol = null;
14718                 }
14719               });
14720
14721               $scope.$on('$destroy', function(){
14722                 //Remove all event handlers associated with this focuser.
14723                 focuser.off();
14724               });
14725
14726             }
14727           };
14728         }
14729       };
14730     }]);
14731
14732   module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
14733     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
14734       return {
14735         replace: true,
14736         priority: -99999, //this needs to run very last
14737         require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
14738         scope: false,
14739         compile: function () {
14740           return {
14741             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14742             },
14743             post: function ($scope, $elm, $attrs, controllers) {
14744               var uiGridCtrl = controllers[0],
14745                 renderContainerCtrl = controllers[1];
14746
14747               // Skip attaching cell-nav specific logic if the directive is not attached above us
14748               if (!uiGridCtrl.grid.api.cellNav) { return; }
14749
14750               var containerId = renderContainerCtrl.containerId;
14751               //no need to process for other containers
14752               if (containerId !== 'body') {
14753                 return;
14754               }
14755
14756               var grid = uiGridCtrl.grid;
14757
14758               grid.api.core.on.scrollBegin($scope, function (args) {
14759
14760                 // Skip if there's no currently-focused cell
14761                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14762                 if (lastRowCol === null) {
14763                   return;
14764                 }
14765
14766                 //if not in my container, move on
14767                 //todo: worry about horiz scroll
14768                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
14769                   return;
14770                 }
14771
14772                 uiGridCtrl.cellNav.clearFocus();
14773
14774               });
14775
14776               grid.api.core.on.scrollEnd($scope, function (args) {
14777                 // Skip if there's no currently-focused cell
14778                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14779                 if (lastRowCol === null) {
14780                   return;
14781                 }
14782
14783                 //if not in my container, move on
14784                 //todo: worry about horiz scroll
14785                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
14786                   return;
14787                 }
14788
14789                 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
14790
14791               });
14792
14793               grid.api.cellNav.on.navigate($scope, function () {
14794                 //focus again because it can be lost
14795                  uiGridCtrl.focus();
14796               });
14797
14798             }
14799           };
14800         }
14801       };
14802     }]);
14803
14804   /**
14805    *  @ngdoc directive
14806    *  @name ui.grid.cellNav.directive:uiGridCell
14807    *  @element div
14808    *  @restrict A
14809    *  @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
14810    */
14811   module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
14812     function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
14813       return {
14814         priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
14815         restrict: 'A',
14816         require: ['^uiGrid', '?^uiGridCellnav'],
14817         scope: false,
14818         link: function ($scope, $elm, $attrs, controllers) {
14819           var uiGridCtrl = controllers[0],
14820               uiGridCellnavCtrl = controllers[1];
14821           // Skip attaching cell-nav specific logic if the directive is not attached above us
14822           if (!uiGridCtrl.grid.api.cellNav) { return; }
14823
14824           if (!$scope.col.colDef.allowCellFocus) {
14825             return;
14826           }
14827
14828           //Convinience local variables
14829           var grid = uiGridCtrl.grid;
14830           $scope.focused = false;
14831
14832           // Make this cell focusable but only with javascript/a mouse click
14833           $elm.attr('tabindex', -1);
14834
14835           // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
14836           $elm.find('div').on('click', function (evt) {
14837             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
14838
14839             evt.stopPropagation();
14840             $scope.$apply();
14841           });
14842
14843
14844           /*
14845            * XXX Hack for screen readers.
14846            * This allows the grid to focus using only the screen reader cursor.
14847            * Since the focus event doesn't include key press information we can't use it
14848            * as our primary source of the event.
14849            */
14850           $elm.on('mousedown', preventMouseDown);
14851
14852           //turn on and off for edit events
14853           if (uiGridCtrl.grid.api.edit) {
14854             uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
14855               $elm.off('mousedown', preventMouseDown);
14856             });
14857
14858             uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
14859               $elm.on('mousedown', preventMouseDown);
14860             });
14861
14862             uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
14863               $elm.on('mousedown', preventMouseDown);
14864             });
14865           }
14866
14867           function preventMouseDown(evt) {
14868             //Prevents the foucus event from firing if the click event is already going to fire.
14869             //If both events fire it will cause bouncing behavior.
14870             evt.preventDefault();
14871           }
14872
14873           //You can only focus on elements with a tabindex value
14874           $elm.on('focus', function (evt) {
14875             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
14876             evt.stopPropagation();
14877             $scope.$apply();
14878           });
14879
14880           // This event is fired for all cells.  If the cell matches, then focus is set
14881           $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown) {
14882             var isFocused = grid.cellNav.focusedCells.some(function(focusedRowCol, index){
14883               return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
14884             });
14885             if (isFocused){
14886               setFocused();
14887             } else {
14888               clearFocus();
14889             }
14890           });
14891
14892           function setFocused() {
14893             if (!$scope.focused){
14894               var div = $elm.find('div');
14895               div.addClass('ui-grid-cell-focus');
14896               $elm.attr('aria-selected', true);
14897               uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
14898               $scope.focused = true;
14899             }
14900           }
14901
14902           function clearFocus() {
14903             if ($scope.focused){
14904               var div = $elm.find('div');
14905               div.removeClass('ui-grid-cell-focus');
14906               $elm.attr('aria-selected', false);
14907               uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
14908               $scope.focused = false;
14909             }
14910           }
14911
14912           $scope.$on('$destroy', function () {
14913             //.off withouth paramaters removes all handlers
14914             $elm.find('div').off();
14915             $elm.off();
14916           });
14917         }
14918       };
14919     }]);
14920
14921 })();
14922
14923 (function () {
14924   'use strict';
14925
14926   /**
14927    * @ngdoc overview
14928    * @name ui.grid.edit
14929    * @description
14930    *
14931    * # ui.grid.edit
14932    *
14933    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
14934    *
14935    * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
14936    * a keyboard.
14937    * <br/>
14938    * <br/>
14939    * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
14940    * user to key data and then tab, arrow, or enter to the cells beside or below.
14941    *
14942    * <div doc-module-components="ui.grid.edit"></div>
14943    */
14944
14945   var module = angular.module('ui.grid.edit', ['ui.grid']);
14946
14947   /**
14948    *  @ngdoc object
14949    *  @name ui.grid.edit.constant:uiGridEditConstants
14950    *
14951    *  @description constants available in edit module
14952    */
14953   module.constant('uiGridEditConstants', {
14954     EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
14955     //must be lowercase because template bulder converts to lower
14956     EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
14957     events: {
14958       BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
14959       END_CELL_EDIT: 'uiGridEventEndCellEdit',
14960       CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
14961     }
14962   });
14963
14964   /**
14965    *  @ngdoc service
14966    *  @name ui.grid.edit.service:uiGridEditService
14967    *
14968    *  @description Services for editing features
14969    */
14970   module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
14971     function ($q, uiGridConstants, gridUtil) {
14972
14973       var service = {
14974
14975         initializeGrid: function (grid) {
14976
14977           service.defaultGridOptions(grid.options);
14978
14979           grid.registerColumnBuilder(service.editColumnBuilder);
14980           grid.edit = {};
14981
14982           /**
14983            *  @ngdoc object
14984            *  @name ui.grid.edit.api:PublicApi
14985            *
14986            *  @description Public Api for edit feature
14987            */
14988           var publicApi = {
14989             events: {
14990               edit: {
14991                 /**
14992                  * @ngdoc event
14993                  * @name afterCellEdit
14994                  * @eventOf  ui.grid.edit.api:PublicApi
14995                  * @description raised when cell editing is complete
14996                  * <pre>
14997                  *      gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
14998                  * </pre>
14999                  * @param {object} rowEntity the options.data element that was edited
15000                  * @param {object} colDef the column that was edited
15001                  * @param {object} newValue new value
15002                  * @param {object} oldValue old value
15003                  */
15004                 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
15005                 },
15006                 /**
15007                  * @ngdoc event
15008                  * @name beginCellEdit
15009                  * @eventOf  ui.grid.edit.api:PublicApi
15010                  * @description raised when cell editing starts on a cell
15011                  * <pre>
15012                  *      gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
15013                  * </pre>
15014                  * @param {object} rowEntity the options.data element that was edited
15015                  * @param {object} colDef the column that was edited
15016                  * @param {object} triggerEvent the event that triggered the edit.  Useful to prevent losing keystrokes on some
15017                  *                 complex editors
15018                  */
15019                 beginCellEdit: function (rowEntity, colDef, triggerEvent) {
15020                 },
15021                 /**
15022                  * @ngdoc event
15023                  * @name cancelCellEdit
15024                  * @eventOf  ui.grid.edit.api:PublicApi
15025                  * @description raised when cell editing is cancelled on a cell
15026                  * <pre>
15027                  *      gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
15028                  * </pre>
15029                  * @param {object} rowEntity the options.data element that was edited
15030                  * @param {object} colDef the column that was edited
15031                  */
15032                 cancelCellEdit: function (rowEntity, colDef) {
15033                 }
15034               }
15035             },
15036             methods: {
15037               edit: { }
15038             }
15039           };
15040
15041           grid.api.registerEventsFromObject(publicApi.events);
15042           //grid.api.registerMethodsFromObject(publicApi.methods);
15043
15044         },
15045
15046         defaultGridOptions: function (gridOptions) {
15047
15048           /**
15049            *  @ngdoc object
15050            *  @name ui.grid.edit.api:GridOptions
15051            *
15052            *  @description Options for configuring the edit feature, these are available to be
15053            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15054            */
15055
15056           /**
15057            *  @ngdoc object
15058            *  @name enableCellEdit
15059            *  @propertyOf  ui.grid.edit.api:GridOptions
15060            *  @description If defined, sets the default value for the editable flag on each individual colDefs
15061            *  if their individual enableCellEdit configuration is not defined. Defaults to undefined.
15062            */
15063
15064           /**
15065            *  @ngdoc object
15066            *  @name cellEditableCondition
15067            *  @propertyOf  ui.grid.edit.api:GridOptions
15068            *  @description If specified, either a value or function to be used by all columns before editing.
15069            *  If falsy, then editing of cell is not allowed.
15070            *  @example
15071            *  <pre>
15072            *  function($scope){
15073            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15074            *    return true;
15075            *  }
15076            *  </pre>
15077            */
15078           gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
15079
15080           /**
15081            *  @ngdoc object
15082            *  @name editableCellTemplate
15083            *  @propertyOf  ui.grid.edit.api:GridOptions
15084            *  @description If specified, cellTemplate to use as the editor for all columns.
15085            *  <br/> defaults to 'ui-grid/cellTextEditor'
15086            */
15087
15088           /**
15089            *  @ngdoc object
15090            *  @name enableCellEditOnFocus
15091            *  @propertyOf  ui.grid.edit.api:GridOptions
15092            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15093            *  <br/>_requires cellNav feature and the edit feature to be enabled_
15094            */
15095             //enableCellEditOnFocus can only be used if cellnav module is used
15096           gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
15097         },
15098
15099         /**
15100          * @ngdoc service
15101          * @name editColumnBuilder
15102          * @methodOf ui.grid.edit.service:uiGridEditService
15103          * @description columnBuilder function that adds edit properties to grid column
15104          * @returns {promise} promise that will load any needed templates when resolved
15105          */
15106         editColumnBuilder: function (colDef, col, gridOptions) {
15107
15108           var promises = [];
15109
15110           /**
15111            *  @ngdoc object
15112            *  @name ui.grid.edit.api:ColumnDef
15113            *
15114            *  @description Column Definition for edit feature, these are available to be
15115            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15116            */
15117
15118           /**
15119            *  @ngdoc object
15120            *  @name enableCellEdit
15121            *  @propertyOf  ui.grid.edit.api:ColumnDef
15122            *  @description enable editing on column
15123            */
15124           colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
15125             (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
15126
15127           /**
15128            *  @ngdoc object
15129            *  @name cellEditableCondition
15130            *  @propertyOf  ui.grid.edit.api:ColumnDef
15131            *  @description If specified, either a value or function evaluated before editing cell.  If falsy, then editing of cell is not allowed.
15132            *  @example
15133            *  <pre>
15134            *  function($scope){
15135            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15136            *    return true;
15137            *  }
15138            *  </pre>
15139            */
15140           colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition :  colDef.cellEditableCondition;
15141
15142           /**
15143            *  @ngdoc object
15144            *  @name editableCellTemplate
15145            *  @propertyOf  ui.grid.edit.api:ColumnDef
15146            *  @description cell template to be used when editing this column. Can be Url or text template
15147            *  <br/>Defaults to gridOptions.editableCellTemplate
15148            */
15149           if (colDef.enableCellEdit) {
15150             colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
15151
15152             promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
15153               .then(
15154               function (template) {
15155                 col.editableCellTemplate = template;
15156               },
15157               function (res) {
15158                 // Todo handle response error here?
15159                 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
15160               }));
15161           }
15162
15163           /**
15164            *  @ngdoc object
15165            *  @name enableCellEditOnFocus
15166            *  @propertyOf  ui.grid.edit.api:ColumnDef
15167            *  @requires ui.grid.cellNav
15168            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15169            *  <br>_requires both the cellNav feature and the edit feature to be enabled_
15170            */
15171             //enableCellEditOnFocus can only be used if cellnav module is used
15172           colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
15173
15174
15175           /**
15176            *  @ngdoc string
15177            *  @name editModelField
15178            *  @propertyOf  ui.grid.edit.api:ColumnDef
15179            *  @description a bindable string value that is used when binding to edit controls instead of colDef.field
15180            *  <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}.  The
15181            *  grid should display state.name in the cell and sort/filter based on the state.name property but the editor
15182            *  requires the full state object.
15183            *  <br/>colDef.field = 'state.name'
15184            *  <br/>colDef.editModelField = 'state'
15185            */
15186           //colDef.editModelField
15187
15188           return $q.all(promises);
15189         },
15190
15191         /**
15192          * @ngdoc service
15193          * @name isStartEditKey
15194          * @methodOf ui.grid.edit.service:uiGridEditService
15195          * @description  Determines if a keypress should start editing.  Decorate this service to override with your
15196          * own key events.  See service decorator in angular docs.
15197          * @param {Event} evt keydown event
15198          * @returns {boolean} true if an edit should start
15199          */
15200         isStartEditKey: function (evt) {
15201           if (evt.metaKey ||
15202               evt.keyCode === uiGridConstants.keymap.ESC ||
15203               evt.keyCode === uiGridConstants.keymap.SHIFT ||
15204               evt.keyCode === uiGridConstants.keymap.CTRL ||
15205               evt.keyCode === uiGridConstants.keymap.ALT ||
15206               evt.keyCode === uiGridConstants.keymap.WIN ||
15207               evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
15208
15209              evt.keyCode === uiGridConstants.keymap.LEFT ||
15210             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
15211
15212             evt.keyCode === uiGridConstants.keymap.RIGHT ||
15213             evt.keyCode === uiGridConstants.keymap.TAB ||
15214
15215             evt.keyCode === uiGridConstants.keymap.UP ||
15216             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
15217
15218             evt.keyCode === uiGridConstants.keymap.DOWN ||
15219             evt.keyCode === uiGridConstants.keymap.ENTER) {
15220             return false;
15221
15222           }
15223           return true;
15224         }
15225
15226
15227       };
15228
15229       return service;
15230
15231     }]);
15232
15233   /**
15234    *  @ngdoc directive
15235    *  @name ui.grid.edit.directive:uiGridEdit
15236    *  @element div
15237    *  @restrict A
15238    *
15239    *  @description Adds editing features to the ui-grid directive.
15240    *
15241    *  @example
15242    <example module="app">
15243    <file name="app.js">
15244    var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
15245
15246    app.controller('MainCtrl', ['$scope', function ($scope) {
15247       $scope.data = [
15248         { name: 'Bob', title: 'CEO' },
15249             { name: 'Frank', title: 'Lowly Developer' }
15250       ];
15251
15252       $scope.columnDefs = [
15253         {name: 'name', enableCellEdit: true},
15254         {name: 'title', enableCellEdit: true}
15255       ];
15256     }]);
15257    </file>
15258    <file name="index.html">
15259    <div ng-controller="MainCtrl">
15260    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
15261    </div>
15262    </file>
15263    </example>
15264    */
15265   module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
15266     return {
15267       replace: true,
15268       priority: 0,
15269       require: '^uiGrid',
15270       scope: false,
15271       compile: function () {
15272         return {
15273           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15274             uiGridEditService.initializeGrid(uiGridCtrl.grid);
15275           },
15276           post: function ($scope, $elm, $attrs, uiGridCtrl) {
15277           }
15278         };
15279       }
15280     };
15281   }]);
15282
15283   /**
15284    *  @ngdoc directive
15285    *  @name ui.grid.edit.directive:uiGridRenderContainer
15286    *  @element div
15287    *  @restrict A
15288    *
15289    *  @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
15290    *
15291    */
15292   module.directive('uiGridViewport', [ 'uiGridEditConstants',
15293     function ( uiGridEditConstants) {
15294       return {
15295         replace: true,
15296         priority: -99998, //run before cellNav
15297         require: ['^uiGrid', '^uiGridRenderContainer'],
15298         scope: false,
15299         compile: function () {
15300           return {
15301             post: function ($scope, $elm, $attrs, controllers) {
15302               var uiGridCtrl = controllers[0];
15303
15304               // Skip attaching if edit and cellNav is not enabled
15305               if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
15306
15307               var containerId =  controllers[1].containerId;
15308               //no need to process for other containers
15309               if (containerId !== 'body') {
15310                 return;
15311               }
15312
15313               //refocus on the grid
15314               $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
15315                 uiGridCtrl.focus();
15316               });
15317               $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
15318                 uiGridCtrl.focus();
15319               });
15320
15321             }
15322           };
15323         }
15324       };
15325     }]);
15326
15327   /**
15328    *  @ngdoc directive
15329    *  @name ui.grid.edit.directive:uiGridCell
15330    *  @element div
15331    *  @restrict A
15332    *
15333    *  @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
15334    *  Editing Actions.
15335    *
15336    *  Binds edit start events to the uiGridCell element.  When the events fire, the gridCell element is appended
15337    *  with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
15338    *
15339    *  The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
15340    *  and do the initial steps needed to edit the cell (setfocus on input element, etc).
15341    *
15342    *  When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
15343    *  it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
15344    *
15345    *  If editableCellTemplate recognizes that the editing has been cancelled (esc key)
15346    *  it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event.  The original value
15347    *  will be set back on the model by the uiGridCell directive.
15348    *
15349    *  Events that invoke editing:
15350    *    - dblclick
15351    *    - F2 keydown (when using cell selection)
15352    *
15353    *  Events that end editing:
15354    *    - Dependent on the specific editableCellTemplate
15355    *    - Standards should be blur and enter keydown
15356    *
15357    *  Events that cancel editing:
15358    *    - Dependent on the specific editableCellTemplate
15359    *    - Standards should be Esc keydown
15360    *
15361    *  Grid Events that end editing:
15362    *    - uiGridConstants.events.GRID_SCROLL
15363    *
15364    */
15365
15366   /**
15367    *  @ngdoc object
15368    *  @name ui.grid.edit.api:GridRow
15369    *
15370    *  @description GridRow options for edit feature, these are available to be
15371    *  set internally only, by other features
15372    */
15373
15374   /**
15375    *  @ngdoc object
15376    *  @name enableCellEdit
15377    *  @propertyOf  ui.grid.edit.api:GridRow
15378    *  @description enable editing on row, grouping for example might disable editing on group header rows
15379    */
15380
15381   module.directive('uiGridCell',
15382     ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope',
15383       function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope) {
15384         var touchstartTimeout = 500;
15385         if ($injector.has('uiGridCellNavService')) {
15386           var uiGridCellNavService = $injector.get('uiGridCellNavService');
15387         }
15388
15389         return {
15390           priority: -100, // run after default uiGridCell directive
15391           restrict: 'A',
15392           scope: false,
15393           require: '?^uiGrid',
15394           link: function ($scope, $elm, $attrs, uiGridCtrl) {
15395             var html;
15396             var origCellValue;
15397             var inEdit = false;
15398             var cellModel;
15399             var cancelTouchstartTimeout;
15400
15401             var editCellScope;
15402
15403             if (!$scope.col.colDef.enableCellEdit) {
15404               return;
15405             }
15406
15407             var cellNavNavigateDereg = function() {};
15408             var viewPortKeyDownDereg = function() {};
15409
15410
15411             var setEditable = function() {
15412               if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
15413                 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
15414                   registerBeginEditEvents();
15415                 }
15416               } else {
15417                 if ($scope.beginEditEventsWired) {
15418                   cancelBeginEditEvents();
15419                 }
15420               }
15421             };
15422
15423             setEditable();
15424
15425             var rowWatchDereg = $scope.$watch('row', function (n, o) {
15426               if (n !== o) {
15427                 setEditable();
15428               }
15429             });
15430
15431
15432             $scope.$on( '$destroy', rowWatchDereg );
15433
15434             function registerBeginEditEvents() {
15435               $elm.on('dblclick', beginEdit);
15436
15437               // Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
15438               $elm.on('touchstart', touchStart);
15439
15440               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15441
15442                 viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
15443                   if (rowCol === null) {
15444                     return;
15445                   }
15446
15447                   if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
15448                     //important to do this before scrollToIfNecessary
15449                     beginEditKeyDown(evt);
15450                   }
15451                 });
15452
15453                 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol) {
15454                   if ($scope.col.colDef.enableCellEditOnFocus) {
15455                     // Don't begin edit if the cell hasn't changed
15456                     if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
15457                       newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
15458                       $timeout(function () {
15459                         beginEdit();
15460                       });
15461                     }
15462                   }
15463                 });
15464               }
15465
15466               $scope.beginEditEventsWired = true;
15467
15468             }
15469
15470             function touchStart(event) {
15471               // jQuery masks events
15472               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
15473                 event = event.originalEvent;
15474               }
15475
15476               // Bind touchend handler
15477               $elm.on('touchend', touchEnd);
15478
15479               // Start a timeout
15480               cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
15481
15482               // Timeout's done! Start the edit
15483               cancelTouchstartTimeout.then(function () {
15484                 // Use setTimeout to start the edit because beginEdit expects to be outside of $digest
15485                 setTimeout(beginEdit, 0);
15486
15487                 // Undbind the touchend handler, we don't need it anymore
15488                 $elm.off('touchend', touchEnd);
15489               });
15490             }
15491
15492             // Cancel any touchstart timeout
15493             function touchEnd(event) {
15494               $timeout.cancel(cancelTouchstartTimeout);
15495               $elm.off('touchend', touchEnd);
15496             }
15497
15498             function cancelBeginEditEvents() {
15499               $elm.off('dblclick', beginEdit);
15500               $elm.off('keydown', beginEditKeyDown);
15501               $elm.off('touchstart', touchStart);
15502               cellNavNavigateDereg();
15503               viewPortKeyDownDereg();
15504               $scope.beginEditEventsWired = false;
15505             }
15506
15507             function beginEditKeyDown(evt) {
15508               if (uiGridEditService.isStartEditKey(evt)) {
15509                 beginEdit(evt);
15510               }
15511             }
15512
15513             function shouldEdit(col, row) {
15514               return !row.isSaving &&
15515                 ( angular.isFunction(col.colDef.cellEditableCondition) ?
15516                     col.colDef.cellEditableCondition($scope) :
15517                     col.colDef.cellEditableCondition );
15518             }
15519
15520
15521             function beginEdit(triggerEvent) {
15522               //we need to scroll the cell into focus before invoking the editor
15523               $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
15524                 .then(function () {
15525                   beginEditAfterScroll(triggerEvent);
15526                 });
15527             }
15528
15529             /**
15530              *  @ngdoc property
15531              *  @name editDropdownOptionsArray
15532              *  @propertyOf ui.grid.edit.api:ColumnDef
15533              *  @description an array of values in the format
15534              *  [ {id: xxx, value: xxx} ], which is populated
15535              *  into the edit dropdown
15536              *
15537              */
15538             /**
15539              *  @ngdoc property
15540              *  @name editDropdownIdLabel
15541              *  @propertyOf ui.grid.edit.api:ColumnDef
15542              *  @description the label for the "id" field
15543              *  in the editDropdownOptionsArray.  Defaults
15544              *  to 'id'
15545              *  @example
15546              *  <pre>
15547              *    $scope.gridOptions = {
15548              *      columnDefs: [
15549              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
15550              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
15551              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
15552              *      ],
15553              *  </pre>
15554              *
15555              */
15556             /**
15557              *  @ngdoc property
15558              *  @name editDropdownRowEntityOptionsArrayPath
15559              *  @propertyOf ui.grid.edit.api:ColumnDef
15560              *  @description a path to a property on row.entity containing an
15561              *  array of values in the format
15562              *  [ {id: xxx, value: xxx} ], which will be used to populate
15563              *  the edit dropdown.  This can be used when the dropdown values are dependent on
15564              *  the backing row entity.
15565              *  If this property is set then editDropdownOptionsArray will be ignored.
15566              *  @example
15567              *  <pre>
15568              *    $scope.gridOptions = {
15569              *      columnDefs: [
15570              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
15571              *          editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
15572              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
15573              *      ],
15574              *  </pre>
15575              *
15576              */
15577             /**
15578              *  @ngdoc property
15579              *  @name editDropdownValueLabel
15580              *  @propertyOf ui.grid.edit.api:ColumnDef
15581              *  @description the label for the "value" field
15582              *  in the editDropdownOptionsArray.  Defaults
15583              *  to 'value'
15584              *  @example
15585              *  <pre>
15586              *    $scope.gridOptions = {
15587              *      columnDefs: [
15588              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
15589              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
15590              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
15591              *      ],
15592              *  </pre>
15593              *
15594              */
15595             /**
15596              *  @ngdoc property
15597              *  @name editDropdownFilter
15598              *  @propertyOf ui.grid.edit.api:ColumnDef
15599              *  @description A filter that you would like to apply to the values in the options list
15600              *  of the dropdown.  For example if you were using angular-translate you might set this
15601              *  to `'translate'`
15602              *  @example
15603              *  <pre>
15604              *    $scope.gridOptions = {
15605              *      columnDefs: [
15606              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
15607              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
15608              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
15609              *      ],
15610              *  </pre>
15611              *
15612              */
15613             function beginEditAfterScroll(triggerEvent) {
15614               // If we are already editing, then just skip this so we don't try editing twice...
15615               if (inEdit) {
15616                 return;
15617               }
15618
15619               if (!shouldEdit($scope.col, $scope.row)) {
15620                 return;
15621               }
15622
15623
15624               cellModel = $parse($scope.row.getQualifiedColField($scope.col));
15625               //get original value from the cell
15626               origCellValue = cellModel($scope);
15627
15628               html = $scope.col.editableCellTemplate;
15629
15630               if ($scope.col.colDef.editModelField) {
15631                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField));
15632               }
15633               else {
15634                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
15635               }
15636
15637               html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
15638
15639               var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
15640               html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
15641
15642               var inputType = 'text';
15643               switch ($scope.col.colDef.type){
15644                 case 'boolean':
15645                   inputType = 'checkbox';
15646                   break;
15647                 case 'number':
15648                   inputType = 'number';
15649                   break;
15650                 case 'date':
15651                   inputType = 'date';
15652                   break;
15653               }
15654               html = html.replace('INPUT_TYPE', inputType);
15655
15656               var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
15657               if (editDropdownRowEntityOptionsArrayPath) {
15658                 $scope.editDropdownOptionsArray =  resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
15659               }
15660               else {
15661                 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
15662               }
15663               $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
15664               $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
15665
15666               var cellElement;
15667               var createEditor = function(){
15668                 inEdit = true;
15669                 cancelBeginEditEvents();
15670                 var cellElement = angular.element(html);
15671                 $elm.append(cellElement);
15672                 editCellScope = $scope.$new();
15673                 $compile(cellElement)(editCellScope);
15674                 var gridCellContentsEl = angular.element($elm.children()[0]);
15675                 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
15676               };
15677               if (!$rootScope.$$phase) {
15678                 $scope.$apply(createEditor);
15679               } else {
15680                 createEditor();
15681               }
15682
15683               //stop editing when grid is scrolled
15684               var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
15685                 if ($scope.grid.disableScrolling) {
15686                   return;
15687                 }
15688                 endEdit();
15689                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
15690                 deregOnGridScroll();
15691                 deregOnEndCellEdit();
15692                 deregOnCancelCellEdit();
15693               });
15694
15695               //end editing
15696               var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
15697                 endEdit();
15698                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
15699                 deregOnEndCellEdit();
15700                 deregOnGridScroll();
15701                 deregOnCancelCellEdit();
15702               });
15703
15704               //cancel editing
15705               var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
15706                 cancelEdit();
15707                 deregOnCancelCellEdit();
15708                 deregOnGridScroll();
15709                 deregOnEndCellEdit();
15710               });
15711
15712               $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
15713               $timeout(function () {
15714                 //execute in a timeout to give any complex editor templates a cycle to completely render
15715                 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
15716               });
15717             }
15718
15719             function endEdit() {
15720               $scope.grid.disableScrolling = false;
15721               if (!inEdit) {
15722                 return;
15723               }
15724
15725               //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
15726               //back to grid here. The focus call needs to be before the $destroy and removal of the control,
15727               //otherwise ng-model-options of UpdateOn: 'blur' will not work.
15728               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15729                 uiGridCtrl.focus();
15730               }
15731
15732               var gridCellContentsEl = angular.element($elm.children()[0]);
15733               //remove edit element
15734               editCellScope.$destroy();
15735               angular.element($elm.children()[1]).remove();
15736               gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
15737               inEdit = false;
15738               registerBeginEditEvents();
15739               $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
15740             }
15741
15742             function cancelEdit() {
15743               $scope.grid.disableScrolling = false;
15744               if (!inEdit) {
15745                 return;
15746               }
15747               cellModel.assign($scope, origCellValue);
15748               $scope.$apply();
15749
15750               $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
15751               endEdit();
15752             }
15753
15754             // resolves a string path against the given object
15755             // shamelessly borrowed from
15756             // http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
15757             function resolveObjectFromPath(object, path) {
15758               path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
15759               path = path.replace(/^\./, '');           // strip a leading dot
15760               var a = path.split('.');
15761               while (a.length) {
15762                   var n = a.shift();
15763                   if (n in object) {
15764                       object = object[n];
15765                   } else {
15766                       return;
15767                   }
15768               }
15769               return object;
15770             }
15771
15772           }
15773         };
15774       }]);
15775
15776   /**
15777    *  @ngdoc directive
15778    *  @name ui.grid.edit.directive:uiGridEditor
15779    *  @element div
15780    *  @restrict A
15781    *
15782    *  @description input editor directive for editable fields.
15783    *  Provides EndEdit and CancelEdit events
15784    *
15785    *  Events that end editing:
15786    *     blur and enter keydown
15787    *
15788    *  Events that cancel editing:
15789    *    - Esc keydown
15790    *
15791    */
15792   module.directive('uiGridEditor',
15793     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
15794       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
15795         return {
15796           scope: true,
15797           require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
15798           compile: function () {
15799             return {
15800               pre: function ($scope, $elm, $attrs) {
15801
15802               },
15803               post: function ($scope, $elm, $attrs, controllers) {
15804                 var uiGridCtrl, renderContainerCtrl, ngModel;
15805                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
15806                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
15807                 if (controllers[2]) { ngModel = controllers[2]; }
15808
15809                 //set focus at start of edit
15810                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
15811                   $timeout(function () {
15812                     $elm[0].focus();
15813                     //only select text if it is not being replaced below in the cellNav viewPortKeyPress
15814                     if ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav)) {
15815                       $elm[0].select();
15816                     }
15817                     else {
15818                       //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
15819                       //fields should not allow setSelectionRange.  We ignore the error for those browsers
15820                       //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
15821                       try {
15822                         $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
15823                       }
15824                       catch (ex) {
15825                         //ignore
15826                       }
15827                     }
15828                   });
15829
15830                   //set the keystroke that started the edit event
15831                   //we must do this because the BeginEdit is done in a different event loop than the intitial
15832                   //keydown event
15833                   //fire this event for the keypress that is received
15834                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15835                     var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
15836                       if (uiGridEditService.isStartEditKey(evt)) {
15837                         ngModel.$setViewValue(String.fromCharCode(evt.keyCode), evt);
15838                         ngModel.$render();
15839                       }
15840                       viewPortKeyDownUnregister();
15841                     });
15842                   }
15843
15844                   $elm.on('blur', function (evt) {
15845                     $scope.stopEdit(evt);
15846                   });
15847                 });
15848
15849
15850                 $scope.deepEdit = false;
15851
15852                 $scope.stopEdit = function (evt) {
15853                   if ($scope.inputForm && !$scope.inputForm.$valid) {
15854                     evt.stopPropagation();
15855                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
15856                   }
15857                   else {
15858                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
15859                   }
15860                   $scope.deepEdit = false;
15861                 };
15862
15863
15864                 $elm.on('click', function (evt) {
15865                   if ($elm[0].type !== 'checkbox') {
15866                     $scope.deepEdit = true;
15867                     $timeout(function () {
15868                       $scope.grid.disableScrolling = true;
15869                     });
15870                   }
15871                 });
15872
15873                 $elm.on('keydown', function (evt) {
15874                   switch (evt.keyCode) {
15875                     case uiGridConstants.keymap.ESC:
15876                       evt.stopPropagation();
15877                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
15878                       break;
15879                   }
15880
15881                   if ($scope.deepEdit &&
15882                     (evt.keyCode === uiGridConstants.keymap.LEFT ||
15883                      evt.keyCode === uiGridConstants.keymap.RIGHT ||
15884                      evt.keyCode === uiGridConstants.keymap.UP ||
15885                      evt.keyCode === uiGridConstants.keymap.DOWN)) {
15886                     evt.stopPropagation();
15887                   }
15888                   // Pass the keydown event off to the cellNav service, if it exists
15889                   else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15890                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
15891                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
15892                       $scope.stopEdit(evt);
15893                     }
15894                   }
15895                   else {
15896                     //handle enter and tab for editing not using cellNav
15897                     switch (evt.keyCode) {
15898                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
15899                       case uiGridConstants.keymap.TAB:
15900                         evt.stopPropagation();
15901                         evt.preventDefault();
15902                         $scope.stopEdit(evt);
15903                         break;
15904                     }
15905                   }
15906
15907                   return true;
15908                 });
15909               }
15910             };
15911           }
15912         };
15913       }]);
15914
15915   /**
15916    *  @ngdoc directive
15917    *  @name ui.grid.edit.directive:input
15918    *  @element input
15919    *  @restrict E
15920    *
15921    *  @description directive to provide binding between input[date] value and ng-model for angular 1.2
15922    *  It is similar to input[date] directive of angular 1.3
15923    *
15924    *  Supported date format for input is 'yyyy-MM-dd'
15925    *  The directive will set the $valid property of input element and the enclosing form to false if
15926    *  model is invalid date or value of input is entered wrong.
15927    *
15928    */
15929     module.directive('uiGridEditor', ['$filter', function ($filter) {
15930       function parseDateString(dateString) {
15931         if (typeof(dateString) === 'undefined' || dateString === '') {
15932           return null;
15933         }
15934         var parts = dateString.split('-');
15935         if (parts.length !== 3) {
15936           return null;
15937         }
15938         var year = parseInt(parts[0], 10);
15939         var month = parseInt(parts[1], 10);
15940         var day = parseInt(parts[2], 10);
15941
15942         if (month < 1 || year < 1 || day < 1) {
15943           return null;
15944         }
15945         return new Date(year, (month - 1), day);
15946       }
15947       return {
15948         priority: -100, // run after default uiGridEditor directive
15949         require: '?ngModel',
15950         link: function (scope, element, attrs, ngModel) {
15951
15952           if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
15953
15954             ngModel.$formatters.push(function (modelValue) {
15955               ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
15956               return $filter('date')(modelValue, 'yyyy-MM-dd');
15957             });
15958
15959             ngModel.$parsers.push(function (viewValue) {
15960               if (viewValue && viewValue.length > 0) {
15961                 var dateValue = parseDateString(viewValue);
15962                 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
15963                 return dateValue;
15964               }
15965               else {
15966                 ngModel.$setValidity(null, true);
15967                 return null;
15968               }
15969             });
15970           }
15971         }
15972       };
15973     }]);
15974
15975
15976   /**
15977    *  @ngdoc directive
15978    *  @name ui.grid.edit.directive:uiGridEditDropdown
15979    *  @element div
15980    *  @restrict A
15981    *
15982    *  @description dropdown editor for editable fields.
15983    *  Provides EndEdit and CancelEdit events
15984    *
15985    *  Events that end editing:
15986    *     blur and enter keydown, and any left/right nav
15987    *
15988    *  Events that cancel editing:
15989    *    - Esc keydown
15990    *
15991    */
15992   module.directive('uiGridEditDropdown',
15993     ['uiGridConstants', 'uiGridEditConstants',
15994       function (uiGridConstants, uiGridEditConstants) {
15995         return {
15996           require: ['?^uiGrid', '?^uiGridRenderContainer'],
15997           scope: true,
15998           compile: function () {
15999             return {
16000               pre: function ($scope, $elm, $attrs) {
16001
16002               },
16003               post: function ($scope, $elm, $attrs, controllers) {
16004                 var uiGridCtrl = controllers[0];
16005                 var renderContainerCtrl = controllers[1];
16006
16007                 //set focus at start of edit
16008                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16009                   $elm[0].focus();
16010                   $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
16011                   $elm.on('blur', function (evt) {
16012                     $scope.stopEdit(evt);
16013                   });
16014                 });
16015
16016
16017                 $scope.stopEdit = function (evt) {
16018                   // no need to validate a dropdown - invalid values shouldn't be
16019                   // available in the list
16020                   $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16021                 };
16022
16023                 $elm.on('keydown', function (evt) {
16024                   switch (evt.keyCode) {
16025                     case uiGridConstants.keymap.ESC:
16026                       evt.stopPropagation();
16027                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16028                       break;
16029                   }
16030                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16031                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16032                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16033                       $scope.stopEdit(evt);
16034                     }
16035                   }
16036                   else {
16037                     //handle enter and tab for editing not using cellNav
16038                     switch (evt.keyCode) {
16039                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16040                       case uiGridConstants.keymap.TAB:
16041                         evt.stopPropagation();
16042                         evt.preventDefault();
16043                         $scope.stopEdit(evt);
16044                         break;
16045                     }
16046                   }
16047                   return true;
16048                 });
16049               }
16050             };
16051           }
16052         };
16053       }]);
16054
16055   /**
16056    *  @ngdoc directive
16057    *  @name ui.grid.edit.directive:uiGridEditFileChooser
16058    *  @element div
16059    *  @restrict A
16060    *
16061    *  @description input editor directive for editable fields.
16062    *  Provides EndEdit and CancelEdit events
16063    *
16064    *  Events that end editing:
16065    *     blur and enter keydown
16066    *
16067    *  Events that cancel editing:
16068    *    - Esc keydown
16069    *
16070    */
16071   module.directive('uiGridEditFileChooser',
16072     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
16073       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
16074         return {
16075           scope: true,
16076           require: ['?^uiGrid', '?^uiGridRenderContainer'],
16077           compile: function () {
16078             return {
16079               pre: function ($scope, $elm, $attrs) {
16080
16081               },
16082               post: function ($scope, $elm, $attrs, controllers) {
16083                 var uiGridCtrl, renderContainerCtrl;
16084                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16085                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16086                 var grid = uiGridCtrl.grid;
16087
16088                 var handleFileSelect = function( event ){
16089                   var target = event.srcElement || event.target;
16090
16091                   if (target && target.files && target.files.length > 0) {
16092                     /**
16093                      *  @ngdoc property
16094                      *  @name editFileChooserCallback
16095                      *  @propertyOf  ui.grid.edit.api:ColumnDef
16096                      *  @description A function that should be called when any files have been chosen
16097                      *  by the user.  You should use this to process the files appropriately for your
16098                      *  application.
16099                      *
16100                      *  It passes the gridCol, the gridRow (from which you can get gridRow.entity),
16101                      *  and the files.  The files are in the format as returned from the file chooser,
16102                      *  an array of files, with each having useful information such as:
16103                      *  - `files[0].lastModifiedDate`
16104                      *  - `files[0].name`
16105                      *  - `files[0].size`  (appears to be in bytes)
16106                      *  - `files[0].type`  (MIME type by the looks)
16107                      *
16108                      *  Typically you would do something with these files - most commonly you would
16109                      *  use the filename or read the file itself in.  The example function does both.
16110                      *
16111                      *  @example
16112                      *  <pre>
16113                      *  editFileChooserCallBack: function(gridRow, gridCol, files ){
16114                      *    // ignore all but the first file, it can only choose one anyway
16115                      *    // set the filename into this column
16116                      *    gridRow.entity.filename = file[0].name;
16117                      *
16118                      *    // read the file and set it into a hidden column, which we may do stuff with later
16119                      *    var setFile = function(fileContent){
16120                      *      gridRow.entity.file = fileContent.currentTarget.result;
16121                      *    };
16122                      *    var reader = new FileReader();
16123                      *    reader.onload = setFile;
16124                      *    reader.readAsText( files[0] );
16125                      *  }
16126                      *  </pre>
16127                      */
16128                     if ( typeof($scope.col.colDef.editFileChooserCallback) === 'function' ) {
16129                       $scope.col.colDef.editFileChooserCallback($scope.row, $scope.col, target.files);
16130                     } else {
16131                       gridUtil.logError('You need to set colDef.editFileChooserCallback to use the file chooser');
16132                     }
16133
16134                     target.form.reset();
16135                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16136                   } else {
16137                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16138                   }
16139                 };
16140
16141                 $elm[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
16142
16143                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16144                   $elm[0].focus();
16145                   $elm[0].select();
16146
16147                   $elm.on('blur', function (evt) {
16148                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16149                   });
16150                 });
16151               }
16152             };
16153           }
16154         };
16155       }]);
16156
16157
16158 })();
16159
16160 (function () {
16161   'use strict';
16162
16163   /**
16164    * @ngdoc overview
16165    * @name ui.grid.expandable
16166    * @description
16167    *
16168    * # ui.grid.expandable
16169    *
16170    * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
16171    *
16172    * This module provides the ability to create subgrids with the ability to expand a row
16173    * to show the subgrid.
16174    *
16175    * <div doc-module-components="ui.grid.expandable"></div>
16176    */
16177   var module = angular.module('ui.grid.expandable', ['ui.grid']);
16178
16179   /**
16180    *  @ngdoc service
16181    *  @name ui.grid.expandable.service:uiGridExpandableService
16182    *
16183    *  @description Services for the expandable grid
16184    */
16185   module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
16186     var service = {
16187       initializeGrid: function (grid) {
16188
16189         grid.expandable = {};
16190         grid.expandable.expandedAll = false;
16191
16192         /**
16193          *  @ngdoc object
16194          *  @name enableExpandable
16195          *  @propertyOf  ui.grid.expandable.api:GridOptions
16196          *  @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
16197          *  within your application, or in specific modes on _this_ grid. Defaults to true.
16198          *  @example
16199          *  <pre>
16200          *    $scope.gridOptions = {
16201          *      enableExpandable: false
16202          *    }
16203          *  </pre>
16204          */
16205         grid.options.enableExpandable = grid.options.enableExpandable !== false;
16206
16207         /**
16208          *  @ngdoc object
16209          *  @name expandableRowHeight
16210          *  @propertyOf  ui.grid.expandable.api:GridOptions
16211          *  @description Height in pixels of the expanded subgrid.  Defaults to
16212          *  150
16213          *  @example
16214          *  <pre>
16215          *    $scope.gridOptions = {
16216          *      expandableRowHeight: 150
16217          *    }
16218          *  </pre>
16219          */
16220         grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
16221
16222         /**
16223          *  @ngdoc object
16224          *  @name
16225          *  @propertyOf  ui.grid.expandable.api:GridOptions
16226          *  @description Width in pixels of the expandable column. Defaults to 40
16227          *  @example
16228          *  <pre>
16229          *    $scope.gridOptions = {
16230          *      expandableRowHeaderWidth: 40
16231          *    }
16232          *  </pre>
16233          */
16234         grid.options.expandableRowHeaderWidth = grid.options.expandableRowHeaderWidth || 40;
16235
16236         /**
16237          *  @ngdoc object
16238          *  @name expandableRowTemplate
16239          *  @propertyOf  ui.grid.expandable.api:GridOptions
16240          *  @description Mandatory. The template for your expanded row
16241          *  @example
16242          *  <pre>
16243          *    $scope.gridOptions = {
16244          *      expandableRowTemplate: 'expandableRowTemplate.html'
16245          *    }
16246          *  </pre>
16247          */
16248         if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
16249           gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
16250           grid.options.enableExpandable = false;
16251         }
16252
16253         /**
16254          *  @ngdoc object
16255          *  @name ui.grid.expandable.api:PublicApi
16256          *
16257          *  @description Public Api for expandable feature
16258          */
16259         /**
16260          *  @ngdoc object
16261          *  @name ui.grid.expandable.api:GridOptions
16262          *
16263          *  @description Options for configuring the expandable feature, these are available to be
16264          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16265          */
16266         var publicApi = {
16267           events: {
16268             expandable: {
16269               /**
16270                * @ngdoc event
16271                * @name rowExpandedStateChanged
16272                * @eventOf  ui.grid.expandable.api:PublicApi
16273                * @description raised when cell editing is complete
16274                * <pre>
16275                *      gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
16276                * </pre>
16277                * @param {GridRow} row the row that was expanded
16278                */
16279               rowExpandedBeforeStateChanged: function(scope,row){
16280               },
16281               rowExpandedStateChanged: function (scope, row) {
16282               }
16283             }
16284           },
16285
16286           methods: {
16287             expandable: {
16288               /**
16289                * @ngdoc method
16290                * @name toggleRowExpansion
16291                * @methodOf  ui.grid.expandable.api:PublicApi
16292                * @description Toggle a specific row
16293                * <pre>
16294                *      gridApi.expandable.toggleRowExpansion(rowEntity);
16295                * </pre>
16296                * @param {object} rowEntity the data entity for the row you want to expand
16297                */
16298               toggleRowExpansion: function (rowEntity) {
16299                 var row = grid.getRow(rowEntity);
16300                 if (row !== null) {
16301                   service.toggleRowExpansion(grid, row);
16302                 }
16303               },
16304
16305               /**
16306                * @ngdoc method
16307                * @name expandAllRows
16308                * @methodOf  ui.grid.expandable.api:PublicApi
16309                * @description Expand all subgrids.
16310                * <pre>
16311                *      gridApi.expandable.expandAllRows();
16312                * </pre>
16313                */
16314               expandAllRows: function() {
16315                 service.expandAllRows(grid);
16316               },
16317
16318               /**
16319                * @ngdoc method
16320                * @name collapseAllRows
16321                * @methodOf  ui.grid.expandable.api:PublicApi
16322                * @description Collapse all subgrids.
16323                * <pre>
16324                *      gridApi.expandable.collapseAllRows();
16325                * </pre>
16326                */
16327               collapseAllRows: function() {
16328                 service.collapseAllRows(grid);
16329               },
16330
16331               /**
16332                * @ngdoc method
16333                * @name toggleAllRows
16334                * @methodOf  ui.grid.expandable.api:PublicApi
16335                * @description Toggle all subgrids.
16336                * <pre>
16337                *      gridApi.expandable.toggleAllRows();
16338                * </pre>
16339                */
16340               toggleAllRows: function() {
16341                 service.toggleAllRows(grid);
16342               }
16343             }
16344           }
16345         };
16346         grid.api.registerEventsFromObject(publicApi.events);
16347         grid.api.registerMethodsFromObject(publicApi.methods);
16348       },
16349
16350       toggleRowExpansion: function (grid, row) {
16351         // trigger the "before change" event. Can change row height dynamically this way.
16352         grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
16353         row.isExpanded = !row.isExpanded;
16354         if (angular.isUndefined(row.expandedRowHeight)){
16355           row.expandedRowHeight = grid.options.expandableRowHeight;
16356         }
16357               
16358         if (row.isExpanded) {
16359           row.height = row.grid.options.rowHeight + row.expandedRowHeight;
16360         }
16361         else {
16362           row.height = row.grid.options.rowHeight;
16363           grid.expandable.expandedAll = false;
16364         }
16365         grid.api.expandable.raise.rowExpandedStateChanged(row);
16366       },
16367
16368       expandAllRows: function(grid, $scope) {
16369         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16370           if (!row.isExpanded) {
16371             service.toggleRowExpansion(grid, row);
16372           }
16373         });
16374         grid.expandable.expandedAll = true;
16375         grid.queueGridRefresh();
16376       },
16377
16378       collapseAllRows: function(grid) {
16379         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16380           if (row.isExpanded) {
16381             service.toggleRowExpansion(grid, row);
16382           }
16383         });
16384         grid.expandable.expandedAll = false;
16385         grid.queueGridRefresh();
16386       },
16387
16388       toggleAllRows: function(grid) {
16389         if (grid.expandable.expandedAll) {
16390           service.collapseAllRows(grid);
16391         }
16392         else {
16393           service.expandAllRows(grid);
16394         }
16395       }
16396     };
16397     return service;
16398   }]);
16399
16400   /**
16401    *  @ngdoc object
16402    *  @name enableExpandableRowHeader
16403    *  @propertyOf  ui.grid.expandable.api:GridOptions
16404    *  @description Show a rowHeader to provide the expandable buttons.  If set to false then implies
16405    *  you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
16406    *  @example
16407    *  <pre>
16408    *    $scope.gridOptions = {
16409    *      enableExpandableRowHeader: false
16410    *    }
16411    *  </pre>
16412    */
16413   module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
16414     function (uiGridExpandableService, $templateCache) {
16415       return {
16416         replace: true,
16417         priority: 0,
16418         require: '^uiGrid',
16419         scope: false,
16420         compile: function () {
16421           return {
16422             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16423               if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
16424                 var expandableRowHeaderColDef = {
16425                   name: 'expandableButtons',
16426                   displayName: '',
16427                   exporterSuppressExport: true,
16428                   enableColumnResizing: false,
16429                   enableColumnMenu: false,
16430                   width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
16431                 };
16432                 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
16433                 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
16434                 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
16435               }
16436               uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
16437             },
16438             post: function ($scope, $elm, $attrs, uiGridCtrl) {
16439             }
16440           };
16441         }
16442       };
16443     }]);
16444
16445   /**
16446    *  @ngdoc directive
16447    *  @name ui.grid.expandable.directive:uiGrid
16448    *  @description stacks on the uiGrid directive to register child grid with parent row when child is created
16449    */
16450   module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
16451     function (uiGridExpandableService, $templateCache) {
16452       return {
16453         replace: true,
16454         priority: 599,
16455         require: '^uiGrid',
16456         scope: false,
16457         compile: function () {
16458           return {
16459             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16460
16461               uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
16462                 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
16463                 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
16464
16465                   /**
16466                    *  @ngdoc directive
16467                    *  @name ui.grid.expandable.class:Grid
16468                    *  @description Additional Grid properties added by expandable module
16469                    */
16470
16471                   /**
16472                    *  @ngdoc object
16473                    *  @name parentRow
16474                    *  @propertyOf ui.grid.expandable.class:Grid
16475                    *  @description reference to the expanded parent row that owns this grid
16476                    */
16477                   uiGridCtrl.grid.parentRow = $scope.row;
16478
16479                   //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
16480                  // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
16481                  //   uiGridCtrl.grid.parentRow = newHeight;
16482                  // });
16483                 }
16484
16485               });
16486             },
16487             post: function ($scope, $elm, $attrs, uiGridCtrl) {
16488
16489             }
16490           };
16491         }
16492       };
16493     }]);
16494
16495   /**
16496    *  @ngdoc directive
16497    *  @name ui.grid.expandable.directive:uiGridExpandableRow
16498    *  @description directive to render the expandable row template
16499    */
16500   module.directive('uiGridExpandableRow',
16501   ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
16502     function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
16503
16504       return {
16505         replace: false,
16506         priority: 0,
16507         scope: false,
16508
16509         compile: function () {
16510           return {
16511             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16512               gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
16513                 function (template) {
16514                   if ($scope.grid.options.expandableRowScope) {
16515                     var expandableRowScope = $scope.grid.options.expandableRowScope;
16516                     for (var property in expandableRowScope) {
16517                       if (expandableRowScope.hasOwnProperty(property)) {
16518                         $scope[property] = expandableRowScope[property];
16519                       }
16520                     }
16521                   }
16522                   var expandedRowElement = $compile(template)($scope);
16523                   $elm.append(expandedRowElement);
16524                   $scope.row.expandedRendered = true;
16525               });
16526             },
16527
16528             post: function ($scope, $elm, $attrs, uiGridCtrl) {
16529               $scope.$on('$destroy', function() {
16530                 $scope.row.expandedRendered = false;
16531               });
16532             }
16533           };
16534         }
16535       };
16536     }]);
16537
16538   /**
16539    *  @ngdoc directive
16540    *  @name ui.grid.expandable.directive:uiGridRow
16541    *  @description stacks on the uiGridRow directive to add support for expandable rows
16542    */
16543   module.directive('uiGridRow',
16544     ['$compile', 'gridUtil', '$templateCache',
16545       function ($compile, gridUtil, $templateCache) {
16546         return {
16547           priority: -200,
16548           scope: false,
16549           compile: function ($elm, $attrs) {
16550             return {
16551               pre: function ($scope, $elm, $attrs, controllers) {
16552
16553                 $scope.expandableRow = {};
16554
16555                 $scope.expandableRow.shouldRenderExpand = function () {
16556                   var ret = $scope.colContainer.name === 'body' &&  $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
16557                   return ret;
16558                 };
16559
16560                 $scope.expandableRow.shouldRenderFiller = function () {
16561                   var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
16562                   return ret;
16563                 };
16564
16565  /*
16566   * Commented out @PaulL1.  This has no purpose that I can see, and causes #2964.  If this code needs to be reinstated for some
16567   * reason it needs to use drawnWidth, not width, and needs to check column visibility.  It should really use render container
16568   * visible column cache also instead of checking column.renderContainer.
16569                   function updateRowContainerWidth() {
16570                       var grid = $scope.grid;
16571                       var colWidth = 0;
16572                       grid.columns.forEach( function (column) {
16573                           if (column.renderContainer === 'left') {
16574                             colWidth += column.width;
16575                           }
16576                       });
16577                       colWidth = Math.floor(colWidth);
16578                       return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
16579                           ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
16580                           ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
16581                   }
16582
16583                   if ($scope.colContainer.name === 'left') {
16584                       $scope.grid.registerStyleComputation({
16585                           priority: 15,
16586                           func: updateRowContainerWidth
16587                       });
16588                   }*/
16589
16590               },
16591               post: function ($scope, $elm, $attrs, controllers) {
16592               }
16593             };
16594           }
16595         };
16596       }]);
16597
16598   /**
16599    *  @ngdoc directive
16600    *  @name ui.grid.expandable.directive:uiGridViewport
16601    *  @description stacks on the uiGridViewport directive to append the expandable row html elements to the
16602    *  default gridRow template
16603    */
16604   module.directive('uiGridViewport',
16605     ['$compile', 'gridUtil', '$templateCache',
16606       function ($compile, gridUtil, $templateCache) {
16607         return {
16608           priority: -200,
16609           scope: false,
16610           compile: function ($elm, $attrs) {
16611             var rowRepeatDiv = angular.element($elm.children().children()[0]);
16612             var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
16613             var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
16614             rowRepeatDiv.append(expandedRowElement);
16615             rowRepeatDiv.append(expandedRowFillerElement);
16616             return {
16617               pre: function ($scope, $elm, $attrs, controllers) {
16618               },
16619               post: function ($scope, $elm, $attrs, controllers) {
16620               }
16621             };
16622           }
16623         };
16624       }]);
16625
16626 })();
16627
16628 /* global console */
16629
16630 (function () {
16631   'use strict';
16632
16633   /**
16634    * @ngdoc overview
16635    * @name ui.grid.exporter
16636    * @description
16637    *
16638    * # ui.grid.exporter
16639    *
16640    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
16641    *
16642    * This module provides the ability to exporter data from the grid.
16643    *
16644    * Data can be exported in a range of formats, and all data, visible
16645    * data, or selected rows can be exported, with all columns or visible
16646    * columns.
16647    *
16648    * No UI is provided, the caller should provide their own UI/buttons
16649    * as appropriate, or enable the gridMenu
16650    *
16651    * <br/>
16652    * <br/>
16653    *
16654    * <div doc-module-components="ui.grid.exporter"></div>
16655    */
16656
16657   var module = angular.module('ui.grid.exporter', ['ui.grid']);
16658
16659   /**
16660    *  @ngdoc object
16661    *  @name ui.grid.exporter.constant:uiGridExporterConstants
16662    *
16663    *  @description constants available in exporter module
16664    */
16665   /**
16666    * @ngdoc property
16667    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
16668    * @name ALL
16669    * @description export all data, including data not visible.  Can
16670    * be set for either rowTypes or colTypes
16671    */
16672   /**
16673    * @ngdoc property
16674    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
16675    * @name VISIBLE
16676    * @description export only visible data, including data not visible.  Can
16677    * be set for either rowTypes or colTypes
16678    */
16679   /**
16680    * @ngdoc property
16681    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
16682    * @name SELECTED
16683    * @description export all data, including data not visible.  Can
16684    * be set only for rowTypes, selection of only some columns is
16685    * not supported
16686    */
16687   module.constant('uiGridExporterConstants', {
16688     featureName: 'exporter',
16689     ALL: 'all',
16690     VISIBLE: 'visible',
16691     SELECTED: 'selected',
16692     CSV_CONTENT: 'CSV_CONTENT',
16693     BUTTON_LABEL: 'BUTTON_LABEL',
16694     FILE_NAME: 'FILE_NAME'
16695   });
16696
16697   /**
16698    *  @ngdoc service
16699    *  @name ui.grid.exporter.service:uiGridExporterService
16700    *
16701    *  @description Services for exporter feature
16702    */
16703   module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
16704     function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
16705
16706       var service = {
16707
16708         delay: 100,
16709
16710         initializeGrid: function (grid) {
16711
16712           //add feature namespace and any properties to grid for needed state
16713           grid.exporter = {};
16714           this.defaultGridOptions(grid.options);
16715
16716           /**
16717            *  @ngdoc object
16718            *  @name ui.grid.exporter.api:PublicApi
16719            *
16720            *  @description Public Api for exporter feature
16721            */
16722           var publicApi = {
16723             events: {
16724               exporter: {
16725               }
16726             },
16727             methods: {
16728               exporter: {
16729                 /**
16730                  * @ngdoc function
16731                  * @name csvExport
16732                  * @methodOf  ui.grid.exporter.api:PublicApi
16733                  * @description Exports rows from the grid in csv format,
16734                  * the data exported is selected based on the provided options
16735                  * @param {string} rowTypes which rows to export, valid values are
16736                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
16737                  * uiGridExporterConstants.SELECTED
16738                  * @param {string} colTypes which columns to export, valid values are
16739                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
16740                  */
16741                 csvExport: function (rowTypes, colTypes) {
16742                   service.csvExport(grid, rowTypes, colTypes);
16743                 },
16744                 /**
16745                  * @ngdoc function
16746                  * @name pdfExport
16747                  * @methodOf  ui.grid.exporter.api:PublicApi
16748                  * @description Exports rows from the grid in pdf format,
16749                  * the data exported is selected based on the provided options
16750                  * Note that this function has a dependency on pdfMake, all
16751                  * going well this has been installed for you.
16752                  * The resulting pdf opens in a new browser window.
16753                  * @param {string} rowTypes which rows to export, valid values are
16754                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
16755                  * uiGridExporterConstants.SELECTED
16756                  * @param {string} colTypes which columns to export, valid values are
16757                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
16758                  */
16759                 pdfExport: function (rowTypes, colTypes) {
16760                   service.pdfExport(grid, rowTypes, colTypes);
16761                 }
16762               }
16763             }
16764           };
16765
16766           grid.api.registerEventsFromObject(publicApi.events);
16767
16768           grid.api.registerMethodsFromObject(publicApi.methods);
16769
16770           if (grid.api.core.addToGridMenu){
16771             service.addToMenu( grid );
16772           } else {
16773             // order of registration is not guaranteed, register in a little while
16774             $interval( function() {
16775               if (grid.api.core.addToGridMenu){
16776                 service.addToMenu( grid );
16777               }
16778             }, this.delay, 1);
16779           }
16780
16781         },
16782
16783         defaultGridOptions: function (gridOptions) {
16784           //default option to true unless it was explicitly set to false
16785           /**
16786            * @ngdoc object
16787            * @name ui.grid.exporter.api:GridOptions
16788            *
16789            * @description GridOptions for exporter feature, these are available to be
16790            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16791            */
16792           /**
16793            * @ngdoc object
16794            * @name ui.grid.exporter.api:ColumnDef
16795            * @description ColumnDef settings for exporter
16796            */
16797           /**
16798            * @ngdoc object
16799            * @name exporterSuppressMenu
16800            * @propertyOf  ui.grid.exporter.api:GridOptions
16801            * @description Don't show the export menu button, implying the user
16802            * will roll their own UI for calling the exporter
16803            * <br/>Defaults to false
16804            */
16805           gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
16806           /**
16807            * @ngdoc object
16808            * @name exporterMenuLabel
16809            * @propertyOf  ui.grid.exporter.api:GridOptions
16810            * @description The text to show on the exporter menu button
16811            * link
16812            * <br/>Defaults to 'Export'
16813            */
16814           gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
16815           /**
16816            * @ngdoc object
16817            * @name exporterSuppressColumns
16818            * @propertyOf  ui.grid.exporter.api:GridOptions
16819            * @description Columns that should not be exported.  The selectionRowHeader is already automatically
16820            * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
16821            * output then add it in this list.  You should provide an array of column names.
16822            * <br/>Defaults to: []
16823            * <pre>
16824            *   gridOptions.exporterSuppressColumns = [ 'buttons' ];
16825            * </pre>
16826            */
16827           gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
16828           /**
16829            * @ngdoc object
16830            * @name exporterCsvColumnSeparator
16831            * @propertyOf  ui.grid.exporter.api:GridOptions
16832            * @description The character to use as column separator
16833            * link
16834            * <br/>Defaults to ','
16835            */
16836           gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
16837           /**
16838            * @ngdoc object
16839            * @name exporterCsvFilename
16840            * @propertyOf  ui.grid.exporter.api:GridOptions
16841            * @description The default filename to use when saving the downloaded csv.
16842            * This will only work in some browsers.
16843            * <br/>Defaults to 'download.csv'
16844            */
16845           gridOptions.exporterCsvFilename = gridOptions.exporterCsvFilename ? gridOptions.exporterCsvFilename : 'download.csv';
16846           /**
16847            * @ngdoc object
16848            * @name exporterPdfFilename
16849            * @propertyOf  ui.grid.exporter.api:GridOptions
16850            * @description The default filename to use when saving the downloaded pdf, only used in IE (other browsers open pdfs in a new window)
16851            * <br/>Defaults to 'download.pdf'
16852            */
16853           gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
16854           /**
16855            * @ngdoc object
16856            * @name exporterOlderExcelCompatibility
16857            * @propertyOf  ui.grid.exporter.api:GridOptions
16858            * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
16859            * through as ï»¿ in the first column header.  Setting this option to false will suppress this, at the
16860            * expense of proper utf-16 handling in applications that do recognise the BOM
16861            * <br/>Defaults to false
16862            */
16863           gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
16864           /**
16865            * @ngdoc object
16866            * @name exporterPdfDefaultStyle
16867            * @propertyOf  ui.grid.exporter.api:GridOptions
16868            * @description The default style in pdfMake format
16869            * <br/>Defaults to:
16870            * <pre>
16871            *   {
16872            *     fontSize: 11
16873            *   }
16874            * </pre>
16875            */
16876           gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
16877           /**
16878            * @ngdoc object
16879            * @name exporterPdfTableStyle
16880            * @propertyOf  ui.grid.exporter.api:GridOptions
16881            * @description The table style in pdfMake format
16882            * <br/>Defaults to:
16883            * <pre>
16884            *   {
16885            *     margin: [0, 5, 0, 15]
16886            *   }
16887            * </pre>
16888            */
16889           gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
16890           /**
16891            * @ngdoc object
16892            * @name exporterPdfTableHeaderStyle
16893            * @propertyOf  ui.grid.exporter.api:GridOptions
16894            * @description The tableHeader style in pdfMake format
16895            * <br/>Defaults to:
16896            * <pre>
16897            *   {
16898            *     bold: true,
16899            *     fontSize: 12,
16900            *     color: 'black'
16901            *   }
16902            * </pre>
16903            */
16904           gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
16905           /**
16906            * @ngdoc object
16907            * @name exporterPdfHeader
16908            * @propertyOf  ui.grid.exporter.api:GridOptions
16909            * @description The header section for pdf exports.  Can be
16910            * simple text:
16911            * <pre>
16912            *   gridOptions.exporterPdfHeader = 'My Header';
16913            * </pre>
16914            * Can be a more complex object in pdfMake format:
16915            * <pre>
16916            *   gridOptions.exporterPdfHeader = {
16917            *     columns: [
16918            *       'Left part',
16919            *       { text: 'Right part', alignment: 'right' }
16920            *     ]
16921            *   };
16922            * </pre>
16923            * Or can be a function, allowing page numbers and the like
16924            * <pre>
16925            *   gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
16926            * </pre>
16927            */
16928           gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
16929           /**
16930            * @ngdoc object
16931            * @name exporterPdfFooter
16932            * @propertyOf  ui.grid.exporter.api:GridOptions
16933            * @description The header section for pdf exports.  Can be
16934            * simple text:
16935            * <pre>
16936            *   gridOptions.exporterPdfFooter = 'My Footer';
16937            * </pre>
16938            * Can be a more complex object in pdfMake format:
16939            * <pre>
16940            *   gridOptions.exporterPdfFooter = {
16941            *     columns: [
16942            *       'Left part',
16943            *       { text: 'Right part', alignment: 'right' }
16944            *     ]
16945            *   };
16946            * </pre>
16947            * Or can be a function, allowing page numbers and the like
16948            * <pre>
16949            *   gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
16950            * </pre>
16951            */
16952           gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
16953           /**
16954            * @ngdoc object
16955            * @name exporterPdfOrientation
16956            * @propertyOf  ui.grid.exporter.api:GridOptions
16957            * @description The orientation, should be a valid pdfMake value,
16958            * 'landscape' or 'portrait'
16959            * <br/>Defaults to landscape
16960            */
16961           gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
16962           /**
16963            * @ngdoc object
16964            * @name exporterPdfPageSize
16965            * @propertyOf  ui.grid.exporter.api:GridOptions
16966            * @description The orientation, should be a valid pdfMake
16967            * paper size, usually 'A4' or 'LETTER'
16968            * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
16969            * <br/>Defaults to A4
16970            */
16971           gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
16972           /**
16973            * @ngdoc object
16974            * @name exporterPdfMaxGridWidth
16975            * @propertyOf  ui.grid.exporter.api:GridOptions
16976            * @description The maxium grid width - the current grid width
16977            * will be scaled to match this, with any fixed width columns
16978            * being adjusted accordingly.
16979            * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
16980            */
16981           gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
16982           /**
16983            * @ngdoc object
16984            * @name exporterPdfTableLayout
16985            * @propertyOf  ui.grid.exporter.api:GridOptions
16986            * @description A tableLayout in pdfMake format,
16987            * controls gridlines and the like.  We use the default
16988            * layout usually.
16989            * <br/>Defaults to null, which means no layout
16990            */
16991
16992           /**
16993            * @ngdoc object
16994            * @name exporterMenuAllData
16995            * @porpertyOf  ui.grid.exporter.api:GridOptions
16996            * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
16997            */
16998           gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
16999
17000           /**
17001            * @ngdoc object
17002            * @name exporterMenuCsv
17003            * @propertyOf  ui.grid.exporter.api:GridOptions
17004            * @description Add csv export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17005            */
17006           gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
17007
17008           /**
17009            * @ngdoc object
17010            * @name exporterMenuPdf
17011            * @propertyOf  ui.grid.exporter.api:GridOptions
17012            * @description Add pdf export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17013            */
17014           gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
17015
17016           /**
17017            * @ngdoc object
17018            * @name exporterPdfCustomFormatter
17019            * @propertyOf  ui.grid.exporter.api:GridOptions
17020            * @description A custom callback routine that changes the pdf document, adding any
17021            * custom styling or content that is supported by pdfMake.  Takes in the complete docDefinition, and
17022            * must return an updated docDefinition ready for pdfMake.
17023            * @example
17024            * In this example we add a style to the style array, so that we can use it in our
17025            * footer definition.
17026            * <pre>
17027            *   gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
17028            *     docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
17029            *     return docDefinition;
17030            *   }
17031            *
17032            *   gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
17033            * </pre>
17034            */
17035           gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
17036
17037           /**
17038            * @ngdoc object
17039            * @name exporterHeaderFilterUseName
17040            * @propertyOf  ui.grid.exporter.api:GridOptions
17041            * @description Defaults to false, which leads to `displayName` being passed into the headerFilter.
17042            * If set to true, then will pass `name` instead.
17043            *
17044            *
17045            * @example
17046            * <pre>
17047            *   gridOptions.exporterHeaderFilterUseName = true;
17048            * </pre>
17049            */
17050           gridOptions.exporterHeaderFilterUseName = gridOptions.exporterHeaderFilterUseName === true;
17051
17052           /**
17053            * @ngdoc object
17054            * @name exporterHeaderFilter
17055            * @propertyOf  ui.grid.exporter.api:GridOptions
17056            * @description A function to apply to the header displayNames before exporting.  Useful for internationalisation,
17057            * for example if you were using angular-translate you'd set this to `$translate.instant`.  Note that this
17058            * call must be synchronous, it cannot be a call that returns a promise.
17059            *
17060            * Behaviour can be changed to pass in `name` instead of `displayName` through use of `exporterHeaderFilterUseName: true`.
17061            *
17062            * @example
17063            * <pre>
17064            *   gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
17065            * </pre>
17066            * OR
17067            * <pre>
17068            *   gridOptions.exporterHeaderFilter = $translate.instant;
17069            * </pre>
17070            */
17071
17072           /**
17073            * @ngdoc function
17074            * @name exporterFieldCallback
17075            * @propertyOf  ui.grid.exporter.api:GridOptions
17076            * @description A function to call for each field before exporting it.  Allows
17077            * massaging of raw data into a display format, for example if you have applied
17078            * filters to convert codes into decodes, or you require
17079            * a specific date format in the exported content.
17080            *
17081            * The method is called once for each field exported, and provides the grid, the
17082            * gridCol and the GridRow for you to use as context in massaging the data.
17083            *
17084            * @param {Grid} grid provides the grid in case you have need of it
17085            * @param {GridRow} row the row from which the data comes
17086            * @param {GridCol} col the column from which the data comes
17087            * @param {object} value the value for your massaging
17088            * @returns {object} you must return the massaged value ready for exporting
17089            *
17090            * @example
17091            * <pre>
17092            *   gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
17093            *     if ( col.name === 'status' ){
17094            *       value = decodeStatus( value );
17095            *     }
17096            *     return value;
17097            *   }
17098            * </pre>
17099            */
17100           gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
17101
17102           /**
17103            * @ngdoc function
17104            * @name exporterAllDataFn
17105            * @propertyOf  ui.grid.exporter.api:GridOptions
17106            * @description This promise is needed when exporting all rows,
17107            * and the data need to be provided by server side. Default is null.
17108            * @returns {Promise} a promise to load all data from server
17109            *
17110            * @example
17111            * <pre>
17112            *   gridOptions.exporterAllDataFn = function () {
17113            *     return $http.get('/data/100.json')
17114            *   }
17115            * </pre>
17116            */
17117           gridOptions.exporterAllDataFn = gridOptions.exporterAllDataFn ? gridOptions.exporterAllDataFn : null;
17118
17119           /**
17120            * @ngdoc function
17121            * @name exporterAllDataPromise
17122            * @propertyOf  ui.grid.exporter.api:GridOptions
17123            * @description DEPRECATED - exporterAllDataFn used to be
17124            * called this, but it wasn't a promise, it was a function that returned
17125            * a promise.  Deprecated, but supported for backward compatibility, use
17126            * exporterAllDataFn instead.
17127            * @returns {Promise} a promise to load all data from server
17128            *
17129            * @example
17130            * <pre>
17131            *   gridOptions.exporterAllDataFn = function () {
17132            *     return $http.get('/data/100.json')
17133            *   }
17134            * </pre>
17135            */
17136           if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
17137             gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
17138           }
17139         },
17140
17141
17142         /**
17143          * @ngdoc function
17144          * @name addToMenu
17145          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17146          * @description Adds export items to the grid menu,
17147          * allowing the user to select export options
17148          * @param {Grid} grid the grid from which data should be exported
17149          */
17150         addToMenu: function ( grid ) {
17151           grid.api.core.addToGridMenu( grid, [
17152             {
17153               title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
17154               action: function ($event) {
17155                 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17156               },
17157               shown: function() {
17158                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
17159               },
17160               order: 200
17161             },
17162             {
17163               title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
17164               action: function ($event) {
17165                 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17166               },
17167               shown: function() {
17168                 return this.grid.options.exporterMenuCsv;
17169               },
17170               order: 201
17171             },
17172             {
17173               title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
17174               action: function ($event) {
17175                 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17176               },
17177               shown: function() {
17178                 return this.grid.options.exporterMenuCsv &&
17179                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17180               },
17181               order: 202
17182             },
17183             {
17184               title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
17185               action: function ($event) {
17186                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17187               },
17188               shown: function() {
17189                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
17190               },
17191               order: 203
17192             },
17193             {
17194               title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
17195               action: function ($event) {
17196                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17197               },
17198               shown: function() {
17199                 return this.grid.options.exporterMenuPdf;
17200               },
17201               order: 204
17202             },
17203             {
17204               title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
17205               action: function ($event) {
17206                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17207               },
17208               shown: function() {
17209                 return this.grid.options.exporterMenuPdf &&
17210                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17211               },
17212               order: 205
17213             }
17214           ]);
17215         },
17216
17217
17218         /**
17219          * @ngdoc function
17220          * @name csvExport
17221          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17222          * @description Exports rows from the grid in csv format,
17223          * the data exported is selected based on the provided options
17224          * @param {Grid} grid the grid from which data should be exported
17225          * @param {string} rowTypes which rows to export, valid values are
17226          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17227          * uiGridExporterConstants.SELECTED
17228          * @param {string} colTypes which columns to export, valid values are
17229          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17230          * uiGridExporterConstants.SELECTED
17231          */
17232         csvExport: function (grid, rowTypes, colTypes) {
17233           var self = this;
17234           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
17235             var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
17236             var exportData = self.getData(grid, rowTypes, colTypes);
17237             var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
17238
17239             self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterOlderExcelCompatibility);
17240           });
17241         },
17242
17243         /**
17244          * @ngdoc function
17245          * @name loadAllDataIfNeeded
17246          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17247          * @description When using server side pagination, use exporterAllDataFn to
17248          * load all data before continuing processing.
17249          * When using client side pagination, return a resolved promise so processing
17250          * continues immediately
17251          * @param {Grid} grid the grid from which data should be exported
17252          * @param {string} rowTypes which rows to export, valid values are
17253          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17254          * uiGridExporterConstants.SELECTED
17255          * @param {string} colTypes which columns to export, valid values are
17256          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17257          * uiGridExporterConstants.SELECTED
17258          */
17259         loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
17260           if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
17261             return grid.options.exporterAllDataFn()
17262               .then(function() {
17263                 grid.modifyRows(grid.options.data);
17264               });
17265           } else {
17266             var deferred = $q.defer();
17267             deferred.resolve();
17268             return deferred.promise;
17269           }
17270         },
17271
17272         /**
17273          * @ngdoc property
17274          * @propertyOf ui.grid.exporter.api:ColumnDef
17275          * @name exporterSuppressExport
17276          * @description Suppresses export for this column.  Used by selection and expandable.
17277          */
17278
17279         /**
17280          * @ngdoc function
17281          * @name getColumnHeaders
17282          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17283          * @description Gets the column headers from the grid to use
17284          * as a title row for the exported file, all headers have
17285          * headerCellFilters applied as appropriate.
17286          *
17287          * Column headers are an array of objects, each object has
17288          * name, displayName, width and align attributes.  Only name is
17289          * used for csv, all attributes are used for pdf.
17290          *
17291          * @param {Grid} grid the grid from which data should be exported
17292          * @param {string} colTypes which columns to export, valid values are
17293          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17294          * uiGridExporterConstants.SELECTED
17295          */
17296         getColumnHeaders: function (grid, colTypes) {
17297           var headers = [];
17298           var columns;
17299
17300           if ( colTypes === uiGridExporterConstants.ALL ){
17301             columns = grid.columns;
17302           } else {
17303             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17304             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17305             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17306
17307             columns = leftColumns.concat(bodyColumns,rightColumns);
17308           }
17309
17310           columns.forEach( function( gridCol, index ) {
17311             if ( gridCol.colDef.exporterSuppressExport !== true &&
17312                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17313               headers.push({
17314                 name: gridCol.field,
17315                 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
17316                 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
17317                 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
17318               });
17319             }
17320           });
17321
17322           return headers;
17323         },
17324
17325
17326         /**
17327          * @ngdoc property
17328          * @propertyOf ui.grid.exporter.api:ColumnDef
17329          * @name exporterPdfAlign
17330          * @description the alignment you'd like for this specific column when
17331          * exported into a pdf.  Can be 'left', 'right', 'center' or any other
17332          * valid pdfMake alignment option.
17333          */
17334
17335
17336         /**
17337          * @ngdoc object
17338          * @name ui.grid.exporter.api:GridRow
17339          * @description GridRow settings for exporter
17340          */
17341         /**
17342          * @ngdoc object
17343          * @name exporterEnableExporting
17344          * @propertyOf  ui.grid.exporter.api:GridRow
17345          * @description If set to false, then don't export this row, notwithstanding visible or
17346          * other settings
17347          * <br/>Defaults to true
17348          */
17349
17350         /**
17351          * @ngdoc function
17352          * @name getData
17353          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17354          * @description Gets data from the grid based on the provided options,
17355          * all cells have cellFilters applied as appropriate.  Any rows marked
17356          * `exporterEnableExporting: false` will not be exported
17357          * @param {Grid} grid the grid from which data should be exported
17358          * @param {string} rowTypes which rows to export, valid values are
17359          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17360          * uiGridExporterConstants.SELECTED
17361          * @param {string} colTypes which columns to export, valid values are
17362          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17363          * uiGridExporterConstants.SELECTED
17364          */
17365         getData: function (grid, rowTypes, colTypes) {
17366           var data = [];
17367           var rows;
17368           var columns;
17369
17370           switch ( rowTypes ) {
17371             case uiGridExporterConstants.ALL:
17372               rows = grid.rows;
17373               break;
17374             case uiGridExporterConstants.VISIBLE:
17375               rows = grid.getVisibleRows();
17376               break;
17377             case uiGridExporterConstants.SELECTED:
17378               if ( grid.api.selection ){
17379                 rows = grid.api.selection.getSelectedGridRows();
17380               } else {
17381                 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
17382               }
17383               break;
17384           }
17385
17386           if ( colTypes === uiGridExporterConstants.ALL ){
17387             columns = grid.columns;
17388           } else {
17389             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17390             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17391             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17392
17393             columns = leftColumns.concat(bodyColumns,rightColumns);
17394           }
17395
17396           rows.forEach( function( row, index ) {
17397
17398             if (row.exporterEnableExporting !== false) {
17399               var extractedRow = [];
17400
17401
17402               columns.forEach( function( gridCol, index ) {
17403               if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
17404                    gridCol.colDef.exporterSuppressExport !== true &&
17405                    grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17406                   var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, grid.getCellValue( row, gridCol ) ) };
17407                   if ( gridCol.colDef.exporterPdfAlign ) {
17408                     extractedField.alignment = gridCol.colDef.exporterPdfAlign;
17409                   }
17410                   extractedRow.push(extractedField);
17411                 }
17412               });
17413
17414               data.push(extractedRow);
17415             }
17416           });
17417
17418           return data;
17419         },
17420
17421
17422         /**
17423          * @ngdoc function
17424          * @name formatAsCSV
17425          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17426          * @description Formats the column headers and data as a CSV,
17427          * and sends that data to the user
17428          * @param {array} exportColumnHeaders an array of column headers,
17429          * where each header is an object with name, width and maybe alignment
17430          * @param {array} exportData an array of rows, where each row is
17431          * an array of column data
17432          * @returns {string} csv the formatted csv as a string
17433          */
17434         formatAsCsv: function (exportColumnHeaders, exportData, separator) {
17435           var self = this;
17436
17437           var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
17438
17439           var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
17440
17441           csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
17442
17443           return csv;
17444         },
17445
17446         /**
17447          * @ngdoc function
17448          * @name formatRowAsCsv
17449          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17450          * @description Renders a single field as a csv field, including
17451          * quotes around the value
17452          * @param {exporterService} exporter pass in exporter
17453          * @param {array} row the row to be turned into a csv string
17454          * @returns {string} a csv-ified version of the row
17455          */
17456         formatRowAsCsv: function (exporter, separator) {
17457           return function (row) {
17458             return row.map(exporter.formatFieldAsCsv).join(separator);
17459           };
17460         },
17461
17462         /**
17463          * @ngdoc function
17464          * @name formatFieldAsCsv
17465          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17466          * @description Renders a single field as a csv field, including
17467          * quotes around the value
17468          * @param {field} field the field to be turned into a csv string,
17469          * may be of any type
17470          * @returns {string} a csv-ified version of the field
17471          */
17472         formatFieldAsCsv: function (field) {
17473           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
17474             return '';
17475           }
17476           if (typeof(field.value) === 'number') {
17477             return field.value;
17478           }
17479           if (typeof(field.value) === 'boolean') {
17480             return (field.value ? 'TRUE' : 'FALSE') ;
17481           }
17482           if (typeof(field.value) === 'string') {
17483             return '"' + field.value.replace(/"/g,'""') + '"';
17484           }
17485
17486           return JSON.stringify(field.value);
17487         },
17488
17489
17490         /**
17491          * @ngdoc function
17492          * @name isIE
17493          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17494          * @description Checks whether current browser is IE and returns it's version if it is
17495         */
17496         isIE: function () {
17497           var match = navigator.userAgent.match(/(?:MSIE |Trident\/.*; rv:)(\d+)/);
17498           return match ? parseInt(match[1]) : false;
17499         },
17500
17501
17502         /**
17503          * @ngdoc function
17504          * @name downloadFile
17505          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17506          * @description Triggers download of a csv file.  Logic provided
17507          * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
17508          * @param {string} fileName the filename we'd like our file to be
17509          * given
17510          * @param {string} csvContent the csv content that we'd like to
17511          * download as a file
17512          * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
17513          */
17514         downloadFile: function (fileName, csvContent, exporterOlderExcelCompatibility) {
17515           var D = document;
17516           var a = D.createElement('a');
17517           var strMimeType = 'application/octet-stream;charset=utf-8';
17518           var rawFile;
17519           var ieVersion;
17520
17521           ieVersion = this.isIE();
17522           if (ieVersion && ieVersion < 10) {
17523             var frame = D.createElement('iframe');
17524             document.body.appendChild(frame);
17525
17526             frame.contentWindow.document.open("text/html", "replace");
17527             frame.contentWindow.document.write('sep=,\r\n' + csvContent);
17528             frame.contentWindow.document.close();
17529             frame.contentWindow.focus();
17530             frame.contentWindow.document.execCommand('SaveAs', true, fileName);
17531
17532             document.body.removeChild(frame);
17533             return true;
17534           }
17535
17536           // IE10+
17537           if (navigator.msSaveBlob) {
17538             return navigator.msSaveOrOpenBlob(
17539               new Blob(
17540                 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
17541                 { type: strMimeType } ),
17542               fileName
17543             );
17544           }
17545
17546           //html5 A[download]
17547           if ('download' in a) {
17548             var blob = new Blob(
17549               [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
17550               { type: strMimeType }
17551             );
17552             rawFile = URL.createObjectURL(blob);
17553             a.setAttribute('download', fileName);
17554           } else {
17555             rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
17556             a.setAttribute('target', '_blank');
17557           }
17558
17559           a.href = rawFile;
17560           a.setAttribute('style', 'display:none;');
17561           D.body.appendChild(a);
17562           setTimeout(function() {
17563             if (a.click) {
17564               a.click();
17565               // Workaround for Safari 5
17566             } else if (document.createEvent) {
17567               var eventObj = document.createEvent('MouseEvents');
17568               eventObj.initEvent('click', true, true);
17569               a.dispatchEvent(eventObj);
17570             }
17571             D.body.removeChild(a);
17572
17573           }, this.delay);
17574         },
17575
17576         /**
17577          * @ngdoc function
17578          * @name pdfExport
17579          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17580          * @description Exports rows from the grid in pdf format,
17581          * the data exported is selected based on the provided options.
17582          * Note that this function has a dependency on pdfMake, which must
17583          * be installed.  The resulting pdf opens in a new
17584          * browser window.
17585          * @param {Grid} grid the grid from which data should be exported
17586          * @param {string} rowTypes which rows to export, valid values are
17587          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17588          * uiGridExporterConstants.SELECTED
17589          * @param {string} colTypes which columns to export, valid values are
17590          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17591          * uiGridExporterConstants.SELECTED
17592          */
17593         pdfExport: function (grid, rowTypes, colTypes) {
17594           var self = this;
17595           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
17596             var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
17597             var exportData = self.getData(grid, rowTypes, colTypes);
17598             var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
17599
17600             if (self.isIE()) {
17601               self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
17602             } else {
17603               pdfMake.createPdf(docDefinition).open();
17604             }
17605           });
17606         },
17607
17608
17609         /**
17610          * @ngdoc function
17611          * @name downloadPdf
17612          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17613          * @description Generates and retrieves the pdf as a blob, then downloads
17614          * it as a file.  Only used in IE, in all other browsers we use the native
17615          * pdfMake.open function to just open the PDF
17616          * @param {string} fileName the filename to give to the pdf, can be set
17617          * through exporterPdfFilename
17618          * @param {object} docDefinition a pdf docDefinition that we can generate
17619          * and get a blob from
17620          */
17621         downloadPDF: function (fileName, docDefinition) {
17622           var D = document;
17623           var a = D.createElement('a');
17624           var strMimeType = 'application/octet-stream;charset=utf-8';
17625           var rawFile;
17626           var ieVersion;
17627
17628           ieVersion = this.isIE();
17629           var doc = pdfMake.createPdf(docDefinition);
17630           var blob;
17631
17632           doc.getBuffer( function (buffer) {
17633             blob = new Blob([buffer]);
17634
17635             if (ieVersion && ieVersion < 10) {
17636               var frame = D.createElement('iframe');
17637               document.body.appendChild(frame);
17638
17639               frame.contentWindow.document.open("text/html", "replace");
17640               frame.contentWindow.document.write(blob);
17641               frame.contentWindow.document.close();
17642               frame.contentWindow.focus();
17643               frame.contentWindow.document.execCommand('SaveAs', true, fileName);
17644
17645               document.body.removeChild(frame);
17646               return true;
17647             }
17648
17649             // IE10+
17650             if (navigator.msSaveBlob) {
17651               return navigator.msSaveBlob(
17652                 blob, fileName
17653               );
17654             }
17655           });
17656         },
17657
17658
17659         /**
17660          * @ngdoc function
17661          * @name renderAsPdf
17662          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17663          * @description Renders the data into a pdf, and opens that pdf.
17664          *
17665          * @param {Grid} grid the grid from which data should be exported
17666          * @param {array} exportColumnHeaders an array of column headers,
17667          * where each header is an object with name, width and maybe alignment
17668          * @param {array} exportData an array of rows, where each row is
17669          * an array of column data
17670          * @returns {object} a pdfMake format document definition, ready
17671          * for generation
17672          */
17673         prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
17674           var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
17675
17676           var headerColumns = exportColumnHeaders.map( function( header ) {
17677             return { text: header.displayName, style: 'tableHeader' };
17678           });
17679
17680           var stringData = exportData.map(this.formatRowAsPdf(this));
17681
17682           var allData = [headerColumns].concat(stringData);
17683
17684           var docDefinition = {
17685             pageOrientation: grid.options.exporterPdfOrientation,
17686             pageSize: grid.options.exporterPdfPageSize,
17687             content: [{
17688               style: 'tableStyle',
17689               table: {
17690                 headerRows: 1,
17691                 widths: headerWidths,
17692                 body: allData
17693               }
17694             }],
17695             styles: {
17696               tableStyle: grid.options.exporterPdfTableStyle,
17697               tableHeader: grid.options.exporterPdfTableHeaderStyle
17698             },
17699             defaultStyle: grid.options.exporterPdfDefaultStyle
17700           };
17701
17702           if ( grid.options.exporterPdfLayout ){
17703             docDefinition.layout = grid.options.exporterPdfLayout;
17704           }
17705
17706           if ( grid.options.exporterPdfHeader ){
17707             docDefinition.header = grid.options.exporterPdfHeader;
17708           }
17709
17710           if ( grid.options.exporterPdfFooter ){
17711             docDefinition.footer = grid.options.exporterPdfFooter;
17712           }
17713
17714           if ( grid.options.exporterPdfCustomFormatter ){
17715             docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
17716           }
17717           return docDefinition;
17718
17719         },
17720
17721
17722         /**
17723          * @ngdoc function
17724          * @name calculatePdfHeaderWidths
17725          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17726          * @description Determines the column widths base on the
17727          * widths we got from the grid.  If the column is drawn
17728          * then we have a drawnWidth.  If the column is not visible
17729          * then we have '*', 'x%' or a width.  When columns are
17730          * not visible they don't contribute to the overall gridWidth,
17731          * so we need to adjust to allow for extra columns
17732          *
17733          * Our basic heuristic is to take the current gridWidth, plus
17734          * numeric columns and call this the base gridwidth.
17735          *
17736          * To that we add 100 for any '*' column, and x% of the base gridWidth
17737          * for any column that is a %
17738          *
17739          * @param {Grid} grid the grid from which data should be exported
17740          * @param {array} exportHeaders array of header information
17741          * @returns {object} an array of header widths
17742          */
17743         calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
17744           var baseGridWidth = 0;
17745           exportHeaders.forEach( function(value){
17746             if (typeof(value.width) === 'number'){
17747               baseGridWidth += value.width;
17748             }
17749           });
17750
17751           var extraColumns = 0;
17752           exportHeaders.forEach( function(value){
17753             if (value.width === '*'){
17754               extraColumns += 100;
17755             }
17756             if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
17757               var percent = parseInt(value.width.match(/(\d)*%/)[0]);
17758
17759               value.width = baseGridWidth * percent / 100;
17760               extraColumns += value.width;
17761             }
17762           });
17763
17764           var gridWidth = baseGridWidth + extraColumns;
17765
17766           return exportHeaders.map(function( header ) {
17767             return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
17768           });
17769
17770         },
17771
17772         /**
17773          * @ngdoc function
17774          * @name formatRowAsPdf
17775          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17776          * @description Renders a row in a format consumable by PDF,
17777          * mainly meaning casting everything to a string
17778          * @param {exporterService} exporter pass in exporter
17779          * @param {array} row the row to be turned into a csv string
17780          * @returns {string} a csv-ified version of the row
17781          */
17782         formatRowAsPdf: function ( exporter ) {
17783           return function( row ) {
17784             return row.map(exporter.formatFieldAsPdfString);
17785           };
17786         },
17787
17788
17789         /**
17790          * @ngdoc function
17791          * @name formatFieldAsCsv
17792          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17793          * @description Renders a single field as a pdf-able field, which
17794          * is different from a csv field only in that strings don't have quotes
17795          * around them
17796          * @param {field} field the field to be turned into a pdf string,
17797          * may be of any type
17798          * @returns {string} a string-ified version of the field
17799          */
17800         formatFieldAsPdfString: function (field) {
17801           var returnVal;
17802           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
17803             returnVal = '';
17804           } else if (typeof(field.value) === 'number') {
17805             returnVal = field.value.toString();
17806           } else if (typeof(field.value) === 'boolean') {
17807             returnVal = (field.value ? 'TRUE' : 'FALSE') ;
17808           } else if (typeof(field.value) === 'string') {
17809             returnVal = field.value.replace(/"/g,'""');
17810           } else {
17811             returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
17812           }
17813
17814           if (field.alignment && typeof(field.alignment) === 'string' ){
17815             returnVal = { text: returnVal, alignment: field.alignment };
17816           }
17817
17818           return returnVal;
17819         }
17820       };
17821
17822       return service;
17823
17824     }
17825   ]);
17826
17827   /**
17828    *  @ngdoc directive
17829    *  @name ui.grid.exporter.directive:uiGridExporter
17830    *  @element div
17831    *  @restrict A
17832    *
17833    *  @description Adds exporter features to grid
17834    *
17835    *  @example
17836    <example module="app">
17837    <file name="app.js">
17838    var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
17839
17840    app.controller('MainCtrl', ['$scope', function ($scope) {
17841       $scope.data = [
17842         { name: 'Bob', title: 'CEO' },
17843             { name: 'Frank', title: 'Lowly Developer' }
17844       ];
17845
17846       $scope.gridOptions = {
17847         enableGridMenu: true,
17848         exporterMenuCsv: false,
17849         columnDefs: [
17850           {name: 'name', enableCellEdit: true},
17851           {name: 'title', enableCellEdit: true}
17852         ],
17853         data: $scope.data
17854       };
17855     }]);
17856    </file>
17857    <file name="index.html">
17858    <div ng-controller="MainCtrl">
17859    <div ui-grid="gridOptions" ui-grid-exporter></div>
17860    </div>
17861    </file>
17862    </example>
17863    */
17864   module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
17865     function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
17866       return {
17867         replace: true,
17868         priority: 0,
17869         require: '^uiGrid',
17870         scope: false,
17871         link: function ($scope, $elm, $attrs, uiGridCtrl) {
17872           uiGridExporterService.initializeGrid(uiGridCtrl.grid);
17873           uiGridCtrl.grid.exporter.$scope = $scope;
17874         }
17875       };
17876     }
17877   ]);
17878 })();
17879
17880 (function () {
17881   'use strict';
17882
17883   /**
17884    * @ngdoc overview
17885    * @name ui.grid.grouping
17886    * @description
17887    *
17888    * # ui.grid.grouping
17889    *
17890    * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
17891    *
17892    * This module provides grouping of rows based on the data in them, similar
17893    * in concept to excel grouping.  You can group multiple columns, resulting in
17894    * nested grouping.
17895    *
17896    * In concept this feature is similar to sorting + grid footer/aggregation, it
17897    * sorts the data based on the grouped columns, then creates group rows that
17898    * reflect a break in the data.  Each of those group rows can have aggregations for
17899    * the data within that group.
17900    *
17901    * This feature leverages treeBase to provide the tree functionality itself,
17902    * the key thing this feature does therefore is to set treeLevels on the rows
17903    * and insert the group headers.
17904    *
17905    * Design information:
17906    * -------------------
17907    *
17908    * Each column will get new menu items - group by, and aggregate by.  Group by
17909    * will cause this column to be sorted (if not already), and will move this column
17910    * to the front of the sorted columns (i.e. grouped columns take precedence over
17911    * sorted columns).  It will respect the sort order already set if there is one,
17912    * and it will allow the sorting logic to change that sort order, it just forces
17913    * the column to the front of the sorting.  You can group by multiple columns, the
17914    * logic will add this column to the sorting after any already grouped columns.
17915    *
17916    * Once a grouping is defined, grouping logic is added to the rowsProcessors.  This
17917    * will process the rows, identifying a break in the data value, and inserting a grouping row.
17918    * Grouping rows have specific attributes on them:
17919    *
17920    *  - internalRow = true: tells us that this isn't a real row, so we can ignore it
17921    *    from any processing that it looking at core data rows.  This is used by the core
17922    *    logic (or will be one day), as it's not grouping specific
17923    *  - groupHeader = true: tells us this is a groupHeader.  This is used by the grouping logic
17924    *    to know if this is a groupHeader row or not
17925    *
17926    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
17927    * row order or filtering or anything like that is changed.  In order to avoid the row instantiation
17928    * time, and to preserve state across invocations, we hold a cache of the rows that we created
17929    * last time, and we use them again this time if we can.
17930    *
17931    * By default rows are collapsed, which means all data rows have their visible property
17932    * set to false, and only level 0 group rows are set to visible.
17933    *
17934    * <br/>
17935    * <br/>
17936    *
17937    * <div doc-module-components="ui.grid.grouping"></div>
17938    */
17939
17940   var module = angular.module('ui.grid.grouping', ['ui.grid', 'ui.grid.treeBase']);
17941
17942   /**
17943    *  @ngdoc object
17944    *  @name ui.grid.grouping.constant:uiGridGroupingConstants
17945    *
17946    *  @description constants available in grouping module, this includes
17947    *  all the constants declared in the treeBase module (these are manually copied
17948    *  as there isn't an easy way to include constants in another constants file, and
17949    *  we don't want to make users include treeBase)
17950    *
17951    */
17952   module.constant('uiGridGroupingConstants', {
17953     featureName: "grouping",
17954     rowHeaderColName: 'treeBaseRowHeaderCol',
17955     EXPANDED: 'expanded',
17956     COLLAPSED: 'collapsed',
17957     aggregation: {
17958       COUNT: 'count',
17959       SUM: 'sum',
17960       MAX: 'max',
17961       MIN: 'min',
17962       AVG: 'avg'
17963     }
17964   });
17965
17966   /**
17967    *  @ngdoc service
17968    *  @name ui.grid.grouping.service:uiGridGroupingService
17969    *
17970    *  @description Services for grouping features
17971    */
17972   module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
17973   function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
17974
17975     var service = {
17976
17977       initializeGrid: function (grid, $scope) {
17978         uiGridTreeBaseService.initializeGrid( grid, $scope );
17979
17980         //add feature namespace and any properties to grid for needed
17981         /**
17982          *  @ngdoc object
17983          *  @name ui.grid.grouping.grid:grouping
17984          *
17985          *  @description Grid properties and functions added for grouping
17986          */
17987         grid.grouping = {};
17988
17989         /**
17990          *  @ngdoc property
17991          *  @propertyOf ui.grid.grouping.grid:grouping
17992          *  @name groupHeaderCache
17993          *
17994          *  @description Cache that holds the group header rows we created last time, we'll
17995          *  reuse these next time, not least because they hold our expanded states.
17996          *
17997          *  We need to take care with these that they don't become a memory leak, we
17998          *  create a new cache each time using the values from the old cache.  This works
17999          *  so long as we're creating group rows for invisible rows as well.
18000          *
18001          *  The cache is a nested hash, indexed on the value we grouped by.  So if we
18002          *  grouped by gender then age, we'd maybe have something like:
18003          *  ```
18004          *    {
18005          *      male: {
18006          *        row: <pointer to the old row>,
18007          *        children: {
18008          *          22: { row: <pointer to the old row> },
18009          *          31: { row: <pointer to the old row> }
18010          *      },
18011          *      female: {
18012          *        row: <pointer to the old row>,
18013          *        children: {
18014          *          28: { row: <pointer to the old row> },
18015          *          55: { row: <pointer to the old row> }
18016          *      }
18017          *    }
18018          *  ```
18019          *
18020          *  We create new rows for any missing rows, this means that they come in as collapsed.
18021          *
18022          */
18023         grid.grouping.groupHeaderCache = {};
18024
18025         service.defaultGridOptions(grid.options);
18026
18027         grid.registerRowsProcessor(service.groupRows, 400);
18028
18029         grid.registerColumnBuilder( service.groupingColumnBuilder);
18030
18031         grid.registerColumnsProcessor(service.groupingColumnProcessor, 400);
18032
18033         /**
18034          *  @ngdoc object
18035          *  @name ui.grid.grouping.api:PublicApi
18036          *
18037          *  @description Public Api for grouping feature
18038          */
18039         var publicApi = {
18040           events: {
18041             grouping: {
18042               /**
18043                * @ngdoc event
18044                * @eventOf ui.grid.grouping.api:PublicApi
18045                * @name aggregationChanged
18046                * @description raised whenever aggregation is changed, added or removed from a column
18047                *
18048                * <pre>
18049                *      gridApi.grouping.on.aggregationChanged(scope,function(col){})
18050                * </pre>
18051                * @param {gridCol} col the column which on which aggregation changed. The aggregation
18052                * type is available as `col.treeAggregation.type`
18053                */
18054               aggregationChanged: {},
18055               /**
18056                * @ngdoc event
18057                * @eventOf ui.grid.grouping.api:PublicApi
18058                * @name groupingChanged
18059                * @description raised whenever the grouped columns changes
18060                *
18061                * <pre>
18062                *      gridApi.grouping.on.groupingChanged(scope,function(col){})
18063                * </pre>
18064                * @param {gridCol} col the column which on which grouping changed. The new grouping is
18065                * available as `col.grouping`
18066                */
18067               groupingChanged: {}
18068             }
18069           },
18070           methods: {
18071             grouping: {
18072               /**
18073                * @ngdoc function
18074                * @name getGrouping
18075                * @methodOf  ui.grid.grouping.api:PublicApi
18076                * @description Get the grouping configuration for this grid,
18077                * used by the saveState feature.  Adds expandedState to the information
18078                * provided by the internal getGrouping, and removes any aggregations that have a source
18079                * of grouping (i.e. will be automatically reapplied when we regroup the column)
18080                * Returned grouping is an object
18081                *   `{ grouping: groupArray, treeAggregations: aggregateArray, expandedState: hash }`
18082                * where grouping contains an array of objects:
18083                *   `{ field: column.field, colName: column.name, groupPriority: column.grouping.groupPriority }`
18084                * and aggregations contains an array of objects:
18085                *   `{ field: column.field, colName: column.name, aggregation: column.grouping.aggregation }`
18086                * and expandedState is a hash of the currently expanded nodes
18087                *
18088                * The groupArray will be sorted by groupPriority.
18089                *
18090                * @param {boolean} getExpanded whether or not to return the expanded state
18091                * @returns {object} grouping configuration
18092                */
18093               getGrouping: function ( getExpanded ) {
18094                 var grouping = service.getGrouping(grid);
18095
18096                 grouping.grouping.forEach( function( group ) {
18097                   group.colName = group.col.name;
18098                   delete group.col;
18099                 });
18100
18101                 grouping.aggregations.forEach( function( aggregation ) {
18102                   aggregation.colName = aggregation.col.name;
18103                   delete aggregation.col;
18104                 });
18105
18106                 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
18107                   return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
18108                 });
18109
18110                 if ( getExpanded ){
18111                   grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
18112                 }
18113
18114                 return grouping;
18115               },
18116
18117               /**
18118                * @ngdoc function
18119                * @name setGrouping
18120                * @methodOf  ui.grid.grouping.api:PublicApi
18121                * @description Set the grouping configuration for this grid,
18122                * used by the saveState feature, but can also be used by any
18123                * user to specify a combined grouping and aggregation configuration
18124                * @param {object} config the config you want to apply, in the format
18125                * provided out by getGrouping
18126                */
18127               setGrouping: function ( config ) {
18128                 service.setGrouping(grid, config);
18129               },
18130
18131               /**
18132                * @ngdoc function
18133                * @name groupColumn
18134                * @methodOf  ui.grid.grouping.api:PublicApi
18135                * @description Adds this column to the existing grouping, at the end of the priority order.
18136                * If the column doesn't have a sort, adds one, by default ASC
18137                *
18138                * This column will move to the left of any non-group columns, the
18139                * move is handled in a columnProcessor, so gets called as part of refresh
18140                *
18141                * @param {string} columnName the name of the column we want to group
18142                */
18143               groupColumn: function( columnName ) {
18144                 var column = grid.getColumn(columnName);
18145                 service.groupColumn(grid, column);
18146               },
18147
18148               /**
18149                * @ngdoc function
18150                * @name ungroupColumn
18151                * @methodOf  ui.grid.grouping.api:PublicApi
18152                * @description Removes the groupPriority from this column.  If the
18153                * column was previously aggregated the aggregation will come back.
18154                * The sort will remain.
18155                *
18156                * This column will move to the right of any other group columns, the
18157                * move is handled in a columnProcessor, so gets called as part of refresh
18158                *
18159                * @param {string} columnName the name of the column we want to ungroup
18160                */
18161               ungroupColumn: function( columnName ) {
18162                 var column = grid.getColumn(columnName);
18163                 service.ungroupColumn(grid, column);
18164               },
18165
18166               /**
18167                * @ngdoc function
18168                * @name clearGrouping
18169                * @methodOf  ui.grid.grouping.api:PublicApi
18170                * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
18171                * as we don't know whether that sorting was added by grouping or was there beforehand
18172                *
18173                */
18174               clearGrouping: function() {
18175                 service.clearGrouping(grid);
18176               },
18177
18178               /**
18179                * @ngdoc function
18180                * @name aggregateColumn
18181                * @methodOf  ui.grid.grouping.api:PublicApi
18182                * @description Sets the aggregation type on a column, if the
18183                * column is currently grouped then it removes the grouping first.
18184                * If the aggregationDef is null then will result in the aggregation
18185                * being removed
18186                *
18187                * @param {string} columnName the column we want to aggregate
18188                * @param {string} or {function} aggregationDef one of the recognised types
18189                * from uiGridGroupingConstants or a custom aggregation function.
18190                * @param {string} aggregationLabel (optional) The label to use for this aggregation.
18191                */
18192               aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
18193                 var column = grid.getColumn(columnName);
18194                 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
18195               }
18196
18197             }
18198           }
18199         };
18200
18201         grid.api.registerEventsFromObject(publicApi.events);
18202
18203         grid.api.registerMethodsFromObject(publicApi.methods);
18204
18205         grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
18206
18207       },
18208
18209       defaultGridOptions: function (gridOptions) {
18210         //default option to true unless it was explicitly set to false
18211         /**
18212          *  @ngdoc object
18213          *  @name ui.grid.grouping.api:GridOptions
18214          *
18215          *  @description GridOptions for grouping feature, these are available to be
18216          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18217          */
18218
18219         /**
18220          *  @ngdoc object
18221          *  @name enableGrouping
18222          *  @propertyOf  ui.grid.grouping.api:GridOptions
18223          *  @description Enable row grouping for entire grid.
18224          *  <br/>Defaults to true
18225          */
18226         gridOptions.enableGrouping = gridOptions.enableGrouping !== false;
18227
18228         /**
18229          *  @ngdoc object
18230          *  @name groupingShowCounts
18231          *  @propertyOf  ui.grid.grouping.api:GridOptions
18232          *  @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
18233          *  sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
18234          *  to break, since the group header rows will always be a string with groupingShowCounts enabled.
18235          *  <br/>Defaults to true except on columns of type 'date'
18236          */
18237         gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
18238
18239         /**
18240          *  @ngdoc object
18241          *  @name groupingNullLabel
18242          *  @propertyOf  ui.grid.grouping.api:GridOptions
18243          *  @description The string to use for the grouping header row label on rows which contain a null or undefined value in the grouped column.
18244          *  <br/>Defaults to "Null"
18245          */
18246         gridOptions.groupingNullLabel = typeof(gridOptions.groupingNullLabel) === 'undefined' ? 'Null' : gridOptions.groupingNullLabel;
18247
18248         /**
18249          *  @ngdoc object
18250          *  @name enableGroupHeaderSelection
18251          *  @propertyOf  ui.grid.grouping.api:GridOptions
18252          *  @description Allows group header rows to be selected.
18253          *  <br/>Defaults to false
18254          */
18255         gridOptions.enableGroupHeaderSelection = gridOptions.enableGroupHeaderSelection === true;
18256       },
18257
18258
18259       /**
18260        * @ngdoc function
18261        * @name groupingColumnBuilder
18262        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18263        * @description Sets the grouping defaults based on the columnDefs
18264        *
18265        * @param {object} colDef columnDef we're basing on
18266        * @param {GridCol} col the column we're to update
18267        * @param {object} gridOptions the options we should use
18268        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
18269        */
18270       groupingColumnBuilder: function (colDef, col, gridOptions) {
18271         /**
18272          *  @ngdoc object
18273          *  @name ui.grid.grouping.api:ColumnDef
18274          *
18275          *  @description ColumnDef for grouping feature, these are available to be
18276          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
18277          */
18278
18279         /**
18280          *  @ngdoc object
18281          *  @name enableGrouping
18282          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18283          *  @description Enable grouping on this column
18284          *  <br/>Defaults to true.
18285          */
18286         if (colDef.enableGrouping === false){
18287           return;
18288         }
18289
18290         /**
18291          *  @ngdoc object
18292          *  @name grouping
18293          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18294          *  @description Set the grouping for a column.  Format is:
18295          *  ```
18296          *    {
18297          *      groupPriority: <number, starts at 0, if less than 0 or undefined then we're aggregating in this column>
18298          *    }
18299          *  ```
18300          *
18301          *  **Note that aggregation used to be included in grouping, but is now separately set on the column via treeAggregation
18302          *  setting in treeBase**
18303          *
18304          *  We group in the priority order given, this will also put these columns to the high order of the sort irrespective
18305          *  of the sort priority given them.  If there is no sort defined then we sort ascending, if there is a sort defined then
18306          *  we use that sort.
18307          *
18308          *  If the groupPriority is undefined or less than 0, then we expect to be aggregating, and we look at the
18309          *  aggregation types to determine what sort of aggregation we can do.  Values are in the constants file, but
18310          *  include SUM, COUNT, MAX, MIN
18311          *
18312          *  groupPriorities should generally be sequential, if they're not then the next time getGrouping is called
18313          *  we'll renumber them to be sequential.
18314          *  <br/>Defaults to undefined.
18315          */
18316
18317         if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
18318           col.grouping = angular.copy(colDef.grouping);
18319           if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
18320             col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
18321             col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
18322           }
18323         } else if (typeof(col.grouping) === 'undefined'){
18324           col.grouping = {};
18325         }
18326
18327         if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
18328           col.suppressRemoveSort = true;
18329         }
18330
18331         var groupColumn = {
18332           name: 'ui.grid.grouping.group',
18333           title: i18nService.get().grouping.group,
18334           icon: 'ui-grid-icon-indent-right',
18335           shown: function () {
18336             return typeof(this.context.col.grouping) === 'undefined' ||
18337                    typeof(this.context.col.grouping.groupPriority) === 'undefined' ||
18338                    this.context.col.grouping.groupPriority < 0;
18339           },
18340           action: function () {
18341             service.groupColumn( this.context.col.grid, this.context.col );
18342           }
18343         };
18344
18345         var ungroupColumn = {
18346           name: 'ui.grid.grouping.ungroup',
18347           title: i18nService.get().grouping.ungroup,
18348           icon: 'ui-grid-icon-indent-left',
18349           shown: function () {
18350             return typeof(this.context.col.grouping) !== 'undefined' &&
18351                    typeof(this.context.col.grouping.groupPriority) !== 'undefined' &&
18352                    this.context.col.grouping.groupPriority >= 0;
18353           },
18354           action: function () {
18355             service.ungroupColumn( this.context.col.grid, this.context.col );
18356           }
18357         };
18358
18359         var aggregateRemove = {
18360           name: 'ui.grid.grouping.aggregateRemove',
18361           title: i18nService.get().grouping.aggregate_remove,
18362           shown: function () {
18363             return typeof(this.context.col.treeAggregationFn) !== 'undefined';
18364           },
18365           action: function () {
18366             service.aggregateColumn( this.context.col.grid, this.context.col, null);
18367           }
18368         };
18369
18370         // generic adder for the aggregation menus, which follow a pattern
18371         var addAggregationMenu = function(type, title){
18372           title = title || i18nService.get().grouping['aggregate_' + type] || type;
18373           var menuItem = {
18374             name: 'ui.grid.grouping.aggregate' + type,
18375             title: title,
18376             shown: function () {
18377               return typeof(this.context.col.treeAggregation) === 'undefined' ||
18378                      typeof(this.context.col.treeAggregation.type) === 'undefined' ||
18379                      this.context.col.treeAggregation.type !== type;
18380             },
18381             action: function () {
18382               service.aggregateColumn( this.context.col.grid, this.context.col, type);
18383             }
18384           };
18385
18386           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregate' + type)) {
18387             col.menuItems.push(menuItem);
18388           }
18389         };
18390
18391         /**
18392          *  @ngdoc object
18393          *  @name groupingShowGroupingMenu
18394          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18395          *  @description Show the grouping (group and ungroup items) menu on this column
18396          *  <br/>Defaults to true.
18397          */
18398         if ( col.colDef.groupingShowGroupingMenu !== false ){
18399           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
18400             col.menuItems.push(groupColumn);
18401           }
18402
18403           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.ungroup')) {
18404             col.menuItems.push(ungroupColumn);
18405           }
18406         }
18407
18408
18409         /**
18410          *  @ngdoc object
18411          *  @name groupingShowAggregationMenu
18412          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18413          *  @description Show the aggregation menu on this column
18414          *  <br/>Defaults to true.
18415          */
18416         if ( col.colDef.groupingShowAggregationMenu !== false ){
18417           angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
18418             addAggregationMenu(name);
18419           });
18420           angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
18421             addAggregationMenu(name, aggregationDef.menuTitle);
18422           });
18423
18424           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregateRemove')) {
18425             col.menuItems.push(aggregateRemove);
18426           }
18427         }
18428       },
18429
18430
18431
18432
18433       /**
18434        * @ngdoc function
18435        * @name groupingColumnProcessor
18436        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18437        * @description Moves the columns around based on which are grouped
18438        *
18439        * @param {array} columns the columns to consider rendering
18440        * @param {array} rows the grid rows, which we don't use but are passed to us
18441        * @returns {array} updated columns array
18442        */
18443       groupingColumnProcessor: function( columns, rows ) {
18444         var grid = this;
18445
18446         columns = service.moveGroupColumns(this, columns, rows);
18447         return columns;
18448       },
18449
18450       /**
18451        * @ngdoc function
18452        * @name groupedFinalizerFn
18453        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18454        * @description Used on group columns to display the rendered value and optionally
18455        * display the count of rows.
18456        *
18457        * @param {aggregation} the aggregation entity for a grouped column
18458        */
18459       groupedFinalizerFn: function( aggregation ){
18460         var col = this;
18461
18462         if ( typeof(aggregation.groupVal) !== 'undefined') {
18463           aggregation.rendered = aggregation.groupVal;
18464           if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' ){
18465             aggregation.rendered += (' (' + aggregation.value + ')');
18466           }
18467         } else {
18468           aggregation.rendered = null;
18469         }
18470       },
18471
18472       /**
18473        * @ngdoc function
18474        * @name moveGroupColumns
18475        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18476        * @description Moves the column order so that the grouped columns are lined up
18477        * to the left (well, unless you're RTL, then it's the right).  By doing this in
18478        * the columnsProcessor, we make it transient - when the column is ungrouped it'll
18479        * go back to where it was.
18480        *
18481        * Does nothing if the option `moveGroupColumns` is set to false.
18482        *
18483        * @param {Grid} grid grid object
18484        * @param {array} columns the columns that we should process/move
18485        * @param {array} rows the grid rows
18486        * @returns {array} updated columns
18487        */
18488       moveGroupColumns: function( grid, columns, rows ){
18489         if ( grid.options.moveGroupColumns === false){
18490           return columns;
18491         }
18492
18493         columns.forEach( function(column, index){
18494           // position used to make stable sort in moveGroupColumns
18495           column.groupingPosition = index;
18496         });
18497
18498         columns.sort(function(a, b){
18499           var a_group, b_group;
18500           if (a.isRowHeader){
18501             a_group = -1000;
18502           }
18503           else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
18504             a_group = null;
18505           } else {
18506             a_group = a.grouping.groupPriority;
18507           }
18508
18509           if (b.isRowHeader){
18510             b_group = -1000;
18511           }
18512           else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
18513             b_group = null;
18514           } else {
18515             b_group = b.grouping.groupPriority;
18516           }
18517
18518           // groups get sorted to the top
18519           if ( a_group !== null && b_group === null) { return -1; }
18520           if ( b_group !== null && a_group === null) { return 1; }
18521           if ( a_group !== null && b_group !== null) {return a_group - b_group; }
18522
18523           return a.groupingPosition - b.groupingPosition;
18524         });
18525
18526         columns.forEach( function(column, index) {
18527           delete column.groupingPosition;
18528         });
18529
18530         return columns;
18531       },
18532
18533
18534       /**
18535        * @ngdoc function
18536        * @name groupColumn
18537        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18538        * @description Adds this column to the existing grouping, at the end of the priority order.
18539        * If the column doesn't have a sort, adds one, by default ASC
18540        *
18541        * This column will move to the left of any non-group columns, the
18542        * move is handled in a columnProcessor, so gets called as part of refresh
18543        *
18544        * @param {Grid} grid grid object
18545        * @param {GridCol} column the column we want to group
18546        */
18547       groupColumn: function( grid, column){
18548         if ( typeof(column.grouping) === 'undefined' ){
18549           column.grouping = {};
18550         }
18551
18552         // set the group priority to the next number in the hierarchy
18553         var existingGrouping = service.getGrouping( grid );
18554         column.grouping.groupPriority = existingGrouping.grouping.length;
18555
18556         // add sort if not present
18557         if ( !column.sort ){
18558           column.sort = { direction: uiGridConstants.ASC };
18559         } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
18560           column.sort.direction = uiGridConstants.ASC;
18561         }
18562
18563         column.treeAggregation = { type: uiGridGroupingConstants.aggregation.COUNT, source: 'grouping' };
18564         column.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
18565         column.treeAggregationFinalizerFn = service.groupedFinalizerFn;
18566
18567         grid.api.grouping.raise.groupingChanged(column);
18568         // This indirectly calls service.tidyPriorities( grid );
18569         grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
18570
18571         grid.queueGridRefresh();
18572       },
18573
18574
18575        /**
18576        * @ngdoc function
18577        * @name ungroupColumn
18578        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18579        * @description Removes the groupPriority from this column.  If the
18580        * column was previously aggregated the aggregation will come back.
18581        * The sort will remain.
18582        *
18583        * This column will move to the right of any other group columns, the
18584        * move is handled in a columnProcessor, so gets called as part of refresh
18585        *
18586        * @param {Grid} grid grid object
18587        * @param {GridCol} column the column we want to ungroup
18588        */
18589       ungroupColumn: function( grid, column){
18590         if ( typeof(column.grouping) === 'undefined' ){
18591           return;
18592         }
18593
18594         delete column.grouping.groupPriority;
18595         delete column.treeAggregation;
18596         delete column.customTreeAggregationFinalizer;
18597
18598         service.tidyPriorities( grid );
18599
18600         grid.api.grouping.raise.groupingChanged(column);
18601
18602         grid.queueGridRefresh();
18603       },
18604
18605       /**
18606        * @ngdoc function
18607        * @name aggregateColumn
18608        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18609        * @description Sets the aggregation type on a column, if the
18610        * column is currently grouped then it removes the grouping first.
18611        *
18612        * @param {Grid} grid grid object
18613        * @param {GridCol} column the column we want to aggregate
18614        * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
18615        */
18616       aggregateColumn: function( grid, column, aggregationType){
18617
18618         if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
18619           service.ungroupColumn( grid, column );
18620         }
18621
18622         var aggregationDef = {};
18623         if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
18624           aggregationDef = grid.options.treeCustomAggregations[aggregationType];
18625         } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
18626           aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
18627         }
18628
18629         column.treeAggregation = { type: aggregationType, label:  i18nService.get().aggregation[aggregationDef.label] || aggregationDef.label };
18630         column.treeAggregationFn = aggregationDef.aggregationFn;
18631         column.treeAggregationFinalizerFn = aggregationDef.finalizerFn;
18632
18633         grid.api.grouping.raise.aggregationChanged(column);
18634
18635         grid.queueGridRefresh();
18636       },
18637
18638
18639       /**
18640        * @ngdoc function
18641        * @name setGrouping
18642        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18643        * @description Set the grouping based on a config object, used by the save state feature
18644        * (more specifically, by the restore function in that feature )
18645        *
18646        * @param {Grid} grid grid object
18647        * @param {object} config the config we want to set, same format as that returned by getGrouping
18648        */
18649       setGrouping: function ( grid, config ){
18650         if ( typeof(config) === 'undefined' ){
18651           return;
18652         }
18653
18654         // first remove any existing grouping
18655         service.clearGrouping(grid);
18656
18657         if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
18658           config.grouping.forEach( function( group ) {
18659             var col = grid.getColumn(group.colName);
18660
18661             if ( col ) {
18662               service.groupColumn( grid, col );
18663             }
18664           });
18665         }
18666
18667         if ( config.aggregations && config.aggregations.length ){
18668           config.aggregations.forEach( function( aggregation ) {
18669             var col = grid.getColumn(aggregation.colName);
18670
18671             if ( col ) {
18672               service.aggregateColumn( grid, col, aggregation.aggregation.type );
18673             }
18674           });
18675         }
18676
18677         if ( config.rowExpandedStates ){
18678           service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
18679         }
18680       },
18681
18682
18683       /**
18684        * @ngdoc function
18685        * @name clearGrouping
18686        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18687        * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
18688        * as we don't know whether that sorting was added by grouping or was there beforehand
18689        *
18690        * @param {Grid} grid grid object
18691        */
18692       clearGrouping: function( grid ) {
18693         var currentGrouping = service.getGrouping(grid);
18694
18695         if ( currentGrouping.grouping.length > 0 ){
18696           currentGrouping.grouping.forEach( function( group ) {
18697             if (!group.col){
18698               // should have a group.colName if there's no col
18699               group.col = grid.getColumn(group.colName);
18700             }
18701             service.ungroupColumn(grid, group.col);
18702           });
18703         }
18704
18705         if ( currentGrouping.aggregations.length > 0 ){
18706           currentGrouping.aggregations.forEach( function( aggregation ){
18707             if (!aggregation.col){
18708               // should have a group.colName if there's no col
18709               aggregation.col = grid.getColumn(aggregation.colName);
18710             }
18711             service.aggregateColumn(grid, aggregation.col, null);
18712           });
18713         }
18714       },
18715
18716
18717       /**
18718        * @ngdoc function
18719        * @name tidyPriorities
18720        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18721        * @description Renumbers groupPriority and sortPriority such that
18722        * groupPriority is contiguous, and sortPriority either matches
18723        * groupPriority (for group columns), and otherwise is contiguous and
18724        * higher than groupPriority.
18725        *
18726        * @param {Grid} grid grid object
18727        */
18728       tidyPriorities: function( grid ){
18729         // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
18730         if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
18731           grid = this.grid;
18732         }
18733
18734         var groupArray = [];
18735         var sortArray = [];
18736
18737         grid.columns.forEach( function(column, index){
18738           if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
18739             groupArray.push(column);
18740           } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
18741             sortArray.push(column);
18742           }
18743         });
18744
18745         groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
18746         groupArray.forEach( function(column, index){
18747           column.grouping.groupPriority = index;
18748           column.suppressRemoveSort = true;
18749           if ( typeof(column.sort) === 'undefined'){
18750             column.sort = {};
18751           }
18752           column.sort.priority = index;
18753         });
18754
18755         var i = groupArray.length;
18756         sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
18757         sortArray.forEach( function(column, index){
18758           column.sort.priority = i;
18759           column.suppressRemoveSort = column.colDef.suppressRemoveSort;
18760           i++;
18761         });
18762       },
18763
18764
18765       /**
18766        * @ngdoc function
18767        * @name groupRows
18768        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18769        * @description The rowProcessor that creates the groupHeaders (i.e. does
18770        * the actual grouping).
18771        *
18772        * Assumes it is always called after the sorting processor, guaranteed by the priority setting
18773        *
18774        * Processes all the rows in order, inserting a groupHeader row whenever there is a change
18775        * in value of a grouped row, based on the sortAlgorithm used for the column.  The group header row
18776        * is looked up in the groupHeaderCache, and used from there if there is one. The entity is reset
18777        * to {} if one is found.
18778        *
18779        * As it processes it maintains a `processingState` array. This records, for each level of grouping we're
18780        * working with, the following information:
18781        * ```
18782        *   {
18783        *     fieldName: name,
18784        *     col: col,
18785        *     initialised: boolean,
18786        *     currentValue: value,
18787        *     currentRow: gridRow,
18788        *   }
18789        * ```
18790        * We look for changes in the currentValue at any of the levels.  Where we find a change we:
18791        *
18792        * - create a new groupHeader row in the array
18793        *
18794        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
18795        * @returns {array} the updated rows, including our new group rows
18796        */
18797       groupRows: function( renderableRows ) {
18798         if (renderableRows.length === 0){
18799           return renderableRows;
18800         }
18801
18802         var grid = this;
18803         grid.grouping.oldGroupingHeaderCache = grid.grouping.groupingHeaderCache || {};
18804         grid.grouping.groupingHeaderCache = {};
18805
18806         var processingState = service.initialiseProcessingState( grid );
18807
18808         // processes each of the fields we are grouping by, checks if the value has changed and inserts a groupHeader
18809         // Broken out as shouldn't create functions in a loop.
18810         var updateProcessingState = function( groupFieldState, stateIndex ) {
18811           var fieldValue = grid.getCellValue(row, groupFieldState.col);
18812
18813           // look for change of value - and insert a header
18814           if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
18815             service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
18816             i++;
18817           }
18818         };
18819
18820         // use a for loop because it's tolerant of the array length changing whilst we go - we can
18821         // manipulate the iterator when we insert groupHeader rows
18822         for (var i = 0; i < renderableRows.length; i++ ){
18823           var row = renderableRows[i];
18824
18825           if ( row.visible ){
18826             processingState.forEach( updateProcessingState );
18827           }
18828         }
18829
18830         delete grid.grouping.oldGroupingHeaderCache;
18831         return renderableRows;
18832       },
18833
18834
18835       /**
18836        * @ngdoc function
18837        * @name initialiseProcessingState
18838        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18839        * @description Creates the processing state array that is used
18840        * for groupRows.
18841        *
18842        * @param {Grid} grid grid object
18843        * @returns {array} an array in the format described in the groupRows method,
18844        * initialised with blank values
18845        */
18846       initialiseProcessingState: function( grid ){
18847         var processingState = [];
18848         var columnSettings = service.getGrouping( grid );
18849
18850         columnSettings.grouping.forEach( function( groupItem, index){
18851           processingState.push({
18852             fieldName: groupItem.field,
18853             col: groupItem.col,
18854             initialised: false,
18855             currentValue: null,
18856             currentRow: null
18857           });
18858         });
18859
18860         return processingState;
18861       },
18862
18863
18864       /**
18865        * @ngdoc function
18866        * @name getGrouping
18867        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18868        * @description Get the grouping settings from the columns.  As a side effect
18869        * this always renumbers the grouping starting at 0
18870        * @param {Grid} grid grid object
18871        * @returns {array} an array of the group fields, in order of priority
18872        */
18873       getGrouping: function( grid ){
18874         var groupArray = [];
18875         var aggregateArray = [];
18876
18877         // get all the grouping
18878         grid.columns.forEach( function(column, columnIndex){
18879           if ( column.grouping ){
18880             if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
18881               groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
18882             }
18883           }
18884           if ( column.treeAggregation && column.treeAggregation.type ){
18885             aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
18886           }
18887         });
18888
18889         // sort grouping into priority order
18890         groupArray.sort( function(a, b){
18891           return a.groupPriority - b.groupPriority;
18892         });
18893
18894         // renumber the priority in case it was somewhat messed up, then remove the grouping reference
18895         groupArray.forEach( function( group, index) {
18896           group.grouping.groupPriority = index;
18897           group.groupPriority = index;
18898           delete group.grouping;
18899         });
18900
18901         return { grouping: groupArray, aggregations: aggregateArray };
18902       },
18903
18904
18905       /**
18906        * @ngdoc function
18907        * @name insertGroupHeader
18908        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18909        * @description Create a group header row, and link it to the various configuration
18910        * items that we use.
18911        *
18912        * Look for the row in the oldGroupingHeaderCache, write the row into the new groupingHeaderCache.
18913        *
18914        * @param {Grid} grid grid object
18915        * @param {array} renderableRows the rows that we are processing
18916        * @param {number} rowIndex the row we were up to processing
18917        * @param {array} processingState the current processing state
18918        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header -
18919        * i.e. the column that we want to create a header for
18920        */
18921       insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
18922         // set the value that caused the end of a group into the header row and the processing state
18923         var fieldName = processingState[stateIndex].fieldName;
18924         var col = processingState[stateIndex].col;
18925
18926         var newValue = grid.getCellValue(renderableRows[rowIndex], col);
18927         var newDisplayValue = newValue;
18928         if ( typeof(newValue) === 'undefined' || newValue === null ) {
18929           newDisplayValue = grid.options.groupingNullLabel;
18930         }
18931
18932         var cacheItem = grid.grouping.oldGroupingHeaderCache;
18933         for ( var i = 0; i < stateIndex; i++ ){
18934           if ( cacheItem && cacheItem[processingState[i].currentValue] ){
18935             cacheItem = cacheItem[processingState[i].currentValue].children;
18936           }
18937         }
18938
18939         var headerRow;
18940         if ( cacheItem && cacheItem[newValue]){
18941           headerRow = cacheItem[newValue].row;
18942           headerRow.entity = {};
18943         } else {
18944           headerRow = new GridRow( {}, null, grid );
18945           gridClassFactory.rowTemplateAssigner.call(grid, headerRow);
18946         }
18947
18948         headerRow.entity['$$' + processingState[stateIndex].col.uid] = { groupVal: newDisplayValue };
18949         headerRow.treeLevel = stateIndex;
18950         headerRow.groupHeader = true;
18951         headerRow.internalRow = true;
18952         headerRow.enableCellEdit = false;
18953         headerRow.enableSelection = grid.options.enableGroupHeaderSelection;
18954         processingState[stateIndex].initialised = true;
18955         processingState[stateIndex].currentValue = newValue;
18956         processingState[stateIndex].currentRow = headerRow;
18957
18958         // set all processing states below this one to not be initialised - change of this state
18959         // means all those need to start again
18960         service.finaliseProcessingState( processingState, stateIndex + 1);
18961
18962         // insert our new header row
18963         renderableRows.splice(rowIndex, 0, headerRow);
18964
18965         // add our new header row to the cache
18966         cacheItem = grid.grouping.groupingHeaderCache;
18967         for ( i = 0; i < stateIndex; i++ ){
18968           cacheItem = cacheItem[processingState[i].currentValue].children;
18969         }
18970         cacheItem[newValue] = { row: headerRow, children: {} };
18971       },
18972
18973
18974       /**
18975        * @ngdoc function
18976        * @name finaliseProcessingState
18977        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18978        * @description Set all processing states lower than the one that had a break in value to
18979        * no longer be initialised.  Render the counts into the entity ready for display.
18980        *
18981        * @param {Grid} grid grid object
18982        * @param {array} processingState the current processing state
18983        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
18984        * processing states after this need to be finalised
18985        */
18986       finaliseProcessingState: function( processingState, stateIndex ){
18987         for ( var i = stateIndex; i < processingState.length; i++){
18988           processingState[i].initialised = false;
18989           processingState[i].currentRow = null;
18990           processingState[i].currentValue = null;
18991         }
18992       },
18993
18994
18995       /**
18996        * @ngdoc function
18997        * @name getRowExpandedStates
18998        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18999        * @description Extract the groupHeaderCache hash, pulling out only the states.
19000        *
19001        * The example below shows a grid that is grouped by gender then age
19002        *
19003        * <pre>
19004        *   {
19005        *     male: {
19006        *       state: 'expanded',
19007        *       children: {
19008        *         22: { state: 'expanded' },
19009        *         30: { state: 'collapsed' }
19010        *       }
19011        *     },
19012        *     female: {
19013        *       state: 'expanded',
19014        *       children: {
19015        *         28: { state: 'expanded' },
19016        *         55: { state: 'collapsed' }
19017        *       }
19018        *     }
19019        *   }
19020        * </pre>
19021        *
19022        * @param {Grid} grid grid object
19023        * @returns {hash} the expanded states as a hash
19024        */
19025       getRowExpandedStates: function(treeChildren){
19026         if ( typeof(treeChildren) === 'undefined' ){
19027           return {};
19028         }
19029
19030         var newChildren = {};
19031
19032         angular.forEach( treeChildren, function( value, key ){
19033           newChildren[key] = { state: value.row.treeNode.state };
19034           if ( value.children ){
19035             newChildren[key].children = service.getRowExpandedStates( value.children );
19036           } else {
19037             newChildren[key].children = {};
19038           }
19039         });
19040
19041         return newChildren;
19042       },
19043
19044
19045       /**
19046        * @ngdoc function
19047        * @name applyRowExpandedStates
19048        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19049        * @description Take a hash in the format as created by getRowExpandedStates,
19050        * and apply it to the grid.grouping.groupHeaderCache.
19051        *
19052        * Takes a treeSubset, and applies to a treeSubset - so can be called
19053        * recursively.
19054        *
19055        * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
19056        * the children of that hash
19057        * @returns {hash} expandedStates can be the full expanded states, or children
19058        * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
19059        */
19060       applyRowExpandedStates: function( currentNode, expandedStates ){
19061         if ( typeof(expandedStates) === 'undefined' ){
19062           return;
19063         }
19064
19065         angular.forEach(expandedStates, function( value, key ) {
19066           if ( currentNode[key] ){
19067             currentNode[key].row.treeNode.state = value.state;
19068
19069             if (value.children && currentNode[key].children){
19070               service.applyRowExpandedStates( currentNode[key].children, value.children );
19071             }
19072           }
19073         });
19074       }
19075
19076
19077     };
19078
19079     return service;
19080
19081   }]);
19082
19083
19084   /**
19085    *  @ngdoc directive
19086    *  @name ui.grid.grouping.directive:uiGridGrouping
19087    *  @element div
19088    *  @restrict A
19089    *
19090    *  @description Adds grouping features to grid
19091    *
19092    *  @example
19093    <example module="app">
19094    <file name="app.js">
19095    var app = angular.module('app', ['ui.grid', 'ui.grid.grouping']);
19096
19097    app.controller('MainCtrl', ['$scope', function ($scope) {
19098       $scope.data = [
19099         { name: 'Bob', title: 'CEO' },
19100             { name: 'Frank', title: 'Lowly Developer' }
19101       ];
19102
19103       $scope.columnDefs = [
19104         {name: 'name', enableCellEdit: true},
19105         {name: 'title', enableCellEdit: true}
19106       ];
19107
19108       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
19109     }]);
19110    </file>
19111    <file name="index.html">
19112    <div ng-controller="MainCtrl">
19113    <div ui-grid="gridOptions" ui-grid-grouping></div>
19114    </div>
19115    </file>
19116    </example>
19117    */
19118   module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
19119   function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
19120     return {
19121       replace: true,
19122       priority: 0,
19123       require: '^uiGrid',
19124       scope: false,
19125       compile: function () {
19126         return {
19127           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
19128             if (uiGridCtrl.grid.options.enableGrouping !== false){
19129               uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
19130             }
19131           },
19132           post: function ($scope, $elm, $attrs, uiGridCtrl) {
19133           }
19134         };
19135       }
19136     };
19137   }]);
19138
19139 })();
19140
19141 (function () {
19142   'use strict';
19143
19144   /**
19145    * @ngdoc overview
19146    * @name ui.grid.importer
19147    * @description
19148    *
19149    * # ui.grid.importer
19150    *
19151    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
19152    *
19153    * This module provides the ability to import data into the grid. It
19154    * uses the column defs to work out which data belongs in which column,
19155    * and creates entities from a configured class (typically a $resource).
19156    *
19157    * If the rowEdit feature is enabled, it also calls save on those newly
19158    * created objects, and then displays any errors in the imported data.
19159    *
19160    * Currently the importer imports only CSV and json files, although provision has been
19161    * made to process other file formats, and these can be added over time.
19162    *
19163    * For json files, the properties within each object in the json must match the column names
19164    * (to put it another way, the importer doesn't process the json, it just copies the objects
19165    * within the json into a new instance of the specified object type)
19166    *
19167    * For CSV import, the default column identification relies on each column in the
19168    * header row matching a column.name or column.displayName. Optionally, a column identification
19169    * callback can be used.  This allows matching using other attributes, which is particularly
19170    * useful if your application has internationalised column headings (i.e. the headings that
19171    * the user sees don't match the column names).
19172    *
19173    * The importer makes use of the grid menu as the UI for requesting an
19174    * import.
19175    *
19176    * <div ui-grid-importer></div>
19177    */
19178
19179   var module = angular.module('ui.grid.importer', ['ui.grid']);
19180
19181   /**
19182    *  @ngdoc object
19183    *  @name ui.grid.importer.constant:uiGridImporterConstants
19184    *
19185    *  @description constants available in importer module
19186    */
19187
19188   module.constant('uiGridImporterConstants', {
19189     featureName: 'importer'
19190   });
19191
19192   /**
19193    *  @ngdoc service
19194    *  @name ui.grid.importer.service:uiGridImporterService
19195    *
19196    *  @description Services for importer feature
19197    */
19198   module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
19199     function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
19200
19201       var service = {
19202
19203         initializeGrid: function ($scope, grid) {
19204
19205           //add feature namespace and any properties to grid for needed state
19206           grid.importer = {
19207             $scope: $scope
19208           };
19209
19210           this.defaultGridOptions(grid.options);
19211
19212           /**
19213            *  @ngdoc object
19214            *  @name ui.grid.importer.api:PublicApi
19215            *
19216            *  @description Public Api for importer feature
19217            */
19218           var publicApi = {
19219             events: {
19220               importer: {
19221               }
19222             },
19223             methods: {
19224               importer: {
19225                 /**
19226                  * @ngdoc function
19227                  * @name importFile
19228                  * @methodOf  ui.grid.importer.api:PublicApi
19229                  * @description Imports a file into the grid using the file object
19230                  * provided.  Bypasses the grid menu
19231                  * @param {File} fileObject the file we want to import, as a javascript
19232                  * File object
19233                  */
19234                 importFile: function ( fileObject ) {
19235                   service.importThisFile( grid, fileObject );
19236                 }
19237               }
19238             }
19239           };
19240
19241           grid.api.registerEventsFromObject(publicApi.events);
19242
19243           grid.api.registerMethodsFromObject(publicApi.methods);
19244
19245           if ( grid.options.enableImporter && grid.options.importerShowMenu ){
19246             if ( grid.api.core.addToGridMenu ){
19247               service.addToMenu( grid );
19248             } else {
19249               // order of registration is not guaranteed, register in a little while
19250               $interval( function() {
19251                 if (grid.api.core.addToGridMenu){
19252                   service.addToMenu( grid );
19253                 }
19254               }, 100, 1);
19255             }
19256           }
19257         },
19258
19259
19260         defaultGridOptions: function (gridOptions) {
19261           //default option to true unless it was explicitly set to false
19262           /**
19263            * @ngdoc object
19264            * @name ui.grid.importer.api:GridOptions
19265            *
19266            * @description GridOptions for importer feature, these are available to be
19267            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
19268            */
19269
19270           /**
19271            * @ngdoc property
19272            * @propertyOf ui.grid.importer.api:GridOptions
19273            * @name enableImporter
19274            * @description Whether or not importer is enabled.  Automatically set
19275            * to false if the user's browser does not support the required fileApi.
19276            * Otherwise defaults to true.
19277            *
19278            */
19279           if (gridOptions.enableImporter  || gridOptions.enableImporter === undefined) {
19280             if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
19281               gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
19282               gridOptions.enableImporter = false;
19283             } else {
19284               gridOptions.enableImporter = true;
19285             }
19286           } else {
19287             gridOptions.enableImporter = false;
19288           }
19289
19290           /**
19291            * @ngdoc method
19292            * @name importerProcessHeaders
19293            * @methodOf ui.grid.importer.api:GridOptions
19294            * @description A callback function that will process headers using custom
19295            * logic.  Set this callback function if the headers that your user will provide in their
19296            * import file don't necessarily match the grid header or field names.  This might commonly
19297            * occur where your application is internationalised, and therefore the field names
19298            * that the user recognises are in a different language than the field names that
19299            * ui-grid knows about.
19300            *
19301            * Defaults to the internal `processHeaders` method, which seeks to match using both
19302            * displayName and column.name.  Any non-matching columns are discarded.
19303            *
19304            * Your callback routine should respond by processing the header array, and returning an array
19305            * of matching column names.  A null value in any given position means "don't import this column"
19306            *
19307            * <pre>
19308            *      gridOptions.importerProcessHeaders: function( headerArray ) {
19309            *        var myHeaderColumns = [];
19310            *        var thisCol;
19311            *        headerArray.forEach( function( value, index ) {
19312            *          thisCol = mySpecialLookupFunction( value );
19313            *          myHeaderColumns.push( thisCol.name );
19314            *        });
19315            *
19316            *        return myHeaderCols;
19317            *      })
19318            * </pre>
19319            * @param {Grid} grid the grid we're importing into
19320            * @param {array} headerArray an array of the text from the first row of the csv file,
19321            * which you need to match to column.names
19322            * @returns {array} array of matching column names, in the same order as the headerArray
19323            *
19324            */
19325           gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
19326
19327           /**
19328            * @ngdoc method
19329            * @name importerHeaderFilter
19330            * @methodOf ui.grid.importer.api:GridOptions
19331            * @description A callback function that will filter (usually translate) a single
19332            * header.  Used when you want to match the passed in column names to the column
19333            * displayName after the header filter.
19334            *
19335            * Your callback routine needs to return the filtered header value.
19336            * <pre>
19337            *      gridOptions.importerHeaderFilter: function( displayName ) {
19338            *        return $translate.instant( displayName );
19339            *      })
19340            * </pre>
19341            *
19342            * or:
19343            * <pre>
19344            *      gridOptions.importerHeaderFilter: $translate.instant
19345            * </pre>
19346            * @param {string} displayName the displayName that we'd like to translate
19347            * @returns {string} the translated name
19348            *
19349            */
19350           gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
19351
19352           /**
19353            * @ngdoc method
19354            * @name importerErrorCallback
19355            * @methodOf ui.grid.importer.api:GridOptions
19356            * @description A callback function that provides custom error handling, rather
19357            * than the standard grid behaviour of an alert box and a console message.  You
19358            * might use this to internationalise the console log messages, or to write to a
19359            * custom logging routine that returned errors to the server.
19360            *
19361            * <pre>
19362            *      gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
19363            *        myUserDisplayRoutine( errorKey );
19364            *        myLoggingRoutine( consoleMessage, context );
19365            *      })
19366            * </pre>
19367            * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
19368            * in some way
19369            * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
19370            * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
19371            * @param {string} consoleMessage the English console message that importer would have written
19372            * @param {object} context the context data that importer would have appended to that console message,
19373            * often the file content itself or the element that is in error
19374            *
19375            */
19376           if ( !gridOptions.importerErrorCallback ||  typeof(gridOptions.importerErrorCallback) !== 'function' ){
19377             delete gridOptions.importerErrorCallback;
19378           }
19379
19380           /**
19381            * @ngdoc method
19382            * @name importerDataAddCallback
19383            * @methodOf ui.grid.importer.api:GridOptions
19384            * @description A mandatory callback function that adds data to the source data array.  The grid
19385            * generally doesn't add rows to the source data array, it is tidier to handle this through a user
19386            * callback.
19387            *
19388            * <pre>
19389            *      gridOptions.importerDataAddCallback: function( grid, newObjects ) {
19390            *        $scope.myData = $scope.myData.concat( newObjects );
19391            *      })
19392            * </pre>
19393            * @param {Grid} grid the grid we're importing into, may be useful in some way
19394            * @param {array} newObjects an array of new objects that you should add to your data
19395            *
19396            */
19397           if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
19398             gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
19399             gridOptions.enableImporter = false;
19400           }
19401
19402           /**
19403            * @ngdoc object
19404            * @name importerNewObject
19405            * @propertyOf  ui.grid.importer.api:GridOptions
19406            * @description An object on which we call `new` to create each new row before inserting it into
19407            * the data array.  Typically this would be a $resource entity, which means that if you're using
19408            * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
19409            *
19410            * Defaults to a vanilla javascript object
19411            *
19412            * @example
19413            * <pre>
19414            *   gridOptions.importerNewObject = MyRes;
19415            * </pre>
19416            *
19417            */
19418
19419           /**
19420            * @ngdoc property
19421            * @propertyOf ui.grid.importer.api:GridOptions
19422            * @name importerShowMenu
19423            * @description Whether or not to show an item in the grid menu.  Defaults to true.
19424            *
19425            */
19426           gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
19427
19428           /**
19429            * @ngdoc method
19430            * @methodOf ui.grid.importer.api:GridOptions
19431            * @name importerObjectCallback
19432            * @description A callback that massages the data for each object.  For example,
19433            * you might have data stored as a code value, but display the decode.  This callback
19434            * can be used to change the decoded value back into a code.  Defaults to doing nothing.
19435            * @param {Grid} grid in case you need it
19436            * @param {object} newObject the new object as importer has created it, modify it
19437            * then return the modified version
19438            * @returns {object} the modified object
19439            * @example
19440            * <pre>
19441            *   gridOptions.importerObjectCallback = function ( grid, newObject ) {
19442            *     switch newObject.status {
19443            *       case 'Active':
19444            *         newObject.status = 1;
19445            *         break;
19446            *       case 'Inactive':
19447            *         newObject.status = 2;
19448            *         break;
19449            *     }
19450            *     return newObject;
19451            *   };
19452            * </pre>
19453            */
19454           gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
19455         },
19456
19457
19458         /**
19459          * @ngdoc function
19460          * @name addToMenu
19461          * @methodOf  ui.grid.importer.service:uiGridImporterService
19462          * @description Adds import menu item to the grid menu,
19463          * allowing the user to request import of a file
19464          * @param {Grid} grid the grid into which data should be imported
19465          */
19466         addToMenu: function ( grid ) {
19467           grid.api.core.addToGridMenu( grid, [
19468             {
19469               title: i18nService.getSafeText('gridMenu.importerTitle'),
19470               order: 150
19471             },
19472             {
19473               templateUrl: 'ui-grid/importerMenuItemContainer',
19474               action: function ($event) {
19475                 this.grid.api.importer.importAFile( grid );
19476               },
19477               order: 151
19478             }
19479           ]);
19480         },
19481
19482
19483         /**
19484          * @ngdoc function
19485          * @name importThisFile
19486          * @methodOf ui.grid.importer.service:uiGridImporterService
19487          * @description Imports the provided file into the grid using the file object
19488          * provided.  Bypasses the grid menu
19489          * @param {Grid} grid the grid we're importing into
19490          * @param {File} fileObject the file we want to import, as returned from the File
19491          * javascript object
19492          */
19493         importThisFile: function ( grid, fileObject ) {
19494           if (!fileObject){
19495             gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
19496             return;
19497           }
19498
19499           var reader = new FileReader();
19500
19501           switch ( fileObject.type ){
19502             case 'application/json':
19503               reader.onload = service.importJsonClosure( grid );
19504               break;
19505             default:
19506               reader.onload = service.importCsvClosure( grid );
19507               break;
19508           }
19509
19510           reader.readAsText( fileObject );
19511         },
19512
19513
19514         /**
19515          * @ngdoc function
19516          * @name importJson
19517          * @methodOf ui.grid.importer.service:uiGridImporterService
19518          * @description Creates a function that imports a json file into the grid.
19519          * The json data is imported into new objects of type `gridOptions.importerNewObject`,
19520          * and if the rowEdit feature is enabled the rows are marked as dirty
19521          * @param {Grid} grid the grid we want to import into
19522          * @param {FileObject} importFile the file that we want to import, as
19523          * a FileObject
19524          */
19525         importJsonClosure: function( grid ) {
19526           return function( importFile ){
19527             var newObjects = [];
19528             var newObject;
19529
19530             var importArray = service.parseJson( grid, importFile );
19531             if (importArray === null){
19532               return;
19533             }
19534             importArray.forEach(  function( value, index ) {
19535               newObject = service.newObject( grid );
19536               angular.extend( newObject, value );
19537               newObject = grid.options.importerObjectCallback( grid, newObject );
19538               newObjects.push( newObject );
19539             });
19540
19541             service.addObjects( grid, newObjects );
19542
19543           };
19544         },
19545
19546
19547         /**
19548          * @ngdoc function
19549          * @name parseJson
19550          * @methodOf ui.grid.importer.service:uiGridImporterService
19551          * @description Parses a json file, returns the parsed data.
19552          * Displays an error if file doesn't parse
19553          * @param {Grid} grid the grid that we want to import into
19554          * @param {FileObject} importFile the file that we want to import, as
19555          * a FileObject
19556          * @returns {array} array of objects from the imported json
19557          */
19558         parseJson: function( grid, importFile ){
19559           var loadedObjects;
19560           try {
19561             loadedObjects = JSON.parse( importFile.target.result );
19562           } catch (e) {
19563             service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
19564             return;
19565           }
19566
19567           if ( !Array.isArray( loadedObjects ) ){
19568             service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
19569             return [];
19570           } else {
19571             return loadedObjects;
19572           }
19573         },
19574
19575
19576
19577         /**
19578          * @ngdoc function
19579          * @name importCsvClosure
19580          * @methodOf ui.grid.importer.service:uiGridImporterService
19581          * @description Creates a function that imports a csv file into the grid
19582          * (allowing it to be used in the reader.onload event)
19583          * @param {Grid} grid the grid that we want to import into
19584          * @param {FileObject} importFile the file that we want to import, as
19585          * a file object
19586          */
19587         importCsvClosure: function( grid ) {
19588           return function( importFile ){
19589             var importArray = service.parseCsv( importFile );
19590             if ( !importArray || importArray.length < 1 ){
19591               service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
19592               return;
19593             }
19594
19595             var newObjects = service.createCsvObjects( grid, importArray );
19596             if ( !newObjects || newObjects.length === 0 ){
19597               service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
19598               return;
19599             }
19600
19601             service.addObjects( grid, newObjects );
19602           };
19603         },
19604
19605
19606         /**
19607          * @ngdoc function
19608          * @name parseCsv
19609          * @methodOf ui.grid.importer.service:uiGridImporterService
19610          * @description Parses a csv file into an array of arrays, with the first
19611          * array being the headers, and the remaining arrays being the data.
19612          * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
19613          * which is noted as being under the MIT license.  The code is modified to pass the jscs yoda condition
19614          * checker
19615          * @param {FileObject} importFile the file that we want to import, as a
19616          * file object
19617          */
19618         parseCsv: function( importFile ) {
19619           var csv = importFile.target.result;
19620
19621           // use the CSV-JS library to parse
19622           return CSV.parse(csv);
19623         },
19624
19625
19626         /**
19627          * @ngdoc function
19628          * @name createCsvObjects
19629          * @methodOf ui.grid.importer.service:uiGridImporterService
19630          * @description Converts an array of arrays (representing the csv file)
19631          * into a set of objects.  Uses the provided `gridOptions.importerNewObject`
19632          * to create the objects, and maps the header row into the individual columns
19633          * using either `gridOptions.importerProcessHeaders`, or by using a native method
19634          * of matching to either the displayName, column name or column field of
19635          * the columns in the column defs.  The resulting objects will have attributes
19636          * that are named based on the column.field or column.name, in that order.
19637          * @param {Grid} grid the grid that we want to import into
19638          * @param {Array} importArray the data that we want to import, as an array
19639          */
19640         createCsvObjects: function( grid, importArray ){
19641           // pull off header row and turn into headers
19642           var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
19643           if ( !headerMapping || headerMapping.length === 0 ){
19644             service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
19645             return [];
19646           }
19647
19648           var newObjects = [];
19649           var newObject;
19650           importArray.forEach( function( row, index ) {
19651             newObject = service.newObject( grid );
19652             if ( row !== null ){
19653               row.forEach( function( field, index ){
19654                 if ( headerMapping[index] !== null ){
19655                   newObject[ headerMapping[index] ] = field;
19656                 }
19657               });
19658             }
19659             newObject = grid.options.importerObjectCallback( grid, newObject );
19660             newObjects.push( newObject );
19661           });
19662
19663           return newObjects;
19664         },
19665
19666
19667         /**
19668          * @ngdoc function
19669          * @name processHeaders
19670          * @methodOf ui.grid.importer.service:uiGridImporterService
19671          * @description Determines the columns that the header row from
19672          * a csv (or other) file represents.
19673          * @param {Grid} grid the grid we're importing into
19674          * @param {array} headerRow the header row that we wish to match against
19675          * the column definitions
19676          * @returns {array} an array of the attribute names that should be used
19677          * for that column, based on matching the headers or creating the headers
19678          *
19679          */
19680         processHeaders: function( grid, headerRow ) {
19681           var headers = [];
19682           if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
19683             // we are going to create new columnDefs for all these columns, so just remove
19684             // spaces from the names to create fields
19685             headerRow.forEach( function( value, index ) {
19686               headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
19687             });
19688             return headers;
19689           } else {
19690             var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
19691             headerRow.forEach(  function( value, index ) {
19692               if ( lookupHash[value] ) {
19693                 headers.push( lookupHash[value] );
19694               } else if ( lookupHash[ value.toLowerCase() ] ) {
19695                 headers.push( lookupHash[ value.toLowerCase() ] );
19696               } else {
19697                 headers.push( null );
19698               }
19699             });
19700             return headers;
19701           }
19702         },
19703
19704
19705         /**
19706          * @name flattenColumnDefs
19707          * @methodOf ui.grid.importer.service:uiGridImporterService
19708          * @description Runs through the column defs and creates a hash of
19709          * the displayName, name and field, and of each of those values forced to lower case,
19710          * with each pointing to the field or name
19711          * (whichever is present).  Used to lookup column headers and decide what
19712          * attribute name to give to the resulting field.
19713          * @param {Grid} grid the grid we're importing into
19714          * @param {array} columnDefs the columnDefs that we should flatten
19715          * @returns {hash} the flattened version of the column def information, allowing
19716          * us to look up a value by `flattenedHash[ headerValue ]`
19717          */
19718         flattenColumnDefs: function( grid, columnDefs ){
19719           var flattenedHash = {};
19720           columnDefs.forEach(  function( columnDef, index) {
19721             if ( columnDef.name ){
19722               flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
19723               flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
19724             }
19725
19726             if ( columnDef.field ){
19727               flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
19728               flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
19729             }
19730
19731             if ( columnDef.displayName ){
19732               flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
19733               flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
19734             }
19735
19736             if ( columnDef.displayName && grid.options.importerHeaderFilter ){
19737               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
19738               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
19739             }
19740           });
19741
19742           return flattenedHash;
19743         },
19744
19745
19746         /**
19747          * @ngdoc function
19748          * @name addObjects
19749          * @methodOf ui.grid.importer.service:uiGridImporterService
19750          * @description Inserts our new objects into the grid data, and
19751          * sets the rows to dirty if the rowEdit feature is being used
19752          *
19753          * Does this by registering a watch on dataChanges, which essentially
19754          * is waiting on the result of the grid data watch, and downstream processing.
19755          *
19756          * When the callback is called, it deregisters itself - we don't want to run
19757          * again next time data is added.
19758          *
19759          * If we never get called, we deregister on destroy.
19760          *
19761          * @param {Grid} grid the grid we're importing into
19762          * @param {array} newObjects the objects we want to insert into the grid data
19763          * @returns {object} the new object
19764          */
19765         addObjects: function( grid, newObjects, $scope ){
19766           if ( grid.api.rowEdit ){
19767             var dataChangeDereg = grid.registerDataChangeCallback( function() {
19768               grid.api.rowEdit.setRowsDirty( newObjects );
19769               dataChangeDereg();
19770             }, [uiGridConstants.dataChange.ROW] );
19771
19772             grid.importer.$scope.$on( '$destroy', dataChangeDereg );
19773           }
19774
19775           grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
19776
19777         },
19778
19779
19780         /**
19781          * @ngdoc function
19782          * @name newObject
19783          * @methodOf ui.grid.importer.service:uiGridImporterService
19784          * @description Makes a new object based on `gridOptions.importerNewObject`,
19785          * or based on an empty object if not present
19786          * @param {Grid} grid the grid we're importing into
19787          * @returns {object} the new object
19788          */
19789         newObject: function( grid ){
19790           if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
19791             return new grid.options.importerNewObject();
19792           } else {
19793             return {};
19794           }
19795         },
19796
19797
19798         /**
19799          * @ngdoc function
19800          * @name alertError
19801          * @methodOf ui.grid.importer.service:uiGridImporterService
19802          * @description Provides an internationalised user alert for the failure,
19803          * and logs a console message including diagnostic content.
19804          * Optionally, if the the `gridOptions.importerErrorCallback` routine
19805          * is defined, then calls that instead, allowing user specified error routines
19806          * @param {Grid} grid the grid we're importing into
19807          * @param {array} headerRow the header row that we wish to match against
19808          * the column definitions
19809          */
19810         alertError: function( grid, alertI18nToken, consoleMessage, context ){
19811           if ( grid.options.importerErrorCallback ){
19812             grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
19813           } else {
19814             $window.alert(i18nService.getSafeText( alertI18nToken ));
19815             gridUtil.logError(consoleMessage + context );
19816           }
19817         }
19818       };
19819
19820       return service;
19821
19822     }
19823   ]);
19824
19825   /**
19826    *  @ngdoc directive
19827    *  @name ui.grid.importer.directive:uiGridImporter
19828    *  @element div
19829    *  @restrict A
19830    *
19831    *  @description Adds importer features to grid
19832    *
19833    */
19834   module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
19835     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
19836       return {
19837         replace: true,
19838         priority: 0,
19839         require: '^uiGrid',
19840         scope: false,
19841         link: function ($scope, $elm, $attrs, uiGridCtrl) {
19842           uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
19843         }
19844       };
19845     }
19846   ]);
19847
19848   /**
19849    *  @ngdoc directive
19850    *  @name ui.grid.importer.directive:uiGridImporterMenuItem
19851    *  @element div
19852    *  @restrict A
19853    *
19854    *  @description Handles the processing from the importer menu item - once a file is
19855    *  selected
19856    *
19857    */
19858   module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
19859     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
19860       return {
19861         replace: true,
19862         priority: 0,
19863         require: '^uiGrid',
19864         scope: false,
19865         templateUrl: 'ui-grid/importerMenuItem',
19866         link: function ($scope, $elm, $attrs, uiGridCtrl) {
19867           var handleFileSelect = function( event ){
19868             var target = event.srcElement || event.target;
19869
19870             if (target && target.files && target.files.length === 1) {
19871               var fileObject = target.files[0];
19872               uiGridImporterService.importThisFile( grid, fileObject );
19873               target.form.reset();
19874             }
19875           };
19876
19877           var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
19878           var grid = uiGridCtrl.grid;
19879
19880           if ( fileChooser.length !== 1 ){
19881             gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
19882           } else {
19883             fileChooser[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
19884           }
19885         }
19886       };
19887     }
19888   ]);
19889 })();
19890
19891 (function() {
19892   'use strict';
19893   /**
19894    *  @ngdoc overview
19895    *  @name ui.grid.infiniteScroll
19896    *
19897    *  @description
19898    *
19899    * #ui.grid.infiniteScroll
19900    *
19901    * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
19902    *
19903    * This module provides infinite scroll functionality to ui-grid
19904    *
19905    */
19906   var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
19907   /**
19908    *  @ngdoc service
19909    *  @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
19910    *
19911    *  @description Service for infinite scroll features
19912    */
19913   module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
19914
19915     var service = {
19916
19917       /**
19918        * @ngdoc function
19919        * @name initializeGrid
19920        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
19921        * @description This method register events and methods into grid public API
19922        */
19923
19924       initializeGrid: function(grid, $scope) {
19925         service.defaultGridOptions(grid.options);
19926
19927         if (!grid.options.enableInfiniteScroll){
19928           return;
19929         }
19930
19931         grid.infiniteScroll = { dataLoading: false };
19932         service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
19933           grid.api.core.on.scrollEnd($scope, service.handleScroll);
19934
19935         /**
19936          *  @ngdoc object
19937          *  @name ui.grid.infiniteScroll.api:PublicAPI
19938          *
19939          *  @description Public API for infinite scroll feature
19940          */
19941         var publicApi = {
19942           events: {
19943             infiniteScroll: {
19944
19945               /**
19946                * @ngdoc event
19947                * @name needLoadMoreData
19948                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
19949                * @description This event fires when scroll reaches bottom percentage of grid
19950                * and needs to load data
19951                */
19952
19953               needLoadMoreData: function ($scope, fn) {
19954               },
19955
19956               /**
19957                * @ngdoc event
19958                * @name needLoadMoreDataTop
19959                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
19960                * @description This event fires when scroll reaches top percentage of grid
19961                * and needs to load data
19962                */
19963
19964               needLoadMoreDataTop: function ($scope, fn) {
19965               }
19966             }
19967           },
19968           methods: {
19969             infiniteScroll: {
19970
19971               /**
19972                * @ngdoc function
19973                * @name dataLoaded
19974                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
19975                * @description Call this function when you have loaded the additional data
19976                * requested.  You should set scrollUp and scrollDown to indicate
19977                * whether there are still more pages in each direction.
19978                *
19979                * If you call dataLoaded without first calling `saveScrollPercentage` then we will
19980                * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
19981                * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
19982                * on variable speed internet connections.  Using `saveScrollPercentage` as demonstrated in the tutorial
19983                * should give a smoother scrolling experience for users.
19984                *
19985                * See infinite_scroll tutorial for example of usage
19986                * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
19987                * any more infinite scroll events upward
19988                * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
19989                * fire any more infinite scroll events downward
19990                * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted.  If you're
19991                * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
19992                */
19993               dataLoaded: function( scrollUp, scrollDown ) {
19994                 service.setScrollDirections(grid, scrollUp, scrollDown);
19995
19996                 var promise = service.adjustScroll(grid).then(function() {
19997                   grid.infiniteScroll.dataLoading = false;
19998                 });
19999
20000                 return promise;
20001               },
20002
20003               /**
20004                * @ngdoc function
20005                * @name resetScroll
20006                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20007                * @description Call this function when you have taken some action that makes the current
20008                * scroll position invalid.  For example, if you're using external sorting and you've resorted
20009                * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
20010                * you've reused an existing grid for a new data set
20011                *
20012                * You must tell us whether there is data upwards or downwards after the reset
20013                *
20014                * @param {boolean} scrollUp flag that there are pages upwards, fire
20015                * infinite scroll events upward
20016                * @param {boolean} scrollDown flag that there are pages downwards, so
20017                * fire infinite scroll events downward
20018                * @returns {promise} promise that is resolved when the scroll reset is complete
20019                */
20020               resetScroll: function( scrollUp, scrollDown ) {
20021                 service.setScrollDirections( grid, scrollUp, scrollDown);
20022
20023                 return service.adjustInfiniteScrollPosition(grid, 0);
20024               },
20025
20026
20027               /**
20028                * @ngdoc function
20029                * @name saveScrollPercentage
20030                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20031                * @description Saves the scroll percentage and number of visible rows before you adjust the data,
20032                * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
20033                */
20034               saveScrollPercentage: function() {
20035                 grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
20036                 grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
20037               },
20038
20039
20040               /**
20041                * @ngdoc function
20042                * @name dataRemovedTop
20043                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20044                * @description Adjusts the scroll position after you've removed data at the top
20045                * @param {boolean} scrollUp flag that there are pages upwards, fire
20046                * infinite scroll events upward
20047                * @param {boolean} scrollDown flag that there are pages downwards, so
20048                * fire infinite scroll events downward
20049                */
20050               dataRemovedTop: function( scrollUp, scrollDown ) {
20051                 service.dataRemovedTop( grid, scrollUp, scrollDown );
20052               },
20053
20054               /**
20055                * @ngdoc function
20056                * @name dataRemovedBottom
20057                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20058                * @description Adjusts the scroll position after you've removed data at the bottom
20059                * @param {boolean} scrollUp flag that there are pages upwards, fire
20060                * infinite scroll events upward
20061                * @param {boolean} scrollDown flag that there are pages downwards, so
20062                * fire infinite scroll events downward
20063                */
20064               dataRemovedBottom: function( scrollUp, scrollDown ) {
20065                 service.dataRemovedBottom( grid, scrollUp, scrollDown );
20066               },
20067
20068               /**
20069                * @ngdoc function
20070                * @name setScrollDirections
20071                * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20072                * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20073                * and also sets the grid.suppressParentScroll
20074                * @param {boolean} scrollUp whether there are pages available up - defaults to false
20075                * @param {boolean} scrollDown whether there are pages available down - defaults to true
20076                */
20077               setScrollDirections:  function ( scrollUp, scrollDown ) {
20078                 service.setScrollDirections( grid, scrollUp, scrollDown );
20079               }
20080
20081             }
20082           }
20083         };
20084         grid.api.registerEventsFromObject(publicApi.events);
20085         grid.api.registerMethodsFromObject(publicApi.methods);
20086       },
20087
20088
20089       defaultGridOptions: function (gridOptions) {
20090         //default option to true unless it was explicitly set to false
20091         /**
20092          *  @ngdoc object
20093          *  @name ui.grid.infiniteScroll.api:GridOptions
20094          *
20095          *  @description GridOptions for infinite scroll feature, these are available to be
20096          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20097          */
20098
20099         /**
20100          *  @ngdoc object
20101          *  @name enableInfiniteScroll
20102          *  @propertyOf  ui.grid.infiniteScroll.api:GridOptions
20103          *  @description Enable infinite scrolling for this grid
20104          *  <br/>Defaults to true
20105          */
20106         gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
20107
20108         /**
20109          * @ngdoc property
20110          * @name infiniteScrollRowsFromEnd
20111          * @propertyOf ui.grid.class:GridOptions
20112          * @description This setting controls how close to the end of the dataset a user gets before
20113          * more data is requested by the infinite scroll, whether scrolling up or down.  This allows you to
20114          * 'prefetch' rows before the user actually runs out of scrolling.
20115          *
20116          * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
20117          * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
20118          * preserve that scroll position
20119          *
20120          * <br> Defaults to 20
20121          */
20122         gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
20123
20124         /**
20125          * @ngdoc property
20126          * @name infiniteScrollUp
20127          * @propertyOf ui.grid.class:GridOptions
20128          * @description Whether you allow infinite scroll up, implying that the first page of data
20129          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20130          * needMoreDataTop event when the user hits the top of the scrollbar.
20131          * <br> Defaults to false
20132          */
20133         gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
20134
20135         /**
20136          * @ngdoc property
20137          * @name infiniteScrollDown
20138          * @propertyOf ui.grid.class:GridOptions
20139          * @description Whether you allow infinite scroll down, implying that the first page of data
20140          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20141          * needMoreData event when the user hits the bottom of the scrollbar.
20142          * <br> Defaults to true
20143          */
20144         gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
20145       },
20146
20147
20148       /**
20149        * @ngdoc function
20150        * @name setScrollDirections
20151        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20152        * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20153        * and also sets the grid.suppressParentScroll
20154        * @param {grid} grid the grid we're operating on
20155        * @param {boolean} scrollUp whether there are pages available up - defaults to false
20156        * @param {boolean} scrollDown whether there are pages available down - defaults to true
20157        */
20158       setScrollDirections:  function ( grid, scrollUp, scrollDown ) {
20159         grid.infiniteScroll.scrollUp = ( scrollUp === true );
20160         grid.suppressParentScrollUp = ( scrollUp === true );
20161
20162         grid.infiniteScroll.scrollDown = ( scrollDown !== false);
20163         grid.suppressParentScrollDown = ( scrollDown !== false);
20164       },
20165
20166
20167       /**
20168        * @ngdoc function
20169        * @name handleScroll
20170        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20171        * @description Called whenever the grid scrolls, determines whether the scroll should
20172        * trigger an infinite scroll request for more data
20173        * @param {object} args the args from the event
20174        */
20175       handleScroll:  function (args) {
20176         // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
20177         if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
20178           return;
20179         }
20180
20181         if (args.y) {
20182           var percentage;
20183           var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
20184           if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
20185             percentage = args.y.percentage;
20186             if (percentage <= targetPercentage){
20187               service.loadData(args.grid);
20188             }
20189           } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
20190             percentage = 1 - args.y.percentage;
20191             if (percentage <= targetPercentage){
20192               service.loadData(args.grid);
20193             }
20194           }
20195         }
20196       },
20197
20198
20199       /**
20200        * @ngdoc function
20201        * @name loadData
20202        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20203        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20204        * and whether there are more pages upwards or downwards.  It also stores the number of rows that we had previously,
20205        * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
20206        * @param {Grid} grid the grid we're working on
20207        */
20208       loadData: function (grid) {
20209         // save number of currently visible rows to calculate new scroll position later - we know that we want
20210         // to be at approximately the row we're currently at
20211         grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20212         grid.infiniteScroll.direction = grid.scrollDirection;
20213         delete grid.infiniteScroll.prevScrollTop;
20214
20215         if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
20216           grid.infiniteScroll.dataLoading = true;
20217           grid.api.infiniteScroll.raise.needLoadMoreDataTop();
20218         } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
20219           grid.infiniteScroll.dataLoading = true;
20220           grid.api.infiniteScroll.raise.needLoadMoreData();
20221         }
20222       },
20223
20224
20225       /**
20226        * @ngdoc function
20227        * @name adjustScroll
20228        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20229        * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
20230        * addition and to make things look clean.
20231        *
20232        * If we're scrolling up we scroll to the first row of the old data set -
20233        * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
20234        * the time the data comes back.  If we're scrolling down we scoll to the last row of the old data set - so we're
20235        * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
20236        * the data comes back.
20237        *
20238        * Neither of these are good assumptions, but making this a smoother experience really requires
20239        * that trigger to not be a percentage, and to be much closer to the end of the data (say, 5 rows off the end).  Even then
20240        * it'd be better still to actually run into the end.  But if the data takes a while to come back, they may have scrolled
20241        * somewhere else in the mean-time, in which case they'll get a jump back to the new data.  Anyway, this will do for
20242        * now, until someone wants to do better.
20243        * @param {Grid} grid the grid we're working on
20244        * @returns {promise} a promise that is resolved when scrolling has finished
20245        */
20246       adjustScroll: function(grid){
20247         var promise = $q.defer();
20248         $timeout(function () {
20249           var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
20250
20251           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
20252           rowHeight = grid.options.rowHeight;
20253
20254           if ( grid.infiniteScroll.direction === undefined ){
20255             // called from initialize, tweak our scroll up a little
20256             service.adjustInfiniteScrollPosition(grid, 0);
20257           }
20258
20259           newVisibleRows = grid.getVisibleRowCount();
20260
20261           // in case not enough data is loaded to enable scroller - load more data
20262           var canvasHeight = rowHeight * newVisibleRows;
20263           if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
20264             grid.api.infiniteScroll.raise.needLoadMoreData();
20265           }
20266
20267           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
20268             oldTop = grid.infiniteScroll.prevScrollTop || 0;
20269             newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
20270             service.adjustInfiniteScrollPosition(grid, newTop);
20271             $timeout( function() {
20272               promise.resolve();
20273             });
20274           }
20275
20276           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
20277             newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
20278             service.adjustInfiniteScrollPosition(grid, newTop);
20279             $timeout( function() {
20280               promise.resolve();
20281             });
20282           }
20283         }, 0);
20284
20285         return promise.promise;
20286       },
20287
20288
20289       /**
20290        * @ngdoc function
20291        * @name adjustInfiniteScrollPosition
20292        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20293        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20294        * @param {Grid} grid the grid we're working on
20295        * @param {number} scrollTop the position through the grid that we want to scroll to
20296        * @returns {promise} a promise that is resolved when the scrolling finishes
20297        */
20298       adjustInfiniteScrollPosition: function (grid, scrollTop) {
20299         var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
20300           visibleRows = grid.getVisibleRowCount(),
20301           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
20302           rowHeight = grid.options.rowHeight,
20303           scrollHeight = visibleRows*rowHeight-viewportHeight;
20304
20305         //for infinite scroll, if there are pages upwards then never allow it to be at the zero position so the up button can be active
20306         if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
20307           // using pixels results in a relative scroll, hence we have to use percentage
20308           scrollEvent.y = {percentage: 1/scrollHeight};
20309         }
20310         else {
20311           scrollEvent.y = {percentage: scrollTop/scrollHeight};
20312         }
20313         grid.scrollContainers('', scrollEvent);
20314       },
20315
20316
20317       /**
20318        * @ngdoc function
20319        * @name dataRemovedTop
20320        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20321        * @description Adjusts the scroll position after you've removed data at the top. You should
20322        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
20323        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
20324        * before you start removing data
20325        * @param {Grid} grid the grid we're working on
20326        * @param {boolean} scrollUp flag that there are pages upwards, fire
20327        * infinite scroll events upward
20328        * @param {boolean} scrollDown flag that there are pages downwards, so
20329        * fire infinite scroll events downward
20330        * @returns {promise} a promise that is resolved when the scrolling finishes
20331        */
20332       dataRemovedTop: function( grid, scrollUp, scrollDown ) {
20333         var newVisibleRows, oldTop, newTop, rowHeight;
20334         service.setScrollDirections( grid, scrollUp, scrollDown );
20335
20336         newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20337         oldTop = grid.infiniteScroll.prevScrollTop;
20338         rowHeight = grid.options.rowHeight;
20339
20340         // since we removed from the top, our new scroll row will be the old scroll row less the number
20341         // of rows removed
20342         newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
20343
20344         return service.adjustInfiniteScrollPosition( grid, newTop );
20345       },
20346
20347       /**
20348        * @ngdoc function
20349        * @name dataRemovedBottom
20350        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20351        * @description Adjusts the scroll position after you've removed data at the bottom.  You should
20352        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
20353        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
20354        * before you start removing data
20355        * @param {Grid} grid the grid we're working on
20356        * @param {boolean} scrollUp flag that there are pages upwards, fire
20357        * infinite scroll events upward
20358        * @param {boolean} scrollDown flag that there are pages downwards, so
20359        * fire infinite scroll events downward
20360        */
20361       dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
20362         var newTop;
20363         service.setScrollDirections( grid, scrollUp, scrollDown );
20364
20365         newTop = grid.infiniteScroll.prevScrollTop;
20366
20367         return service.adjustInfiniteScrollPosition( grid, newTop );
20368       }
20369     };
20370     return service;
20371   }]);
20372   /**
20373    *  @ngdoc directive
20374    *  @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
20375    *  @element div
20376    *  @restrict A
20377    *
20378    *  @description Adds infinite scroll features to grid
20379    *
20380    *  @example
20381    <example module="app">
20382    <file name="app.js">
20383    var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
20384
20385    app.controller('MainCtrl', ['$scope', function ($scope) {
20386       $scope.data = [
20387         { name: 'Alex', car: 'Toyota' },
20388             { name: 'Sam', car: 'Lexus' }
20389       ];
20390
20391       $scope.columnDefs = [
20392         {name: 'name'},
20393         {name: 'car'}
20394       ];
20395     }]);
20396    </file>
20397    <file name="index.html">
20398    <div ng-controller="MainCtrl">
20399    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
20400    </div>
20401    </file>
20402    </example>
20403    */
20404
20405   module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
20406     function (uiGridInfiniteScrollService) {
20407       return {
20408         priority: -200,
20409         scope: false,
20410         require: '^uiGrid',
20411         compile: function($scope, $elm, $attr){
20412           return {
20413             pre: function($scope, $elm, $attr, uiGridCtrl) {
20414               uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
20415             },
20416             post: function($scope, $elm, $attr) {
20417             }
20418           };
20419         }
20420       };
20421     }]);
20422
20423 })();
20424
20425 (function () {
20426   'use strict';
20427
20428   /**
20429    * @ngdoc overview
20430    * @name ui.grid.moveColumns
20431    * @description
20432    *
20433    * # ui.grid.moveColumns
20434    *
20435    * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
20436    *
20437    * This module provides column moving capability to ui.grid. It enables to change the position of columns.
20438    * <div doc-module-components="ui.grid.moveColumns"></div>
20439    */
20440   var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
20441
20442   /**
20443    *  @ngdoc service
20444    *  @name ui.grid.moveColumns.service:uiGridMoveColumnService
20445    *  @description Service for column moving feature.
20446    */
20447   module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
20448
20449     var service = {
20450       initializeGrid: function (grid) {
20451         var self = this;
20452         this.registerPublicApi(grid);
20453         this.defaultGridOptions(grid.options);
20454         grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
20455         grid.registerColumnBuilder(self.movableColumnBuilder);
20456         grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
20457       },
20458       registerPublicApi: function (grid) {
20459         var self = this;
20460         /**
20461          *  @ngdoc object
20462          *  @name ui.grid.moveColumns.api:PublicApi
20463          *  @description Public Api for column moving feature.
20464          */
20465         var publicApi = {
20466           events: {
20467             /**
20468              * @ngdoc event
20469              * @name columnPositionChanged
20470              * @eventOf  ui.grid.moveColumns.api:PublicApi
20471              * @description raised when column is moved
20472              * <pre>
20473              *      gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
20474              * </pre>
20475              * @param {object} colDef the column that was moved
20476              * @param {integer} originalPosition of the column
20477              * @param {integer} finalPosition of the column
20478              */
20479             colMovable: {
20480               columnPositionChanged: function (colDef, originalPosition, newPosition) {
20481               }
20482             }
20483           },
20484           methods: {
20485             /**
20486              * @ngdoc method
20487              * @name moveColumn
20488              * @methodOf  ui.grid.moveColumns.api:PublicApi
20489              * @description Method can be used to change column position.
20490              * <pre>
20491              *      gridApi.colMovable.moveColumn(oldPosition, newPosition)
20492              * </pre>
20493              * @param {integer} originalPosition of the column
20494              * @param {integer} finalPosition of the column
20495              */
20496             colMovable: {
20497               moveColumn: function (originalPosition, finalPosition) {
20498                 var columns = grid.columns;
20499                 if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
20500                   gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
20501                   return;
20502                 }
20503                 var nonMovableColumns = 0;
20504                 for (var i = 0; i < columns.length; i++) {
20505                   if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
20506                     nonMovableColumns++;
20507                   }
20508                 }
20509                 if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
20510                   gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
20511                   return;
20512                 }
20513                 var findPositionForRenderIndex = function (index) {
20514                   var position = index;
20515                   for (var i = 0; i <= position; i++) {
20516                     if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
20517                       position++;
20518                     }
20519                   }
20520                   return position;
20521                 };
20522                 self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
20523               }
20524             }
20525           }
20526         };
20527         grid.api.registerEventsFromObject(publicApi.events);
20528         grid.api.registerMethodsFromObject(publicApi.methods);
20529       },
20530       defaultGridOptions: function (gridOptions) {
20531         /**
20532          *  @ngdoc object
20533          *  @name ui.grid.moveColumns.api:GridOptions
20534          *
20535          *  @description Options for configuring the move column feature, these are available to be
20536          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20537          */
20538         /**
20539          *  @ngdoc object
20540          *  @name enableColumnMoving
20541          *  @propertyOf  ui.grid.moveColumns.api:GridOptions
20542          *  @description If defined, sets the default value for the colMovable flag on each individual colDefs
20543          *  if their individual enableColumnMoving configuration is not defined. Defaults to true.
20544          */
20545         gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
20546       },
20547       movableColumnBuilder: function (colDef, col, gridOptions) {
20548         var promises = [];
20549         /**
20550          *  @ngdoc object
20551          *  @name ui.grid.moveColumns.api:ColumnDef
20552          *
20553          *  @description Column Definition for move column feature, these are available to be
20554          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
20555          */
20556         /**
20557          *  @ngdoc object
20558          *  @name enableColumnMoving
20559          *  @propertyOf  ui.grid.moveColumns.api:ColumnDef
20560          *  @description Enable column moving for the column.
20561          */
20562         colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
20563           : colDef.enableColumnMoving;
20564         return $q.all(promises);
20565       },
20566       /**
20567        * @ngdoc method
20568        * @name updateColumnCache
20569        * @methodOf  ui.grid.moveColumns
20570        * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
20571        */
20572       updateColumnCache: function(grid){
20573         grid.moveColumns.orderCache = grid.getOnlyDataColumns();
20574       },
20575       /**
20576        * @ngdoc method
20577        * @name verifyColumnOrder
20578        * @methodOf  ui.grid.moveColumns
20579        * @description dataChangeCallback which uses the cached column order to restore the column order
20580        * when it is reset by altering the columnDefs array.
20581        */
20582       verifyColumnOrder: function(grid){
20583         var headerRowOffset = grid.rowHeaderColumns.length;
20584         var newIndex;
20585
20586         angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
20587           newIndex = grid.columns.indexOf(cacheCol);
20588           if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
20589             var column = grid.columns.splice(newIndex, 1)[0];
20590             grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
20591           }
20592         });
20593       },
20594       redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
20595
20596         var columns = grid.columns;
20597
20598         var originalColumn = columns[originalPosition];
20599         if (originalColumn.colDef.enableColumnMoving) {
20600           if (originalPosition > newPosition) {
20601             for (var i1 = originalPosition; i1 > newPosition; i1--) {
20602               columns[i1] = columns[i1 - 1];
20603             }
20604           }
20605           else if (newPosition > originalPosition) {
20606             for (var i2 = originalPosition; i2 < newPosition; i2++) {
20607               columns[i2] = columns[i2 + 1];
20608             }
20609           }
20610           columns[newPosition] = originalColumn;
20611           service.updateColumnCache(grid);
20612           grid.queueGridRefresh();
20613           $timeout(function () {
20614             grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
20615             grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
20616           });
20617         }
20618       }
20619     };
20620     return service;
20621   }]);
20622
20623   /**
20624    *  @ngdoc directive
20625    *  @name ui.grid.moveColumns.directive:uiGridMoveColumns
20626    *  @element div
20627    *  @restrict A
20628    *  @description Adds column moving features to the ui-grid directive.
20629    *  @example
20630    <example module="app">
20631    <file name="app.js">
20632    var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
20633    app.controller('MainCtrl', ['$scope', function ($scope) {
20634         $scope.data = [
20635           { name: 'Bob', title: 'CEO', age: 45 },
20636           { name: 'Frank', title: 'Lowly Developer', age: 25 },
20637           { name: 'Jenny', title: 'Highly Developer', age: 35 }
20638         ];
20639         $scope.columnDefs = [
20640           {name: 'name'},
20641           {name: 'title'},
20642           {name: 'age'}
20643         ];
20644       }]);
20645    </file>
20646    <file name="main.css">
20647    .grid {
20648       width: 100%;
20649       height: 150px;
20650     }
20651    </file>
20652    <file name="index.html">
20653    <div ng-controller="MainCtrl">
20654    <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
20655    </div>
20656    </file>
20657    </example>
20658    */
20659   module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
20660     return {
20661       replace: true,
20662       priority: 0,
20663       require: '^uiGrid',
20664       scope: false,
20665       compile: function () {
20666         return {
20667           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
20668             uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
20669           },
20670           post: function ($scope, $elm, $attrs, uiGridCtrl) {
20671           }
20672         };
20673       }
20674     };
20675   }]);
20676
20677   /**
20678    *  @ngdoc directive
20679    *  @name ui.grid.moveColumns.directive:uiGridHeaderCell
20680    *  @element div
20681    *  @restrict A
20682    *
20683    *  @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
20684    *
20685    *  On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
20686    *  In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
20687    *  On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
20688    *
20689    *  Events that invoke cloning of header cell:
20690    *    - mousedown
20691    *
20692    *  Events that invoke movement of cloned header cell:
20693    *    - mousemove
20694    *
20695    *  Events that invoke repositioning of column:
20696    *    - mouseup
20697    */
20698   module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
20699     function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
20700       return {
20701         priority: -10,
20702         require: '^uiGrid',
20703         compile: function () {
20704           return {
20705             post: function ($scope, $elm, $attrs, uiGridCtrl) {
20706
20707               if ($scope.col.colDef.enableColumnMoving) {
20708
20709                 /*
20710                  * Our general approach to column move is that we listen to a touchstart or mousedown
20711                  * event over the column header.  When we hear one, then we wait for a move of the same type
20712                  * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
20713                  * a mousemove (i.e. a drag) before we decide that there's a move underway.  If there's never a move,
20714                  * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
20715                  *
20716                  */
20717                 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
20718
20719                 var gridLeft;
20720                 var previousMouseX;
20721                 var totalMouseMovement;
20722                 var rightMoveLimit;
20723                 var elmCloned = false;
20724                 var movingElm;
20725                 var reducedWidth;
20726                 var moveOccurred = false;
20727
20728                 var downFn = function( event ){
20729                   //Setting some variables required for calculations.
20730                   gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
20731                   if ( $scope.grid.hasLeftContainer() ){
20732                     gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
20733                   }
20734
20735                   previousMouseX = event.pageX;
20736                   totalMouseMovement = 0;
20737                   rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
20738
20739                   if ( event.type === 'mousedown' ){
20740                     $document.on('mousemove', moveFn);
20741                     $document.on('mouseup', upFn);
20742                   } else if ( event.type === 'touchstart' ){
20743                     $document.on('touchmove', moveFn);
20744                     $document.on('touchend', upFn);
20745                   }
20746                 };
20747
20748                 var moveFn = function( event ) {
20749                   var changeValue = event.pageX - previousMouseX;
20750                   if ( changeValue === 0 ){ return; }
20751                   //Disable text selection in Chrome during column move
20752                   document.onselectstart = function() { return false; };
20753
20754                   moveOccurred = true;
20755
20756                   if (!elmCloned) {
20757                     cloneElement();
20758                   }
20759                   else if (elmCloned) {
20760                     moveElement(changeValue);
20761                     previousMouseX = event.pageX;
20762                   }
20763                 };
20764
20765                 var upFn = function( event ){
20766                   //Re-enable text selection after column move
20767                   document.onselectstart = null;
20768
20769                   //Remove the cloned element on mouse up.
20770                   if (movingElm) {
20771                     movingElm.remove();
20772                     elmCloned = false;
20773                   }
20774
20775                   offAllEvents();
20776                   onDownEvents();
20777
20778                   if (!moveOccurred){
20779                     return;
20780                   }
20781
20782                   var columns = $scope.grid.columns;
20783                   var columnIndex = 0;
20784                   for (var i = 0; i < columns.length; i++) {
20785                     if (columns[i].colDef.name !== $scope.col.colDef.name) {
20786                       columnIndex++;
20787                     }
20788                     else {
20789                       break;
20790                     }
20791                   }
20792
20793                   //Case where column should be moved to a position on its left
20794                   if (totalMouseMovement < 0) {
20795                     var totalColumnsLeftWidth = 0;
20796                     for (var il = columnIndex - 1; il >= 0; il--) {
20797                       if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
20798                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
20799                         if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
20800                           uiGridMoveColumnService.redrawColumnAtPosition
20801                           ($scope.grid, columnIndex, il + 1);
20802                           break;
20803                         }
20804                       }
20805                     }
20806                     //Case where column should be moved to beginning of the grid.
20807                     if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
20808                       uiGridMoveColumnService.redrawColumnAtPosition
20809                       ($scope.grid, columnIndex, 0);
20810                     }
20811                   }
20812
20813                   //Case where column should be moved to a position on its right
20814                   else if (totalMouseMovement > 0) {
20815                     var totalColumnsRightWidth = 0;
20816                     for (var ir = columnIndex + 1; ir < columns.length; ir++) {
20817                       if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
20818                         totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
20819                         if (totalColumnsRightWidth > totalMouseMovement) {
20820                           uiGridMoveColumnService.redrawColumnAtPosition
20821                           ($scope.grid, columnIndex, ir - 1);
20822                           break;
20823                         }
20824                       }
20825                     }
20826                     //Case where column should be moved to end of the grid.
20827                     if (totalColumnsRightWidth < totalMouseMovement) {
20828                       uiGridMoveColumnService.redrawColumnAtPosition
20829                       ($scope.grid, columnIndex, columns.length - 1);
20830                     }
20831                   }
20832                 };
20833
20834                 var onDownEvents = function(){
20835                   $contentsElm.on('touchstart', downFn);
20836                   $contentsElm.on('mousedown', downFn);
20837                 };
20838
20839                 var offAllEvents = function() {
20840                   $contentsElm.off('touchstart', downFn);
20841                   $contentsElm.off('mousedown', downFn);
20842
20843                   $document.off('mousemove', moveFn);
20844                   $document.off('touchmove', moveFn);
20845
20846                   $document.off('mouseup', upFn);
20847                   $document.off('touchend', upFn);
20848                 };
20849
20850                 onDownEvents();
20851
20852
20853                 var cloneElement = function () {
20854                   elmCloned = true;
20855
20856                   //Cloning header cell and appending to current header cell.
20857                   movingElm = $elm.clone();
20858                   $elm.parent().append(movingElm);
20859
20860                   //Left of cloned element should be aligned to original header cell.
20861                   movingElm.addClass('movingColumn');
20862                   var movingElementStyles = {};
20863                   var elmLeft;
20864                   if (gridUtil.detectBrowser() === 'safari') {
20865                     //Correction for Safari getBoundingClientRect,
20866                     //which does not correctly compute when there is an horizontal scroll
20867                     elmLeft = $elm[0].offsetLeft + $elm[0].offsetWidth - $elm[0].getBoundingClientRect().width;
20868                   }
20869                   else {
20870                     elmLeft = $elm[0].getBoundingClientRect().left;
20871                   }
20872                   movingElementStyles.left = (elmLeft - gridLeft) + 'px';
20873                   var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
20874                   var elmRight = $elm[0].getBoundingClientRect().right;
20875                   if (elmRight > gridRight) {
20876                     reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
20877                     movingElementStyles.width = reducedWidth + 'px';
20878                   }
20879                   movingElm.css(movingElementStyles);
20880                 };
20881
20882                 var moveElement = function (changeValue) {
20883                   //Calculate total column width
20884                   var columns = $scope.grid.columns;
20885                   var totalColumnWidth = 0;
20886                   for (var i = 0; i < columns.length; i++) {
20887                     if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
20888                       totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
20889                     }
20890                   }
20891
20892                   //Calculate new position of left of column
20893                   var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
20894                   var currentElmRight = movingElm[0].getBoundingClientRect().right;
20895                   var newElementLeft;
20896
20897                   newElementLeft = currentElmLeft - gridLeft + changeValue;
20898                   newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
20899
20900                   //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
20901                   if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
20902                     movingElm.css({visibility: 'visible', 'left': newElementLeft + 'px'});
20903                   }
20904                   else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
20905                     changeValue *= 8;
20906                     var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
20907                     scrollEvent.x = {pixels: changeValue};
20908                     scrollEvent.grid.scrollContainers('',scrollEvent);
20909                   }
20910
20911                   //Calculate total width of columns on the left of the moving column and the mouse movement
20912                   var totalColumnsLeftWidth = 0;
20913                   for (var il = 0; il < columns.length; il++) {
20914                     if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
20915                       if (columns[il].colDef.name !== $scope.col.colDef.name) {
20916                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
20917                       }
20918                       else {
20919                         break;
20920                       }
20921                     }
20922                   }
20923                   if ($scope.newScrollLeft === undefined) {
20924                     totalMouseMovement += changeValue;
20925                   }
20926                   else {
20927                     totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
20928                   }
20929
20930                   //Increase width of moving column, in case the rightmost column was moved and its width was
20931                   //decreased because of overflow
20932                   if (reducedWidth < $scope.col.drawnWidth) {
20933                     reducedWidth += Math.abs(changeValue);
20934                     movingElm.css({'width': reducedWidth + 'px'});
20935                   }
20936                 };
20937               }
20938             }
20939           };
20940         }
20941       };
20942     }]);
20943 })();
20944
20945 (function() {
20946   'use strict';
20947
20948   /**
20949    * @ngdoc overview
20950    * @name ui.grid.pagination
20951    *
20952    * @description
20953    *
20954    * # ui.grid.pagination
20955    *
20956    * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
20957    *
20958    * This module provides pagination support to ui-grid
20959    */
20960   var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);
20961
20962   /**
20963    * @ngdoc service
20964    * @name ui.grid.pagination.service:uiGridPaginationService
20965    *
20966    * @description Service for the pagination feature
20967    */
20968   module.service('uiGridPaginationService', ['gridUtil',
20969     function (gridUtil) {
20970       var service = {
20971         /**
20972          * @ngdoc method
20973          * @name initializeGrid
20974          * @methodOf ui.grid.pagination.service:uiGridPaginationService
20975          * @description Attaches the service to a certain grid
20976          * @param {Grid} grid The grid we want to work with
20977          */
20978         initializeGrid: function (grid) {
20979           service.defaultGridOptions(grid.options);
20980
20981           /**
20982           * @ngdoc object
20983           * @name ui.grid.pagination.api:PublicAPI
20984           *
20985           * @description Public API for the pagination feature
20986           */
20987           var publicApi = {
20988             events: {
20989               pagination: {
20990               /**
20991                * @ngdoc event
20992                * @name paginationChanged
20993                * @eventOf ui.grid.pagination.api:PublicAPI
20994                * @description This event fires when the pageSize or currentPage changes
20995                * @param {int} currentPage requested page number
20996                * @param {int} pageSize requested page size
20997                */
20998                 paginationChanged: function (currentPage, pageSize) { }
20999               }
21000             },
21001             methods: {
21002               pagination: {
21003                 /**
21004                  * @ngdoc method
21005                  * @name getPage
21006                  * @methodOf ui.grid.pagination.api:PublicAPI
21007                  * @description Returns the number of the current page
21008                  */
21009                 getPage: function () {
21010                   return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
21011                 },
21012                 /**
21013                  * @ngdoc method
21014                  * @name getTotalPages
21015                  * @methodOf ui.grid.pagination.api:PublicAPI
21016                  * @description Returns the total number of pages
21017                  */
21018                 getTotalPages: function () {
21019                   if (!grid.options.enablePagination) {
21020                     return null;
21021                   }
21022
21023                   return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
21024                 },
21025                 /**
21026                  * @ngdoc method
21027                  * @name nextPage
21028                  * @methodOf ui.grid.pagination.api:PublicAPI
21029                  * @description Moves to the next page, if possible
21030                  */
21031                 nextPage: function () {
21032                   if (!grid.options.enablePagination) {
21033                     return;
21034                   }
21035
21036                   if (grid.options.totalItems > 0) {
21037                     grid.options.paginationCurrentPage = Math.min(
21038                       grid.options.paginationCurrentPage + 1,
21039                       publicApi.methods.pagination.getTotalPages()
21040                     );
21041                   } else {
21042                     grid.options.paginationCurrentPage++;
21043                   }
21044                 },
21045                 /**
21046                  * @ngdoc method
21047                  * @name previousPage
21048                  * @methodOf ui.grid.pagination.api:PublicAPI
21049                  * @description Moves to the previous page, if we're not on the first page
21050                  */
21051                 previousPage: function () {
21052                   if (!grid.options.enablePagination) {
21053                     return;
21054                   }
21055
21056                   grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
21057                 },
21058                 /**
21059                  * @ngdoc method
21060                  * @name seek
21061                  * @methodOf ui.grid.pagination.api:PublicAPI
21062                  * @description Moves to the requested page
21063                  * @param {int} page The number of the page that should be displayed
21064                  */
21065                 seek: function (page) {
21066                   if (!grid.options.enablePagination) {
21067                     return;
21068                   }
21069                   if (!angular.isNumber(page) || page < 1) {
21070                     throw 'Invalid page number: ' + page;
21071                   }
21072
21073                   grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
21074                 }
21075               }
21076             }
21077           };
21078
21079           grid.api.registerEventsFromObject(publicApi.events);
21080           grid.api.registerMethodsFromObject(publicApi.methods);
21081
21082           var processPagination = function( renderableRows ){
21083             if (grid.options.useExternalPagination || !grid.options.enablePagination) {
21084               return renderableRows;
21085             }
21086             //client side pagination
21087             var pageSize = parseInt(grid.options.paginationPageSize, 10);
21088             var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
21089
21090             var visibleRows = renderableRows.filter(function (row) { return row.visible; });
21091             grid.options.totalItems = visibleRows.length;
21092
21093             var firstRow = (currentPage - 1) * pageSize;
21094             if (firstRow > visibleRows.length) {
21095               currentPage = grid.options.paginationCurrentPage = 1;
21096               firstRow = (currentPage - 1) * pageSize;
21097             }
21098             return visibleRows.slice(firstRow, firstRow + pageSize);
21099           };
21100
21101           grid.registerRowsProcessor(processPagination, 900 );
21102
21103         },
21104         defaultGridOptions: function (gridOptions) {
21105           /**
21106            * @ngdoc object
21107            * @name ui.grid.pagination.api:GridOptions
21108            *
21109            * @description GridOptions for the pagination feature, these are available to be
21110            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21111            */
21112
21113           /**
21114            * @ngdoc property
21115            * @name enablePagination
21116            * @propertyOf ui.grid.pagination.api:GridOptions
21117            * @description Enables pagination, defaults to true
21118            */
21119           gridOptions.enablePagination = gridOptions.enablePagination !== false;
21120           /**
21121            * @ngdoc property
21122            * @name enablePaginationControls
21123            * @propertyOf ui.grid.pagination.api:GridOptions
21124            * @description Enables the paginator at the bottom of the grid. Turn this off, if you want to implement your
21125            *              own controls outside the grid.
21126            */
21127           gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
21128           /**
21129            * @ngdoc property
21130            * @name useExternalPagination
21131            * @propertyOf ui.grid.pagination.api:GridOptions
21132            * @description Disables client side pagination. When true, handle the paginationChanged event and set data
21133            *              and totalItems, defaults to `false`
21134            */
21135           gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
21136           /**
21137            * @ngdoc property
21138            * @name totalItems
21139            * @propertyOf ui.grid.pagination.api:GridOptions
21140            * @description Total number of items, set automatically when client side pagination, needs set by user
21141            *              for server side pagination
21142            */
21143           if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
21144             gridOptions.totalItems = 0;
21145           }
21146           /**
21147            * @ngdoc property
21148            * @name paginationPageSizes
21149            * @propertyOf ui.grid.pagination.api:GridOptions
21150            * @description Array of page sizes, defaults to `[250, 500, 1000]`
21151            */
21152           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
21153             gridOptions.paginationPageSizes = [250, 500, 1000];
21154           }
21155           /**
21156            * @ngdoc property
21157            * @name paginationPageSize
21158            * @propertyOf ui.grid.pagination.api:GridOptions
21159            * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
21160            */
21161           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
21162             if (gridOptions.paginationPageSizes.length > 0) {
21163               gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
21164             } else {
21165               gridOptions.paginationPageSize = 0;
21166             }
21167           }
21168           /**
21169            * @ngdoc property
21170            * @name paginationCurrentPage
21171            * @propertyOf ui.grid.pagination.api:GridOptions
21172            * @description Current page number, defaults to 1
21173            */
21174           if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
21175             gridOptions.paginationCurrentPage = 1;
21176           }
21177
21178           /**
21179            * @ngdoc property
21180            * @name paginationTemplate
21181            * @propertyOf ui.grid.pagination.api:GridOptions
21182            * @description A custom template for the pager, defaults to `ui-grid/pagination`
21183            */
21184           if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
21185             gridOptions.paginationTemplate = 'ui-grid/pagination';
21186           }
21187         },
21188         /**
21189          * @ngdoc method
21190          * @methodOf ui.grid.pagination.service:uiGridPaginationService
21191          * @name uiGridPaginationService
21192          * @description  Raises paginationChanged and calls refresh for client side pagination
21193          * @param {Grid} grid the grid for which the pagination changed
21194          * @param {int} currentPage requested page number
21195          * @param {int} pageSize requested page size
21196          */
21197         onPaginationChanged: function (grid, currentPage, pageSize) {
21198             grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
21199             if (!grid.options.useExternalPagination) {
21200               grid.queueGridRefresh(); //client side pagination
21201             }
21202         }
21203       };
21204
21205       return service;
21206     }
21207   ]);
21208   /**
21209    *  @ngdoc directive
21210    *  @name ui.grid.pagination.directive:uiGridPagination
21211    *  @element div
21212    *  @restrict A
21213    *
21214    *  @description Adds pagination features to grid
21215    *  @example
21216    <example module="app">
21217    <file name="app.js">
21218    var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);
21219
21220    app.controller('MainCtrl', ['$scope', function ($scope) {
21221       $scope.data = [
21222         { name: 'Alex', car: 'Toyota' },
21223         { name: 'Sam', car: 'Lexus' },
21224         { name: 'Joe', car: 'Dodge' },
21225         { name: 'Bob', car: 'Buick' },
21226         { name: 'Cindy', car: 'Ford' },
21227         { name: 'Brian', car: 'Audi' },
21228         { name: 'Malcom', car: 'Mercedes Benz' },
21229         { name: 'Dave', car: 'Ford' },
21230         { name: 'Stacey', car: 'Audi' },
21231         { name: 'Amy', car: 'Acura' },
21232         { name: 'Scott', car: 'Toyota' },
21233         { name: 'Ryan', car: 'BMW' },
21234       ];
21235
21236       $scope.gridOptions = {
21237         data: 'data',
21238         paginationPageSizes: [5, 10, 25],
21239         paginationPageSize: 5,
21240         columnDefs: [
21241           {name: 'name'},
21242           {name: 'car'}
21243         ]
21244        }
21245     }]);
21246    </file>
21247    <file name="index.html">
21248    <div ng-controller="MainCtrl">
21249    <div ui-grid="gridOptions" ui-grid-pagination></div>
21250    </div>
21251    </file>
21252    </example>
21253    */
21254   module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
21255     function (gridUtil, uiGridPaginationService) {
21256       return {
21257         priority: -200,
21258         scope: false,
21259         require: 'uiGrid',
21260         link: {
21261           pre: function ($scope, $elm, $attr, uiGridCtrl) {
21262             uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
21263
21264             gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
21265               .then(function (contents) {
21266                 var template = angular.element(contents);
21267                 $elm.append(template);
21268                 uiGridCtrl.innerCompile(template);
21269               });
21270           }
21271         }
21272       };
21273     }
21274   ]);
21275
21276   /**
21277    *  @ngdoc directive
21278    *  @name ui.grid.pagination.directive:uiGridPager
21279    *  @element div
21280    *
21281    *  @description Panel for handling pagination
21282    */
21283   module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
21284     function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
21285       return {
21286         priority: -200,
21287         scope: true,
21288         require: '^uiGrid',
21289         link: function ($scope, $elm, $attr, uiGridCtrl) {
21290           var defaultFocusElementSelector = '.ui-grid-pager-control-input';
21291           $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
21292
21293           $scope.paginationApi = uiGridCtrl.grid.api.pagination;
21294           $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
21295           $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
21296           $scope.paginationOf = i18nService.getSafeText('pagination.of');
21297           $scope.paginationThrough = i18nService.getSafeText('pagination.through');
21298
21299           var options = uiGridCtrl.grid.options;
21300
21301           uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
21302             adjustment.height = adjustment.height - gridUtil.elementHeight($elm);
21303             return adjustment;
21304           });
21305
21306           var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
21307             if (!grid.options.useExternalPagination) {
21308               grid.options.totalItems = grid.rows.length;
21309             }
21310           }, [uiGridConstants.dataChange.ROW]);
21311
21312           $scope.$on('$destroy', dataChangeDereg);
21313
21314           var setShowing = function () {
21315             $scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
21316             $scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
21317           };
21318
21319           var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);
21320
21321           var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
21322               if (newValues === oldValues || oldValues === undefined) {
21323                 return;
21324               }
21325
21326               if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
21327                 options.paginationCurrentPage = 1;
21328                 return;
21329               }
21330
21331               if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
21332                 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
21333                 return;
21334               }
21335
21336               setShowing();
21337               uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
21338             }
21339           );
21340
21341           $scope.$on('$destroy', function() {
21342             deregT();
21343             deregP();
21344           });
21345
21346           $scope.cantPageForward = function () {
21347             if (options.totalItems > 0) {
21348               return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
21349             } else {
21350               return options.data.length < 1;
21351             }
21352           };
21353
21354           $scope.cantPageToLast = function () {
21355             if (options.totalItems > 0) {
21356               return $scope.cantPageForward();
21357             } else {
21358               return true;
21359             }
21360           };
21361
21362           $scope.cantPageBackward = function () {
21363             return options.paginationCurrentPage <= 1;
21364           };
21365
21366           var focusToInputIf = function(condition){
21367             if (condition){
21368               gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
21369             }
21370           };
21371
21372           //Takes care of setting focus to the middle element when focus is lost
21373           $scope.pageFirstPageClick = function () {
21374             $scope.paginationApi.seek(1);
21375             focusToInputIf($scope.cantPageBackward());
21376           };
21377
21378           $scope.pagePreviousPageClick = function () {
21379             $scope.paginationApi.previousPage();
21380             focusToInputIf($scope.cantPageBackward());
21381           };
21382
21383           $scope.pageNextPageClick = function () {
21384             $scope.paginationApi.nextPage();
21385             focusToInputIf($scope.cantPageForward());
21386           };
21387
21388           $scope.pageLastPageClick = function () {
21389             $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
21390             focusToInputIf($scope.cantPageToLast());
21391           };
21392
21393         }
21394       };
21395     }
21396   ]);
21397 })();
21398
21399 (function () {
21400   'use strict';
21401
21402   /**
21403    * @ngdoc overview
21404    * @name ui.grid.pinning
21405    * @description
21406    *
21407    * # ui.grid.pinning
21408    *
21409    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
21410    *
21411    * This module provides column pinning to the end user via menu options in the column header
21412    *
21413    * <div doc-module-components="ui.grid.pinning"></div>
21414    */
21415
21416   var module = angular.module('ui.grid.pinning', ['ui.grid']);
21417
21418   module.constant('uiGridPinningConstants', {
21419     container: {
21420       LEFT: 'left',
21421       RIGHT: 'right',
21422       NONE: ''
21423     }
21424   });
21425
21426   module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) {
21427     var service = {
21428
21429       initializeGrid: function (grid) {
21430         service.defaultGridOptions(grid.options);
21431
21432         // Register a column builder to add new menu items for pinning left and right
21433         grid.registerColumnBuilder(service.pinningColumnBuilder);
21434
21435         /**
21436          *  @ngdoc object
21437          *  @name ui.grid.pinning.api:PublicApi
21438          *
21439          *  @description Public Api for pinning feature
21440          */
21441         var publicApi = {
21442           events: {
21443             pinning: {
21444               /**
21445                * @ngdoc event
21446                * @name columnPin
21447                * @eventOf ui.grid.pinning.api:PublicApi
21448                * @description raised when column pin state has changed
21449                * <pre>
21450                *   gridApi.pinning.on.columnPinned(scope, function(colDef){})
21451                * </pre>
21452                * @param {object} colDef the column that was changed
21453                * @param {string} container the render container the column is in ('left', 'right', '')
21454                */
21455               columnPinned: function(colDef, container) {
21456               }
21457             }
21458           },
21459           methods: {
21460             pinning: {
21461               /**
21462                * @ngdoc function
21463                * @name pinColumn
21464                * @methodOf ui.grid.pinning.api:PublicApi
21465                * @description pin column left, right, or none
21466                * <pre>
21467                *   gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
21468                * </pre>
21469                * @param {gridColumn} col the column being pinned
21470                * @param {string} container one of the recognised types
21471                * from uiGridPinningConstants
21472                */
21473               pinColumn: function(col, container) {
21474                 service.pinColumn(grid, col, container);
21475               }
21476             }
21477           }
21478         };
21479
21480         grid.api.registerEventsFromObject(publicApi.events);
21481         grid.api.registerMethodsFromObject(publicApi.methods);
21482       },
21483
21484       defaultGridOptions: function (gridOptions) {
21485         //default option to true unless it was explicitly set to false
21486         /**
21487          *  @ngdoc object
21488          *  @name ui.grid.pinning.api:GridOptions
21489          *
21490          *  @description GridOptions for pinning feature, these are available to be
21491            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21492          */
21493
21494         /**
21495          *  @ngdoc object
21496          *  @name enablePinning
21497          *  @propertyOf  ui.grid.pinning.api:GridOptions
21498          *  @description Enable pinning for the entire grid.
21499          *  <br/>Defaults to true
21500          */
21501         gridOptions.enablePinning = gridOptions.enablePinning !== false;
21502
21503       },
21504
21505       pinningColumnBuilder: function (colDef, col, gridOptions) {
21506         //default to true unless gridOptions or colDef is explicitly false
21507
21508         /**
21509          *  @ngdoc object
21510          *  @name ui.grid.pinning.api:ColumnDef
21511          *
21512          *  @description ColumnDef for pinning feature, these are available to be
21513          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
21514          */
21515
21516         /**
21517          *  @ngdoc object
21518          *  @name enablePinning
21519          *  @propertyOf  ui.grid.pinning.api:ColumnDef
21520          *  @description Enable pinning for the individual column.
21521          *  <br/>Defaults to true
21522          */
21523         colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
21524
21525
21526         /**
21527          *  @ngdoc object
21528          *  @name pinnedLeft
21529          *  @propertyOf  ui.grid.pinning.api:ColumnDef
21530          *  @description Column is pinned left when grid is rendered
21531          *  <br/>Defaults to false
21532          */
21533
21534         /**
21535          *  @ngdoc object
21536          *  @name pinnedRight
21537          *  @propertyOf  ui.grid.pinning.api:ColumnDef
21538          *  @description Column is pinned right when grid is rendered
21539          *  <br/>Defaults to false
21540          */
21541         if (colDef.pinnedLeft) {
21542           col.renderContainer = 'left';
21543           col.grid.createLeftContainer();
21544         }
21545         else if (colDef.pinnedRight) {
21546           col.renderContainer = 'right';
21547           col.grid.createRightContainer();
21548         }
21549
21550         if (!colDef.enablePinning) {
21551           return;
21552         }
21553
21554         var pinColumnLeftAction = {
21555           name: 'ui.grid.pinning.pinLeft',
21556           title: i18nService.get().pinning.pinLeft,
21557           icon: 'ui-grid-icon-left-open',
21558           shown: function () {
21559             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
21560           },
21561           action: function () {
21562             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT);
21563           }
21564         };
21565
21566         var pinColumnRightAction = {
21567           name: 'ui.grid.pinning.pinRight',
21568           title: i18nService.get().pinning.pinRight,
21569           icon: 'ui-grid-icon-right-open',
21570           shown: function () {
21571             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
21572           },
21573           action: function () {
21574             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT);
21575           }
21576         };
21577
21578         var removePinAction = {
21579           name: 'ui.grid.pinning.unpin',
21580           title: i18nService.get().pinning.unpin,
21581           icon: 'ui-grid-icon-cancel',
21582           shown: function () {
21583             return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
21584           },
21585           action: function () {
21586             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.UNPIN);
21587           }
21588         };
21589
21590         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
21591           col.menuItems.push(pinColumnLeftAction);
21592         }
21593         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
21594           col.menuItems.push(pinColumnRightAction);
21595         }
21596         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
21597           col.menuItems.push(removePinAction);
21598         }
21599       },
21600
21601       pinColumn: function(grid, col, container) {
21602         if (container === uiGridPinningConstants.container.NONE) {
21603           col.renderContainer = null;
21604         }
21605         else {
21606           col.renderContainer = container;
21607           if (container === uiGridPinningConstants.container.LEFT) {
21608             grid.createLeftContainer();
21609           }
21610           else if (container === uiGridPinningConstants.container.RIGHT) {
21611             grid.createRightContainer();
21612           }
21613         }
21614
21615         grid.refresh()
21616         .then(function() {
21617           grid.api.pinning.raise.columnPinned( col.colDef, container );
21618         });
21619       }
21620     };
21621
21622     return service;
21623   }]);
21624
21625   module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
21626     function (gridUtil, uiGridPinningService) {
21627       return {
21628         require: 'uiGrid',
21629         scope: false,
21630         compile: function () {
21631           return {
21632             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
21633               uiGridPinningService.initializeGrid(uiGridCtrl.grid);
21634             },
21635             post: function ($scope, $elm, $attrs, uiGridCtrl) {
21636             }
21637           };
21638         }
21639       };
21640     }]);
21641
21642
21643 })();
21644
21645 (function(){
21646   'use strict';
21647
21648   /**
21649    * @ngdoc overview
21650    * @name ui.grid.resizeColumns
21651    * @description
21652    *
21653    * # ui.grid.resizeColumns
21654    *
21655    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
21656    *
21657    * This module allows columns to be resized.
21658    */
21659   var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
21660
21661   module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
21662     function (gridUtil, $q, $timeout) {
21663
21664       var service = {
21665         defaultGridOptions: function(gridOptions){
21666           //default option to true unless it was explicitly set to false
21667           /**
21668            *  @ngdoc object
21669            *  @name ui.grid.resizeColumns.api:GridOptions
21670            *
21671            *  @description GridOptions for resizeColumns feature, these are available to be
21672            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21673            */
21674
21675           /**
21676            *  @ngdoc object
21677            *  @name enableColumnResizing
21678            *  @propertyOf  ui.grid.resizeColumns.api:GridOptions
21679            *  @description Enable column resizing on the entire grid
21680            *  <br/>Defaults to true
21681            */
21682           gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
21683
21684           //legacy support
21685           //use old name if it is explicitly false
21686           if (gridOptions.enableColumnResize === false){
21687             gridOptions.enableColumnResizing = false;
21688           }
21689         },
21690
21691         colResizerColumnBuilder: function (colDef, col, gridOptions) {
21692
21693           var promises = [];
21694           /**
21695            *  @ngdoc object
21696            *  @name ui.grid.resizeColumns.api:ColumnDef
21697            *
21698            *  @description ColumnDef for resizeColumns feature, these are available to be
21699            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
21700            */
21701
21702           /**
21703            *  @ngdoc object
21704            *  @name enableColumnResizing
21705            *  @propertyOf  ui.grid.resizeColumns.api:ColumnDef
21706            *  @description Enable column resizing on an individual column
21707            *  <br/>Defaults to GridOptions.enableColumnResizing
21708            */
21709           //default to true unless gridOptions or colDef is explicitly false
21710           colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
21711
21712
21713           //legacy support of old option name
21714           if (colDef.enableColumnResize === false){
21715             colDef.enableColumnResizing = false;
21716           }
21717
21718           return $q.all(promises);
21719         },
21720
21721         registerPublicApi: function (grid) {
21722             /**
21723              *  @ngdoc object
21724              *  @name ui.grid.resizeColumns.api:PublicApi
21725              *  @description Public Api for column resize feature.
21726              */
21727             var publicApi = {
21728               events: {
21729                 /**
21730                  * @ngdoc event
21731                  * @name columnSizeChanged
21732                  * @eventOf  ui.grid.resizeColumns.api:PublicApi
21733                  * @description raised when column is resized
21734                  * <pre>
21735                  *      gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
21736                  * </pre>
21737                  * @param {object} colDef the column that was resized
21738                  * @param {integer} delta of the column size change
21739                  */
21740                 colResizable: {
21741                   columnSizeChanged: function (colDef, deltaChange) {
21742                   }
21743                 }
21744               }
21745             };
21746             grid.api.registerEventsFromObject(publicApi.events);
21747         },
21748
21749         fireColumnSizeChanged: function (grid, colDef, deltaChange) {
21750           $timeout(function () {
21751             if ( grid.api.colResizable ){
21752               grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
21753             } else {
21754               gridUtil.logError("The resizeable api is not registered, this may indicate that you've included the module but not added the 'ui-grid-resize-columns' directive to your grid definition.  Cannot raise any events.");
21755             }
21756           });
21757         },
21758
21759         // get either this column, or the column next to this column, to resize,
21760         // returns the column we're going to resize
21761         findTargetCol: function(col, position, rtlMultiplier){
21762           var renderContainer = col.getRenderContainer();
21763
21764           if (position === 'left') {
21765             // Get the column to the left of this one
21766             var colIndex = renderContainer.visibleColumnCache.indexOf(col);
21767             return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
21768           } else {
21769             return col;
21770           }
21771         }
21772
21773       };
21774
21775       return service;
21776
21777     }]);
21778
21779
21780   /**
21781    * @ngdoc directive
21782    * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
21783    * @element div
21784    * @restrict A
21785    * @description
21786    * Enables resizing for all columns on the grid. If, for some reason, you want to use the ui-grid-resize-columns directive, but not allow column resizing, you can explicitly set the
21787    * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
21788    *
21789    * @example
21790    <doc:example module="app">
21791    <doc:source>
21792    <script>
21793    var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
21794
21795    app.controller('MainCtrl', ['$scope', function ($scope) {
21796           $scope.gridOpts = {
21797             data: [
21798               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
21799               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
21800               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
21801               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
21802             ]
21803           };
21804         }]);
21805    </script>
21806
21807    <div ng-controller="MainCtrl">
21808    <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
21809    </div>
21810    </doc:source>
21811    <doc:scenario>
21812
21813    </doc:scenario>
21814    </doc:example>
21815    */
21816   module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
21817     return {
21818       replace: true,
21819       priority: 0,
21820       require: '^uiGrid',
21821       scope: false,
21822       compile: function () {
21823         return {
21824           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
21825             uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
21826             uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
21827             uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
21828           },
21829           post: function ($scope, $elm, $attrs, uiGridCtrl) {
21830           }
21831         };
21832       }
21833     };
21834   }]);
21835
21836   // Extend the uiGridHeaderCell directive
21837   module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
21838     return {
21839       // Run after the original uiGridHeaderCell
21840       priority: -10,
21841       require: '^uiGrid',
21842       // scope: false,
21843       compile: function() {
21844         return {
21845           post: function ($scope, $elm, $attrs, uiGridCtrl) {
21846             var grid = uiGridCtrl.grid;
21847
21848             if (grid.options.enableColumnResizing) {
21849               var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
21850
21851               var rtlMultiplier = 1;
21852               //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
21853               if (grid.isRTL()) {
21854                 $scope.position = 'left';
21855                 rtlMultiplier = -1;
21856               }
21857
21858               var displayResizers = function(){
21859
21860                 // remove any existing resizers.
21861                 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
21862                 for ( var i = 0; i < resizers.length; i++ ){
21863                   angular.element(resizers[i]).remove();
21864                 }
21865
21866                 // get the target column for the left resizer
21867                 var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
21868                 var renderContainer = $scope.col.getRenderContainer();
21869
21870                 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
21871                 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
21872                   var resizerLeft = angular.element(columnResizerElm).clone();
21873                   resizerLeft.attr('position', 'left');
21874
21875                   $elm.prepend(resizerLeft);
21876                   $compile(resizerLeft)($scope);
21877                 }
21878
21879                 // Don't append the right resizer if this column has resizing disabled
21880                 if ($scope.col.colDef.enableColumnResizing !== false) {
21881                   var resizerRight = angular.element(columnResizerElm).clone();
21882                   resizerRight.attr('position', 'right');
21883
21884                   $elm.append(resizerRight);
21885                   $compile(resizerRight)($scope);
21886                 }
21887               };
21888
21889               displayResizers();
21890
21891               var waitDisplay = function(){
21892                 $timeout(displayResizers);
21893               };
21894
21895               var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
21896
21897               $scope.$on( '$destroy', dataChangeDereg );
21898             }
21899           }
21900         };
21901       }
21902     };
21903   }]);
21904
21905
21906
21907   /**
21908    * @ngdoc directive
21909    * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
21910    * @element div
21911    * @restrict A
21912    *
21913    * @description
21914    * Draggable handle that controls column resizing.
21915    *
21916    * @example
21917    <doc:example module="app">
21918      <doc:source>
21919        <script>
21920         var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
21921
21922         app.controller('MainCtrl', ['$scope', function ($scope) {
21923           $scope.gridOpts = {
21924             enableColumnResizing: true,
21925             data: [
21926               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
21927               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
21928               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
21929               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
21930             ]
21931           };
21932         }]);
21933        </script>
21934
21935        <div ng-controller="MainCtrl">
21936         <div class="testGrid" ui-grid="gridOpts"></div>
21937        </div>
21938      </doc:source>
21939      <doc:scenario>
21940       // TODO: e2e specs?
21941
21942       // TODO: post-resize a horizontal scroll event should be fired
21943      </doc:scenario>
21944    </doc:example>
21945    */
21946   module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
21947     var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
21948
21949     var resizer = {
21950       priority: 0,
21951       scope: {
21952         col: '=',
21953         position: '@',
21954         renderIndex: '='
21955       },
21956       require: '?^uiGrid',
21957       link: function ($scope, $elm, $attrs, uiGridCtrl) {
21958         var startX = 0,
21959             x = 0,
21960             gridLeft = 0,
21961             rtlMultiplier = 1;
21962
21963         //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
21964         if (uiGridCtrl.grid.isRTL()) {
21965           $scope.position = 'left';
21966           rtlMultiplier = -1;
21967         }
21968
21969         if ($scope.position === 'left') {
21970           $elm.addClass('left');
21971         }
21972         else if ($scope.position === 'right') {
21973           $elm.addClass('right');
21974         }
21975
21976         // Refresh the grid canvas
21977         //   takes an argument representing the diff along the X-axis that the resize had
21978         function refreshCanvas(xDiff) {
21979           // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
21980           uiGridCtrl.grid.refreshCanvas(true).then( function() {
21981             uiGridCtrl.grid.queueGridRefresh();
21982           });
21983         }
21984
21985         // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
21986         // Returns the new recommended with, after constraints applied
21987         function constrainWidth(col, width){
21988           var newWidth = width;
21989
21990           // If the new width would be less than the column's allowably minimum width, don't allow it
21991           if (col.minWidth && newWidth < col.minWidth) {
21992             newWidth = col.minWidth;
21993           }
21994           else if (col.maxWidth && newWidth > col.maxWidth) {
21995             newWidth = col.maxWidth;
21996           }
21997
21998           return newWidth;
21999         }
22000
22001
22002         /*
22003          * Our approach to event handling aims to deal with both touch devices and mouse devices
22004          * We register down handlers on both touch and mouse.  When a touchstart or mousedown event
22005          * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
22006          *
22007          * This way we can listen for both without worrying about the fact many touch devices also emulate
22008          * mouse events - basically whichever one we hear first is what we'll go with.
22009          */
22010         function moveFunction(event, args) {
22011           if (event.originalEvent) { event = event.originalEvent; }
22012           event.preventDefault();
22013
22014           x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22015
22016           if (x < 0) { x = 0; }
22017           else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
22018
22019           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22020
22021           // Don't resize if it's disabled on this column
22022           if (col.colDef.enableColumnResizing === false) {
22023             return;
22024           }
22025
22026           if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
22027             uiGridCtrl.grid.element.addClass('column-resizing');
22028           }
22029
22030           // Get the diff along the X axis
22031           var xDiff = x - startX;
22032
22033           // Get the width that this mouse would give the column
22034           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22035
22036           // check we're not outside the allowable bounds for this column
22037           x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
22038
22039           resizeOverlay.css({ left: x + 'px' });
22040
22041           uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
22042         }
22043
22044
22045         function upFunction(event, args) {
22046           if (event.originalEvent) { event = event.originalEvent; }
22047           event.preventDefault();
22048
22049           uiGridCtrl.grid.element.removeClass('column-resizing');
22050
22051           resizeOverlay.remove();
22052
22053           // Resize the column
22054           x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
22055           var xDiff = x - startX;
22056
22057           if (xDiff === 0) {
22058             // no movement, so just reset event handlers, including turning back on both
22059             // down events - we turned one off when this event started
22060             offAllEvents();
22061             onDownEvents();
22062             return;
22063           }
22064
22065           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22066
22067           // Don't resize if it's disabled on this column
22068           if (col.colDef.enableColumnResizing === false) {
22069             return;
22070           }
22071
22072           // Get the new width
22073           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22074
22075           // check we're not outside the allowable bounds for this column
22076           col.width = constrainWidth(col, newWidth);
22077           col.hasCustomWidth = true;
22078
22079           refreshCanvas(xDiff);
22080
22081           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
22082
22083           // stop listening of up and move events - wait for next down
22084           // reset the down events - we will have turned one off when this event started
22085           offAllEvents();
22086           onDownEvents();
22087         }
22088
22089
22090         var downFunction = function(event, args) {
22091           if (event.originalEvent) { event = event.originalEvent; }
22092           event.stopPropagation();
22093
22094           // Get the left offset of the grid
22095           // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
22096           gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
22097
22098           // Get the starting X position, which is the X coordinate of the click minus the grid's offset
22099           startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22100
22101           // Append the resizer overlay
22102           uiGridCtrl.grid.element.append(resizeOverlay);
22103
22104           // Place the resizer overlay at the start position
22105           resizeOverlay.css({ left: startX });
22106
22107           // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
22108           // we were touchdown then we listen for touchmove and touchup.  Also remove the handler for the equivalent
22109           // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
22110           // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
22111           if ( event.type === 'touchstart' ){
22112             $document.on('touchend', upFunction);
22113             $document.on('touchmove', moveFunction);
22114             $elm.off('mousedown', downFunction);
22115           } else {
22116             $document.on('mouseup', upFunction);
22117             $document.on('mousemove', moveFunction);
22118             $elm.off('touchstart', downFunction);
22119           }
22120         };
22121
22122         var onDownEvents = function() {
22123           $elm.on('mousedown', downFunction);
22124           $elm.on('touchstart', downFunction);
22125         };
22126
22127         var offAllEvents = function() {
22128           $document.off('mouseup', upFunction);
22129           $document.off('touchend', upFunction);
22130           $document.off('mousemove', moveFunction);
22131           $document.off('touchmove', moveFunction);
22132           $elm.off('mousedown', downFunction);
22133           $elm.off('touchstart', downFunction);
22134         };
22135
22136         onDownEvents();
22137
22138
22139         // On doubleclick, resize to fit all rendered cells
22140         var dblClickFn = function(event, args){
22141           event.stopPropagation();
22142
22143           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22144
22145           // Don't resize if it's disabled on this column
22146           if (col.colDef.enableColumnResizing === false) {
22147             return;
22148           }
22149
22150           // Go through the rendered rows and find out the max size for the data in this column
22151           var maxWidth = 0;
22152           var xDiff = 0;
22153
22154           // Get the parent render container element
22155           var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
22156
22157           // Get the cell contents so we measure correctly. For the header cell we have to account for the sort icon and the menu buttons, if present
22158           var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
22159           Array.prototype.forEach.call(cells, function (cell) {
22160               // Get the cell width
22161               // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
22162
22163               // Account for the menu button if it exists
22164               var menuButton;
22165               if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
22166                 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
22167               }
22168
22169               gridUtil.fakeElement(cell, {}, function(newElm) {
22170                 // Make the element float since it's a div and can expand to fill its container
22171                 var e = angular.element(newElm);
22172                 e.attr('style', 'float: left');
22173
22174                 var width = gridUtil.elementWidth(e);
22175
22176                 if (menuButton) {
22177                   var menuButtonWidth = gridUtil.elementWidth(menuButton);
22178                   width = width + menuButtonWidth;
22179                 }
22180
22181                 if (width > maxWidth) {
22182                   maxWidth = width;
22183                   xDiff = maxWidth - width;
22184                 }
22185               });
22186             });
22187
22188           // check we're not outside the allowable bounds for this column
22189           col.width = constrainWidth(col, maxWidth);
22190           col.hasCustomWidth = true;
22191
22192           refreshCanvas(xDiff);
22193
22194           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);        };
22195         $elm.on('dblclick', dblClickFn);
22196
22197         $elm.on('$destroy', function() {
22198           $elm.off('dblclick', dblClickFn);
22199           offAllEvents();
22200         });
22201       }
22202     };
22203
22204     return resizer;
22205   }]);
22206
22207 })();
22208
22209 (function () {
22210   'use strict';
22211
22212   /**
22213    * @ngdoc overview
22214    * @name ui.grid.rowEdit
22215    * @description
22216    *
22217    * # ui.grid.rowEdit
22218    *
22219    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
22220    *
22221    * This module extends the edit feature to provide tracking and saving of rows
22222    * of data.  The tutorial provides more information on how this feature is best
22223    * used {@link tutorial/205_row_editable here}.
22224    * <br/>
22225    * This feature depends on usage of the ui-grid-edit feature, and also benefits
22226    * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
22227    * experience
22228    *
22229    */
22230
22231   var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
22232
22233   /**
22234    *  @ngdoc object
22235    *  @name ui.grid.rowEdit.constant:uiGridRowEditConstants
22236    *
22237    *  @description constants available in row edit module
22238    */
22239   module.constant('uiGridRowEditConstants', {
22240   });
22241
22242   /**
22243    *  @ngdoc service
22244    *  @name ui.grid.rowEdit.service:uiGridRowEditService
22245    *
22246    *  @description Services for row editing features
22247    */
22248   module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
22249     function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
22250
22251       var service = {
22252
22253         initializeGrid: function (scope, grid) {
22254           /**
22255            *  @ngdoc object
22256            *  @name ui.grid.rowEdit.api:PublicApi
22257            *
22258            *  @description Public Api for rowEdit feature
22259            */
22260
22261           grid.rowEdit = {};
22262
22263           var publicApi = {
22264             events: {
22265               rowEdit: {
22266                 /**
22267                  * @ngdoc event
22268                  * @eventOf ui.grid.rowEdit.api:PublicApi
22269                  * @name saveRow
22270                  * @description raised when a row is ready for saving.  Once your
22271                  * row has saved you may need to use angular.extend to update the
22272                  * data entity with any changed data from your save (for example,
22273                  * lock version information if you're using optimistic locking,
22274                  * or last update time/user information).
22275                  *
22276                  * Your method should call setSavePromise somewhere in the body before
22277                  * returning control.  The feature will then wait, with the gridRow greyed out
22278                  * whilst this promise is being resolved.
22279                  *
22280                  * <pre>
22281                  *      gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
22282                  * </pre>
22283                  * and somewhere within the event handler:
22284                  * <pre>
22285                  *      gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
22286                  * </pre>
22287                  * @param {object} rowEntity the options.data element that was edited
22288                  * @returns {promise} Your saveRow method should return a promise, the
22289                  * promise should either be resolved (implying successful save), or
22290                  * rejected (implying an error).
22291                  */
22292                 saveRow: function (rowEntity) {
22293                 }
22294               }
22295             },
22296             methods: {
22297               rowEdit: {
22298                 /**
22299                  * @ngdoc method
22300                  * @methodOf ui.grid.rowEdit.api:PublicApi
22301                  * @name setSavePromise
22302                  * @description Sets the promise associated with the row save, mandatory that
22303                  * the saveRow event handler calls this method somewhere before returning.
22304                  * <pre>
22305                  *      gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
22306                  * </pre>
22307                  * @param {object} rowEntity a data row from the grid for which a save has
22308                  * been initiated
22309                  * @param {promise} savePromise the promise that will be resolved when the
22310                  * save is successful, or rejected if the save fails
22311                  *
22312                  */
22313                 setSavePromise: function ( rowEntity, savePromise) {
22314                   service.setSavePromise(grid, rowEntity, savePromise);
22315                 },
22316                 /**
22317                  * @ngdoc method
22318                  * @methodOf ui.grid.rowEdit.api:PublicApi
22319                  * @name getDirtyRows
22320                  * @description Returns all currently dirty rows
22321                  * <pre>
22322                  *      gridApi.rowEdit.getDirtyRows(grid)
22323                  * </pre>
22324                  * @returns {array} An array of gridRows that are currently dirty
22325                  *
22326                  */
22327                 getDirtyRows: function () {
22328                   return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
22329                 },
22330                 /**
22331                  * @ngdoc method
22332                  * @methodOf ui.grid.rowEdit.api:PublicApi
22333                  * @name getErrorRows
22334                  * @description Returns all currently errored rows
22335                  * <pre>
22336                  *      gridApi.rowEdit.getErrorRows(grid)
22337                  * </pre>
22338                  * @returns {array} An array of gridRows that are currently in error
22339                  *
22340                  */
22341                 getErrorRows: function () {
22342                   return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
22343                 },
22344                 /**
22345                  * @ngdoc method
22346                  * @methodOf ui.grid.rowEdit.api:PublicApi
22347                  * @name flushDirtyRows
22348                  * @description Triggers a save event for all currently dirty rows, could
22349                  * be used where user presses a save button or navigates away from the page
22350                  * <pre>
22351                  *      gridApi.rowEdit.flushDirtyRows(grid)
22352                  * </pre>
22353                  * @returns {promise} a promise that represents the aggregate of all
22354                  * of the individual save promises - i.e. it will be resolved when all
22355                  * the individual save promises have been resolved.
22356                  *
22357                  */
22358                 flushDirtyRows: function () {
22359                   return service.flushDirtyRows(grid);
22360                 },
22361
22362                 /**
22363                  * @ngdoc method
22364                  * @methodOf ui.grid.rowEdit.api:PublicApi
22365                  * @name setRowsDirty
22366                  * @description Sets each of the rows passed in dataRows
22367                  * to be dirty.  note that if you have only just inserted the
22368                  * rows into your data you will need to wait for a $digest cycle
22369                  * before the gridRows are present - so often you would wrap this
22370                  * call in a $interval or $timeout
22371                  * <pre>
22372                  *      $interval( function() {
22373                  *        gridApi.rowEdit.setRowsDirty(myDataRows);
22374                  *      }, 0, 1);
22375                  * </pre>
22376                  * @param {array} dataRows the data entities for which the gridRows
22377                  * should be set dirty.
22378                  *
22379                  */
22380                 setRowsDirty: function ( dataRows) {
22381                   service.setRowsDirty(grid, dataRows);
22382                 },
22383
22384                 /**
22385                  * @ngdoc method
22386                  * @methodOf ui.grid.rowEdit.api:PublicApi
22387                  * @name setRowsClean
22388                  * @description Sets each of the rows passed in dataRows
22389                  * to be clean, removing them from the dirty cache and the error cache,
22390                  * and clearing the error flag and the dirty flag
22391                  * <pre>
22392                  *      var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
22393                  *      var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
22394                  *      $scope.gridApi.rowEdit.setRowsClean( dataRows );
22395                  * </pre>
22396                  * @param {array} dataRows the data entities for which the gridRows
22397                  * should be set clean.
22398                  *
22399                  */
22400                 setRowsClean: function ( dataRows) {
22401                   service.setRowsClean(grid, dataRows);
22402                 }
22403               }
22404             }
22405           };
22406
22407           grid.api.registerEventsFromObject(publicApi.events);
22408           grid.api.registerMethodsFromObject(publicApi.methods);
22409
22410           grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
22411             grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
22412             grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
22413             grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
22414
22415             if ( grid.api.cellNav ) {
22416               grid.api.cellNav.on.navigate( scope, service.navigate );
22417             }
22418           });
22419
22420         },
22421
22422         defaultGridOptions: function (gridOptions) {
22423
22424           /**
22425            *  @ngdoc object
22426            *  @name ui.grid.rowEdit.api:GridOptions
22427            *
22428            *  @description Options for configuring the rowEdit feature, these are available to be
22429            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22430            */
22431
22432         },
22433
22434
22435         /**
22436          * @ngdoc method
22437          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22438          * @name saveRow
22439          * @description  Returns a function that saves the specified row from the grid,
22440          * and returns a promise
22441          * @param {object} grid the grid for which dirty rows should be flushed
22442          * @param {GridRow} gridRow the row that should be saved
22443          * @returns {function} the saveRow function returns a function.  That function
22444          * in turn, when called, returns a promise relating to the save callback
22445          */
22446         saveRow: function ( grid, gridRow ) {
22447           var self = this;
22448
22449           return function() {
22450             gridRow.isSaving = true;
22451
22452             if ( gridRow.rowEditSavePromise ){
22453               // don't save the row again if it's already saving - that causes stale object exceptions
22454               return gridRow.rowEditSavePromise;
22455             }
22456
22457             var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
22458
22459             if ( gridRow.rowEditSavePromise ){
22460               gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
22461             } else {
22462               gridUtil.logError( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
22463             }
22464             return promise;
22465           };
22466         },
22467
22468
22469         /**
22470          * @ngdoc method
22471          * @methodOf  ui.grid.rowEdit.service:uiGridRowEditService
22472          * @name setSavePromise
22473          * @description Sets the promise associated with the row save, mandatory that
22474          * the saveRow event handler calls this method somewhere before returning.
22475          * <pre>
22476          *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
22477          * </pre>
22478          * @param {object} grid the grid for which dirty rows should be returned
22479          * @param {object} rowEntity a data row from the grid for which a save has
22480          * been initiated
22481          * @param {promise} savePromise the promise that will be resolved when the
22482          * save is successful, or rejected if the save fails
22483          *
22484          */
22485         setSavePromise: function (grid, rowEntity, savePromise) {
22486           var gridRow = grid.getRow( rowEntity );
22487           gridRow.rowEditSavePromise = savePromise;
22488         },
22489
22490
22491         /**
22492          * @ngdoc method
22493          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22494          * @name processSuccessPromise
22495          * @description  Returns a function that processes the successful
22496          * resolution of a save promise
22497          * @param {object} grid the grid for which the promise should be processed
22498          * @param {GridRow} gridRow the row that has been saved
22499          * @returns {function} the success handling function
22500          */
22501         processSuccessPromise: function ( grid, gridRow ) {
22502           var self = this;
22503
22504           return function() {
22505             delete gridRow.isSaving;
22506             delete gridRow.isDirty;
22507             delete gridRow.isError;
22508             delete gridRow.rowEditSaveTimer;
22509             delete gridRow.rowEditSavePromise;
22510             self.removeRow( grid.rowEdit.errorRows, gridRow );
22511             self.removeRow( grid.rowEdit.dirtyRows, gridRow );
22512           };
22513         },
22514
22515
22516         /**
22517          * @ngdoc method
22518          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22519          * @name processErrorPromise
22520          * @description  Returns a function that processes the failed
22521          * resolution of a save promise
22522          * @param {object} grid the grid for which the promise should be processed
22523          * @param {GridRow} gridRow the row that is now in error
22524          * @returns {function} the error handling function
22525          */
22526         processErrorPromise: function ( grid, gridRow ) {
22527           return function() {
22528             delete gridRow.isSaving;
22529             delete gridRow.rowEditSaveTimer;
22530             delete gridRow.rowEditSavePromise;
22531
22532             gridRow.isError = true;
22533
22534             if (!grid.rowEdit.errorRows){
22535               grid.rowEdit.errorRows = [];
22536             }
22537             if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
22538               grid.rowEdit.errorRows.push( gridRow );
22539             }
22540           };
22541         },
22542
22543
22544         /**
22545          * @ngdoc method
22546          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22547          * @name removeRow
22548          * @description  Removes a row from a cache of rows - either
22549          * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows.  If the row
22550          * is not present silently does nothing.
22551          * @param {array} rowArray the array from which to remove the row
22552          * @param {GridRow} gridRow the row that should be removed
22553          */
22554         removeRow: function( rowArray, removeGridRow ){
22555           if (typeof(rowArray) === 'undefined' || rowArray === null){
22556             return;
22557           }
22558
22559           rowArray.forEach( function( gridRow, index ){
22560             if ( gridRow.uid === removeGridRow.uid ){
22561               rowArray.splice( index, 1);
22562             }
22563           });
22564         },
22565
22566
22567         /**
22568          * @ngdoc method
22569          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22570          * @name isRowPresent
22571          * @description  Checks whether a row is already present
22572          * in the given array
22573          * @param {array} rowArray the array in which to look for the row
22574          * @param {GridRow} gridRow the row that should be looked for
22575          */
22576         isRowPresent: function( rowArray, removeGridRow ){
22577           var present = false;
22578           rowArray.forEach( function( gridRow, index ){
22579             if ( gridRow.uid === removeGridRow.uid ){
22580               present = true;
22581             }
22582           });
22583           return present;
22584         },
22585
22586
22587         /**
22588          * @ngdoc method
22589          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22590          * @name flushDirtyRows
22591          * @description Triggers a save event for all currently dirty rows, could
22592          * be used where user presses a save button or navigates away from the page
22593          * <pre>
22594          *      gridApi.rowEdit.flushDirtyRows(grid)
22595          * </pre>
22596          * @param {object} grid the grid for which dirty rows should be flushed
22597          * @returns {promise} a promise that represents the aggregate of all
22598          * of the individual save promises - i.e. it will be resolved when all
22599          * the individual save promises have been resolved.
22600          *
22601          */
22602         flushDirtyRows: function(grid){
22603           var promises = [];
22604           grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
22605             service.saveRow( grid, gridRow )();
22606             promises.push( gridRow.rowEditSavePromise );
22607           });
22608
22609           return $q.all( promises );
22610         },
22611
22612
22613         /**
22614          * @ngdoc method
22615          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22616          * @name endEditCell
22617          * @description Receives an afterCellEdit event from the edit function,
22618          * and sets flags as appropriate.  Only the rowEntity parameter
22619          * is processed, although other params are available.  Grid
22620          * is automatically provided by the gridApi.
22621          * @param {object} rowEntity the data entity for which the cell
22622          * was edited
22623          */
22624         endEditCell: function( rowEntity, colDef, newValue, previousValue ){
22625           var grid = this.grid;
22626           var gridRow = grid.getRow( rowEntity );
22627           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
22628
22629           if ( newValue !== previousValue || gridRow.isDirty ){
22630             if ( !grid.rowEdit.dirtyRows ){
22631               grid.rowEdit.dirtyRows = [];
22632             }
22633
22634             if ( !gridRow.isDirty ){
22635               gridRow.isDirty = true;
22636               grid.rowEdit.dirtyRows.push( gridRow );
22637             }
22638
22639             delete gridRow.isError;
22640
22641             service.considerSetTimer( grid, gridRow );
22642           }
22643         },
22644
22645
22646         /**
22647          * @ngdoc method
22648          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22649          * @name beginEditCell
22650          * @description Receives a beginCellEdit event from the edit function,
22651          * and cancels any rowEditSaveTimers if present, as the user is still editing
22652          * this row.  Only the rowEntity parameter
22653          * is processed, although other params are available.  Grid
22654          * is automatically provided by the gridApi.
22655          * @param {object} rowEntity the data entity for which the cell
22656          * editing has commenced
22657          */
22658         beginEditCell: function( rowEntity, colDef ){
22659           var grid = this.grid;
22660           var gridRow = grid.getRow( rowEntity );
22661           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
22662
22663           service.cancelTimer( grid, gridRow );
22664         },
22665
22666
22667         /**
22668          * @ngdoc method
22669          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22670          * @name cancelEditCell
22671          * @description Receives a cancelCellEdit event from the edit function,
22672          * and if the row was already dirty, restarts the save timer.  If the row
22673          * was not already dirty, then it's not dirty now either and does nothing.
22674          *
22675          * Only the rowEntity parameter
22676          * is processed, although other params are available.  Grid
22677          * is automatically provided by the gridApi.
22678          *
22679          * @param {object} rowEntity the data entity for which the cell
22680          * editing was cancelled
22681          */
22682         cancelEditCell: function( rowEntity, colDef ){
22683           var grid = this.grid;
22684           var gridRow = grid.getRow( rowEntity );
22685           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
22686
22687           service.considerSetTimer( grid, gridRow );
22688         },
22689
22690
22691         /**
22692          * @ngdoc method
22693          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22694          * @name navigate
22695          * @description cellNav tells us that the selected cell has changed.  If
22696          * the new row had a timer running, then stop it similar to in a beginCellEdit
22697          * call.  If the old row is dirty and not the same as the new row, then
22698          * start a timer on it.
22699          * @param {object} newRowCol the row and column that were selected
22700          * @param {object} oldRowCol the row and column that was left
22701          *
22702          */
22703         navigate: function( newRowCol, oldRowCol ){
22704           var grid = this.grid;
22705           if ( newRowCol.row.rowEditSaveTimer ){
22706             service.cancelTimer( grid, newRowCol.row );
22707           }
22708
22709           if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
22710             service.considerSetTimer( grid, oldRowCol.row );
22711           }
22712         },
22713
22714
22715         /**
22716          * @ngdoc property
22717          * @propertyOf ui.grid.rowEdit.api:GridOptions
22718          * @name rowEditWaitInterval
22719          * @description How long the grid should wait for another change on this row
22720          * before triggering a save (in milliseconds).  If set to -1, then saves are
22721          * never triggered by timer (implying that the user will call flushDirtyRows()
22722          * manually)
22723          *
22724          * @example
22725          * Setting the wait interval to 4 seconds
22726          * <pre>
22727          *   $scope.gridOptions = { rowEditWaitInterval: 4000 }
22728          * </pre>
22729          *
22730          */
22731         /**
22732          * @ngdoc method
22733          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22734          * @name considerSetTimer
22735          * @description Consider setting a timer on this row (if it is dirty).  if there is a timer running
22736          * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
22737          * dirty and not currently saving then set a new timer
22738          * @param {object} grid the grid for which we are processing
22739          * @param {GridRow} gridRow the row for which the timer should be adjusted
22740          *
22741          */
22742         considerSetTimer: function( grid, gridRow ){
22743           service.cancelTimer( grid, gridRow );
22744
22745           if ( gridRow.isDirty && !gridRow.isSaving ){
22746             if ( grid.options.rowEditWaitInterval !== -1 ){
22747               var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
22748               gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
22749             }
22750           }
22751         },
22752
22753
22754         /**
22755          * @ngdoc method
22756          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22757          * @name cancelTimer
22758          * @description cancel the $interval for any timer running on this row
22759          * then delete the timer itself
22760          * @param {object} grid the grid for which we are processing
22761          * @param {GridRow} gridRow the row for which the timer should be adjusted
22762          *
22763          */
22764         cancelTimer: function( grid, gridRow ){
22765           if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
22766             $interval.cancel(gridRow.rowEditSaveTimer);
22767             delete gridRow.rowEditSaveTimer;
22768           }
22769         },
22770
22771
22772         /**
22773          * @ngdoc method
22774          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22775          * @name setRowsDirty
22776          * @description Sets each of the rows passed in dataRows
22777          * to be dirty.  note that if you have only just inserted the
22778          * rows into your data you will need to wait for a $digest cycle
22779          * before the gridRows are present - so often you would wrap this
22780          * call in a $interval or $timeout
22781          * <pre>
22782          *      $interval( function() {
22783          *        gridApi.rowEdit.setRowsDirty( myDataRows);
22784          *      }, 0, 1);
22785          * </pre>
22786          * @param {object} grid the grid for which rows should be set dirty
22787          * @param {array} dataRows the data entities for which the gridRows
22788          * should be set dirty.
22789          *
22790          */
22791         setRowsDirty: function( grid, myDataRows ) {
22792           var gridRow;
22793           myDataRows.forEach( function( value, index ){
22794             gridRow = grid.getRow( value );
22795             if ( gridRow ){
22796               if ( !grid.rowEdit.dirtyRows ){
22797                 grid.rowEdit.dirtyRows = [];
22798               }
22799
22800               if ( !gridRow.isDirty ){
22801                 gridRow.isDirty = true;
22802                 grid.rowEdit.dirtyRows.push( gridRow );
22803               }
22804
22805               delete gridRow.isError;
22806
22807               service.considerSetTimer( grid, gridRow );
22808             } else {
22809               gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
22810             }
22811           });
22812         },
22813
22814
22815         /**
22816          * @ngdoc method
22817          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
22818          * @name setRowsClean
22819          * @description Sets each of the rows passed in dataRows
22820          * to be clean, clearing the dirty flag and the error flag, and removing
22821          * the rows from the dirty and error caches.
22822          * @param {object} grid the grid for which rows should be set clean
22823          * @param {array} dataRows the data entities for which the gridRows
22824          * should be set clean.
22825          *
22826          */
22827         setRowsClean: function( grid, myDataRows ) {
22828           var gridRow;
22829
22830           myDataRows.forEach( function( value, index ){
22831             gridRow = grid.getRow( value );
22832             if ( gridRow ){
22833               delete gridRow.isDirty;
22834               service.removeRow( grid.rowEdit.dirtyRows, gridRow );
22835               service.cancelTimer( grid, gridRow );
22836
22837               delete gridRow.isError;
22838               service.removeRow( grid.rowEdit.errorRows, gridRow );
22839             } else {
22840               gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
22841             }
22842           });
22843         }
22844
22845       };
22846
22847       return service;
22848
22849     }]);
22850
22851   /**
22852    *  @ngdoc directive
22853    *  @name ui.grid.rowEdit.directive:uiGridEdit
22854    *  @element div
22855    *  @restrict A
22856    *
22857    *  @description Adds row editing features to the ui-grid-edit directive.
22858    *
22859    */
22860   module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
22861   function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
22862     return {
22863       replace: true,
22864       priority: 0,
22865       require: '^uiGrid',
22866       scope: false,
22867       compile: function () {
22868         return {
22869           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22870             uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
22871           },
22872           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22873           }
22874         };
22875       }
22876     };
22877   }]);
22878
22879
22880   /**
22881    *  @ngdoc directive
22882    *  @name ui.grid.rowEdit.directive:uiGridViewport
22883    *  @element div
22884    *
22885    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
22886    *  for the grid row to allow coloring of saving and error rows
22887    */
22888   module.directive('uiGridViewport',
22889     ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
22890       function ($compile, uiGridConstants, gridUtil, $parse) {
22891         return {
22892           priority: -200, // run after default  directive
22893           scope: false,
22894           compile: function ($elm, $attrs) {
22895             var rowRepeatDiv = angular.element($elm.children().children()[0]);
22896
22897             var existingNgClass = rowRepeatDiv.attr("ng-class");
22898             var newNgClass = '';
22899             if ( existingNgClass ) {
22900               newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
22901             } else {
22902               newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
22903             }
22904             rowRepeatDiv.attr("ng-class", newNgClass);
22905
22906             return {
22907               pre: function ($scope, $elm, $attrs, controllers) {
22908
22909               },
22910               post: function ($scope, $elm, $attrs, controllers) {
22911               }
22912             };
22913           }
22914         };
22915       }]);
22916
22917 })();
22918
22919 (function () {
22920   'use strict';
22921
22922   /**
22923    * @ngdoc overview
22924    * @name ui.grid.saveState
22925    * @description
22926    *
22927    * # ui.grid.saveState
22928    *
22929    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
22930    *
22931    * This module provides the ability to save the grid state, and restore
22932    * it when the user returns to the page.
22933    *
22934    * No UI is provided, the caller should provide their own UI/buttons
22935    * as appropriate. Usually the navigate events would be used to save
22936    * the grid state and restore it.
22937    *
22938    * <br/>
22939    * <br/>
22940    *
22941    * <div doc-module-components="ui.grid.save-state"></div>
22942    */
22943
22944   var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
22945
22946   /**
22947    *  @ngdoc object
22948    *  @name ui.grid.saveState.constant:uiGridSaveStateConstants
22949    *
22950    *  @description constants available in save state module
22951    */
22952
22953   module.constant('uiGridSaveStateConstants', {
22954     featureName: 'saveState'
22955   });
22956
22957   /**
22958    *  @ngdoc service
22959    *  @name ui.grid.saveState.service:uiGridSaveStateService
22960    *
22961    *  @description Services for saveState feature
22962    */
22963   module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
22964     function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
22965
22966       var service = {
22967
22968         initializeGrid: function (grid) {
22969
22970           //add feature namespace and any properties to grid for needed state
22971           grid.saveState = {};
22972           this.defaultGridOptions(grid.options);
22973
22974           /**
22975            *  @ngdoc object
22976            *  @name ui.grid.saveState.api:PublicApi
22977            *
22978            *  @description Public Api for saveState feature
22979            */
22980           var publicApi = {
22981             events: {
22982               saveState: {
22983               }
22984             },
22985             methods: {
22986               saveState: {
22987                 /**
22988                  * @ngdoc function
22989                  * @name save
22990                  * @methodOf  ui.grid.saveState.api:PublicApi
22991                  * @description Packages the current state of the grid into
22992                  * an object, and provides it to the user for saving
22993                  * @returns {object} the state as a javascript object that can be saved
22994                  */
22995                 save: function () {
22996                   return service.save(grid);
22997                 },
22998                 /**
22999                  * @ngdoc function
23000                  * @name restore
23001                  * @methodOf  ui.grid.saveState.api:PublicApi
23002                  * @description Restores the provided state into the grid
23003                  * @param {scope} $scope a scope that we can broadcast on
23004                  * @param {object} state the state that should be restored into the grid
23005                  */
23006                 restore: function ( $scope, state) {
23007                   service.restore(grid, $scope, state);
23008                 }
23009               }
23010             }
23011           };
23012
23013           grid.api.registerEventsFromObject(publicApi.events);
23014
23015           grid.api.registerMethodsFromObject(publicApi.methods);
23016
23017         },
23018
23019         defaultGridOptions: function (gridOptions) {
23020           //default option to true unless it was explicitly set to false
23021           /**
23022            * @ngdoc object
23023            * @name ui.grid.saveState.api:GridOptions
23024            *
23025            * @description GridOptions for saveState feature, these are available to be
23026            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23027            */
23028           /**
23029            * @ngdoc object
23030            * @name saveWidths
23031            * @propertyOf  ui.grid.saveState.api:GridOptions
23032            * @description Save the current column widths.  Note that unless
23033            * you've provided the user with some way to resize their columns (say
23034            * the resize columns feature), then this makes little sense.
23035            * <br/>Defaults to true
23036            */
23037           gridOptions.saveWidths = gridOptions.saveWidths !== false;
23038           /**
23039            * @ngdoc object
23040            * @name saveOrder
23041            * @propertyOf  ui.grid.saveState.api:GridOptions
23042            * @description Restore the current column order.  Note that unless
23043            * you've provided the user with some way to reorder their columns (for
23044            * example the move columns feature), this makes little sense.
23045            * <br/>Defaults to true
23046            */
23047           gridOptions.saveOrder = gridOptions.saveOrder !== false;
23048           /**
23049            * @ngdoc object
23050            * @name saveScroll
23051            * @propertyOf  ui.grid.saveState.api:GridOptions
23052            * @description Save the current scroll position.  Note that this
23053            * is saved as the percentage of the grid scrolled - so if your
23054            * user returns to a grid with a significantly different number of
23055            * rows (perhaps some data has been deleted) then the scroll won't
23056            * actually show the same rows as before.  If you want to scroll to
23057            * a specific row then you should instead use the saveFocus option, which
23058            * is the default.
23059            *
23060            * Note that this element will only be saved if the cellNav feature is
23061            * enabled
23062            * <br/>Defaults to false
23063            */
23064           gridOptions.saveScroll = gridOptions.saveScroll === true;
23065           /**
23066            * @ngdoc object
23067            * @name saveFocus
23068            * @propertyOf  ui.grid.saveState.api:GridOptions
23069            * @description Save the current focused cell.  On returning
23070            * to this focused cell we'll also scroll.  This option is
23071            * preferred to the saveScroll option, so is set to true by
23072            * default.  If saveScroll is set to true then this option will
23073            * be disabled.
23074            *
23075            * By default this option saves the current row number and column
23076            * number, and returns to that row and column.  However, if you define
23077            * a saveRowIdentity function, then it will return you to the currently
23078            * selected column within that row (in a business sense - so if some
23079            * rows have been deleted, it will still find the same data, presuming it
23080            * still exists in the list.  If it isn't in the list then it will instead
23081            * return to the same row number - i.e. scroll percentage)
23082            *
23083            * Note that this option will do nothing if the cellNav
23084            * feature is not enabled.
23085            *
23086            * <br/>Defaults to true (unless saveScroll is true)
23087            */
23088           gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
23089           /**
23090            * @ngdoc object
23091            * @name saveRowIdentity
23092            * @propertyOf  ui.grid.saveState.api:GridOptions
23093            * @description A function that can be called, passing in a rowEntity,
23094            * and that will return a unique id for that row.  This might simply
23095            * return the `id` field from that row (if you have one), or it might
23096            * concatenate some fields within the row to make a unique value.
23097            *
23098            * This value will be used to find the same row again and set the focus
23099            * to it, if it exists when we return.
23100            *
23101            * <br/>Defaults to undefined
23102            */
23103           /**
23104            * @ngdoc object
23105            * @name saveVisible
23106            * @propertyOf  ui.grid.saveState.api:GridOptions
23107            * @description Save whether or not columns are visible.
23108            *
23109            * <br/>Defaults to true
23110            */
23111           gridOptions.saveVisible = gridOptions.saveVisible !== false;
23112           /**
23113            * @ngdoc object
23114            * @name saveSort
23115            * @propertyOf  ui.grid.saveState.api:GridOptions
23116            * @description Save the current sort state for each column
23117            *
23118            * <br/>Defaults to true
23119            */
23120           gridOptions.saveSort = gridOptions.saveSort !== false;
23121           /**
23122            * @ngdoc object
23123            * @name saveFilter
23124            * @propertyOf  ui.grid.saveState.api:GridOptions
23125            * @description Save the current filter state for each column
23126            *
23127            * <br/>Defaults to true
23128            */
23129           gridOptions.saveFilter = gridOptions.saveFilter !== false;
23130           /**
23131            * @ngdoc object
23132            * @name saveSelection
23133            * @propertyOf  ui.grid.saveState.api:GridOptions
23134            * @description Save the currently selected rows.  If the `saveRowIdentity` callback
23135            * is defined, then it will save the id of the row and select that.  If not, then
23136            * it will attempt to select the rows by row number, which will give the wrong results
23137            * if the data set has changed in the mean-time.
23138            *
23139            * Note that this option only does anything
23140            * if the selection feature is enabled.
23141            *
23142            * <br/>Defaults to true
23143            */
23144           gridOptions.saveSelection = gridOptions.saveSelection !== false;
23145           /**
23146            * @ngdoc object
23147            * @name saveGrouping
23148            * @propertyOf  ui.grid.saveState.api:GridOptions
23149            * @description Save the grouping configuration.  If set to true and the
23150            * grouping feature is not enabled then does nothing.
23151            *
23152            * <br/>Defaults to true
23153            */
23154           gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
23155           /**
23156            * @ngdoc object
23157            * @name saveGroupingExpandedStates
23158            * @propertyOf  ui.grid.saveState.api:GridOptions
23159            * @description Save the grouping row expanded states.  If set to true and the
23160            * grouping feature is not enabled then does nothing.
23161            *
23162            * This can be quite a bit of data, in many cases you wouldn't want to save this
23163            * information.
23164            *
23165            * <br/>Defaults to false
23166            */
23167           gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true;
23168           /**
23169            * @ngdoc object
23170            * @name savePinning
23171            * @propertyOf ui.grid.saveState.api:GridOptions
23172            * @description Save pinning state for columns.
23173            *
23174            * <br/>Defaults to true
23175            */
23176           gridOptions.savePinning = gridOptions.savePinning !== false;
23177           /**
23178            * @ngdoc object
23179            * @name saveTreeView
23180            * @propertyOf  ui.grid.saveState.api:GridOptions
23181            * @description Save the treeView configuration.  If set to true and the
23182            * treeView feature is not enabled then does nothing.
23183            *
23184            * <br/>Defaults to true
23185            */
23186           gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
23187         },
23188
23189
23190
23191         /**
23192          * @ngdoc function
23193          * @name save
23194          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23195          * @description Saves the current grid state into an object, and
23196          * passes that object back to the caller
23197          * @param {Grid} grid the grid whose state we'd like to save
23198          * @returns {object} the state ready to be saved
23199          */
23200         save: function (grid) {
23201           var savedState = {};
23202
23203           savedState.columns = service.saveColumns( grid );
23204           savedState.scrollFocus = service.saveScrollFocus( grid );
23205           savedState.selection = service.saveSelection( grid );
23206           savedState.grouping = service.saveGrouping( grid );
23207           savedState.treeView = service.saveTreeView( grid );
23208
23209           return savedState;
23210         },
23211
23212
23213         /**
23214          * @ngdoc function
23215          * @name restore
23216          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23217          * @description Applies the provided state to the grid
23218          *
23219          * @param {Grid} grid the grid whose state we'd like to restore
23220          * @param {scope} $scope a scope that we can broadcast on
23221          * @param {object} state the state we'd like to restore
23222          */
23223         restore: function( grid, $scope, state ){
23224           if ( state.columns ) {
23225             service.restoreColumns( grid, state.columns );
23226           }
23227
23228           if ( state.scrollFocus ){
23229             service.restoreScrollFocus( grid, $scope, state.scrollFocus );
23230           }
23231
23232           if ( state.selection ){
23233             service.restoreSelection( grid, state.selection );
23234           }
23235
23236           if ( state.grouping ){
23237             service.restoreGrouping( grid, state.grouping );
23238           }
23239
23240           if ( state.treeView ){
23241             service.restoreTreeView( grid, state.treeView );
23242           }
23243
23244           grid.refresh();
23245         },
23246
23247
23248         /**
23249          * @ngdoc function
23250          * @name saveColumns
23251          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23252          * @description Saves the column setup, including sort, filters, ordering,
23253          * pinning and column widths.
23254          *
23255          * Works through the current columns, storing them in order.  Stores the
23256          * column name, then the visible flag, width, sort and filters for each column.
23257          *
23258          * @param {Grid} grid the grid whose state we'd like to save
23259          * @returns {array} the columns state ready to be saved
23260          */
23261         saveColumns: function( grid ) {
23262           var columns = [];
23263           grid.getOnlyDataColumns().forEach( function( column ) {
23264             var savedColumn = {};
23265             savedColumn.name = column.name;
23266
23267             if ( grid.options.saveVisible ){
23268               savedColumn.visible = column.visible;
23269             }
23270
23271             if ( grid.options.saveWidths ){
23272               savedColumn.width = column.width;
23273             }
23274
23275             // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
23276             if ( grid.options.saveSort ){
23277               savedColumn.sort = angular.copy( column.sort );
23278             }
23279
23280             if ( grid.options.saveFilter ){
23281               savedColumn.filters = [];
23282               column.filters.forEach( function( filter ){
23283                 var copiedFilter = {};
23284                 angular.forEach( filter, function( value, key) {
23285                   if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
23286                     copiedFilter[key] = value;
23287                   }
23288                 });
23289                 savedColumn.filters.push(copiedFilter);
23290               });
23291             }
23292
23293             if ( !!grid.api.pinning && grid.options.savePinning ){
23294               savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
23295             }
23296
23297             columns.push( savedColumn );
23298           });
23299
23300           return columns;
23301         },
23302
23303
23304         /**
23305          * @ngdoc function
23306          * @name saveScrollFocus
23307          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23308          * @description Saves the currently scroll or focus.
23309          *
23310          * If cellNav isn't present then does nothing - we can't return
23311          * to the scroll position without cellNav anyway.
23312          *
23313          * If the cellNav module is present, and saveFocus is true, then
23314          * it saves the currently focused cell.  If rowIdentity is present
23315          * then saves using rowIdentity, otherwise saves visibleRowNum.
23316          *
23317          * If the cellNav module is not present, and saveScroll is true, then
23318          * it approximates the current scroll row and column, and saves that.
23319          *
23320          * @param {Grid} grid the grid whose state we'd like to save
23321          * @returns {object} the selection state ready to be saved
23322          */
23323         saveScrollFocus: function( grid ){
23324           if ( !grid.api.cellNav ){
23325             return {};
23326           }
23327
23328           var scrollFocus = {};
23329           if ( grid.options.saveFocus ){
23330             scrollFocus.focus = true;
23331             var rowCol = grid.api.cellNav.getFocusedCell();
23332             if ( rowCol !== null ) {
23333               if ( rowCol.col !== null ){
23334                 scrollFocus.colName = rowCol.col.colDef.name;
23335               }
23336               if ( rowCol.row !== null ){
23337                 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
23338               }
23339             }
23340           }
23341
23342           if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
23343             scrollFocus.focus = false;
23344             if ( grid.renderContainers.body.prevRowScrollIndex ){
23345               scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
23346             }
23347
23348             if ( grid.renderContainers.body.prevColScrollIndex ){
23349               scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
23350             }
23351           }
23352
23353           return scrollFocus;
23354         },
23355
23356
23357         /**
23358          * @ngdoc function
23359          * @name saveSelection
23360          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23361          * @description Saves the currently selected rows, if the selection feature is enabled
23362          * @param {Grid} grid the grid whose state we'd like to save
23363          * @returns {array} the selection state ready to be saved
23364          */
23365         saveSelection: function( grid ){
23366           if ( !grid.api.selection || !grid.options.saveSelection ){
23367             return [];
23368           }
23369
23370           var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
23371             return service.getRowVal( grid, gridRow );
23372           });
23373
23374           return selection;
23375         },
23376
23377
23378         /**
23379          * @ngdoc function
23380          * @name saveGrouping
23381          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23382          * @description Saves the grouping state, if the grouping feature is enabled
23383          * @param {Grid} grid the grid whose state we'd like to save
23384          * @returns {object} the grouping state ready to be saved
23385          */
23386         saveGrouping: function( grid ){
23387           if ( !grid.api.grouping || !grid.options.saveGrouping ){
23388             return {};
23389           }
23390
23391           return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
23392         },
23393
23394
23395         /**
23396          * @ngdoc function
23397          * @name saveTreeView
23398          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23399          * @description Saves the tree view state, if the tree feature is enabled
23400          * @param {Grid} grid the grid whose state we'd like to save
23401          * @returns {object} the tree view state ready to be saved
23402          */
23403         saveTreeView: function( grid ){
23404           if ( !grid.api.treeView || !grid.options.saveTreeView ){
23405             return {};
23406           }
23407
23408           return grid.api.treeView.getTreeView();
23409         },
23410
23411
23412         /**
23413          * @ngdoc function
23414          * @name getRowVal
23415          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23416          * @description Helper function that gets either the rowNum or
23417          * the saveRowIdentity, given a gridRow
23418          * @param {Grid} grid the grid the row is in
23419          * @param {GridRow} gridRow the row we want the rowNum for
23420          * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
23421          *
23422          */
23423         getRowVal: function( grid, gridRow ){
23424           if ( !gridRow ) {
23425             return null;
23426           }
23427
23428           var rowVal = {};
23429           if ( grid.options.saveRowIdentity ){
23430             rowVal.identity = true;
23431             rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
23432           } else {
23433             rowVal.identity = false;
23434             rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
23435           }
23436           return rowVal;
23437         },
23438
23439
23440         /**
23441          * @ngdoc function
23442          * @name restoreColumns
23443          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23444          * @description Restores the columns, including order, visible, width,
23445          * pinning, sort and filters.
23446          *
23447          * @param {Grid} grid the grid whose state we'd like to restore
23448          * @param {object} columnsState the list of columns we had before, with their state
23449          */
23450         restoreColumns: function( grid, columnsState ){
23451           var isSortChanged = false;
23452
23453           columnsState.forEach( function( columnState, index ) {
23454             var currentCol = grid.getColumn( columnState.name );
23455
23456             if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
23457               if ( grid.options.saveVisible &&
23458                    ( currentCol.visible !== columnState.visible ||
23459                      currentCol.colDef.visible !== columnState.visible ) ){
23460                 currentCol.visible = columnState.visible;
23461                 currentCol.colDef.visible = columnState.visible;
23462                 grid.api.core.raise.columnVisibilityChanged(currentCol);
23463               }
23464
23465               if ( grid.options.saveWidths ){
23466                 currentCol.width = columnState.width;
23467               }
23468
23469               if ( grid.options.saveSort &&
23470                    !angular.equals(currentCol.sort, columnState.sort) &&
23471                    !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
23472                 currentCol.sort = angular.copy( columnState.sort );
23473                 isSortChanged = true;
23474               }
23475
23476               if ( grid.options.saveFilter &&
23477                    !angular.equals(currentCol.filters, columnState.filters ) ){
23478                 columnState.filters.forEach( function( filter, index ){
23479                   angular.extend( currentCol.filters[index], filter );
23480                   if ( typeof(filter.term) === 'undefined' || filter.term === null ){
23481                     delete currentCol.filters[index].term;
23482                   }
23483                 });
23484                 grid.api.core.raise.filterChanged();
23485               }
23486
23487               if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
23488                 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
23489               }
23490
23491               var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
23492               if (currentIndex !== -1) {
23493                 if (grid.options.saveOrder && currentIndex !== index) {
23494                   var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
23495                   grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
23496                 }
23497               }
23498             }
23499           });
23500
23501           if ( isSortChanged ) {
23502             grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
23503           }
23504         },
23505
23506
23507         /**
23508          * @ngdoc function
23509          * @name restoreScrollFocus
23510          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23511          * @description Scrolls to the position that was saved.  If focus is true, then
23512          * sets focus to the specified row/col.  If focus is false, then scrolls to the
23513          * specified row/col.
23514          *
23515          * @param {Grid} grid the grid whose state we'd like to restore
23516          * @param {scope} $scope a scope that we can broadcast on
23517          * @param {object} scrollFocusState the scroll/focus state ready to be restored
23518          */
23519         restoreScrollFocus: function( grid, $scope, scrollFocusState ){
23520           if ( !grid.api.cellNav ){
23521             return;
23522           }
23523
23524           var colDef, row;
23525           if ( scrollFocusState.colName ){
23526             var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
23527             if ( colDefs.length > 0 ){
23528               colDef = colDefs[0];
23529             }
23530           }
23531
23532           if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
23533             if ( scrollFocusState.rowVal.identity ){
23534               row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
23535             } else {
23536               row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
23537             }
23538           }
23539
23540           var entity = row && row.entity ? row.entity : null ;
23541
23542           if ( colDef || entity ) {
23543             if (scrollFocusState.focus ){
23544               grid.api.cellNav.scrollToFocus( entity, colDef );
23545             } else {
23546               grid.scrollTo( entity, colDef );
23547             }
23548           }
23549         },
23550
23551
23552         /**
23553          * @ngdoc function
23554          * @name restoreSelection
23555          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23556          * @description Selects the rows that are provided in the selection
23557          * state.  If you are using `saveRowIdentity` and more than one row matches the identity
23558          * function then only the first is selected.
23559          * @param {Grid} grid the grid whose state we'd like to restore
23560          * @param {object} selectionState the selection state ready to be restored
23561          */
23562         restoreSelection: function( grid, selectionState ){
23563           if ( !grid.api.selection ){
23564             return;
23565           }
23566
23567           grid.api.selection.clearSelectedRows();
23568
23569           selectionState.forEach(  function( rowVal ) {
23570             if ( rowVal.identity ){
23571               var foundRow = service.findRowByIdentity( grid, rowVal );
23572
23573               if ( foundRow ){
23574                 grid.api.selection.selectRow( foundRow.entity );
23575               }
23576
23577             } else {
23578               grid.api.selection.selectRowByVisibleIndex( rowVal.row );
23579             }
23580           });
23581         },
23582
23583
23584         /**
23585          * @ngdoc function
23586          * @name restoreGrouping
23587          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23588          * @description Restores the grouping configuration, if the grouping feature
23589          * is enabled.
23590          * @param {Grid} grid the grid whose state we'd like to restore
23591          * @param {object} groupingState the grouping state ready to be restored
23592          */
23593         restoreGrouping: function( grid, groupingState ){
23594           if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
23595             return;
23596           }
23597
23598           grid.api.grouping.setGrouping( groupingState );
23599         },
23600
23601         /**
23602          * @ngdoc function
23603          * @name restoreTreeView
23604          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23605          * @description Restores the tree view configuration, if the tree view feature
23606          * is enabled.
23607          * @param {Grid} grid the grid whose state we'd like to restore
23608          * @param {object} treeViewState the tree view state ready to be restored
23609          */
23610         restoreTreeView: function( grid, treeViewState ){
23611           if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
23612             return;
23613           }
23614
23615           grid.api.treeView.setTreeView( treeViewState );
23616         },
23617
23618         /**
23619          * @ngdoc function
23620          * @name findRowByIdentity
23621          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23622          * @description Finds a row given it's identity value, returns the first found row
23623          * if any are found, otherwise returns null if no rows are found.
23624          * @param {Grid} grid the grid whose state we'd like to restore
23625          * @param {object} rowVal the row we'd like to find
23626          * @returns {gridRow} the found row, or null if none found
23627          */
23628         findRowByIdentity: function( grid, rowVal ){
23629           if ( !grid.options.saveRowIdentity ){
23630             return null;
23631           }
23632
23633           var filteredRows = grid.rows.filter( function( gridRow ) {
23634             if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
23635               return true;
23636             } else {
23637               return false;
23638             }
23639           });
23640
23641           if ( filteredRows.length > 0 ){
23642             return filteredRows[0];
23643           } else {
23644             return null;
23645           }
23646         }
23647       };
23648
23649       return service;
23650
23651     }
23652   ]);
23653
23654   /**
23655    *  @ngdoc directive
23656    *  @name ui.grid.saveState.directive:uiGridSaveState
23657    *  @element div
23658    *  @restrict A
23659    *
23660    *  @description Adds saveState features to grid
23661    *
23662    *  @example
23663    <example module="app">
23664    <file name="app.js">
23665    var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
23666
23667    app.controller('MainCtrl', ['$scope', function ($scope) {
23668       $scope.data = [
23669         { name: 'Bob', title: 'CEO' },
23670         { name: 'Frank', title: 'Lowly Developer' }
23671       ];
23672
23673       $scope.gridOptions = {
23674         columnDefs: [
23675           {name: 'name'},
23676           {name: 'title', enableCellEdit: true}
23677         ],
23678         data: $scope.data
23679       };
23680     }]);
23681    </file>
23682    <file name="index.html">
23683    <div ng-controller="MainCtrl">
23684    <div ui-grid="gridOptions" ui-grid-save-state></div>
23685    </div>
23686    </file>
23687    </example>
23688    */
23689   module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
23690     function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
23691       return {
23692         replace: true,
23693         priority: 0,
23694         require: '^uiGrid',
23695         scope: false,
23696         link: function ($scope, $elm, $attrs, uiGridCtrl) {
23697           uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
23698         }
23699       };
23700     }
23701   ]);
23702 })();
23703
23704 (function () {
23705   'use strict';
23706
23707   /**
23708    * @ngdoc overview
23709    * @name ui.grid.selection
23710    * @description
23711    *
23712    * # ui.grid.selection
23713    * This module provides row selection
23714    *
23715    * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
23716    *
23717    * <div doc-module-components="ui.grid.selection"></div>
23718    */
23719
23720   var module = angular.module('ui.grid.selection', ['ui.grid']);
23721
23722   /**
23723    *  @ngdoc object
23724    *  @name ui.grid.selection.constant:uiGridSelectionConstants
23725    *
23726    *  @description constants available in selection module
23727    */
23728   module.constant('uiGridSelectionConstants', {
23729     featureName: "selection",
23730     selectionRowHeaderColName: 'selectionRowHeaderCol'
23731   });
23732
23733   //add methods to GridRow
23734   angular.module('ui.grid').config(['$provide', function($provide) {
23735     $provide.decorator('GridRow', ['$delegate', function($delegate) {
23736
23737       /**
23738        *  @ngdoc object
23739        *  @name ui.grid.selection.api:GridRow
23740        *
23741        *  @description GridRow prototype functions added for selection
23742        */
23743
23744       /**
23745        *  @ngdoc object
23746        *  @name enableSelection
23747        *  @propertyOf  ui.grid.selection.api:GridRow
23748        *  @description Enable row selection for this row, only settable by internal code.
23749        *
23750        *  The grouping feature, for example, might set group header rows to not be selectable.
23751        *  <br/>Defaults to true
23752        */
23753
23754       /**
23755        *  @ngdoc object
23756        *  @name isSelected
23757        *  @propertyOf  ui.grid.selection.api:GridRow
23758        *  @description Selected state of row.  Should be readonly. Make any changes to selected state using setSelected().
23759        *  <br/>Defaults to false
23760        */
23761
23762
23763         /**
23764          * @ngdoc function
23765          * @name setSelected
23766          * @methodOf ui.grid.selection.api:GridRow
23767          * @description Sets the isSelected property and updates the selectedCount
23768          * Changes to isSelected state should only be made via this function
23769          * @param {bool} selected value to set
23770          */
23771         $delegate.prototype.setSelected = function(selected) {
23772           this.isSelected = selected;
23773           if (selected) {
23774             this.grid.selection.selectedCount++;
23775           }
23776           else {
23777             this.grid.selection.selectedCount--;
23778           }
23779         };
23780
23781       return $delegate;
23782     }]);
23783   }]);
23784
23785   /**
23786    *  @ngdoc service
23787    *  @name ui.grid.selection.service:uiGridSelectionService
23788    *
23789    *  @description Services for selection features
23790    */
23791   module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
23792     function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
23793
23794       var service = {
23795
23796         initializeGrid: function (grid) {
23797
23798           //add feature namespace and any properties to grid for needed
23799           /**
23800            *  @ngdoc object
23801            *  @name ui.grid.selection.grid:selection
23802            *
23803            *  @description Grid properties and functions added for selection
23804            */
23805           grid.selection = {};
23806           grid.selection.lastSelectedRow = null;
23807           grid.selection.selectAll = false;
23808
23809
23810           /**
23811            *  @ngdoc object
23812            *  @name selectedCount
23813            *  @propertyOf  ui.grid.selection.grid:selection
23814            *  @description Current count of selected rows
23815            *  @example
23816            *  var count = grid.selection.selectedCount
23817            */
23818           grid.selection.selectedCount = 0;
23819
23820           service.defaultGridOptions(grid.options);
23821
23822           /**
23823            *  @ngdoc object
23824            *  @name ui.grid.selection.api:PublicApi
23825            *
23826            *  @description Public Api for selection feature
23827            */
23828           var publicApi = {
23829             events: {
23830               selection: {
23831                 /**
23832                  * @ngdoc event
23833                  * @name rowSelectionChanged
23834                  * @eventOf  ui.grid.selection.api:PublicApi
23835                  * @description  is raised after the row.isSelected state is changed
23836                  * @param {GridRow} row the row that was selected/deselected
23837                  * @param {Event} event object if raised from an event
23838                  */
23839                 rowSelectionChanged: function (scope, row, evt) {
23840                 },
23841                 /**
23842                  * @ngdoc event
23843                  * @name rowSelectionChangedBatch
23844                  * @eventOf  ui.grid.selection.api:PublicApi
23845                  * @description  is raised after the row.isSelected state is changed
23846                  * in bulk, if the `enableSelectionBatchEvent` option is set to true
23847                  * (which it is by default).  This allows more efficient processing
23848                  * of bulk events.
23849                  * @param {array} rows the rows that were selected/deselected
23850                  * @param {Event} event object if raised from an event
23851                  */
23852                 rowSelectionChangedBatch: function (scope, rows, evt) {
23853                 }
23854               }
23855             },
23856             methods: {
23857               selection: {
23858                 /**
23859                  * @ngdoc function
23860                  * @name toggleRowSelection
23861                  * @methodOf  ui.grid.selection.api:PublicApi
23862                  * @description Toggles data row as selected or unselected
23863                  * @param {object} rowEntity gridOptions.data[] array instance
23864                  * @param {Event} event object if raised from an event
23865                  */
23866                 toggleRowSelection: function (rowEntity, evt) {
23867                   var row = grid.getRow(rowEntity);
23868                   if (row !== null) {
23869                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
23870                   }
23871                 },
23872                 /**
23873                  * @ngdoc function
23874                  * @name selectRow
23875                  * @methodOf  ui.grid.selection.api:PublicApi
23876                  * @description Select the data row
23877                  * @param {object} rowEntity gridOptions.data[] array instance
23878                  * @param {Event} event object if raised from an event
23879                  */
23880                 selectRow: function (rowEntity, evt) {
23881                   var row = grid.getRow(rowEntity);
23882                   if (row !== null && !row.isSelected) {
23883                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
23884                   }
23885                 },
23886                 /**
23887                  * @ngdoc function
23888                  * @name selectRowByVisibleIndex
23889                  * @methodOf  ui.grid.selection.api:PublicApi
23890                  * @description Select the specified row by visible index (i.e. if you
23891                  * specify row 0 you'll get the first visible row selected).  In this context
23892                  * visible means of those rows that are theoretically visible (i.e. not filtered),
23893                  * rather than rows currently rendered on the screen.
23894                  * @param {number} index index within the rowsVisible array
23895                  * @param {Event} event object if raised from an event
23896                  */
23897                 selectRowByVisibleIndex: function ( rowNum, evt ) {
23898                   var row = grid.renderContainers.body.visibleRowCache[rowNum];
23899                   if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
23900                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
23901                   }
23902                 },
23903                 /**
23904                  * @ngdoc function
23905                  * @name unSelectRow
23906                  * @methodOf  ui.grid.selection.api:PublicApi
23907                  * @description UnSelect the data row
23908                  * @param {object} rowEntity gridOptions.data[] array instance
23909                  * @param {Event} event object if raised from an event
23910                  */
23911                 unSelectRow: function (rowEntity, evt) {
23912                   var row = grid.getRow(rowEntity);
23913                   if (row !== null && row.isSelected) {
23914                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
23915                   }
23916                 },
23917                 /**
23918                  * @ngdoc function
23919                  * @name selectAllRows
23920                  * @methodOf  ui.grid.selection.api:PublicApi
23921                  * @description Selects all rows.  Does nothing if multiSelect = false
23922                  * @param {Event} event object if raised from an event
23923                  */
23924                 selectAllRows: function (evt) {
23925                   if (grid.options.multiSelect === false) {
23926                     return;
23927                   }
23928
23929                   var changedRows = [];
23930                   grid.rows.forEach(function (row) {
23931                     if ( !row.isSelected && row.enableSelection !== false ){
23932                       row.setSelected(true);
23933                       service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23934                     }
23935                   });
23936                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
23937                   grid.selection.selectAll = true;
23938                 },
23939                 /**
23940                  * @ngdoc function
23941                  * @name selectAllVisibleRows
23942                  * @methodOf  ui.grid.selection.api:PublicApi
23943                  * @description Selects all visible rows.  Does nothing if multiSelect = false
23944                  * @param {Event} event object if raised from an event
23945                  */
23946                 selectAllVisibleRows: function (evt) {
23947                   if (grid.options.multiSelect === false) {
23948                     return;
23949                   }
23950
23951                   var changedRows = [];
23952                   grid.rows.forEach(function (row) {
23953                     if (row.visible) {
23954                       if (!row.isSelected && row.enableSelection !== false){
23955                         row.setSelected(true);
23956                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23957                       }
23958                     } else {
23959                       if (row.isSelected){
23960                         row.setSelected(false);
23961                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23962                       }
23963                     }
23964                   });
23965                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
23966                   grid.selection.selectAll = true;
23967                 },
23968                 /**
23969                  * @ngdoc function
23970                  * @name clearSelectedRows
23971                  * @methodOf  ui.grid.selection.api:PublicApi
23972                  * @description Unselects all rows
23973                  * @param {Event} event object if raised from an event
23974                  */
23975                 clearSelectedRows: function (evt) {
23976                   service.clearSelectedRows(grid, evt);
23977                 },
23978                 /**
23979                  * @ngdoc function
23980                  * @name getSelectedRows
23981                  * @methodOf  ui.grid.selection.api:PublicApi
23982                  * @description returns all selectedRow's entity references
23983                  */
23984                 getSelectedRows: function () {
23985                   return service.getSelectedRows(grid).map(function (gridRow) {
23986                     return gridRow.entity;
23987                   });
23988                 },
23989                 /**
23990                  * @ngdoc function
23991                  * @name getSelectedGridRows
23992                  * @methodOf  ui.grid.selection.api:PublicApi
23993                  * @description returns all selectedRow's as gridRows
23994                  */
23995                 getSelectedGridRows: function () {
23996                   return service.getSelectedRows(grid);
23997                 },
23998                 /**
23999                  * @ngdoc function
24000                  * @name setMultiSelect
24001                  * @methodOf  ui.grid.selection.api:PublicApi
24002                  * @description Sets the current gridOption.multiSelect to true or false
24003                  * @param {bool} multiSelect true to allow multiple rows
24004                  */
24005                 setMultiSelect: function (multiSelect) {
24006                   grid.options.multiSelect = multiSelect;
24007                 },
24008                 /**
24009                  * @ngdoc function
24010                  * @name setModifierKeysToMultiSelect
24011                  * @methodOf  ui.grid.selection.api:PublicApi
24012                  * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
24013                  * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
24014                  */
24015                 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
24016                   grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
24017                 },
24018                 /**
24019                  * @ngdoc function
24020                  * @name getSelectAllState
24021                  * @methodOf  ui.grid.selection.api:PublicApi
24022                  * @description Returns whether or not the selectAll checkbox is currently ticked.  The
24023                  * grid doesn't automatically select rows when you add extra data - so when you add data
24024                  * you need to explicitly check whether the selectAll is set, and then call setVisible rows
24025                  * if it is
24026                  */
24027                 getSelectAllState: function () {
24028                   return grid.selection.selectAll;
24029                 }
24030
24031               }
24032             }
24033           };
24034
24035           grid.api.registerEventsFromObject(publicApi.events);
24036
24037           grid.api.registerMethodsFromObject(publicApi.methods);
24038
24039         },
24040
24041         defaultGridOptions: function (gridOptions) {
24042           //default option to true unless it was explicitly set to false
24043           /**
24044            *  @ngdoc object
24045            *  @name ui.grid.selection.api:GridOptions
24046            *
24047            *  @description GridOptions for selection feature, these are available to be
24048            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
24049            */
24050
24051           /**
24052            *  @ngdoc object
24053            *  @name enableRowSelection
24054            *  @propertyOf  ui.grid.selection.api:GridOptions
24055            *  @description Enable row selection for entire grid.
24056            *  <br/>Defaults to true
24057            */
24058           gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
24059           /**
24060            *  @ngdoc object
24061            *  @name multiSelect
24062            *  @propertyOf  ui.grid.selection.api:GridOptions
24063            *  @description Enable multiple row selection for entire grid
24064            *  <br/>Defaults to true
24065            */
24066           gridOptions.multiSelect = gridOptions.multiSelect !== false;
24067           /**
24068            *  @ngdoc object
24069            *  @name noUnselect
24070            *  @propertyOf  ui.grid.selection.api:GridOptions
24071            *  @description Prevent a row from being unselected.  Works in conjunction
24072            *  with `multiselect = false` and `gridApi.selection.selectRow()` to allow
24073            *  you to create a single selection only grid - a row is always selected, you
24074            *  can only select different rows, you can't unselect the row.
24075            *  <br/>Defaults to false
24076            */
24077           gridOptions.noUnselect = gridOptions.noUnselect === true;
24078           /**
24079            *  @ngdoc object
24080            *  @name modifierKeysToMultiSelect
24081            *  @propertyOf  ui.grid.selection.api:GridOptions
24082            *  @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
24083            *  <br/>Defaults to false
24084            */
24085           gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
24086           /**
24087            *  @ngdoc object
24088            *  @name enableRowHeaderSelection
24089            *  @propertyOf  ui.grid.selection.api:GridOptions
24090            *  @description Enable a row header to be used for selection
24091            *  <br/>Defaults to true
24092            */
24093           gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
24094           /**
24095            *  @ngdoc object
24096            *  @name enableFullRowSelection
24097            *  @propertyOf  ui.grid.selection.api:GridOptions
24098            *  @description Enable selection by clicking anywhere on the row.  Defaults to
24099            *  false if `enableRowHeaderSelection` is true, otherwise defaults to false.
24100            */
24101           if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
24102             gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
24103           }
24104           /**
24105            *  @ngdoc object
24106            *  @name enableSelectAll
24107            *  @propertyOf  ui.grid.selection.api:GridOptions
24108            *  @description Enable the select all checkbox at the top of the selectionRowHeader
24109            *  <br/>Defaults to true
24110            */
24111           gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
24112           /**
24113            *  @ngdoc object
24114            *  @name enableSelectionBatchEvent
24115            *  @propertyOf  ui.grid.selection.api:GridOptions
24116            *  @description If selected rows are changed in bulk, either via the API or
24117            *  via the selectAll checkbox, then a separate event is fired.  Setting this
24118            *  option to false will cause the rowSelectionChanged event to be called multiple times
24119            *  instead
24120            *  <br/>Defaults to true
24121            */
24122           gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
24123           /**
24124            *  @ngdoc object
24125            *  @name selectionRowHeaderWidth
24126            *  @propertyOf  ui.grid.selection.api:GridOptions
24127            *  @description can be used to set a custom width for the row header selection column
24128            *  <br/>Defaults to 30px
24129            */
24130           gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
24131
24132           /**
24133            *  @ngdoc object
24134            *  @name enableFooterTotalSelected
24135            *  @propertyOf  ui.grid.selection.api:GridOptions
24136            *  @description Shows the total number of selected items in footer if true.
24137            *  <br/>Defaults to true.
24138            *  <br/>GridOptions.showGridFooter must also be set to true.
24139            */
24140           gridOptions.enableFooterTotalSelected = gridOptions.enableFooterTotalSelected !== false;
24141
24142           /**
24143            *  @ngdoc object
24144            *  @name isRowSelectable
24145            *  @propertyOf  ui.grid.selection.api:GridOptions
24146            *  @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
24147            */
24148
24149           gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
24150         },
24151
24152         /**
24153          * @ngdoc function
24154          * @name toggleRowSelection
24155          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24156          * @description Toggles row as selected or unselected
24157          * @param {Grid} grid grid object
24158          * @param {GridRow} row row to select or deselect
24159          * @param {Event} event object if resulting from event
24160          * @param {bool} multiSelect if false, only one row at time can be selected
24161          * @param {bool} noUnselect if true then rows cannot be unselected
24162          */
24163         toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
24164           var selected = row.isSelected;
24165
24166           if ( row.enableSelection === false && !selected ){
24167             return;
24168           }
24169
24170           var selectedRows;
24171           if (!multiSelect && !selected) {
24172             service.clearSelectedRows(grid, evt);
24173           } else if (!multiSelect && selected) {
24174             selectedRows = service.getSelectedRows(grid);
24175             if (selectedRows.length > 1) {
24176               selected = false; // Enable reselect of the row
24177               service.clearSelectedRows(grid, evt);
24178             }
24179           }
24180
24181           if (selected && noUnselect){
24182             // don't deselect the row
24183           } else {
24184             row.setSelected(!selected);
24185             if (row.isSelected === true) {
24186               grid.selection.lastSelectedRow = row;
24187             }
24188
24189             selectedRows = service.getSelectedRows(grid);
24190             grid.selection.selectAll = grid.rows.length === selectedRows.length;
24191
24192             grid.api.selection.raise.rowSelectionChanged(row, evt);
24193           }
24194         },
24195         /**
24196          * @ngdoc function
24197          * @name shiftSelect
24198          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24199          * @description selects a group of rows from the last selected row using the shift key
24200          * @param {Grid} grid grid object
24201          * @param {GridRow} clicked row
24202          * @param {Event} event object if raised from an event
24203          * @param {bool} multiSelect if false, does nothing this is for multiSelect only
24204          */
24205         shiftSelect: function (grid, row, evt, multiSelect) {
24206           if (!multiSelect) {
24207             return;
24208           }
24209           var selectedRows = service.getSelectedRows(grid);
24210           var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
24211           var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
24212           //reverse select direction
24213           if (fromRow > toRow) {
24214             var tmp = fromRow;
24215             fromRow = toRow;
24216             toRow = tmp;
24217           }
24218
24219           var changedRows = [];
24220           for (var i = fromRow; i <= toRow; i++) {
24221             var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
24222             if (rowToSelect) {
24223               if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
24224                 rowToSelect.setSelected(true);
24225                 grid.selection.lastSelectedRow = rowToSelect;
24226                 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
24227               }
24228             }
24229           }
24230           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24231         },
24232         /**
24233          * @ngdoc function
24234          * @name getSelectedRows
24235          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24236          * @description Returns all the selected rows
24237          * @param {Grid} grid grid object
24238          */
24239         getSelectedRows: function (grid) {
24240           return grid.rows.filter(function (row) {
24241             return row.isSelected;
24242           });
24243         },
24244
24245         /**
24246          * @ngdoc function
24247          * @name clearSelectedRows
24248          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24249          * @description Clears all selected rows
24250          * @param {Grid} grid grid object
24251          * @param {Event} event object if raised from an event
24252          */
24253         clearSelectedRows: function (grid, evt) {
24254           var changedRows = [];
24255           service.getSelectedRows(grid).forEach(function (row) {
24256             if ( row.isSelected ){
24257               row.setSelected(false);
24258               service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24259             }
24260           });
24261           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24262           grid.selection.selectAll = false;
24263           grid.selection.selectedCount = 0;
24264         },
24265
24266         /**
24267          * @ngdoc function
24268          * @name decideRaiseSelectionEvent
24269          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24270          * @description Decides whether to raise a single event or a batch event
24271          * @param {Grid} grid grid object
24272          * @param {GridRow} row row that has changed
24273          * @param {array} changedRows an array to which we can append the changed
24274          * @param {Event} event object if raised from an event
24275          * row if we're doing batch events
24276          */
24277         decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
24278           if ( !grid.options.enableSelectionBatchEvent ){
24279             grid.api.selection.raise.rowSelectionChanged(row, evt);
24280           } else {
24281             changedRows.push(row);
24282           }
24283         },
24284
24285         /**
24286          * @ngdoc function
24287          * @name raiseSelectionEvent
24288          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24289          * @description Decides whether we need to raise a batch event, and
24290          * raises it if we do.
24291          * @param {Grid} grid grid object
24292          * @param {array} changedRows an array of changed rows, only populated
24293          * @param {Event} event object if raised from an event
24294          * if we're doing batch events
24295          */
24296         decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
24297           if ( changedRows.length > 0 ){
24298             grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
24299           }
24300         }
24301       };
24302
24303       return service;
24304
24305     }]);
24306
24307   /**
24308    *  @ngdoc directive
24309    *  @name ui.grid.selection.directive:uiGridSelection
24310    *  @element div
24311    *  @restrict A
24312    *
24313    *  @description Adds selection features to grid
24314    *
24315    *  @example
24316    <example module="app">
24317    <file name="app.js">
24318    var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
24319
24320    app.controller('MainCtrl', ['$scope', function ($scope) {
24321       $scope.data = [
24322         { name: 'Bob', title: 'CEO' },
24323             { name: 'Frank', title: 'Lowly Developer' }
24324       ];
24325
24326       $scope.columnDefs = [
24327         {name: 'name', enableCellEdit: true},
24328         {name: 'title', enableCellEdit: true}
24329       ];
24330     }]);
24331    </file>
24332    <file name="index.html">
24333    <div ng-controller="MainCtrl">
24334    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
24335    </div>
24336    </file>
24337    </example>
24338    */
24339   module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
24340     function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
24341       return {
24342         replace: true,
24343         priority: 0,
24344         require: '^uiGrid',
24345         scope: false,
24346         compile: function () {
24347           return {
24348             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24349               uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
24350               if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
24351                 var selectionRowHeaderDef = {
24352                   name: uiGridSelectionConstants.selectionRowHeaderColName,
24353                   displayName: '',
24354                   width:  uiGridCtrl.grid.options.selectionRowHeaderWidth,
24355                   minWidth: 10,
24356                   cellTemplate: 'ui-grid/selectionRowHeader',
24357                   headerCellTemplate: 'ui-grid/selectionHeaderCell',
24358                   enableColumnResizing: false,
24359                   enableColumnMenu: false,
24360                   exporterSuppressExport: true,
24361                   allowCellFocus: true
24362                 };
24363
24364                 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
24365               }
24366
24367               var processorSet = false;
24368
24369               var processSelectableRows = function( rows ){
24370                 rows.forEach(function(row){
24371                   row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
24372                 });
24373                 return rows;
24374               };
24375
24376               var updateOptions = function(){
24377                 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
24378                   uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
24379                   processorSet = true;
24380                 }
24381               };
24382
24383               updateOptions();
24384
24385               var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
24386
24387               $scope.$on( '$destroy', dataChangeDereg);
24388             },
24389             post: function ($scope, $elm, $attrs, uiGridCtrl) {
24390
24391             }
24392           };
24393         }
24394       };
24395     }]);
24396
24397   module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService', 'gridUtil',
24398     function ($templateCache, uiGridSelectionService, gridUtil) {
24399       return {
24400         replace: true,
24401         restrict: 'E',
24402         template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
24403         scope: true,
24404         require: '^uiGrid',
24405         link: function($scope, $elm, $attrs, uiGridCtrl) {
24406           var self = uiGridCtrl.grid;
24407           $scope.selectButtonClick = selectButtonClick;
24408
24409           // On IE, prevent mousedowns on the select button from starting a selection.
24410           //   If this is not done and you shift+click on another row, the browser will select a big chunk of text
24411           if (gridUtil.detectBrowser() === 'ie') {
24412             $elm.on('mousedown', selectButtonMouseDown);
24413           }
24414
24415
24416           function selectButtonClick(row, evt) {
24417             evt.stopPropagation();
24418
24419             if (evt.shiftKey) {
24420               uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
24421             }
24422             else if (evt.ctrlKey || evt.metaKey) {
24423               uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
24424             }
24425             else {
24426               uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
24427             }
24428           }
24429
24430           function selectButtonMouseDown(evt) {
24431             if (evt.ctrlKey || evt.shiftKey) {
24432               evt.target.onselectstart = function () { return false; };
24433               window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
24434             }
24435           }
24436         }
24437       };
24438     }]);
24439
24440   module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
24441     function ($templateCache, uiGridSelectionService) {
24442       return {
24443         replace: true,
24444         restrict: 'E',
24445         template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
24446         scope: false,
24447         link: function($scope, $elm, $attrs, uiGridCtrl) {
24448           var self = $scope.col.grid;
24449
24450           $scope.headerButtonClick = function(row, evt) {
24451             if ( self.selection.selectAll ){
24452               uiGridSelectionService.clearSelectedRows(self, evt);
24453               if ( self.options.noUnselect ){
24454                 self.api.selection.selectRowByVisibleIndex(0, evt);
24455               }
24456               self.selection.selectAll = false;
24457             } else {
24458               if ( self.options.multiSelect ){
24459                 self.api.selection.selectAllVisibleRows(evt);
24460                 self.selection.selectAll = true;
24461               }
24462             }
24463           };
24464         }
24465       };
24466     }]);
24467
24468   /**
24469    *  @ngdoc directive
24470    *  @name ui.grid.selection.directive:uiGridViewport
24471    *  @element div
24472    *
24473    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
24474    *  for the grid row
24475    */
24476   module.directive('uiGridViewport',
24477     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
24478       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
24479         return {
24480           priority: -200, // run after default  directive
24481           scope: false,
24482           compile: function ($elm, $attrs) {
24483             var rowRepeatDiv = angular.element($elm.children().children()[0]);
24484
24485             var existingNgClass = rowRepeatDiv.attr("ng-class");
24486             var newNgClass = '';
24487             if ( existingNgClass ) {
24488               newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
24489             } else {
24490               newNgClass = "{'ui-grid-row-selected': row.isSelected}";
24491             }
24492             rowRepeatDiv.attr("ng-class", newNgClass);
24493
24494             return {
24495               pre: function ($scope, $elm, $attrs, controllers) {
24496
24497               },
24498               post: function ($scope, $elm, $attrs, controllers) {
24499               }
24500             };
24501           }
24502         };
24503       }]);
24504
24505   /**
24506    *  @ngdoc directive
24507    *  @name ui.grid.selection.directive:uiGridCell
24508    *  @element div
24509    *  @restrict A
24510    *
24511    *  @description Stacks on top of ui.grid.uiGridCell to provide selection feature
24512    */
24513   module.directive('uiGridCell',
24514     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
24515       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
24516         return {
24517           priority: -200, // run after default uiGridCell directive
24518           restrict: 'A',
24519           require: '?^uiGrid',
24520           scope: false,
24521           link: function ($scope, $elm, $attrs, uiGridCtrl) {
24522
24523             var touchStartTime = 0;
24524             var touchTimeout = 300;
24525
24526             // Bind to keydown events in the render container
24527             if (uiGridCtrl.grid.api.cellNav) {
24528
24529               uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
24530                 if (rowCol === null ||
24531                   rowCol.row !== $scope.row ||
24532                   rowCol.col !== $scope.col) {
24533                   return;
24534                 }
24535
24536                 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
24537                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
24538                   $scope.$apply();
24539                 }
24540
24541               //  uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
24542               });
24543             }
24544
24545             //$elm.bind('keydown', function (evt) {
24546             //  if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
24547             //    uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
24548             //    $scope.$apply();
24549             //  }
24550             //});
24551
24552             var selectCells = function(evt){
24553               // if we get a click, then stop listening for touchend
24554               $elm.off('touchend', touchEnd);
24555
24556               if (evt.shiftKey) {
24557                 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
24558               }
24559               else if (evt.ctrlKey || evt.metaKey) {
24560                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
24561               }
24562               else {
24563                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
24564               }
24565               $scope.$apply();
24566
24567               // don't re-enable the touchend handler for a little while - some devices generate both, and it will
24568               // take a little while to move your hand from the mouse to the screen if you have both modes of input
24569               $timeout(function() {
24570                 $elm.on('touchend', touchEnd);
24571               }, touchTimeout);
24572             };
24573
24574             var touchStart = function(evt){
24575               touchStartTime = (new Date()).getTime();
24576
24577               // if we get a touch event, then stop listening for click
24578               $elm.off('click', selectCells);
24579             };
24580
24581             var touchEnd = function(evt) {
24582               var touchEndTime = (new Date()).getTime();
24583               var touchTime = touchEndTime - touchStartTime;
24584
24585               if (touchTime < touchTimeout ) {
24586                 // short touch
24587                 selectCells(evt);
24588               }
24589
24590               // don't re-enable the click handler for a little while - some devices generate both, and it will
24591               // take a little while to move your hand from the screen to the mouse if you have both modes of input
24592               $timeout(function() {
24593                 $elm.on('click', selectCells);
24594               }, touchTimeout);
24595             };
24596
24597             function registerRowSelectionEvents() {
24598               if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
24599                 $elm.addClass('ui-grid-disable-selection');
24600                 $elm.on('touchstart', touchStart);
24601                 $elm.on('touchend', touchEnd);
24602                 $elm.on('click', selectCells);
24603
24604                 $scope.registered = true;
24605               }
24606             }
24607
24608             function deregisterRowSelectionEvents() {
24609               if ($scope.registered){
24610                 $elm.removeClass('ui-grid-disable-selection');
24611
24612                 $elm.off('touchstart', touchStart);
24613                 $elm.off('touchend', touchEnd);
24614                 $elm.off('click', selectCells);
24615
24616                 $scope.registered = false;
24617               }
24618             }
24619
24620             registerRowSelectionEvents();
24621             // register a dataChange callback so that we can change the selection configuration dynamically
24622             // if the user changes the options
24623             var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
24624               if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
24625                 !$scope.registered ){
24626                 registerRowSelectionEvents();
24627               } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
24628                 $scope.registered ){
24629                 deregisterRowSelectionEvents();
24630               }
24631             }, [uiGridConstants.dataChange.OPTIONS] );
24632
24633             $elm.on( '$destroy', dataChangeDereg);
24634           }
24635         };
24636       }]);
24637
24638   module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
24639     return {
24640       restrict: 'EA',
24641       replace: true,
24642       priority: -1000,
24643       require: '^uiGrid',
24644       scope: true,
24645       compile: function ($elm, $attrs) {
24646         return {
24647           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24648
24649             if (!uiGridCtrl.grid.options.showGridFooter) {
24650               return;
24651             }
24652
24653
24654             gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
24655               .then(function (contents) {
24656                 var template = angular.element(contents);
24657
24658                 var newElm = $compile(template)($scope);
24659
24660                 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
24661               });
24662           },
24663
24664           post: function ($scope, $elm, $attrs, controllers) {
24665
24666           }
24667         };
24668       }
24669     };
24670   }]);
24671
24672 })();
24673
24674 (function () {
24675   'use strict';
24676
24677   /**
24678    * @ngdoc overview
24679    * @name ui.grid.treeBase
24680    * @description
24681    *
24682    * # ui.grid.treeBase
24683    *
24684    * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
24685    *
24686    * This module provides base tree handling functions that are shared by other features, notably grouping
24687    * and treeView.  It provides a tree view of the data, with nodes in that
24688    * tree and leaves.
24689    *
24690    * Design information:
24691    * -------------------
24692    *
24693    * The raw data that is provided must come with a $$treeLevel on any non-leaf node.  Grouping will create
24694    * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
24695    * TreeBase will run a rowsProcessor that:
24696    *  - builds `treeBase.tree` out of the provided rows
24697    *  - permits a recursive sort of the tree
24698    *  - maintains the expand/collapse state of each node
24699    *  - provides the expand/collapse all button and the expand/collapse buttons
24700    *  - maintains the count of children for each node
24701    *
24702    * Each row is updated with a link to the tree node that represents it.  Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
24703    * for information.
24704    *
24705    *  TreeBase adds information to the rows
24706    *  - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
24707    *  - treeNode: pointer to the node in the grid.treeBase.tree that refers
24708    *    to this row, allowing us to manipulate the state
24709    *
24710    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
24711    * row order or filtering or anything like that is changed.  We recall the expanded state
24712    * across invocations of the rowsProcessors by the reference to the treeNode on the individual
24713    * rows.  We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
24714    * get the state, but we overwrite the other data in that treeNode.
24715    *
24716    * By default rows are collapsed, which means all data rows have their visible property
24717    * set to false, and only level 0 group rows are set to visible.
24718    *
24719    * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
24720    * grid.treeBase.tree, then call refresh.  This is because we can't easily change the visible
24721    * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
24722    * well use it all the time.
24723    *
24724    * Tree base provides sorting (on non-grouped columns).
24725    *
24726    * Sorting works in two passes.  The standard sorting is performed for any columns that are important to building
24727    * the tree (for example, any grouped columns).  Then after the tree is built, a recursive tree sort is performed
24728    * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
24729    * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
24730    *
24731    * To achieve this we make use of the `ignoreSort` property on the sort configuration.  The parent feature (treeView or grouping)
24732    * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
24733    * the `ignoreSort`on any sort that it wants to run on the tree.  TreeBase will clear the ignoreSort on all sorts - so it
24734    * will turn on any sorts that haven't run.  It will then call a recursive sort on the tree.
24735    *
24736    * Tree base provides treeAggregation.  It checks the treeAggregation configuration on each column, and aggregates based on
24737    * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
24738    * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
24739    * treeAggregations in the column footer.  Aggregation information will be collected in the format:
24740    *
24741    * ```
24742    *   {
24743    *     type: 'count',
24744    *     value: 4,
24745    *     label: 'count: ',
24746    *     rendered: 'count: 4'
24747    *   }
24748    * ```
24749    *
24750    * A callback is provided to format the value once it is finalised (aka a valueFilter).
24751    *
24752    * <br/>
24753    * <br/>
24754    *
24755    * <div doc-module-components="ui.grid.treeBase"></div>
24756    */
24757
24758   var module = angular.module('ui.grid.treeBase', ['ui.grid']);
24759
24760   /**
24761    *  @ngdoc object
24762    *  @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
24763    *
24764    *  @description constants available in treeBase module.
24765    *
24766    *  These constants are manually copied into grouping and treeView,
24767    *  as I haven't found a way to simply include them, and it's not worth
24768    *  investing time in for something that changes very infrequently.
24769    *
24770    */
24771   module.constant('uiGridTreeBaseConstants', {
24772     featureName: "treeBase",
24773     rowHeaderColName: 'treeBaseRowHeaderCol',
24774     EXPANDED: 'expanded',
24775     COLLAPSED: 'collapsed',
24776     aggregation: {
24777       COUNT: 'count',
24778       SUM: 'sum',
24779       MAX: 'max',
24780       MIN: 'min',
24781       AVG: 'avg'
24782     }
24783   });
24784
24785   /**
24786    *  @ngdoc service
24787    *  @name ui.grid.treeBase.service:uiGridTreeBaseService
24788    *
24789    *  @description Services for treeBase feature
24790    */
24791   /**
24792    *  @ngdoc object
24793    *  @name ui.grid.treeBase.api:ColumnDef
24794    *
24795    *  @description ColumnDef for tree feature, these are available to be
24796    *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
24797    */
24798
24799   module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
24800   function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
24801
24802     var service = {
24803
24804       initializeGrid: function (grid, $scope) {
24805
24806         //add feature namespace and any properties to grid for needed
24807         /**
24808          *  @ngdoc object
24809          *  @name ui.grid.treeBase.grid:treeBase
24810          *
24811          *  @description Grid properties and functions added for treeBase
24812          */
24813         grid.treeBase = {};
24814
24815         /**
24816          *  @ngdoc property
24817          *  @propertyOf ui.grid.treeBase.grid:treeBase
24818          *  @name numberLevels
24819          *
24820          *  @description Total number of tree levels currently used, calculated by the rowsProcessor by
24821          *  retaining the highest tree level it sees
24822          */
24823         grid.treeBase.numberLevels = 0;
24824
24825         /**
24826          *  @ngdoc property
24827          *  @propertyOf ui.grid.treeBase.grid:treeBase
24828          *  @name expandAll
24829          *
24830          *  @description Whether or not the expandAll box is selected
24831          */
24832         grid.treeBase.expandAll = false;
24833
24834         /**
24835          *  @ngdoc property
24836          *  @propertyOf ui.grid.treeBase.grid:treeBase
24837          *  @name tree
24838          *
24839          *  @description Tree represented as a nested array that holds the state of each node, along with a
24840          *  pointer to the row.  The array order is material - we will display the children in the order
24841          *  they are stored in the array
24842          *
24843          *  Each node stores:
24844          *
24845          *    - the state of this node
24846          *    - an array of children of this node
24847          *    - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
24848          *    - the number of children of this node
24849          *    - aggregation information calculated from the nodes
24850          *
24851          *  ```
24852          *    [{
24853          *      state: 'expanded',
24854          *      row: <reference to row>,
24855          *      parentRow: null,
24856          *      aggregations: [{
24857          *        type: 'count',
24858          *        col: <gridCol>,
24859          *        value: 2,
24860          *        label: 'count: ',
24861          *        rendered: 'count: 2'
24862          *      }],
24863          *      children: [
24864          *        {
24865          *          state: 'expanded',
24866          *          row: <reference to row>,
24867          *          parentRow: <reference to row>,
24868          *          aggregations: [{
24869          *            type: 'count',
24870          *            col: '<gridCol>,
24871          *            value: 4,
24872          *            label: 'count: ',
24873          *            rendered: 'count: 4'
24874          *          }],
24875          *          children: [
24876          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
24877          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
24878          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
24879          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
24880          *          ]
24881          *        },
24882          *        {
24883          *          state: 'collapsed',
24884          *          row: <reference to row>,
24885          *          parentRow: <reference to row>,
24886          *          aggregations: [{
24887          *            type: 'count',
24888          *            col: <gridCol>,
24889          *            value: 3,
24890          *            label: 'count: ',
24891          *            rendered: 'count: 3'
24892          *          }],
24893          *          children: [
24894          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
24895          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
24896          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
24897          *          ]
24898          *        }
24899          *      ]
24900          *    }, {<another level 0 node maybe>} ]
24901          *  ```
24902          *  Missing state values are false - meaning they aren't expanded.
24903          *
24904          *  This is used because the rowProcessors run every time the grid is refreshed, so
24905          *  we'd lose the expanded state every time the grid was refreshed.  This instead gives
24906          *  us a reliable lookup that persists across rowProcessors.
24907          *
24908          *  This tree is rebuilt every time we run the rowsProcessors.  Since each row holds a pointer
24909          *  to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
24910          *  all transient information on the tree (children, childCount) and recalculate it
24911          *
24912          */
24913         grid.treeBase.tree = {};
24914
24915         service.defaultGridOptions(grid.options);
24916
24917         grid.registerRowsProcessor(service.treeRows, 410);
24918
24919         grid.registerColumnBuilder( service.treeBaseColumnBuilder );
24920
24921         service.createRowHeader( grid );
24922
24923         /**
24924          *  @ngdoc object
24925          *  @name ui.grid.treeBase.api:PublicApi
24926          *
24927          *  @description Public Api for treeBase feature
24928          */
24929         var publicApi = {
24930           events: {
24931             treeBase: {
24932               /**
24933                * @ngdoc event
24934                * @eventOf ui.grid.treeBase.api:PublicApi
24935                * @name rowExpanded
24936                * @description raised whenever a row is expanded.  If you are dynamically
24937                * rendering your tree you can listen to this event, and then retrieve
24938                * the children of this row and load them into the grid data.
24939                *
24940                * When the data is loaded the grid will automatically refresh to show these new rows
24941                *
24942                * <pre>
24943                *      gridApi.treeBase.on.rowExpanded(scope,function(row){})
24944                * </pre>
24945                * @param {gridRow} row the row that was expanded.  You can also
24946                * retrieve the grid from this row with row.grid
24947                */
24948               rowExpanded: {},
24949
24950               /**
24951                * @ngdoc event
24952                * @eventOf ui.grid.treeBase.api:PublicApi
24953                * @name rowCollapsed
24954                * @description raised whenever a row is collapsed.  Doesn't really have
24955                * a purpose at the moment, included for symmetry
24956                *
24957                * <pre>
24958                *      gridApi.treeBase.on.rowCollapsed(scope,function(row){})
24959                * </pre>
24960                * @param {gridRow} row the row that was collapsed.  You can also
24961                * retrieve the grid from this row with row.grid
24962                */
24963               rowCollapsed: {}
24964             }
24965           },
24966
24967           methods: {
24968             treeBase: {
24969               /**
24970                * @ngdoc function
24971                * @name expandAllRows
24972                * @methodOf  ui.grid.treeBase.api:PublicApi
24973                * @description Expands all tree rows
24974                */
24975               expandAllRows: function () {
24976                 service.expandAllRows(grid);
24977               },
24978
24979               /**
24980                * @ngdoc function
24981                * @name collapseAllRows
24982                * @methodOf  ui.grid.treeBase.api:PublicApi
24983                * @description collapse all tree rows
24984                */
24985               collapseAllRows: function () {
24986                 service.collapseAllRows(grid);
24987               },
24988
24989               /**
24990                * @ngdoc function
24991                * @name toggleRowTreeState
24992                * @methodOf  ui.grid.treeBase.api:PublicApi
24993                * @description  call expand if the row is collapsed, collapse if it is expanded
24994                * @param {gridRow} row the row you wish to toggle
24995                */
24996               toggleRowTreeState: function (row) {
24997                 service.toggleRowTreeState(grid, row);
24998               },
24999
25000               /**
25001                * @ngdoc function
25002                * @name expandRow
25003                * @methodOf  ui.grid.treeBase.api:PublicApi
25004                * @description expand the immediate children of the specified row
25005                * @param {gridRow} row the row you wish to expand
25006                */
25007               expandRow: function (row) {
25008                 service.expandRow(grid, row);
25009               },
25010
25011               /**
25012                * @ngdoc function
25013                * @name expandRowChildren
25014                * @methodOf  ui.grid.treeBase.api:PublicApi
25015                * @description expand all children of the specified row
25016                * @param {gridRow} row the row you wish to expand
25017                */
25018               expandRowChildren: function (row) {
25019                 service.expandRowChildren(grid, row);
25020               },
25021
25022               /**
25023                * @ngdoc function
25024                * @name collapseRow
25025                * @methodOf  ui.grid.treeBase.api:PublicApi
25026                * @description collapse  the specified row.  When
25027                * you expand the row again, all grandchildren will retain their state
25028                * @param {gridRow} row the row you wish to collapse
25029                */
25030               collapseRow: function ( row ) {
25031                 service.collapseRow(grid, row);
25032               },
25033
25034               /**
25035                * @ngdoc function
25036                * @name collapseRowChildren
25037                * @methodOf  ui.grid.treeBase.api:PublicApi
25038                * @description collapse all children of the specified row.  When
25039                * you expand the row again, all grandchildren will be collapsed
25040                * @param {gridRow} row the row you wish to collapse children for
25041                */
25042               collapseRowChildren: function ( row ) {
25043                 service.collapseRowChildren(grid, row);
25044               },
25045
25046               /**
25047                * @ngdoc function
25048                * @name getTreeState
25049                * @methodOf  ui.grid.treeBase.api:PublicApi
25050                * @description Get the tree state for this grid,
25051                * used by the saveState feature
25052                * Returned treeState as an object
25053                *   `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
25054                * where expandedState is a hash of row uid and the current expanded state
25055                *
25056                * @returns {object} tree state
25057                *
25058                * TODO - this needs work - we need an identifier that persists across instantiations,
25059                * not uid.  This really means we need a row identity defined, but that won't work for
25060                * grouping.  Perhaps this needs to be moved up to treeView and grouping, rather than
25061                * being in base.
25062                */
25063               getTreeExpandedState: function () {
25064                 return { expandedState: service.getTreeState(grid) };
25065               },
25066
25067               /**
25068                * @ngdoc function
25069                * @name setTreeState
25070                * @methodOf  ui.grid.treeBase.api:PublicApi
25071                * @description Set the expanded states of the tree
25072                * @param {object} config the config you want to apply, in the format
25073                * provided by getTreeState
25074                */
25075               setTreeState: function ( config ) {
25076                 service.setTreeState( grid, config );
25077               },
25078
25079               /**
25080                * @ngdoc function
25081                * @name getRowChildren
25082                * @methodOf  ui.grid.treeBase.api:PublicApi
25083                * @description Get the children of the specified row
25084                * @param {GridRow} row the row you want the children of
25085                * @returns {Array} array of children of this row, the children
25086                * are all gridRows
25087                */
25088               getRowChildren: function ( row ){
25089                 return row.treeNode.children.map( function( childNode ){
25090                   return childNode.row;
25091                 });
25092               }
25093             }
25094           }
25095         };
25096
25097         grid.api.registerEventsFromObject(publicApi.events);
25098
25099         grid.api.registerMethodsFromObject(publicApi.methods);
25100       },
25101
25102
25103       defaultGridOptions: function (gridOptions) {
25104         //default option to true unless it was explicitly set to false
25105         /**
25106          *  @ngdoc object
25107          *  @name ui.grid.treeBase.api:GridOptions
25108          *
25109          *  @description GridOptions for treeBase feature, these are available to be
25110          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
25111          */
25112
25113         /**
25114          *  @ngdoc object
25115          *  @name treeRowHeaderBaseWidth
25116          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25117          *  @description Base width of the tree header, provides for a single level of tree.  This
25118          *  is incremented by `treeIndent` for each extra level
25119          *  <br/>Defaults to 30
25120          */
25121         gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
25122
25123         /**
25124          *  @ngdoc object
25125          *  @name treeIndent
25126          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25127          *  @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
25128          *  but will make the tree row header wider
25129          *  <br/>Defaults to 10
25130          */
25131         gridOptions.treeIndent = gridOptions.treeIndent || 10;
25132
25133         /**
25134          *  @ngdoc object
25135          *  @name showTreeRowHeader
25136          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25137          *  @description If set to false, don't create the row header.  Youll need to programatically control the expand
25138          *  states
25139          *  <br/>Defaults to true
25140          */
25141         gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
25142
25143         /**
25144          *  @ngdoc object
25145          *  @name showTreeExpandNoChildren
25146          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25147          *  @description If set to true, show the expand/collapse button even if there are no
25148          *  children of a node.  You'd use this if you're planning to dynamically load the children
25149          *
25150          *  <br/>Defaults to true, grouping overrides to false
25151          */
25152         gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
25153
25154         /**
25155          *  @ngdoc object
25156          *  @name treeRowHeaderAlwaysVisible
25157          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25158          *  @description If set to true, row header even if there are no tree nodes
25159          *
25160          *  <br/>Defaults to true
25161          */
25162         gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
25163
25164         /**
25165          *  @ngdoc object
25166          *  @name treeCustomAggregations
25167          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25168          *  @description Define custom aggregation functions. The properties of this object will be
25169          *  aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
25170          *  If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
25171          *  The object format is:
25172          *
25173          *  <pre>
25174          *    {
25175          *      aggregationName: {
25176          *        label: (optional) string,
25177          *        aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
25178          *        finalizerFn: (optional) function( aggregation ){...}
25179        *        },
25180          *      mean: {
25181          *        label: 'mean',
25182          *        aggregationFn: function( aggregation, fieldValue, numValue ){
25183        *            aggregation.count = (aggregation.count || 1) + 1;
25184          *          aggregation.sum = (aggregation.sum || 0) + numValue;
25185          *        },
25186          *        finalizerFn: function( aggregation ){
25187          *          aggregation.value = aggregation.sum / aggregation.count
25188          *        }
25189          *      }
25190          *    }
25191          *  </pre>
25192          *
25193          *  <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
25194          *  apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
25195          *  rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
25196          *  the label and the value.
25197          *
25198          *  <br/>Defaults to {}
25199          */
25200         gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
25201       },
25202
25203
25204       /**
25205        * @ngdoc function
25206        * @name treeBaseColumnBuilder
25207        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25208        * @description Sets the tree defaults based on the columnDefs
25209        *
25210        * @param {object} colDef columnDef we're basing on
25211        * @param {GridCol} col the column we're to update
25212        * @param {object} gridOptions the options we should use
25213        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
25214        */
25215       treeBaseColumnBuilder: function (colDef, col, gridOptions) {
25216
25217
25218         /**
25219          *  @ngdoc object
25220          *  @name customTreeAggregationFn
25221          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25222          *  @description A custom function that aggregates rows into some form of
25223          *  total.  Aggregations run row-by-row, the function needs to be capable of
25224          *  creating a running total.
25225          *
25226          *  The function will be provided the aggregation item (in which you can store running
25227          *  totals), the row value that is to be aggregated, and that same row value converted to
25228          *  a number (most aggregations work on numbers)
25229          *  @example
25230          *  <pre>
25231          *    customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
25232          *      // calculates the average of the squares of the values
25233          *      if ( typeof(aggregation.count) === 'undefined' ){
25234          *        aggregation.count = 0;
25235          *      }
25236          *      aggregation.count++;
25237          *
25238          *      if ( !isNaN(numValue) ){
25239          *        if ( typeof(aggregation.total) === 'undefined' ){
25240          *          aggregation.total = 0;
25241          *        }
25242          *        aggregation.total = aggregation.total + numValue * numValue;
25243          *      }
25244          *
25245          *      aggregation.value = aggregation.total / aggregation.count;
25246          *    }
25247          *  </pre>
25248          *  <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
25249          */
25250         if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
25251           col.treeAggregationFn = colDef.customTreeAggregationFn;
25252         }
25253
25254         /**
25255          *  @ngdoc object
25256          *  @name treeAggregationType
25257          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25258          *  @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
25259          *  Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
25260          *  name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
25261          *
25262          *  <pre>
25263          *      treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
25264          *    }
25265          *  </pre>
25266          *
25267          *  If you are using aggregations you should either:
25268          *
25269          *   - also use grouping, in which case the aggregations are displayed in the group header, OR
25270          *   - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
25271          *     treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
25272          *     in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
25273          *
25274          *  <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
25275          *  <br/>Defaults to undefined.
25276          */
25277         if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
25278           col.treeAggregation = { type: colDef.treeAggregationType };
25279           if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
25280             col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
25281             col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
25282             col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
25283           } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
25284             col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
25285             col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
25286           }
25287         }
25288
25289          /**
25290          *  @ngdoc object
25291          *  @name treeAggregationLabel
25292          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25293          *  @description A custom label to use for this aggregation. If provided we don't use native i18n.
25294          */
25295         if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
25296           if (typeof(col.treeAggregation) === 'undefined' ){
25297             col.treeAggregation = {};
25298           }
25299           col.treeAggregation.label = colDef.treeAggregationLabel;
25300         }
25301
25302         /**
25303          *  @ngdoc object
25304          *  @name treeAggregationUpdateEntity
25305          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25306          *  @description Store calculated aggregations into the entity, allowing them
25307          *  to be displayed in the grid using a standard cellTemplate.  This defaults to true,
25308          *  if you are using grouping then you shouldn't set it to false, as then the aggregations won't
25309          *  display.
25310          *
25311          *  If you are using treeView in most cases you'll want to set this to true.  This will result in
25312          *  getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
25313          *  the entity.  If you want to render the underlying entity value (and do something else with the aggregation)
25314          *  then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
25315          *
25316          *  <br/>Defaults to true
25317          *
25318          *  @example
25319          *  <pre>
25320          *    gridOptions.columns = [{
25321          *      name: 'myCol',
25322          *      treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
25323          *      treeAggregationUpdateEntity: true
25324          *      cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
25325          *    }];
25326          * </pre>
25327          */
25328         col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
25329
25330         /**
25331          *  @ngdoc object
25332          *  @name customTreeAggregationFinalizerFn
25333          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25334          *  @description A custom function that populates aggregation.rendered, this is called when
25335          *  a particular aggregation has been fully calculated, and we want to render the value.
25336          *
25337          *  With the native aggregation options we just concatenate `aggregation.label` and
25338          *  `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
25339          *  or the value, you can do so with this function. This function will be called after the
25340          *  the default `finalizerFn`.
25341          *
25342          *  @example
25343          *  <pre>
25344          *    customTreeAggregationFinalizerFn = function ( aggregation ){
25345          *      aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
25346          *    }
25347          *  </pre>
25348          *  <br/>Defaults to undefined.
25349          */
25350         if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
25351           col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
25352         }
25353
25354       },
25355
25356
25357       /**
25358        * @ngdoc function
25359        * @name createRowHeader
25360        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25361        * @description Create the rowHeader.  If treeRowHeaderAlwaysVisible then
25362        * set it to visible, otherwise set it to invisible
25363        *
25364        * @param {Grid} grid grid object
25365        */
25366       createRowHeader: function( grid ){
25367         var rowHeaderColumnDef = {
25368           name: uiGridTreeBaseConstants.rowHeaderColName,
25369           displayName: '',
25370           width:  grid.options.treeRowHeaderBaseWidth,
25371           minWidth: 10,
25372           cellTemplate: 'ui-grid/treeBaseRowHeader',
25373           headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
25374           enableColumnResizing: false,
25375           enableColumnMenu: false,
25376           exporterSuppressExport: true,
25377           allowCellFocus: true
25378         };
25379
25380         rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
25381         grid.addRowHeaderColumn( rowHeaderColumnDef );
25382       },
25383
25384
25385       /**
25386        * @ngdoc function
25387        * @name expandAllRows
25388        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25389        * @description Expands all nodes in the tree
25390        *
25391        * @param {Grid} grid grid object
25392        */
25393       expandAllRows: function (grid) {
25394         grid.treeBase.tree.forEach( function( node ) {
25395           service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
25396         });
25397         grid.treeBase.expandAll = true;
25398         grid.queueGridRefresh();
25399       },
25400
25401
25402       /**
25403        * @ngdoc function
25404        * @name collapseAllRows
25405        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25406        * @description Collapses all nodes in the tree
25407        *
25408        * @param {Grid} grid grid object
25409        */
25410       collapseAllRows: function (grid) {
25411         grid.treeBase.tree.forEach( function( node ) {
25412           service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
25413         });
25414         grid.treeBase.expandAll = false;
25415         grid.queueGridRefresh();
25416       },
25417
25418
25419       /**
25420        * @ngdoc function
25421        * @name setAllNodes
25422        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25423        * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
25424        * all child nodes (and their descendents) of the provided node to the given state.
25425        *
25426        * Calls itself recursively on all nodes so as to achieve this.
25427        *
25428        * @param {Grid} grid the grid we're operating on (so we can raise events)
25429        * @param {object} treeNode a node in the tree that we want to update
25430        * @param {string} targetState the state we want to set it to
25431        */
25432       setAllNodes: function (grid, treeNode, targetState) {
25433         if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
25434           treeNode.state = targetState;
25435
25436           if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
25437             grid.api.treeBase.raise.rowExpanded(treeNode.row);
25438           } else {
25439             grid.api.treeBase.raise.rowCollapsed(treeNode.row);
25440           }
25441         }
25442
25443         // set all child nodes
25444         if ( treeNode.children ){
25445           treeNode.children.forEach(function( childNode ){
25446             service.setAllNodes(grid, childNode, targetState);
25447           });
25448         }
25449       },
25450
25451
25452       /**
25453        * @ngdoc function
25454        * @name toggleRowTreeState
25455        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25456        * @description Toggles the expand or collapse state of this grouped row, if
25457        * it's a parent row
25458        *
25459        * @param {Grid} grid grid object
25460        * @param {GridRow} row the row we want to toggle
25461        */
25462       toggleRowTreeState: function ( grid, row ){
25463         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25464           return;
25465         }
25466
25467         if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
25468           service.collapseRow(grid, row);
25469         } else {
25470           service.expandRow(grid, row);
25471         }
25472
25473         grid.queueGridRefresh();
25474       },
25475
25476
25477       /**
25478        * @ngdoc function
25479        * @name expandRow
25480        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25481        * @description Expands this specific row, showing only immediate children.
25482        *
25483        * @param {Grid} grid grid object
25484        * @param {GridRow} row the row we want to expand
25485        */
25486       expandRow: function ( grid, row ){
25487         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25488           return;
25489         }
25490
25491         if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
25492           row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
25493           grid.api.treeBase.raise.rowExpanded(row);
25494           grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
25495           grid.queueGridRefresh();
25496         }
25497       },
25498
25499
25500       /**
25501        * @ngdoc function
25502        * @name expandRowChildren
25503        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25504        * @description Expands this specific row, showing all children.
25505        *
25506        * @param {Grid} grid grid object
25507        * @param {GridRow} row the row we want to expand
25508        */
25509       expandRowChildren: function ( grid, row ){
25510         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25511           return;
25512         }
25513
25514         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
25515         grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
25516         grid.queueGridRefresh();
25517       },
25518
25519
25520       /**
25521        * @ngdoc function
25522        * @name collapseRow
25523        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25524        * @description Collapses this specific row
25525        *
25526        * @param {Grid} grid grid object
25527        * @param {GridRow} row the row we want to collapse
25528        */
25529       collapseRow: function( grid, row ){
25530         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25531           return;
25532         }
25533
25534         if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
25535           row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
25536           grid.treeBase.expandAll = false;
25537           grid.api.treeBase.raise.rowCollapsed(row);
25538           grid.queueGridRefresh();
25539         }
25540       },
25541
25542
25543       /**
25544        * @ngdoc function
25545        * @name collapseRowChildren
25546        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25547        * @description Collapses this specific row and all children
25548        *
25549        * @param {Grid} grid grid object
25550        * @param {GridRow} row the row we want to collapse
25551        */
25552       collapseRowChildren: function( grid, row ){
25553         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25554           return;
25555         }
25556
25557         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
25558         grid.treeBase.expandAll = false;
25559         grid.queueGridRefresh();
25560       },
25561
25562
25563       /**
25564        * @ngdoc function
25565        * @name allExpanded
25566        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25567        * @description Returns true if all rows are expanded, false
25568        * if they're not.  Walks the tree to determine this.  Used
25569        * to set the expandAll state.
25570        *
25571        * If the node has no children, then return true (it's immaterial
25572        * whether it is expanded).  If the node has children, then return
25573        * false if this node is collapsed, or if any child node is not all expanded
25574        *
25575        * @param {object} tree the grid to check
25576        * @returns {boolean} whether or not the tree is all expanded
25577        */
25578       allExpanded: function( tree ){
25579         var allExpanded = true;
25580         tree.forEach( function( node ){
25581           if ( !service.allExpandedInternal( node ) ){
25582             allExpanded = false;
25583           }
25584         });
25585         return allExpanded;
25586       },
25587
25588       allExpandedInternal: function( treeNode ){
25589         if ( treeNode.children && treeNode.children.length > 0 ){
25590           if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
25591             return false;
25592           }
25593           var allExpanded = true;
25594           treeNode.children.forEach( function( node ){
25595             if ( !service.allExpandedInternal( node ) ){
25596               allExpanded = false;
25597             }
25598           });
25599           return allExpanded;
25600         } else {
25601           return true;
25602         }
25603       },
25604
25605
25606       /**
25607        * @ngdoc function
25608        * @name treeRows
25609        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25610        * @description The rowProcessor that adds the nodes to the tree, and sets the visible
25611        * state of each row based on it's parent state
25612        *
25613        * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
25614        * Performs any tree sorts itself after having built the tree
25615        *
25616        * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
25617        * entity, and setting the visible state based on the parent's state.
25618        *
25619        * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
25620        * sized.
25621        *
25622        * Aggregates if necessary along the way.
25623        *
25624        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
25625        * @returns {array} the updated rows
25626        */
25627       treeRows: function( renderableRows ) {
25628         if (renderableRows.length === 0){
25629           return renderableRows;
25630         }
25631
25632         var grid = this;
25633         var currentLevel = 0;
25634         var currentState = uiGridTreeBaseConstants.EXPANDED;
25635         var parents = [];
25636
25637         grid.treeBase.tree = service.createTree( grid, renderableRows );
25638         service.updateRowHeaderWidth( grid );
25639
25640         service.sortTree( grid );
25641         service.fixFilter( grid );
25642
25643         return service.renderTree( grid.treeBase.tree );
25644       },
25645
25646
25647       /**
25648        * @ngdoc function
25649        * @name createOrUpdateRowHeaderWidth
25650        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25651        * @description Calculates the rowHeader width.
25652        *
25653        * If rowHeader is always present, updates the width.
25654        *
25655        * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
25656        * should be one, then creates or removes it as appropriate, with the created rowHeader having the
25657        * right width.
25658        *
25659        * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
25660        *
25661        * @param {Grid} grid the grid we want to set the row header on
25662        */
25663       updateRowHeaderWidth: function( grid ){
25664         var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
25665
25666         var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
25667         if ( rowHeader && newWidth !== rowHeader.width ){
25668           rowHeader.width = newWidth;
25669           grid.queueRefresh();
25670         }
25671
25672         var newVisibility = true;
25673         if ( grid.options.showTreeRowHeader === false ){
25674           newVisibility = false;
25675         }
25676         if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
25677           newVisibility = false;
25678         }
25679         if ( rowHeader.visible !== newVisibility ) {
25680           rowHeader.visible = newVisibility;
25681           rowHeader.colDef.visible = newVisibility;
25682           grid.queueGridRefresh();
25683         }
25684       },
25685
25686
25687       /**
25688        * @ngdoc function
25689        * @name renderTree
25690        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25691        * @description Creates an array of rows based on the tree, exporting only
25692        * the visible nodes and leaves
25693        *
25694        * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
25695        * we're calling recursively
25696        * @returns {array} renderable rows
25697        */
25698       renderTree: function( nodeList ){
25699         var renderableRows = [];
25700
25701         nodeList.forEach( function ( node ){
25702           if ( node.row.visible ){
25703             renderableRows.push( node.row );
25704           }
25705           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
25706             renderableRows = renderableRows.concat( service.renderTree( node.children ) );
25707           }
25708         });
25709         return renderableRows;
25710       },
25711
25712
25713       /**
25714        * @ngdoc function
25715        * @name createTree
25716        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25717        * @description Creates a tree from the renderableRows
25718        *
25719        * @param {Grid} grid the grid
25720        * @param {array} renderableRows the rows we want to create a tree from
25721        * @returns {object} the tree we've build
25722        */
25723       createTree: function( grid, renderableRows ) {
25724         var currentLevel = -1;
25725         var parents = [];
25726         var currentState;
25727         grid.treeBase.tree = [];
25728         grid.treeBase.numberLevels = 0;
25729         var aggregations = service.getAggregations( grid );
25730
25731         var createNode = function( row ){
25732           if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
25733             row.treeLevel = row.entity.$$treeLevel;
25734           }
25735
25736           if ( row.treeLevel <= currentLevel ){
25737             // pop any levels that aren't parents of this level, formatting the aggregation at the same time
25738             while ( row.treeLevel <= currentLevel ){
25739               var lastParent = parents.pop();
25740               service.finaliseAggregations( lastParent );
25741               currentLevel--;
25742             }
25743
25744             // reset our current state based on the new parent, set to expanded if this is a level 0 node
25745             if ( parents.length > 0 ){
25746               currentState = service.setCurrentState(parents);
25747             } else {
25748               currentState = uiGridTreeBaseConstants.EXPANDED;
25749             }
25750           }
25751
25752           // aggregate if this is a leaf node
25753           if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible  ){
25754             service.aggregate( grid, row, parents );
25755           }
25756
25757           // add this node to the tree
25758           service.addOrUseNode(grid, row, parents, aggregations);
25759
25760           if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
25761             parents.push(row);
25762             currentLevel++;
25763             currentState = service.setCurrentState(parents);
25764           }
25765
25766           // update the tree number of levels, so we can set header width if we need to
25767           if ( grid.treeBase.numberLevels < row.treeLevel + 1){
25768             grid.treeBase.numberLevels = row.treeLevel + 1;
25769           }
25770         };
25771
25772         renderableRows.forEach( createNode );
25773
25774         // finalise remaining aggregations
25775         while ( parents.length > 0 ){
25776           var lastParent = parents.pop();
25777           service.finaliseAggregations( lastParent );
25778         }
25779
25780         return grid.treeBase.tree;
25781       },
25782
25783
25784       /**
25785        * @ngdoc function
25786        * @name addOrUseNode
25787        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25788        * @description Creates a tree node for this row.  If this row already has a treeNode
25789        * recorded against it, preserves the state, but otherwise overwrites the data.
25790        *
25791        * @param {grid} grid the grid we're operating on
25792        * @param {gridRow} row the row we want to set
25793        * @param {array} parents an array of the parents this row should have
25794        * @param {array} aggregationBase empty aggregation information
25795        * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
25796        * grid.treeBase.tree
25797        */
25798       addOrUseNode: function( grid, row, parents, aggregationBase ){
25799         var newAggregations = [];
25800         aggregationBase.forEach( function(aggregation){
25801           newAggregations.push(service.buildAggregationObject(aggregation.col));
25802         });
25803
25804         var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
25805         if ( row.treeNode ){
25806           newNode.state = row.treeNode.state;
25807         }
25808         if ( parents.length > 0 ){
25809           newNode.parentRow = parents[parents.length - 1];
25810         }
25811         row.treeNode = newNode;
25812
25813         if ( parents.length === 0 ){
25814           grid.treeBase.tree.push( newNode );
25815         } else {
25816           parents[parents.length - 1].treeNode.children.push( newNode );
25817         }
25818       },
25819
25820
25821       /**
25822        * @ngdoc function
25823        * @name setCurrentState
25824        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25825        * @description Looks at the parents array to determine our current state.
25826        * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
25827        * expanded.
25828        *
25829        * @param {array} parents an array of the parents this row should have
25830        * @returns {string} the state we should be setting to any nodes we see
25831        */
25832       setCurrentState: function( parents ){
25833         var currentState = uiGridTreeBaseConstants.EXPANDED;
25834         parents.forEach( function(parent){
25835           if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
25836             currentState = uiGridTreeBaseConstants.COLLAPSED;
25837           }
25838         });
25839         return currentState;
25840       },
25841
25842
25843       /**
25844        * @ngdoc function
25845        * @name sortTree
25846        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25847        * @description Performs a recursive sort on the tree nodes, sorting the
25848        * children of each node and putting them back into the children array.
25849        *
25850        * Before doing this it turns back on all the sortIgnore - things that were previously
25851        * ignored we process now.  Since we're sorting within the nodes, presumably anything
25852        * that was already sorted is how we derived the nodes, we can keep those sorts too.
25853        *
25854        * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
25855        * nodes
25856        *
25857        * @param {Grid} grid the grid to get the aggregation information from
25858        * @returns {array} the aggregation information
25859        */
25860       sortTree: function( grid ){
25861         grid.columns.forEach( function( column ) {
25862           if ( column.sort && column.sort.ignoreSort ){
25863             delete column.sort.ignoreSort;
25864           }
25865         });
25866
25867         grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
25868       },
25869
25870       sortInternal: function( grid, treeList ){
25871         var rows = treeList.map( function( node ){
25872           return node.row;
25873         });
25874
25875         rows = rowSorter.sort( grid, rows, grid.columns );
25876
25877         var treeNodes = rows.map( function( row ){
25878           return row.treeNode;
25879         });
25880
25881         treeNodes.forEach( function( node ){
25882           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
25883             node.children = service.sortInternal( grid, node.children );
25884           }
25885         });
25886
25887         return treeNodes;
25888       },
25889
25890       /**
25891        * @ngdoc function
25892        * @name fixFilter
25893        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25894        * @description After filtering has run, we need to go back through the tree
25895        * and make sure the parent rows are always visible if any of the child rows
25896        * are visible (filtering may make a child visible, but the parent may not
25897        * match the filter criteria)
25898        *
25899        * This has a risk of being computationally expensive, we do it by walking
25900        * the tree and remembering whether there are any invisible nodes on the
25901        * way down.
25902        *
25903        * @param {Grid} grid the grid to fix filters on
25904        */
25905       fixFilter: function( grid ){
25906         var parentsVisible;
25907
25908         grid.treeBase.tree.forEach( function( node ){
25909           if ( node.children && node.children.length > 0 ){
25910             parentsVisible = node.row.visible;
25911             service.fixFilterInternal( node.children, parentsVisible );
25912           }
25913         });
25914       },
25915
25916       fixFilterInternal: function( nodes, parentsVisible) {
25917         nodes.forEach( function( node ){
25918           if ( node.row.visible && !parentsVisible ){
25919             service.setParentsVisible( node );
25920             parentsVisible = true;
25921           }
25922
25923           if ( node.children && node.children.length > 0 ){
25924             if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
25925               parentsVisible = true;
25926             }
25927           }
25928         });
25929
25930         return parentsVisible;
25931       },
25932
25933       setParentsVisible: function( node ){
25934         while ( node.parentRow ){
25935           node.parentRow.visible = true;
25936           node = node.parentRow.treeNode;
25937         }
25938       },
25939
25940       /**
25941        * @ngdoc function
25942        * @name buildAggregationObject
25943        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25944        * @description Build the object which is stored on the column for holding meta-data about the aggregation.
25945        * This method should only be called with columns which have an aggregation.
25946        *
25947        * @param {Column} the column which this object relates to
25948        * @returns {object} {col: Column object, label: string, type: string (optional)}
25949        */
25950       buildAggregationObject: function( column ){
25951         var newAggregation = { col: column };
25952
25953         if ( column.treeAggregation && column.treeAggregation.type ){
25954           newAggregation.type = column.treeAggregation.type;
25955         }
25956
25957         if ( column.treeAggregation && column.treeAggregation.label ){
25958           newAggregation.label = column.treeAggregation.label;
25959         }
25960
25961         return newAggregation;
25962       },
25963
25964       /**
25965        * @ngdoc function
25966        * @name getAggregations
25967        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25968        * @description Looks through the grid columns to find those with aggregations,
25969        * and collates the aggregation information into an array, returns that array
25970        *
25971        * @param {Grid} grid the grid to get the aggregation information from
25972        * @returns {array} the aggregation information
25973        */
25974       getAggregations: function( grid ){
25975         var aggregateArray = [];
25976
25977         grid.columns.forEach( function(column){
25978           if ( typeof(column.treeAggregationFn) !== 'undefined' ){
25979             aggregateArray.push( service.buildAggregationObject(column) );
25980
25981             if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
25982               // Add aggregation object for footer
25983               column.treeFooterAggregation = service.buildAggregationObject(column);
25984               column.aggregationType = service.treeFooterAggregationType;
25985             }
25986           }
25987         });
25988         return aggregateArray;
25989       },
25990
25991
25992       /**
25993        * @ngdoc function
25994        * @name aggregate
25995        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25996        * @description Accumulate the data from this row onto the aggregations for each parent
25997        *
25998        * Iterate over the parents, then iterate over the aggregations for each of those parents,
25999        * and perform the aggregation for each individual aggregation
26000        *
26001        * @param {Grid} grid grid object
26002        * @param {GridRow} row the row we want to set grouping visibility on
26003        * @param {array} parents the parents that we would want to aggregate onto
26004        */
26005       aggregate: function( grid, row, parents ){
26006         if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
26007           row.treeNode.aggregations.forEach(function(aggregation){
26008             // Calculate aggregations for footer even if there are no grouped rows
26009             if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
26010               var fieldValue = grid.getCellValue(row, aggregation.col);
26011               var numValue = Number(fieldValue);
26012               aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26013             }
26014           });
26015         }
26016
26017         parents.forEach( function( parent, index ){
26018           if ( parent.treeNode.aggregations ){
26019             parent.treeNode.aggregations.forEach( function( aggregation ){
26020               var fieldValue = grid.getCellValue(row, aggregation.col);
26021               var numValue = Number(fieldValue);
26022               aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
26023
26024               if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
26025                 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26026               }
26027             });
26028           }
26029         });
26030       },
26031
26032
26033       // Aggregation routines - no doco needed as self evident
26034       nativeAggregations: function() {
26035         var nativeAggregations = {
26036           count: {
26037             label: i18nService.get().aggregation.count,
26038             menuTitle: i18nService.get().grouping.aggregate_count,
26039             aggregationFn: function (aggregation, fieldValue, numValue) {
26040               if (typeof(aggregation.value) === 'undefined') {
26041                 aggregation.value = 1;
26042               } else {
26043                 aggregation.value++;
26044               }
26045             }
26046           },
26047
26048           sum: {
26049             label: i18nService.get().aggregation.sum,
26050             menuTitle: i18nService.get().grouping.aggregate_sum,
26051             aggregationFn: function( aggregation, fieldValue, numValue ) {
26052               if (!isNaN(numValue)) {
26053                 if (typeof(aggregation.value) === 'undefined') {
26054                   aggregation.value = numValue;
26055                 } else {
26056                   aggregation.value += numValue;
26057                 }
26058               }
26059             }
26060           },
26061
26062           min: {
26063             label: i18nService.get().aggregation.min,
26064             menuTitle: i18nService.get().grouping.aggregate_min,
26065             aggregationFn: function( aggregation, fieldValue, numValue ) {
26066               if (typeof(aggregation.value) === 'undefined') {
26067                 aggregation.value = fieldValue;
26068               } else {
26069                 if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
26070                   aggregation.value = fieldValue;
26071                 }
26072               }
26073             }
26074           },
26075
26076           max: {
26077             label: i18nService.get().aggregation.max,
26078             menuTitle: i18nService.get().grouping.aggregate_max,
26079             aggregationFn: function( aggregation, fieldValue, numValue ){
26080               if ( typeof(aggregation.value) === 'undefined' ){
26081                 aggregation.value = fieldValue;
26082               } else {
26083                 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
26084                   aggregation.value = fieldValue;
26085                 }
26086               }
26087             }
26088           },
26089
26090           avg: {
26091             label: i18nService.get().aggregation.avg,
26092             menuTitle: i18nService.get().grouping.aggregate_avg,
26093             aggregationFn: function( aggregation, fieldValue, numValue ){
26094               if ( typeof(aggregation.count) === 'undefined' ){
26095                 aggregation.count = 1;
26096               } else {
26097                 aggregation.count++;
26098               }
26099
26100               if ( isNaN(numValue) ){
26101                 return;
26102               }
26103
26104               if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
26105                 aggregation.value = numValue;
26106                 aggregation.sum = numValue;
26107               } else {
26108                 aggregation.sum += numValue;
26109                 aggregation.value = aggregation.sum / aggregation.count;
26110               }
26111             }
26112           }
26113         };
26114         return nativeAggregations;
26115       },
26116
26117       /**
26118        * @ngdoc function
26119        * @name finaliseAggregation
26120        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26121        * @description Helper function used to finalize aggregation nodes and footer cells
26122        *
26123        * @param {gridRow} row the parent we're finalising
26124        * @param {aggregation} the aggregation object manipulated by the aggregationFn
26125        */
26126       finaliseAggregation: function(row, aggregation){
26127         if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
26128           angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
26129         }
26130
26131         if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
26132           aggregation.col.treeAggregationFinalizerFn( aggregation );
26133         }
26134         if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
26135           aggregation.col.customTreeAggregationFinalizerFn( aggregation );
26136         }
26137         if ( typeof(aggregation.rendered) === 'undefined' ){
26138           aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
26139         }
26140       },
26141
26142       /**
26143        * @ngdoc function
26144        * @name finaliseAggregations
26145        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26146        * @description Format the data from the aggregation into the rendered text
26147        * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
26148        *
26149        * As part of this we call any formatting callback routines we've been provided.
26150        *
26151        * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
26152        * set on the column - we don't overwrite any information that's already there, we append
26153        * to it so that grouping can have set the groupVal beforehand without us overwriting it.
26154        *
26155        * We need to copy the data from the row.entity first before we finalise the aggregation,
26156        * we need that information for the finaliserFn
26157        *
26158        * @param {gridRow} row the parent we're finalising
26159        */
26160       finaliseAggregations: function( row ){
26161         if ( typeof(row.treeNode.aggregations) === 'undefined' ){
26162           return;
26163         }
26164
26165         row.treeNode.aggregations.forEach( function( aggregation ) {
26166           service.finaliseAggregation(row, aggregation);
26167
26168           if ( aggregation.col.treeAggregationUpdateEntity ){
26169             var aggregationCopy = {};
26170             angular.forEach( aggregation, function( value, key ){
26171               if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
26172                 aggregationCopy[key] = value;
26173               }
26174             });
26175
26176             row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
26177           }
26178         });
26179       },
26180
26181       /**
26182        * @ngdoc function
26183        * @name treeFooterAggregationType
26184        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26185        * @description Uses the tree aggregation functions and finalizers to set the
26186        * column footer aggregations.
26187        *
26188        * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
26189        * @param {gridColumn} the column we are finalizing
26190        */
26191       treeFooterAggregationType: function( rows, column ) {
26192         service.finaliseAggregation(undefined, column.treeFooterAggregation);
26193         if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
26194           // The was apparently no aggregation performed (perhaps this is a grouped column
26195           return '';
26196         }
26197         return column.treeFooterAggregation.rendered;
26198       }
26199     };
26200
26201     return service;
26202
26203   }]);
26204
26205
26206   /**
26207    *  @ngdoc directive
26208    *  @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
26209    *  @element div
26210    *
26211    *  @description Provides the expand/collapse button on rows
26212    */
26213   module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
26214   function ($templateCache, uiGridTreeBaseService) {
26215     return {
26216       replace: true,
26217       restrict: 'E',
26218       template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
26219       scope: true,
26220       require: '^uiGrid',
26221       link: function($scope, $elm, $attrs, uiGridCtrl) {
26222         var self = uiGridCtrl.grid;
26223         $scope.treeButtonClick = function(row, evt) {
26224           uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
26225         };
26226       }
26227     };
26228   }]);
26229
26230
26231   /**
26232    *  @ngdoc directive
26233    *  @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
26234    *  @element div
26235    *
26236    *  @description Provides the expand/collapse all button
26237    */
26238   module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
26239   function ($templateCache, uiGridTreeBaseService) {
26240     return {
26241       replace: true,
26242       restrict: 'E',
26243       template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
26244       scope: false,
26245       link: function($scope, $elm, $attrs, uiGridCtrl) {
26246         var self = $scope.col.grid;
26247
26248         $scope.headerButtonClick = function(row, evt) {
26249           if ( self.treeBase.expandAll ){
26250             uiGridTreeBaseService.collapseAllRows(self, evt);
26251           } else {
26252             uiGridTreeBaseService.expandAllRows(self, evt);
26253           }
26254         };
26255       }
26256     };
26257   }]);
26258
26259
26260   /**
26261    *  @ngdoc directive
26262    *  @name ui.grid.treeBase.directive:uiGridViewport
26263    *  @element div
26264    *
26265    *  @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
26266    */
26267   module.directive('uiGridViewport',
26268   ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
26269     function ($compile, uiGridConstants, gridUtil, $parse) {
26270       return {
26271         priority: -200, // run after default  directive
26272         scope: false,
26273         compile: function ($elm, $attrs) {
26274           var rowRepeatDiv = angular.element($elm.children().children()[0]);
26275
26276           var existingNgClass = rowRepeatDiv.attr("ng-class");
26277           var newNgClass = '';
26278           if ( existingNgClass ) {
26279             newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
26280           } else {
26281             newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
26282           }
26283           rowRepeatDiv.attr("ng-class", newNgClass);
26284
26285           return {
26286             pre: function ($scope, $elm, $attrs, controllers) {
26287
26288             },
26289             post: function ($scope, $elm, $attrs, controllers) {
26290             }
26291           };
26292         }
26293       };
26294     }]);
26295 })();
26296
26297 (function () {
26298   'use strict';
26299
26300   /**
26301    * @ngdoc overview
26302    * @name ui.grid.treeView
26303    * @description
26304    *
26305    * # ui.grid.treeView
26306    *
26307    * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
26308    *
26309    * This module provides a tree view of the data that it is provided, with nodes in that
26310    * tree and leaves.  Unlike grouping, the tree is an inherent property of the data and must
26311    * be provided with your data array.
26312    *
26313    * Design information:
26314    * -------------------
26315    *
26316    * TreeView uses treeBase for the underlying functionality, and is a very thin wrapper around
26317    * that logic.  Most of the design information has now moved to treebase.
26318    * <br/>
26319    * <br/>
26320    *
26321    * <div doc-module-components="ui.grid.treeView"></div>
26322    */
26323
26324   var module = angular.module('ui.grid.treeView', ['ui.grid', 'ui.grid.treeBase']);
26325
26326   /**
26327    *  @ngdoc object
26328    *  @name ui.grid.treeView.constant:uiGridTreeViewConstants
26329    *
26330    *  @description constants available in treeView module, this includes
26331    *  all the constants declared in the treeBase module (these are manually copied
26332    *  as there isn't an easy way to include constants in another constants file, and
26333    *  we don't want to make users include treeBase)
26334    *
26335    */
26336   module.constant('uiGridTreeViewConstants', {
26337     featureName: "treeView",
26338     rowHeaderColName: 'treeBaseRowHeaderCol',
26339     EXPANDED: 'expanded',
26340     COLLAPSED: 'collapsed',
26341     aggregation: {
26342       COUNT: 'count',
26343       SUM: 'sum',
26344       MAX: 'max',
26345       MIN: 'min',
26346       AVG: 'avg'
26347     }
26348   });
26349
26350   /**
26351    *  @ngdoc service
26352    *  @name ui.grid.treeView.service:uiGridTreeViewService
26353    *
26354    *  @description Services for treeView features
26355    */
26356   module.service('uiGridTreeViewService', ['$q', 'uiGridTreeViewConstants', 'uiGridTreeBaseConstants', 'uiGridTreeBaseService', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants',
26357   function ($q, uiGridTreeViewConstants, uiGridTreeBaseConstants, uiGridTreeBaseService, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants) {
26358
26359     var service = {
26360
26361       initializeGrid: function (grid, $scope) {
26362         uiGridTreeBaseService.initializeGrid( grid, $scope );
26363
26364         /**
26365          *  @ngdoc object
26366          *  @name ui.grid.treeView.grid:treeView
26367          *
26368          *  @description Grid properties and functions added for treeView
26369          */
26370         grid.treeView = {};
26371
26372         grid.registerRowsProcessor(service.adjustSorting, 60);
26373
26374         /**
26375          *  @ngdoc object
26376          *  @name ui.grid.treeView.api:PublicApi
26377          *
26378          *  @description Public Api for treeView feature
26379          */
26380         var publicApi = {
26381           events: {
26382             treeView: {
26383             }
26384           },
26385           methods: {
26386             treeView: {
26387             }
26388           }
26389         };
26390
26391         grid.api.registerEventsFromObject(publicApi.events);
26392
26393         grid.api.registerMethodsFromObject(publicApi.methods);
26394
26395       },
26396
26397       defaultGridOptions: function (gridOptions) {
26398         //default option to true unless it was explicitly set to false
26399         /**
26400          *  @ngdoc object
26401          *  @name ui.grid.treeView.api:GridOptions
26402          *
26403          *  @description GridOptions for treeView feature, these are available to be
26404          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
26405          *
26406          *  Many tree options are set on treeBase, make sure to look at that feature in
26407          *  conjunction with these options.
26408          */
26409
26410         /**
26411          *  @ngdoc object
26412          *  @name enableTreeView
26413          *  @propertyOf  ui.grid.treeView.api:GridOptions
26414          *  @description Enable row tree view for entire grid.
26415          *  <br/>Defaults to true
26416          */
26417         gridOptions.enableTreeView = gridOptions.enableTreeView !== false;
26418
26419       },
26420
26421
26422       /**
26423        * @ngdoc function
26424        * @name adjustSorting
26425        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26426        * @description Trees cannot be sorted the same as flat lists of rows -
26427        * trees are sorted recursively within each level - so the children of each
26428        * node are sorted, but not the full set of rows.
26429        *
26430        * To achieve this, we suppress the normal sorting by setting ignoreSort on
26431        * each of the sort columns.  When the treeBase rowsProcessor runs it will then
26432        * unignore these, and will perform a recursive sort against the tree that it builds.
26433        *
26434        * @param {array} renderableRows the rows that we need to pass on through
26435        * @returns {array} renderableRows that we passed on through
26436        */
26437       adjustSorting: function( renderableRows ) {
26438         var grid = this;
26439
26440         grid.columns.forEach( function( column ){
26441           if ( column.sort ){
26442             column.sort.ignoreSort = true;
26443           }
26444         });
26445
26446         return renderableRows;
26447       }
26448
26449     };
26450
26451     return service;
26452
26453   }]);
26454
26455   /**
26456    *  @ngdoc directive
26457    *  @name ui.grid.treeView.directive:uiGridTreeView
26458    *  @element div
26459    *  @restrict A
26460    *
26461    *  @description Adds treeView features to grid
26462    *
26463    *  @example
26464    <example module="app">
26465    <file name="app.js">
26466    var app = angular.module('app', ['ui.grid', 'ui.grid.treeView']);
26467
26468    app.controller('MainCtrl', ['$scope', function ($scope) {
26469       $scope.data = [
26470         { name: 'Bob', title: 'CEO' },
26471             { name: 'Frank', title: 'Lowly Developer' }
26472       ];
26473
26474       $scope.columnDefs = [
26475         {name: 'name', enableCellEdit: true},
26476         {name: 'title', enableCellEdit: true}
26477       ];
26478
26479       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
26480     }]);
26481    </file>
26482    <file name="index.html">
26483    <div ng-controller="MainCtrl">
26484    <div ui-grid="gridOptions" ui-grid-tree-view></div>
26485    </div>
26486    </file>
26487    </example>
26488    */
26489   module.directive('uiGridTreeView', ['uiGridTreeViewConstants', 'uiGridTreeViewService', '$templateCache',
26490   function (uiGridTreeViewConstants, uiGridTreeViewService, $templateCache) {
26491     return {
26492       replace: true,
26493       priority: 0,
26494       require: '^uiGrid',
26495       scope: false,
26496       compile: function () {
26497         return {
26498           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
26499             if (uiGridCtrl.grid.options.enableTreeView !== false){
26500               uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
26501             }
26502           },
26503           post: function ($scope, $elm, $attrs, uiGridCtrl) {
26504
26505           }
26506         };
26507       }
26508     };
26509   }]);
26510 })();
26511
26512 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
26513   'use strict';
26514
26515   $templateCache.put('ui-grid/ui-grid-filter',
26516     "<div class=\"ui-grid-filter-container\" ng-repeat=\"colFilter in col.filters\" ng-class=\"{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }\"><div ng-if=\"colFilter.type !== 'select'\"><input type=\"text\" class=\"ui-grid-filter-input ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\" aria-label=\"{{colFilter.ariaLabel || aria.defaultFilterLabel}}\"><div role=\"button\" class=\"ui-grid-filter-button\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term !== null && colFilter.term !== ''\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div><div ng-if=\"colFilter.type === 'select'\"><select class=\"ui-grid-filter-select ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || aria.defaultFilterLabel}}\" aria-label=\"{{colFilter.ariaLabel || ''}}\" ng-options=\"option.value as option.label for option in colFilter.selectOptions\"><option value=\"\"></option></select><div role=\"button\" class=\"ui-grid-filter-button-select\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term != null\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div></div>"
26517   );
26518
26519
26520   $templateCache.put('ui-grid/ui-grid-footer',
26521     "<div class=\"ui-grid-footer-panel ui-grid-footer-aggregates-row\"><!-- tfooter --><div class=\"ui-grid-footer ui-grid-footer-viewport\"><div class=\"ui-grid-footer-canvas\"><div class=\"ui-grid-footer-cell-wrapper\" ng-style=\"colContainer.headerCellWrapperStyle()\"><div role=\"row\" class=\"ui-grid-footer-cell-row\"><div ui-grid-footer-cell role=\"gridcell\" ng-repeat=\"col in colContainer.renderedColumns track by col.uid\" col=\"col\" render-index=\"$index\" class=\"ui-grid-footer-cell ui-grid-clearfix\"></div></div></div></div></div></div>"
26522   );
26523
26524
26525   $templateCache.put('ui-grid/ui-grid-grid-footer',
26526     "<div class=\"ui-grid-footer-info ui-grid-grid-footer\"><span>{{'search.totalItems' | t}} {{grid.rows.length}}</span> <span ng-if=\"grid.renderContainers.body.visibleRowCache.length !== grid.rows.length\" class=\"ngLabel\">({{\"search.showingItems\" | t}} {{grid.renderContainers.body.visibleRowCache.length}})</span></div>"
26527   );
26528
26529
26530   $templateCache.put('ui-grid/ui-grid-group-panel',
26531     "<div class=\"ui-grid-group-panel\"><div ui-t=\"groupPanel.description\" class=\"description\" ng-show=\"groupings.length == 0\"></div><ul ng-show=\"groupings.length > 0\" class=\"ngGroupList\"><li class=\"ngGroupItem\" ng-repeat=\"group in configGroups\"><span class=\"ngGroupElement\"><span class=\"ngGroupName\">{{group.displayName}} <span ng-click=\"removeGroup($index)\" class=\"ngRemoveGroup\">x</span></span> <span ng-hide=\"$last\" class=\"ngGroupArrow\"></span></span></li></ul></div>"
26532   );
26533
26534
26535   $templateCache.put('ui-grid/ui-grid-header',
26536     "<div role=\"rowgroup\" class=\"ui-grid-header\"><!-- theader --><div class=\"ui-grid-top-panel\"><div class=\"ui-grid-header-viewport\"><div class=\"ui-grid-header-canvas\"><div class=\"ui-grid-header-cell-wrapper\" ng-style=\"colContainer.headerCellWrapperStyle()\"><div role=\"row\" class=\"ui-grid-header-cell-row\"><div class=\"ui-grid-header-cell ui-grid-clearfix\" ng-repeat=\"col in colContainer.renderedColumns track by col.uid\" ui-grid-header-cell col=\"col\" render-index=\"$index\"></div></div></div></div></div></div></div>"
26537   );
26538
26539
26540   $templateCache.put('ui-grid/ui-grid-menu-button',
26541     "<div class=\"ui-grid-menu-button\"><div role=\"button\" ui-grid-one-bind-id-grid=\"'grid-menu'\" class=\"ui-grid-icon-container\" ng-click=\"toggleMenu()\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-menu\" ui-grid-one-bind-aria-label=\"i18n.aria.buttonLabel\">&nbsp;</i></div><div ui-grid-menu menu-items=\"menuItems\"></div></div>"
26542   );
26543
26544
26545   $templateCache.put('ui-grid/ui-grid-no-header',
26546     "<div class=\"ui-grid-top-panel\"></div>"
26547   );
26548
26549
26550   $templateCache.put('ui-grid/ui-grid-row',
26551     "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>"
26552   );
26553
26554
26555   $templateCache.put('ui-grid/ui-grid',
26556     "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
26557     "      /* Styles for the grid */\n" +
26558     "    }\n" +
26559     "\n" +
26560     "    .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
26561     "      height: {{ grid.options.rowHeight }}px;\n" +
26562     "    }\n" +
26563     "\n" +
26564     "    .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
26565     "      border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
26566     "    }\n" +
26567     "\n" +
26568     "    {{ grid.verticalScrollbarStyles }}\n" +
26569     "    {{ grid.horizontalScrollbarStyles }}\n" +
26570     "\n" +
26571     "    /*\n" +
26572     "    .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
26573     "      padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
26574     "    }\n" +
26575     "    */\n" +
26576     "\n" +
26577     "    {{ grid.customStyles }}</style><div class=\"ui-grid-contents-wrapper\"><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></div><div ng-if=\"grid.hasLeftContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'left'\"></div><div ui-grid-render-container container-id=\"'body'\" col-container-name=\"'body'\" row-container-name=\"'body'\" bind-scroll-horizontal=\"true\" bind-scroll-vertical=\"true\" enable-horizontal-scrollbar=\"grid.options.enableHorizontalScrollbar\" enable-vertical-scrollbar=\"grid.options.enableVerticalScrollbar\"></div><div ng-if=\"grid.hasRightContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'right'\"></div><div ui-grid-grid-footer ng-if=\"grid.options.showGridFooter\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div></div>"
26578   );
26579
26580
26581   $templateCache.put('ui-grid/uiGridCell',
26582     "<div class=\"ui-grid-cell-contents\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
26583   );
26584
26585
26586   $templateCache.put('ui-grid/uiGridColumnMenu',
26587     "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
26588     "    <div class=\"inner\" ng-show=\"menuShown\">\n" +
26589     "      <ul>\n" +
26590     "        <div ng-show=\"grid.options.enableSorting\">\n" +
26591     "          <li ng-click=\"sortColumn($event, asc)\" ng-class=\"{ 'selected' : col.sort.direction == asc }\"><i class=\"ui-grid-icon-sort-alt-up\"></i> Sort Ascending</li>\n" +
26592     "          <li ng-click=\"sortColumn($event, desc)\" ng-class=\"{ 'selected' : col.sort.direction == desc }\"><i class=\"ui-grid-icon-sort-alt-down\"></i> Sort Descending</li>\n" +
26593     "          <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
26594     "        </div>\n" +
26595     "      </ul>\n" +
26596     "    </div>\n" +
26597     "  </div> --></div></div>"
26598   );
26599
26600
26601   $templateCache.put('ui-grid/uiGridFooterCell',
26602     "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationText() + ( col.getAggregationValue() CUSTOM_FILTERS ) }}</div></div>"
26603   );
26604
26605
26606   $templateCache.put('ui-grid/uiGridHeaderCell',
26607     "<div role=\"columnheader\" ng-class=\"{ 'sortable': sortable }\" ui-grid-one-bind-aria-labelledby-grid=\"col.uid + '-header-text ' + col.uid + '-sortdir-text'\" aria-sort=\"{{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending' : (!col.sort.direction ? 'none' : 'other'))}}\"><div role=\"button\" tabindex=\"0\" class=\"ui-grid-cell-contents ui-grid-header-cell-primary-focus\" col-index=\"renderIndex\" title=\"TOOLTIP\"><span class=\"ui-grid-header-cell-label\" ui-grid-one-bind-id-grid=\"col.uid + '-header-text'\">{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-one-bind-id-grid=\"col.uid + '-sortdir-text'\" ui-grid-visible=\"col.sort.direction\" aria-label=\"{{getSortDirectionAriaLabel()}}\"><i ng-class=\"{ 'ui-grid-icon-up-dir': col.sort.direction == asc, 'ui-grid-icon-down-dir': col.sort.direction == desc, 'ui-grid-icon-blank': !col.sort.direction }\" title=\"{{col.sort.priority ? i18n.headerCell.priority + ' ' + col.sort.priority : null}}\" aria-hidden=\"true\"></i> <sub class=\"ui-grid-sort-priority-number\">{{col.sort.priority}}</sub></span></div><div role=\"button\" tabindex=\"0\" ui-grid-one-bind-id-grid=\"col.uid + '-menu-button'\" class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader  && col.colDef.enableColumnMenu !== false\" ng-click=\"toggleMenu($event)\" ng-class=\"{'ui-grid-column-menu-button-last-col': isLastCol}\" ui-grid-one-bind-aria-label=\"i18n.headerCell.aria.columnMenuButtonLabel\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-angle-down\" aria-hidden=\"true\">&nbsp;</i></div><div ui-grid-filter></div></div>"
26608   );
26609
26610
26611   $templateCache.put('ui-grid/uiGridMenu',
26612     "<div class=\"ui-grid-menu\" ng-if=\"shown\"><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\"><button type=\"button\" ng-focus=\"focus=true\" ng-blur=\"focus=false\" class=\"ui-grid-menu-close-button\" ng-class=\"{'ui-grid-sr-only': (!focus)}\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"i18n.close\"></i></button><ul role=\"menu\" class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" role=\"menuitem\" ui-grid-menu-item ui-grid-one-bind-id=\"'menuitem-'+$index\" action=\"item.action\" name=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\" leave-open=\"item.leaveOpen\" screen-reader-only=\"item.screenReaderOnly\"></li></ul></div></div></div>"
26613   );
26614
26615
26616   $templateCache.put('ui-grid/uiGridMenuItem',
26617     "<button type=\"button\" class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active': active(), 'ui-grid-sr-only': (!focus && screenReaderOnly) }\" aria-pressed=\"{{active()}}\" tabindex=\"0\" ng-focus=\"focus=true\" ng-blur=\"focus=false\"><i ng-class=\"icon\" aria-hidden=\"true\">&nbsp;</i> {{ name }}</button>"
26618   );
26619
26620
26621   $templateCache.put('ui-grid/uiGridRenderContainer',
26622     "<div role=\"grid\" ui-grid-one-bind-id-grid=\"'grid-container'\" class=\"ui-grid-render-container\" ng-style=\"{ 'margin-left': colContainer.getMargin('left') + 'px', 'margin-right': colContainer.getMargin('right') + 'px' }\"><!-- All of these dom elements are replaced in place --><div ui-grid-header></div><div ui-grid-viewport></div><div ng-if=\"colContainer.needsHScrollbarPlaceholder()\" class=\"ui-grid-scrollbar-placeholder\" ng-style=\"{height:colContainer.grid.scrollbarHeight + 'px'}\"></div><ui-grid-footer ng-if=\"grid.options.showColumnFooter\"></ui-grid-footer></div>"
26623   );
26624
26625
26626   $templateCache.put('ui-grid/uiGridViewport',
26627     "<div role=\"rowgroup\" class=\"ui-grid-viewport\" ng-style=\"colContainer.getViewportStyle()\"><!-- tbody --><div class=\"ui-grid-canvas\"><div ng-repeat=\"(rowRenderIndex, row) in rowContainer.renderedRows track by $index\" class=\"ui-grid-row\" ng-style=\"Viewport.rowStyle(rowRenderIndex)\"><div role=\"row\" ui-grid-row=\"row\" row-render-index=\"rowRenderIndex\"></div></div></div></div>"
26628   );
26629
26630
26631   $templateCache.put('ui-grid/cellEditor',
26632     "<div><form name=\"inputForm\"><input type=\"INPUT_TYPE\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
26633   );
26634
26635
26636   $templateCache.put('ui-grid/dropdownEditor',
26637     "<div><form name=\"inputForm\"><select ng-class=\"'colt' + col.uid\" ui-grid-edit-dropdown ng-model=\"MODEL_COL_FIELD\" ng-options=\"field[editDropdownIdLabel] as field[editDropdownValueLabel] CUSTOM_FILTERS for field in editDropdownOptionsArray\"></select></form></div>"
26638   );
26639
26640
26641   $templateCache.put('ui-grid/fileChooserEditor',
26642     "<div><form name=\"inputForm\"><input ng-class=\"'colt' + col.uid\" ui-grid-edit-file-chooser type=\"file\" id=\"files\" name=\"files[]\" ng-model=\"MODEL_COL_FIELD\"></form></div>"
26643   );
26644
26645
26646   $templateCache.put('ui-grid/expandableRow',
26647     "<div ui-grid-expandable-row ng-if=\"expandableRow.shouldRenderExpand()\" class=\"expandableRow\" style=\"float:left; margin-top: 1px; margin-bottom: 1px\" ng-style=\"{width: (grid.renderContainers.body.getCanvasWidth()) + 'px', height: row.expandedRowHeight + 'px'}\"></div>"
26648   );
26649
26650
26651   $templateCache.put('ui-grid/expandableRowHeader',
26652     "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity)\"></i></div></div>"
26653   );
26654
26655
26656   $templateCache.put('ui-grid/expandableScrollFiller',
26657     "<div ng-if=\"expandableRow.shouldRenderFiller()\" ng-class=\"{scrollFiller:true, scrollFillerClass:(colContainer.name === 'body')}\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px', height: row.expandedRowHeight + 2 + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{'margin-top': ( row.expandedRowHeight/2 - 5) + 'px', 'margin-left' : ((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5) + 'px'}\"></i></div>"
26658   );
26659
26660
26661   $templateCache.put('ui-grid/expandableTopRowHeader',
26662     "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !grid.expandable.expandedAll, 'ui-grid-icon-minus-squared' : grid.expandable.expandedAll }\" ng-click=\"grid.api.expandable.toggleAllRows()\"></i></div></div>"
26663   );
26664
26665
26666   $templateCache.put('ui-grid/csvLink',
26667     "<span class=\"ui-grid-exporter-csv-link-span\"><a href=\"data:text/csv;charset=UTF-8,CSV_CONTENT\" download=\"FILE_NAME\">LINK_LABEL</a></span>"
26668   );
26669
26670
26671   $templateCache.put('ui-grid/importerMenuItem',
26672     "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
26673   );
26674
26675
26676   $templateCache.put('ui-grid/importerMenuItemContainer',
26677     "<div ui-grid-importer-menu-item></div>"
26678   );
26679
26680
26681   $templateCache.put('ui-grid/pagination',
26682     "<div role=\"contentinfo\" class=\"ui-grid-pager-panel\" ui-grid-pager ng-show=\"grid.options.enablePaginationControls\"><div role=\"navigation\" class=\"ui-grid-pager-container\"><div role=\"menubar\" class=\"ui-grid-pager-control\"><button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-first\" ui-grid-one-bind-title=\"aria.pageToFirst\" ui-grid-one-bind-aria-label=\"aria.pageToFirst\" ng-click=\"pageFirstPageClick()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle\"><div class=\"first-bar\"></div></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-previous\" ui-grid-one-bind-title=\"aria.pageBack\" ui-grid-one-bind-aria-label=\"aria.pageBack\" ng-click=\"pagePreviousPageClick()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle prev-triangle\"></div></button> <input type=\"number\" ui-grid-one-bind-title=\"aria.pageSelected\" ui-grid-one-bind-aria-label=\"aria.pageSelected\" class=\"ui-grid-pager-control-input\" ng-model=\"grid.options.paginationCurrentPage\" min=\"1\" max=\"{{ paginationApi.getTotalPages() }}\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"paginationApi.getTotalPages() > 0\"><abbr ui-grid-one-bind-title=\"paginationOf\">/</abbr> {{ paginationApi.getTotalPages() }}</span> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-next\" ui-grid-one-bind-title=\"aria.pageForward\" ui-grid-one-bind-aria-label=\"aria.pageForward\" ng-click=\"pageNextPageClick()\" ng-disabled=\"cantPageForward()\"><div class=\"last-triangle next-triangle\"></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-last\" ui-grid-one-bind-title=\"aria.pageToLast\" ui-grid-one-bind-aria-label=\"aria.pageToLast\" ng-click=\"pageLastPageClick()\" ng-disabled=\"cantPageToLast()\"><div class=\"last-triangle\"><div class=\"last-bar\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\" ng-if=\"grid.options.paginationPageSizes.length > 1\"><select ui-grid-one-bind-aria-labelledby-grid=\"'items-per-page-label'\" ng-model=\"grid.options.paginationPageSize\" ng-options=\"o as o for o in grid.options.paginationPageSizes\"></select><span ui-grid-one-bind-id-grid=\"'items-per-page-label'\" class=\"ui-grid-pager-row-count-label\">&nbsp;{{sizesLabel}}</span></div><span ng-if=\"grid.options.paginationPageSizes.length <= 1\" class=\"ui-grid-pager-row-count-label\">{{grid.options.paginationPageSize}}&nbsp;{{sizesLabel}}</span></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{showingLow}} <abbr ui-grid-one-bind-title=\"paginationThrough\">-</abbr> {{showingHigh}} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
26683   );
26684
26685
26686   $templateCache.put('ui-grid/columnResizer',
26687     "<div ui-grid-column-resizer ng-if=\"grid.options.enableColumnResizing\" class=\"ui-grid-column-resizer\" col=\"col\" position=\"right\" render-index=\"renderIndex\" unselectable=\"on\"></div>"
26688   );
26689
26690
26691   $templateCache.put('ui-grid/gridFooterSelectedItems',
26692     "<span ng-if=\"grid.selection.selectedCount !== 0 && grid.options.enableFooterTotalSelected\">({{\"search.selectedItems\" | t}} {{grid.selection.selectedCount}})</span>"
26693   );
26694
26695
26696   $templateCache.put('ui-grid/selectionHeaderCell',
26697     "<div><!-- <div class=\"ui-grid-vertical-bar\">&nbsp;</div> --><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-selection-select-all-buttons ng-if=\"grid.options.enableSelectAll\"></ui-grid-selection-select-all-buttons></div></div>"
26698   );
26699
26700
26701   $templateCache.put('ui-grid/selectionRowHeader',
26702     "<div class=\"ui-grid-disable-selection\"><div class=\"ui-grid-cell-contents\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div></div>"
26703   );
26704
26705
26706   $templateCache.put('ui-grid/selectionRowHeaderButtons',
26707     "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ng-click=\"selectButtonClick(row, $event)\">&nbsp;</div>"
26708   );
26709
26710
26711   $templateCache.put('ui-grid/selectionSelectAllButtons',
26712     "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-click=\"headerButtonClick($event)\"></div>"
26713   );
26714
26715
26716   $templateCache.put('ui-grid/treeBaseExpandAllButtons',
26717     "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-icon-minus-squared': grid.treeBase.numberLevels > 0 && grid.treeBase.expandAll, 'ui-grid-icon-plus-squared': grid.treeBase.numberLevels > 0 && !grid.treeBase.expandAll}\" ng-click=\"headerButtonClick($event)\"></div>"
26718   );
26719
26720
26721   $templateCache.put('ui-grid/treeBaseHeaderCell',
26722     "<div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-tree-base-expand-all-buttons></ui-grid-tree-base-expand-all-buttons></div></div>"
26723   );
26724
26725
26726   $templateCache.put('ui-grid/treeBaseRowHeader',
26727     "<div class=\"ui-grid-cell-contents\"><ui-grid-tree-base-row-header-buttons></ui-grid-tree-base-row-header-buttons></div>"
26728   );
26729
26730
26731   $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
26732     "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-tree-base-header': row.treeLevel > -1 }\" ng-click=\"treeButtonClick(row, $event)\"><i ng-class=\"{'ui-grid-icon-minus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'expanded', 'ui-grid-icon-plus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'collapsed'}\" ng-style=\"{'padding-left': grid.options.treeIndent * row.treeLevel + 'px'}\"></i> &nbsp;</div>"
26733   );
26734
26735 }]);