Built motion from commit 99feb03.|0.0.140
[motion.git] / public / bower_components / angular-ui-grid / ui-grid.js
1 /*!
2  * ui-grid - v3.1.0 - 2016-01-19
3  * Copyright (c) 2016 ; 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       uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
577
578       $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
579
580       // Set the menu items for use with the column menu. The user can later add additional items via the watch
581       $scope.menuItems = $scope.defaultMenuItems;
582       uiGridColumnMenuService.setColMenuItemWatch( $scope );
583
584
585       /**
586        * @ngdoc method
587        * @methodOf ui.grid.directive:uiGridColumnMenu
588        * @name showMenu
589        * @description Shows the column menu.  If the menu is already displayed it
590        * calls the menu to ask it to hide (it will animate), then it repositions the menu
591        * to the right place whilst hidden (it will make an assumption on menu width),
592        * then it asks the menu to show (it will animate), then it repositions the menu again
593        * once we can calculate it's size.
594        * @param {GridCol} column the column we want to position below
595        * @param {element} $columnElement the column element we want to position below
596        */
597       $scope.showMenu = function(column, $columnElement, event) {
598         // Swap to this column
599         $scope.col = column;
600
601         // Get the position information for the column element
602         var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
603
604         if ($scope.menuShown) {
605           // we want to hide, then reposition, then show, but we want to wait for animations
606           // we set a variable, and then rely on the menu-hidden event to call the reposition and show
607           $scope.colElement = $columnElement;
608           $scope.colElementPosition = colElementPosition;
609           $scope.hideThenShow = true;
610
611           $scope.$broadcast('hide-menu', { originalEvent: event });
612         } else {
613           $scope.menuShown = true;
614           uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
615
616           $scope.colElement = $columnElement;
617           $scope.colElementPosition = colElementPosition;
618           $scope.$broadcast('show-menu', { originalEvent: event });
619         }
620       };
621
622
623       /**
624        * @ngdoc method
625        * @methodOf ui.grid.directive:uiGridColumnMenu
626        * @name hideMenu
627        * @description Hides the column menu.
628        * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
629        * from the menu itself - in which case don't broadcast again as we'll get
630        * an infinite loop
631        */
632       $scope.hideMenu = function( broadcastTrigger ) {
633         $scope.menuShown = false;
634         if ( !broadcastTrigger ){
635           $scope.$broadcast('hide-menu');
636         }
637       };
638
639
640       $scope.$on('menu-hidden', function() {
641         if ( $scope.hideThenShow ){
642           delete $scope.hideThenShow;
643
644           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
645           $scope.$broadcast('show-menu');
646
647           $scope.menuShown = true;
648         } else {
649           $scope.hideMenu( true );
650
651           if ($scope.col) {
652             //Focus on the menu button
653             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
654           }
655         }
656       });
657
658       $scope.$on('menu-shown', function() {
659         $timeout( function() {
660           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
661           delete $scope.colElementPosition;
662           delete $scope.columnElement;
663         }, 200);
664       });
665
666
667       /* Column methods */
668       $scope.sortColumn = function (event, dir) {
669         event.stopPropagation();
670
671         $scope.grid.sortColumn($scope.col, dir, true)
672           .then(function () {
673             $scope.grid.refresh();
674             $scope.hideMenu();
675           });
676       };
677
678       $scope.unsortColumn = function () {
679         $scope.col.unsort();
680
681         $scope.grid.refresh();
682         $scope.hideMenu();
683       };
684
685       //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
686       var setFocusOnHideColumn = function(){
687         $timeout(function(){
688           // Get the UID of the first
689           var focusToGridMenu = function(){
690             return gridUtil.focus.byId('grid-menu', $scope.grid);
691           };
692
693           var thisIndex;
694           $scope.grid.columns.some(function(element, index){
695             if (angular.equals(element, $scope.col)) {
696               thisIndex = index;
697               return true;
698             }
699           });
700
701           var previousVisibleCol;
702           // Try and find the next lower or nearest column to focus on
703           $scope.grid.columns.some(function(element, index){
704             if (!element.visible){
705               return false;
706             } // This columns index is below the current column index
707             else if ( index < thisIndex){
708               previousVisibleCol = element;
709             } // This elements index is above this column index and we haven't found one that is lower
710             else if ( index > thisIndex && !previousVisibleCol) {
711               // This is the next best thing
712               previousVisibleCol = element;
713               // We've found one so use it.
714               return true;
715             } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
716             else if (index > thisIndex && previousVisibleCol) {
717               // We are done.
718               return true;
719             }
720           });
721           // If found then focus on it
722           if (previousVisibleCol){
723             var colClass = previousVisibleCol.getColClass();
724             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
725               if (reason !== 'canceled'){ // If this is canceled then don't perform the action
726                 //The fallback action is to focus on the grid menu
727                 return focusToGridMenu();
728               }
729             });
730           } else {
731             // Fallback action to focus on the grid menu
732             focusToGridMenu();
733           }
734         });
735       };
736
737       $scope.hideColumn = function () {
738         $scope.col.colDef.visible = false;
739         $scope.col.visible = false;
740
741         $scope.grid.queueGridRefresh();
742         $scope.hideMenu();
743         $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
744         $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
745
746         // We are hiding so the default action of focusing on the button that opened this menu will fail.
747         setFocusOnHideColumn();
748       };
749     },
750
751
752
753     controller: ['$scope', function ($scope) {
754       var self = this;
755
756       $scope.$watch('menuItems', function (n, o) {
757         self.menuItems = n;
758       });
759     }]
760   };
761
762   return uiGridColumnMenu;
763
764 }]);
765
766 })();
767
768 (function(){
769   'use strict';
770
771   angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
772
773     return {
774       compile: function() {
775         return {
776           pre: function ($scope, $elm, $attrs, controllers) {
777             $scope.col.updateFilters = function( filterable ){
778               $elm.children().remove();
779               if ( filterable ){
780                 var template = $scope.col.filterHeaderTemplate;
781
782                 $elm.append($compile(template)($scope));
783               }
784             };
785
786             $scope.$on( '$destroy', function() {
787               delete $scope.col.updateFilters;
788             });
789           },
790           post: function ($scope, $elm, $attrs, controllers){
791             $scope.aria = i18nService.getSafeText('headerCell.aria');
792             $scope.removeFilter = function(colFilter, index){
793               colFilter.term = null;
794               //Set the focus to the filter input after the action disables the button
795               gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
796             };
797           }
798         };
799       }
800     };
801   }]);
802 })();
803
804 (function () {
805   'use strict';
806
807   angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
808   function ($timeout, gridUtil, uiGridConstants, $compile) {
809     var uiGridFooterCell = {
810       priority: 0,
811       scope: {
812         col: '=',
813         row: '=',
814         renderIndex: '='
815       },
816       replace: true,
817       require: '^uiGrid',
818       compile: function compile(tElement, tAttrs, transclude) {
819         return {
820           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
821             var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
822             $elm.append(cellFooter);
823           },
824           post: function ($scope, $elm, $attrs, uiGridCtrl) {
825             //$elm.addClass($scope.col.getColClass(false));
826             $scope.grid = uiGridCtrl.grid;
827
828             var initColClass = $scope.col.getColClass(false);
829             $elm.addClass(initColClass);
830
831             // apply any footerCellClass
832             var classAdded;
833             var updateClass = function( grid ){
834               var contents = $elm;
835               if ( classAdded ){
836                 contents.removeClass( classAdded );
837                 classAdded = null;
838               }
839   
840               if (angular.isFunction($scope.col.footerCellClass)) {
841                 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
842               }
843               else {
844                 classAdded = $scope.col.footerCellClass;
845               }
846               contents.addClass(classAdded);
847             };
848   
849             if ($scope.col.footerCellClass) {
850               updateClass();
851             }
852
853             $scope.col.updateAggregationValue();
854
855             // Watch for column changes so we can alter the col cell class properly
856 /* shouldn't be needed any more, given track by col.name
857             $scope.$watch('col', function (n, o) {
858               if (n !== o) {
859                 // See if the column's internal class has changed
860                 var newColClass = $scope.col.getColClass(false);
861                 if (newColClass !== initColClass) {
862                   $elm.removeClass(initColClass);
863                   $elm.addClass(newColClass);
864                   initColClass = newColClass;
865                 }
866               }
867             });
868 */
869
870
871             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
872             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
873             // listen for visible rows change and update aggregation values
874             $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
875             $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
876             $scope.$on( '$destroy', dataChangeDereg );
877           }
878         };
879       }
880     };
881
882     return uiGridFooterCell;
883   }]);
884
885 })();
886
887 (function () {
888   'use strict';
889
890   angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
891
892     return {
893       restrict: 'EA',
894       replace: true,
895       // priority: 1000,
896       require: ['^uiGrid', '^uiGridRenderContainer'],
897       scope: true,
898       compile: function ($elm, $attrs) {
899         return {
900           pre: function ($scope, $elm, $attrs, controllers) {
901             var uiGridCtrl = controllers[0];
902             var containerCtrl = controllers[1];
903
904             $scope.grid = uiGridCtrl.grid;
905             $scope.colContainer = containerCtrl.colContainer;
906
907             containerCtrl.footer = $elm;
908
909             var footerTemplate = $scope.grid.options.footerTemplate;
910             gridUtil.getTemplate(footerTemplate)
911               .then(function (contents) {
912                 var template = angular.element(contents);
913
914                 var newElm = $compile(template)($scope);
915                 $elm.append(newElm);
916
917                 if (containerCtrl) {
918                   // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
919                   var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
920
921                   if (footerViewport) {
922                     containerCtrl.footerViewport = footerViewport;
923                   }
924                 }
925               });
926           },
927
928           post: function ($scope, $elm, $attrs, controllers) {
929             var uiGridCtrl = controllers[0];
930             var containerCtrl = controllers[1];
931
932             // gridUtil.logDebug('ui-grid-footer link');
933
934             var grid = uiGridCtrl.grid;
935
936             // Don't animate footer cells
937             gridUtil.disableAnimations($elm);
938
939             containerCtrl.footer = $elm;
940
941             var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
942             if (footerViewport) {
943               containerCtrl.footerViewport = footerViewport;
944             }
945           }
946         };
947       }
948     };
949   }]);
950
951 })();
952 (function () {
953   'use strict';
954
955   angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
956
957     return {
958       restrict: 'EA',
959       replace: true,
960       // priority: 1000,
961       require: '^uiGrid',
962       scope: true,
963       compile: function ($elm, $attrs) {
964         return {
965           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
966
967             $scope.grid = uiGridCtrl.grid;
968
969
970
971             var footerTemplate = $scope.grid.options.gridFooterTemplate;
972             gridUtil.getTemplate(footerTemplate)
973               .then(function (contents) {
974                 var template = angular.element(contents);
975
976                 var newElm = $compile(template)($scope);
977                 $elm.append(newElm);
978               });
979           },
980
981           post: function ($scope, $elm, $attrs, controllers) {
982
983           }
984         };
985       }
986     };
987   }]);
988
989 })();
990 (function(){
991   'use strict';
992
993   angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
994     var defaultTemplate = 'ui-grid/ui-grid-group-panel';
995
996     return {
997       restrict: 'EA',
998       replace: true,
999       require: '?^uiGrid',
1000       scope: false,
1001       compile: function($elm, $attrs) {
1002         return {
1003           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1004             var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
1005
1006              gridUtil.getTemplate(groupPanelTemplate)
1007               .then(function (contents) {
1008                 var template = angular.element(contents);
1009                 
1010                 var newElm = $compile(template)($scope);
1011                 $elm.append(newElm);
1012               });
1013           },
1014
1015           post: function ($scope, $elm, $attrs, uiGridCtrl) {
1016             $elm.bind('$destroy', function() {
1017               // scrollUnbinder();
1018             });
1019           }
1020         };
1021       }
1022     };
1023   }]);
1024
1025 })();
1026 (function(){
1027   'use strict';
1028
1029   angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
1030   function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
1031     // Do stuff after mouse has been down this many ms on the header cell
1032     var mousedownTimeout = 500;
1033     var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
1034
1035     var uiGridHeaderCell = {
1036       priority: 0,
1037       scope: {
1038         col: '=',
1039         row: '=',
1040         renderIndex: '='
1041       },
1042       require: ['^uiGrid', '^uiGridRenderContainer'],
1043       replace: true,
1044       compile: function() {
1045         return {
1046           pre: function ($scope, $elm, $attrs) {
1047             var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1048             $elm.append(cellHeader);
1049           },
1050
1051           post: function ($scope, $elm, $attrs, controllers) {
1052             var uiGridCtrl = controllers[0];
1053             var renderContainerCtrl = controllers[1];
1054
1055             $scope.i18n = {
1056               headerCell: i18nService.getSafeText('headerCell'),
1057               sort: i18nService.getSafeText('sort')
1058             };
1059             $scope.isSortPriorityVisible = function() {
1060               //show sort priority if column is sorted and there is at least one other sorted column
1061               return $scope.col.sort.priority && $scope.grid.columns.some(function(element, index){
1062                   return element.sort.priority && element !== $scope.col;
1063                 });
1064             };
1065             $scope.getSortDirectionAriaLabel = function(){
1066               var col = $scope.col;
1067               //Trying to recreate this sort of thing but it was getting messy having it in the template.
1068               //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1069               var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1070               var label = sortDirectionText;
1071
1072               if ($scope.isSortPriorityVisible()) {
1073                 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1074               }
1075               return label;
1076             };
1077
1078             $scope.grid = uiGridCtrl.grid;
1079
1080             $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
1081
1082             var initColClass = $scope.col.getColClass(false);
1083             $elm.addClass(initColClass);
1084
1085             // Hide the menu by default
1086             $scope.menuShown = false;
1087
1088             // Put asc and desc sort directions in scope
1089             $scope.asc = uiGridConstants.ASC;
1090             $scope.desc = uiGridConstants.DESC;
1091
1092             // Store a reference to menu element
1093             var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1094
1095             var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
1096
1097
1098             // apply any headerCellClass
1099             var classAdded;
1100             var previousMouseX;
1101
1102             // filter watchers
1103             var filterDeregisters = [];
1104
1105
1106             /*
1107              * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
1108              * Once we have a down event, we need to work out whether we have a click, a drag, or a
1109              * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
1110              * we ignore it.  A hold would open the menu.
1111              *
1112              * So, on down event, we put in place handlers for move and up events, and a timer.  If the
1113              * timer expires before we see a move or up, then we have a long press and hence a column menu open.
1114              * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
1115              * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
1116              * will handle it.
1117              *
1118              * To deal with touch enabled devices that also have mice, we only create our handlers when
1119              * we get the down event, and we create the corresponding handlers - if we're touchstart then
1120              * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
1121              *
1122              * We also suppress the click action whilst this is happening - otherwise after the mouseup there
1123              * will be a click event and that can cause the column menu to close
1124              *
1125              */
1126
1127             $scope.downFn = function( event ){
1128               event.stopPropagation();
1129
1130               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
1131                 event = event.originalEvent;
1132               }
1133
1134               // Don't show the menu if it's not the left button
1135               if (event.button && event.button !== 0) {
1136                 return;
1137               }
1138               previousMouseX = event.pageX;
1139
1140               $scope.mousedownStartTime = (new Date()).getTime();
1141               $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
1142
1143               $scope.mousedownTimeout.then(function () {
1144                 if ( $scope.colMenu ) {
1145                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
1146                 }
1147               });
1148
1149               uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
1150
1151               $scope.offAllEvents();
1152               if ( event.type === 'touchstart'){
1153                 $document.on('touchend', $scope.upFn);
1154                 $document.on('touchmove', $scope.moveFn);
1155               } else if ( event.type === 'mousedown' ){
1156                 $document.on('mouseup', $scope.upFn);
1157                 $document.on('mousemove', $scope.moveFn);
1158               }
1159             };
1160
1161             $scope.upFn = function( event ){
1162               event.stopPropagation();
1163               $timeout.cancel($scope.mousedownTimeout);
1164               $scope.offAllEvents();
1165               $scope.onDownEvents(event.type);
1166
1167               var mousedownEndTime = (new Date()).getTime();
1168               var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
1169
1170               if (mousedownTime > mousedownTimeout) {
1171                 // long click, handled above with mousedown
1172               }
1173               else {
1174                 // short click
1175                 if ( $scope.sortable ){
1176                   $scope.handleClick(event);
1177                 }
1178               }
1179             };
1180
1181             $scope.moveFn = function( event ){
1182               // Chrome is known to fire some bogus move events.
1183               var changeValue = event.pageX - previousMouseX;
1184               if ( changeValue === 0 ){ return; }
1185
1186               // we're a move, so do nothing and leave for column move (if enabled) to take over
1187               $timeout.cancel($scope.mousedownTimeout);
1188               $scope.offAllEvents();
1189               $scope.onDownEvents(event.type);
1190             };
1191
1192             $scope.clickFn = function ( event ){
1193               event.stopPropagation();
1194               $contentsElm.off('click', $scope.clickFn);
1195             };
1196
1197
1198             $scope.offAllEvents = function(){
1199               $contentsElm.off('touchstart', $scope.downFn);
1200               $contentsElm.off('mousedown', $scope.downFn);
1201
1202               $document.off('touchend', $scope.upFn);
1203               $document.off('mouseup', $scope.upFn);
1204
1205               $document.off('touchmove', $scope.moveFn);
1206               $document.off('mousemove', $scope.moveFn);
1207
1208               $contentsElm.off('click', $scope.clickFn);
1209             };
1210
1211             $scope.onDownEvents = function( type ){
1212               // If there is a previous event, then wait a while before
1213               // activating the other mode - i.e. if the last event was a touch event then
1214               // don't enable mouse events for a wee while (500ms or so)
1215               // Avoids problems with devices that emulate mouse events when you have touch events
1216
1217               switch (type){
1218                 case 'touchmove':
1219                 case 'touchend':
1220                   $contentsElm.on('click', $scope.clickFn);
1221                   $contentsElm.on('touchstart', $scope.downFn);
1222                   $timeout(function(){
1223                     $contentsElm.on('mousedown', $scope.downFn);
1224                   }, changeModeTimeout);
1225                   break;
1226                 case 'mousemove':
1227                 case 'mouseup':
1228                   $contentsElm.on('click', $scope.clickFn);
1229                   $contentsElm.on('mousedown', $scope.downFn);
1230                   $timeout(function(){
1231                     $contentsElm.on('touchstart', $scope.downFn);
1232                   }, changeModeTimeout);
1233                   break;
1234                 default:
1235                   $contentsElm.on('click', $scope.clickFn);
1236                   $contentsElm.on('touchstart', $scope.downFn);
1237                   $contentsElm.on('mousedown', $scope.downFn);
1238               }
1239             };
1240
1241
1242             var updateHeaderOptions = function( grid ){
1243               var contents = $elm;
1244               if ( classAdded ){
1245                 contents.removeClass( classAdded );
1246                 classAdded = null;
1247               }
1248
1249               if (angular.isFunction($scope.col.headerCellClass)) {
1250                 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
1251               }
1252               else {
1253                 classAdded = $scope.col.headerCellClass;
1254               }
1255               contents.addClass(classAdded);
1256
1257               $timeout(function (){
1258                 var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1259                 $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1260               });
1261
1262               // Figure out whether this column is sortable or not
1263               if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
1264                 $scope.sortable = true;
1265               }
1266               else {
1267                 $scope.sortable = false;
1268               }
1269
1270               // Figure out whether this column is filterable or not
1271               var oldFilterable = $scope.filterable;
1272               if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1273                 $scope.filterable = true;
1274               }
1275               else {
1276                 $scope.filterable = false;
1277               }
1278
1279               if ( oldFilterable !== $scope.filterable){
1280                 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1281                   $scope.col.updateFilters($scope.filterable);
1282                 }
1283
1284                 // if column is filterable add a filter watcher
1285                 if ($scope.filterable) {
1286                   $scope.col.filters.forEach( function(filter, i) {
1287                     filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1288                       if (n !== o) {
1289                         uiGridCtrl.grid.api.core.raise.filterChanged();
1290                         uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1291                         uiGridCtrl.grid.queueGridRefresh();
1292                       }
1293                     }));
1294                   });
1295                   $scope.$on('$destroy', function() {
1296                     filterDeregisters.forEach( function(filterDeregister) {
1297                       filterDeregister();
1298                     });
1299                   });
1300                 } else {
1301                   filterDeregisters.forEach( function(filterDeregister) {
1302                     filterDeregister();
1303                   });
1304                 }
1305
1306               }
1307
1308               // figure out whether we support column menus
1309               if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1310                       $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1311                 $scope.colMenu = true;
1312               } else {
1313                 $scope.colMenu = false;
1314               }
1315
1316               /**
1317               * @ngdoc property
1318               * @name enableColumnMenu
1319               * @propertyOf ui.grid.class:GridOptions.columnDef
1320               * @description if column menus are enabled, controls the column menus for this specific
1321               * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1322               * using this option. If gridOptions.enableColumnMenus === false then you get no column
1323               * menus irrespective of the value of this option ).  Defaults to true.
1324               *
1325               */
1326               /**
1327               * @ngdoc property
1328               * @name enableColumnMenus
1329               * @propertyOf ui.grid.class:GridOptions.columnDef
1330               * @description Override for column menus everywhere - if set to false then you get no
1331               * column menus.  Defaults to true.
1332               *
1333               */
1334
1335               $scope.offAllEvents();
1336
1337               if ($scope.sortable || $scope.colMenu) {
1338                 $scope.onDownEvents();
1339
1340                 $scope.$on('$destroy', function () {
1341                   $scope.offAllEvents();
1342                 });
1343               }
1344             };
1345
1346 /*
1347             $scope.$watch('col', function (n, o) {
1348               if (n !== o) {
1349                 // See if the column's internal class has changed
1350                 var newColClass = $scope.col.getColClass(false);
1351                 if (newColClass !== initColClass) {
1352                   $elm.removeClass(initColClass);
1353                   $elm.addClass(newColClass);
1354                   initColClass = newColClass;
1355                 }
1356               }
1357             });
1358 */
1359             updateHeaderOptions();
1360
1361             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
1362             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
1363
1364             $scope.$on( '$destroy', dataChangeDereg );
1365
1366             $scope.handleClick = function(event) {
1367               // If the shift key is being held down, add this column to the sort
1368               var add = false;
1369               if (event.shiftKey) {
1370                 add = true;
1371               }
1372
1373               // Sort this column then rebuild the grid's rows
1374               uiGridCtrl.grid.sortColumn($scope.col, add)
1375                 .then(function () {
1376                   if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
1377                   uiGridCtrl.grid.refresh();
1378                 });
1379             };
1380
1381
1382             $scope.toggleMenu = function(event) {
1383               event.stopPropagation();
1384
1385               // If the menu is already showing...
1386               if (uiGridCtrl.columnMenuScope.menuShown) {
1387                 // ... and we're the column the menu is on...
1388                 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1389                   // ... hide it
1390                   uiGridCtrl.columnMenuScope.hideMenu();
1391                 }
1392                 // ... and we're NOT the column the menu is on
1393                 else {
1394                   // ... move the menu to our column
1395                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1396                 }
1397               }
1398               // If the menu is NOT showing
1399               else {
1400                 // ... show it on our column
1401                 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1402               }
1403             };
1404           }
1405         };
1406       }
1407     };
1408
1409     return uiGridHeaderCell;
1410   }]);
1411
1412 })();
1413
1414 (function(){
1415   'use strict';
1416
1417   angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
1418     function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1419     var defaultTemplate = 'ui-grid/ui-grid-header';
1420     var emptyTemplate = 'ui-grid/ui-grid-no-header';
1421
1422     return {
1423       restrict: 'EA',
1424       // templateUrl: 'ui-grid/ui-grid-header',
1425       replace: true,
1426       // priority: 1000,
1427       require: ['^uiGrid', '^uiGridRenderContainer'],
1428       scope: true,
1429       compile: function($elm, $attrs) {
1430         return {
1431           pre: function ($scope, $elm, $attrs, controllers) {
1432             var uiGridCtrl = controllers[0];
1433             var containerCtrl = controllers[1];
1434
1435             $scope.grid = uiGridCtrl.grid;
1436             $scope.colContainer = containerCtrl.colContainer;
1437
1438             updateHeaderReferences();
1439             
1440             var headerTemplate;
1441             if (!$scope.grid.options.showHeader) {
1442               headerTemplate = emptyTemplate;
1443             }
1444             else {
1445               headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
1446             }
1447
1448             gridUtil.getTemplate(headerTemplate)
1449               .then(function (contents) {
1450                 var template = angular.element(contents);
1451                 
1452                 var newElm = $compile(template)($scope);
1453                 $elm.replaceWith(newElm);
1454
1455                 // And update $elm to be the new element
1456                 $elm = newElm;
1457
1458                 updateHeaderReferences();
1459
1460                 if (containerCtrl) {
1461                   // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1462                   var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1463
1464
1465                   if (headerViewport) {
1466                     containerCtrl.headerViewport = headerViewport;
1467                     angular.element(headerViewport).on('scroll', scrollHandler);
1468                     $scope.$on('$destroy', function () {
1469                       angular.element(headerViewport).off('scroll', scrollHandler);
1470                     });
1471                   }
1472                 }
1473
1474                 $scope.grid.queueRefresh();
1475               });
1476
1477             function updateHeaderReferences() {
1478               containerCtrl.header = containerCtrl.colContainer.header = $elm;
1479
1480               var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
1481
1482               if (headerCanvases.length > 0) {
1483                 containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
1484               }
1485               else {
1486                 containerCtrl.headerCanvas = null;
1487               }
1488             }
1489
1490             function scrollHandler(evt) {
1491               if (uiGridCtrl.grid.isScrollingHorizontally) {
1492                 return;
1493               }
1494               var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
1495               var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
1496
1497               var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
1498               scrollEvent.newScrollLeft = newScrollLeft;
1499               if ( horizScrollPercentage > -1 ){
1500                 scrollEvent.x = { percentage: horizScrollPercentage };
1501               }
1502
1503               uiGridCtrl.grid.scrollContainers(null, scrollEvent);
1504             }
1505           },
1506
1507           post: function ($scope, $elm, $attrs, controllers) {
1508             var uiGridCtrl = controllers[0];
1509             var containerCtrl = controllers[1];
1510
1511             // gridUtil.logDebug('ui-grid-header link');
1512
1513             var grid = uiGridCtrl.grid;
1514
1515             // Don't animate header cells
1516             gridUtil.disableAnimations($elm);
1517
1518             function updateColumnWidths() {
1519               // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
1520               // already being populated correctly
1521
1522               var columnCache = containerCtrl.colContainer.visibleColumnCache;
1523               
1524               // Build the CSS
1525               // uiGridCtrl.grid.columns.forEach(function (column) {
1526               var ret = '';
1527               var canvasWidth = 0;
1528               columnCache.forEach(function (column) {
1529                 ret = ret + column.getColClassDefinition();
1530                 canvasWidth += column.drawnWidth;
1531               });
1532
1533               containerCtrl.colContainer.canvasWidth = canvasWidth;
1534               
1535               // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1536               return ret;
1537             }
1538             
1539             containerCtrl.header = $elm;
1540             
1541             var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1542             if (headerViewport) {
1543               containerCtrl.headerViewport = headerViewport;
1544             }
1545
1546             //todo: remove this if by injecting gridCtrl into unit tests
1547             if (uiGridCtrl) {
1548               uiGridCtrl.grid.registerStyleComputation({
1549                 priority: 15,
1550                 func: updateColumnWidths
1551               });
1552             }
1553           }
1554         };
1555       }
1556     };
1557   }]);
1558
1559 })();
1560
1561 (function(){
1562
1563 angular.module('ui.grid')
1564 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
1565   /**
1566    *  @ngdoc service
1567    *  @name ui.grid.gridMenuService
1568    *
1569    *  @description Methods for working with the grid menu
1570    */
1571
1572   var service = {
1573     /**
1574      * @ngdoc method
1575      * @methodOf ui.grid.gridMenuService
1576      * @name initialize
1577      * @description Sets up the gridMenu. Most importantly, sets our
1578      * scope onto the grid object as grid.gridMenuScope, allowing us
1579      * to operate when passed only the grid.  Second most importantly,
1580      * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1581      * on the core api.
1582      * @param {$scope} $scope the scope of this gridMenu
1583      * @param {Grid} grid the grid to which this gridMenu is associated
1584      */
1585     initialize: function( $scope, grid ){
1586       grid.gridMenuScope = $scope;
1587       $scope.grid = grid;
1588       $scope.registeredMenuItems = [];
1589
1590       // not certain this is needed, but would be bad to create a memory leak
1591       $scope.$on('$destroy', function() {
1592         if ( $scope.grid && $scope.grid.gridMenuScope ){
1593           $scope.grid.gridMenuScope = null;
1594         }
1595         if ( $scope.grid ){
1596           $scope.grid = null;
1597         }
1598         if ( $scope.registeredMenuItems ){
1599           $scope.registeredMenuItems = null;
1600         }
1601       });
1602
1603       $scope.registeredMenuItems = [];
1604
1605       /**
1606        * @ngdoc function
1607        * @name addToGridMenu
1608        * @methodOf ui.grid.core.api:PublicApi
1609        * @description add items to the grid menu.  Used by features
1610        * to add their menu items if they are enabled, can also be used by
1611        * end users to add menu items.  This method has the advantage of allowing
1612        * remove again, which can simplify management of which items are included
1613        * in the menu when.  (Noting that in most cases the shown and active functions
1614        * provide a better way to handle visibility of menu items)
1615        * @param {Grid} grid the grid on which we are acting
1616        * @param {array} items menu items in the format as described in the tutorial, with
1617        * the added note that if you want to use remove you must also specify an `id` field,
1618        * which is provided when you want to remove an item.  The id should be unique.
1619        *
1620        */
1621       grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1622
1623       /**
1624        * @ngdoc function
1625        * @name removeFromGridMenu
1626        * @methodOf ui.grid.core.api:PublicApi
1627        * @description Remove an item from the grid menu based on a provided id. Assumes
1628        * that the id is unique, removes only the last instance of that id. Does nothing if
1629        * the specified id is not found
1630        * @param {Grid} grid the grid on which we are acting
1631        * @param {string} id the id we'd like to remove from the menu
1632        *
1633        */
1634       grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1635     },
1636
1637
1638     /**
1639      * @ngdoc function
1640      * @name addToGridMenu
1641      * @propertyOf ui.grid.gridMenuService
1642      * @description add items to the grid menu.  Used by features
1643      * to add their menu items if they are enabled, can also be used by
1644      * end users to add menu items.  This method has the advantage of allowing
1645      * remove again, which can simplify management of which items are included
1646      * in the menu when.  (Noting that in most cases the shown and active functions
1647      * provide a better way to handle visibility of menu items)
1648      * @param {Grid} grid the grid on which we are acting
1649      * @param {array} items menu items in the format as described in the tutorial, with
1650      * the added note that if you want to use remove you must also specify an `id` field,
1651      * which is provided when you want to remove an item.  The id should be unique.
1652      *
1653      */
1654     addToGridMenu: function( grid, menuItems ) {
1655       if ( !angular.isArray( menuItems ) ) {
1656         gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1657       } else {
1658         if ( grid.gridMenuScope ){
1659           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1660           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1661         } else {
1662           gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
1663         }
1664       }
1665     },
1666
1667
1668     /**
1669      * @ngdoc function
1670      * @name removeFromGridMenu
1671      * @methodOf ui.grid.gridMenuService
1672      * @description Remove an item from the grid menu based on a provided id.  Assumes
1673      * that the id is unique, removes only the last instance of that id.  Does nothing if
1674      * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
1675      * then do nothing silently - the desired result is those menu items not be present and they
1676      * aren't.
1677      * @param {Grid} grid the grid on which we are acting
1678      * @param {string} id the id we'd like to remove from the menu
1679      *
1680      */
1681     removeFromGridMenu: function( grid, id ){
1682       var foundIndex = -1;
1683
1684       if ( grid && grid.gridMenuScope ){
1685         grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1686           if ( value.id === id ){
1687             if (foundIndex > -1) {
1688               gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1689             } else {
1690
1691               foundIndex = index;
1692             }
1693           }
1694         });
1695       }
1696
1697       if ( foundIndex > -1 ){
1698         grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1699       }
1700     },
1701
1702
1703     /**
1704      * @ngdoc array
1705      * @name gridMenuCustomItems
1706      * @propertyOf ui.grid.class:GridOptions
1707      * @description (optional) An array of menu items that should be added to
1708      * the gridMenu.  Follow the format documented in the tutorial for column
1709      * menu customisation.  The context provided to the action function will
1710      * include context.grid.  An alternative if working with dynamic menus is to use the
1711      * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1712      * some of the management of items for you.
1713      *
1714      */
1715     /**
1716      * @ngdoc boolean
1717      * @name gridMenuShowHideColumns
1718      * @propertyOf ui.grid.class:GridOptions
1719      * @description true by default, whether the grid menu should allow hide/show
1720      * of columns
1721      *
1722      */
1723     /**
1724      * @ngdoc method
1725      * @methodOf ui.grid.gridMenuService
1726      * @name getMenuItems
1727      * @description Decides the menu items to show in the menu.  This is a
1728      * combination of:
1729      *
1730      * - the default menu items that are always included,
1731      * - any menu items that have been provided through the addMenuItem api. These
1732      *   are typically added by features within the grid
1733      * - any menu items included in grid.options.gridMenuCustomItems.  These can be
1734      *   changed dynamically, as they're always recalculated whenever we show the
1735      *   menu
1736      * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1737      * the information that we need
1738      * @returns {array} an array of menu items that can be shown
1739      */
1740     getMenuItems: function( $scope ) {
1741       var menuItems = [
1742         // this is where we add any menu items we want to always include
1743       ];
1744
1745       if ( $scope.grid.options.gridMenuCustomItems ){
1746         if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1747           gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1748         } else {
1749           menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1750         }
1751       }
1752
1753       var clearFilters = [{
1754         title: i18nService.getSafeText('gridMenu.clearAllFilters'),
1755         action: function ($event) {
1756           $scope.grid.clearAllFilters(undefined, true, undefined);
1757         },
1758         shown: function() {
1759           return $scope.grid.options.enableFiltering;
1760         },
1761         order: 100
1762       }];
1763       menuItems = menuItems.concat( clearFilters );
1764
1765       menuItems = menuItems.concat( $scope.registeredMenuItems );
1766
1767       if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1768         menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1769       }
1770
1771       menuItems.sort(function(a, b){
1772         return a.order - b.order;
1773       });
1774
1775       return menuItems;
1776     },
1777
1778
1779     /**
1780      * @ngdoc array
1781      * @name gridMenuTitleFilter
1782      * @propertyOf ui.grid.class:GridOptions
1783      * @description (optional) A function that takes a title string
1784      * (usually the col.displayName), and converts it into a display value.  The function
1785      * must return either a string or a promise.
1786      *
1787      * Used for internationalization of the grid menu column names - for angular-translate
1788      * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1789      * function
1790      * @example
1791      * <pre>
1792      *   gridOptions = {
1793      *     gridMenuTitleFilter: $translate
1794      *   }
1795      * </pre>
1796      */
1797     /**
1798      * @ngdoc method
1799      * @methodOf ui.grid.gridMenuService
1800      * @name showHideColumns
1801      * @description Adds two menu items for each of the columns in columnDefs.  One
1802      * menu item for hide, one menu item for show.  Each is visible when appropriate
1803      * (show when column is not visible, hide when column is visible).  Each toggles
1804      * the visible property on the columnDef using toggleColumnVisibility
1805      * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1806      */
1807     showHideColumns: function( $scope ){
1808       var showHideColumns = [];
1809       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1810         return showHideColumns;
1811       }
1812
1813       // add header for columns
1814       showHideColumns.push({
1815         title: i18nService.getSafeText('gridMenu.columns'),
1816         order: 300
1817       });
1818
1819       $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1820
1821       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1822         if ( colDef.enableHiding !== false ){
1823           // add hide menu item - shows an OK icon as we only show when column is already visible
1824           var menuItem = {
1825             icon: 'ui-grid-icon-ok',
1826             action: function($event) {
1827               $event.stopPropagation();
1828               service.toggleColumnVisibility( this.context.gridCol );
1829             },
1830             shown: function() {
1831               return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1832             },
1833             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1834             leaveOpen: true,
1835             order: 301 + index * 2
1836           };
1837           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1838           showHideColumns.push( menuItem );
1839
1840           // add show menu item - shows no icon as we only show when column is invisible
1841           menuItem = {
1842             icon: 'ui-grid-icon-cancel',
1843             action: function($event) {
1844               $event.stopPropagation();
1845               service.toggleColumnVisibility( this.context.gridCol );
1846             },
1847             shown: function() {
1848               return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1849             },
1850             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1851             leaveOpen: true,
1852             order: 301 + index * 2 + 1
1853           };
1854           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1855           showHideColumns.push( menuItem );
1856         }
1857       });
1858       return showHideColumns;
1859     },
1860
1861
1862     /**
1863      * @ngdoc method
1864      * @methodOf ui.grid.gridMenuService
1865      * @name setMenuItemTitle
1866      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1867      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1868      * putting the result into the title
1869      * @param {object} menuItem the menuItem we want to put the title on
1870      * @param {object} colDef the colDef from which we can get displayName, name or field
1871      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1872      *
1873      */
1874     setMenuItemTitle: function( menuItem, colDef, grid ){
1875       var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
1876
1877       if ( typeof(title) === 'string' ){
1878         menuItem.title = title;
1879       } else if ( title.then ){
1880         // must be a promise
1881         menuItem.title = "";
1882         title.then( function( successValue ) {
1883           menuItem.title = successValue;
1884         }, function( errorValue ) {
1885           menuItem.title = errorValue;
1886         });
1887       } else {
1888         gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
1889         menuItem.title = 'badconfig';
1890       }
1891     },
1892
1893     /**
1894      * @ngdoc method
1895      * @methodOf ui.grid.gridMenuService
1896      * @name toggleColumnVisibility
1897      * @description Toggles the visibility of an individual column.  Expects to be
1898      * provided a context that has on it a gridColumn, which is the column that
1899      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
1900      * @param {GridCol} gridCol the column that we want to toggle
1901      *
1902      */
1903     toggleColumnVisibility: function( gridCol ) {
1904       gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
1905
1906       gridCol.grid.refresh();
1907       gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1908       gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
1909     }
1910   };
1911
1912   return service;
1913 }])
1914
1915
1916
1917 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
1918 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
1919
1920   return {
1921     priority: 0,
1922     scope: true,
1923     require: ['^uiGrid'],
1924     templateUrl: 'ui-grid/ui-grid-menu-button',
1925     replace: true,
1926
1927     link: function ($scope, $elm, $attrs, controllers) {
1928       var uiGridCtrl = controllers[0];
1929
1930       // For the aria label
1931       $scope.i18n = {
1932         aria: i18nService.getSafeText('gridMenu.aria')
1933       };
1934
1935       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
1936
1937       $scope.shown = false;
1938
1939       $scope.toggleMenu = function () {
1940         if ( $scope.shown ){
1941           $scope.$broadcast('hide-menu');
1942           $scope.shown = false;
1943         } else {
1944           $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
1945           $scope.$broadcast('show-menu');
1946           $scope.shown = true;
1947         }
1948       };
1949
1950       $scope.$on('menu-hidden', function() {
1951         $scope.shown = false;
1952         gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
1953       });
1954     }
1955   };
1956
1957 }]);
1958
1959 })();
1960
1961 (function(){
1962
1963 /**
1964  * @ngdoc directive
1965  * @name ui.grid.directive:uiGridMenu
1966  * @element style
1967  * @restrict A
1968  *
1969  * @description
1970  * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1971  *
1972  * @example
1973  <doc:example module="app">
1974  <doc:source>
1975  <script>
1976  var app = angular.module('app', ['ui.grid']);
1977
1978  app.controller('MainCtrl', ['$scope', function ($scope) {
1979
1980  }]);
1981  </script>
1982
1983  <div ng-controller="MainCtrl">
1984    <div ui-grid-menu shown="true"  ></div>
1985  </div>
1986  </doc:source>
1987  <doc:scenario>
1988  </doc:scenario>
1989  </doc:example>
1990  */
1991 angular.module('ui.grid')
1992
1993 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
1994 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
1995   var uiGridMenu = {
1996     priority: 0,
1997     scope: {
1998       // shown: '&',
1999       menuItems: '=',
2000       autoHide: '=?'
2001     },
2002     require: '?^uiGrid',
2003     templateUrl: 'ui-grid/uiGridMenu',
2004     replace: false,
2005     link: function ($scope, $elm, $attrs, uiGridCtrl) {
2006       var menuMid;
2007       var $animate;
2008       var gridMenuMaxHeight;
2009
2010       $scope.dynamicStyles = '';
2011
2012       if (uiGridCtrl) {
2013         // magic number of 30 because the grid menu displays somewhat below
2014         // the top of the grid. It is approximately 30px.
2015         gridMenuMaxHeight = uiGridCtrl.grid.gridHeight - 30;
2016         $scope.dynamicStyles = [
2017           '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
2018             'max-height: ' + gridMenuMaxHeight + 'px;',
2019           '}'
2020         ].join(' ');
2021       }
2022
2023       $scope.i18n = {
2024         close: i18nService.getSafeText('columnMenu.close')
2025       };
2026
2027     // *** Show/Hide functions ******
2028       $scope.showMenu = function(event, args) {
2029         if ( !$scope.shown ){
2030
2031           /*
2032            * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
2033            * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
2034            * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
2035            * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
2036            * on scroll events.
2037            *
2038            * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
2039            * like they do, but angular has a default $animate provider that is just a stub, and that's what's
2040            * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
2041            * angular-translate.js, it's not using it.  You need to test animations in an external application.
2042            */
2043           $scope.shown = true;
2044
2045           $timeout( function() {
2046             $scope.shownMid = true;
2047             $scope.$emit('menu-shown');
2048           });
2049         } else if ( !$scope.shownMid ) {
2050           // we're probably doing a hide then show, so we don't need to wait for ng-if
2051           $scope.shownMid = true;
2052           $scope.$emit('menu-shown');
2053         }
2054
2055         var docEventType = 'click';
2056         if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
2057           docEventType = args.originalEvent.type;
2058         }
2059
2060         // Turn off an existing document click handler
2061         angular.element(document).off('click touchstart', applyHideMenu);
2062
2063         // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
2064         $timeout(function() {
2065           angular.element(document).on(docEventType, applyHideMenu);
2066         });
2067         //automatically set the focus to the first button element in the now open menu.
2068         gridUtil.focus.bySelector($elm, 'button[type=button]', true);
2069       };
2070
2071
2072       $scope.hideMenu = function(event, args) {
2073         if ( $scope.shown ){
2074           /*
2075            * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
2076            * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
2077            * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
2078            *
2079            * The user may have clicked on the menu again whilst
2080            * we're waiting, so we check that the mid isn't shown before applying the ng-if.
2081            */
2082           $scope.shownMid = false;
2083           $timeout( function() {
2084             if ( !$scope.shownMid ){
2085               $scope.shown = false;
2086               $scope.$emit('menu-hidden');
2087             }
2088           }, 200);
2089         }
2090
2091         angular.element(document).off('click touchstart', applyHideMenu);
2092       };
2093
2094       $scope.$on('hide-menu', function (event, args) {
2095         $scope.hideMenu(event, args);
2096       });
2097
2098       $scope.$on('show-menu', function (event, args) {
2099         $scope.showMenu(event, args);
2100       });
2101
2102
2103     // *** Auto hide when click elsewhere ******
2104       var applyHideMenu = function(){
2105         if ($scope.shown) {
2106           $scope.$apply(function () {
2107             $scope.hideMenu();
2108           });
2109         }
2110       };
2111
2112       if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
2113         $scope.autoHide = true;
2114       }
2115
2116       if ($scope.autoHide) {
2117         angular.element($window).on('resize', applyHideMenu);
2118       }
2119
2120       $scope.$on('$destroy', function () {
2121         angular.element(document).off('click touchstart', applyHideMenu);
2122       });
2123
2124
2125       $scope.$on('$destroy', function() {
2126         angular.element($window).off('resize', applyHideMenu);
2127       });
2128
2129       if (uiGridCtrl) {
2130        $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
2131       }
2132
2133       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2134     },
2135
2136
2137     controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
2138       var self = this;
2139     }]
2140   };
2141
2142   return uiGridMenu;
2143 }])
2144
2145 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2146   var uiGridMenuItem = {
2147     priority: 0,
2148     scope: {
2149       name: '=',
2150       active: '=',
2151       action: '=',
2152       icon: '=',
2153       shown: '=',
2154       context: '=',
2155       templateUrl: '=',
2156       leaveOpen: '=',
2157       screenReaderOnly: '='
2158     },
2159     require: ['?^uiGrid', '^uiGridMenu'],
2160     templateUrl: 'ui-grid/uiGridMenuItem',
2161     replace: false,
2162     compile: function($elm, $attrs) {
2163       return {
2164         pre: function ($scope, $elm, $attrs, controllers) {
2165           var uiGridCtrl = controllers[0],
2166               uiGridMenuCtrl = controllers[1];
2167
2168           if ($scope.templateUrl) {
2169             gridUtil.getTemplate($scope.templateUrl)
2170                 .then(function (contents) {
2171                   var template = angular.element(contents);
2172
2173                   var newElm = $compile(template)($scope);
2174                   $elm.replaceWith(newElm);
2175                 });
2176           }
2177         },
2178         post: function ($scope, $elm, $attrs, controllers) {
2179           var uiGridCtrl = controllers[0],
2180               uiGridMenuCtrl = controllers[1];
2181
2182           // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
2183           // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
2184           //   throw new TypeError("$scope.shown is defined but not a function");
2185           // }
2186           if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
2187             $scope.shown = function() { return true; };
2188           }
2189
2190           $scope.itemShown = function () {
2191             var context = {};
2192             if ($scope.context) {
2193               context.context = $scope.context;
2194             }
2195
2196             if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2197               context.grid = uiGridCtrl.grid;
2198             }
2199
2200             return $scope.shown.call(context);
2201           };
2202
2203           $scope.itemAction = function($event,title) {
2204             gridUtil.logDebug('itemAction');
2205             $event.stopPropagation();
2206
2207             if (typeof($scope.action) === 'function') {
2208               var context = {};
2209
2210               if ($scope.context) {
2211                 context.context = $scope.context;
2212               }
2213
2214               // Add the grid to the function call context if the uiGrid controller is present
2215               if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2216                 context.grid = uiGridCtrl.grid;
2217               }
2218
2219               $scope.action.call(context, $event, title);
2220
2221               if ( !$scope.leaveOpen ){
2222                 $scope.$emit('hide-menu');
2223               } else {
2224                 /*
2225                  * XXX: Fix after column refactor
2226                  * Ideally the focus would remain on the item.
2227                  * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2228                  */
2229                 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2230               }
2231             }
2232           };
2233
2234           $scope.i18n = i18nService.get();
2235         }
2236       };
2237     }
2238   };
2239
2240   return uiGridMenuItem;
2241 }]);
2242
2243 })();
2244
2245 (function(){
2246   'use strict';
2247   /**
2248    * @ngdoc overview
2249    * @name ui.grid.directive:uiGridOneBind
2250    * @summary A group of directives that provide a one time bind to a dom element.
2251    * @description A group of directives that provide a one time bind to a dom element.
2252    * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
2253    * This is done to reduce the number of watchers on the dom.
2254    * <br/>
2255    * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
2256    * <pre>
2257         <div ng-init="imageName = 'myImageDir.jpg'">
2258           <img ui-grid-one-bind-src="imageName"></img>
2259         </div>
2260      </pre>
2261    * Will become:
2262    * <pre>
2263        <div ng-init="imageName = 'myImageDir.jpg'">
2264          <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
2265        </div>
2266      </pre>
2267      </br>
2268      <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
2269    * <pre>
2270         <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
2271      </pre>
2272    * Will become:
2273    * <pre>
2274    <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
2275      </pre>
2276      </br>
2277    * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
2278    * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
2279    *
2280    */
2281   //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2282   var oneBinders = angular.module('ui.grid');
2283   angular.forEach([
2284       /**
2285        * @ngdoc directive
2286        * @name ui.grid.directive:uiGridOneBindSrc
2287        * @memberof ui.grid.directive:uiGridOneBind
2288        * @element img
2289        * @restrict A
2290        * @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>.
2291        * @description One time binding for the src dom tag.
2292        *
2293        */
2294       {tag: 'Src', method: 'attr'},
2295       /**
2296        * @ngdoc directive
2297        * @name ui.grid.directive:uiGridOneBindText
2298        * @element div
2299        * @restrict A
2300        * @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>.
2301        * @description One time binding for the text dom tag.
2302        */
2303       {tag: 'Text', method: 'text'},
2304       /**
2305        * @ngdoc directive
2306        * @name ui.grid.directive:uiGridOneBindHref
2307        * @element div
2308        * @restrict A
2309        * @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>.
2310        * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2311        */
2312       {tag: 'Href', method: 'attr'},
2313       /**
2314        * @ngdoc directive
2315        * @name ui.grid.directive:uiGridOneBindClass
2316        * @element div
2317        * @restrict A
2318        * @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>.
2319        * @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.
2320        *                                    this is to prevent the watcher from being removed before the scope is initialized.
2321        * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
2322        * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2323        */
2324       {tag: 'Class', method: 'addClass'},
2325       /**
2326        * @ngdoc directive
2327        * @name ui.grid.directive:uiGridOneBindHtml
2328        * @element div
2329        * @restrict A
2330        * @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>.
2331        * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
2332        */
2333       {tag: 'Html', method: 'html'},
2334       /**
2335        * @ngdoc directive
2336        * @name ui.grid.directive:uiGridOneBindAlt
2337        * @element div
2338        * @restrict A
2339        * @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>.
2340        * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2341        */
2342       {tag: 'Alt', method: 'attr'},
2343       /**
2344        * @ngdoc directive
2345        * @name ui.grid.directive:uiGridOneBindStyle
2346        * @element div
2347        * @restrict A
2348        * @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>.
2349        * @description One time binding for the style dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2350        */
2351       {tag: 'Style', method: 'css'},
2352       /**
2353        * @ngdoc directive
2354        * @name ui.grid.directive:uiGridOneBindValue
2355        * @element div
2356        * @restrict A
2357        * @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>.
2358        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2359        */
2360       {tag: 'Value', method: 'attr'},
2361       /**
2362        * @ngdoc directive
2363        * @name ui.grid.directive:uiGridOneBindId
2364        * @element div
2365        * @restrict A
2366        * @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>.
2367        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2368        */
2369       {tag: 'Id', method: 'attr'},
2370       /**
2371        * @ngdoc directive
2372        * @name ui.grid.directive:uiGridOneBindIdGrid
2373        * @element div
2374        * @restrict A
2375        * @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>.
2376        * @description One time binding for the id dom tag.
2377        * <h1>Important Note!</h1>
2378        * If the id tag passed as a parameter does <b>not</b> contain the grid id as a substring
2379        * then the directive will search the scope and the parent controller (if it is a uiGridController) for the grid.id value.
2380        * 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.
2381        * This is done in order to ensure uniqueness of id tags across the grid.
2382        * This is to prevent two grids in the same document having duplicate id tags.
2383        */
2384       {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2385       /**
2386        * @ngdoc directive
2387        * @name ui.grid.directive:uiGridOneBindTitle
2388        * @element div
2389        * @restrict A
2390        * @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>.
2391        * @description One time binding for the title dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2392        */
2393       {tag: 'Title', method: 'attr'},
2394       /**
2395        * @ngdoc directive
2396        * @name ui.grid.directive:uiGridOneBindAriaLabel
2397        * @element div
2398        * @restrict A
2399        * @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>.
2400        * @description One time binding for the aria-label dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2401        *<br/>
2402        * <pre>
2403             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text"></div>
2404          </pre>
2405        * Will become:
2406        * <pre>
2407             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
2408          </pre>
2409        */
2410       {tag: 'Label', method: 'attr', aria:true},
2411       /**
2412        * @ngdoc directive
2413        * @name ui.grid.directive:uiGridOneBindAriaLabelledby
2414        * @element div
2415        * @restrict A
2416        * @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>.
2417        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2418        *<br/>
2419        * <pre>
2420             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId"></div>
2421          </pre>
2422        * Will become:
2423        * <pre>
2424             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
2425          </pre>
2426        */
2427       {tag: 'Labelledby', method: 'attr', aria:true},
2428       /**
2429        * @ngdoc directive
2430        * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
2431        * @element div
2432        * @restrict A
2433        * @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>.
2434        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2435        * 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
2436        * grid id to each one.
2437        *<br/>
2438        * <pre>
2439             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId"></div>
2440          </pre>
2441        * Will become ([grid.id] will be replaced by the actual grid id):
2442        * <pre>
2443             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
2444          </pre>
2445        */
2446       {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2447       /**
2448        * @ngdoc directive
2449        * @name ui.grid.directive:uiGridOneBindAriaDescribedby
2450        * @element ANY
2451        * @restrict A
2452        * @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>.
2453        * @description One time binding for the aria-describedby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2454        *<br/>
2455        * <pre>
2456             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId"></div>
2457          </pre>
2458        * Will become:
2459        * <pre>
2460             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
2461          </pre>
2462        */
2463       {tag: 'Describedby', method: 'attr', aria:true},
2464       /**
2465        * @ngdoc directive
2466        * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
2467        * @element ANY
2468        * @restrict A
2469        * @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>.
2470        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2471        * 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
2472        * grid id to each one.
2473        *<br/>
2474        * <pre>
2475             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId"></div>
2476          </pre>
2477        * Will become ([grid.id] will be replaced by the actual grid id):
2478        * <pre>
2479             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
2480          </pre>
2481        */
2482       {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2483     function(v){
2484
2485       var baseDirectiveName = 'uiGridOneBind';
2486       //If it is an aria tag then append the aria label seperately
2487       //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2488       //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.
2489       var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2490       oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2491         return {
2492           restrict: 'A',
2493           require: ['?uiGrid','?^uiGrid'],
2494           link: function(scope, iElement, iAttrs, controllers){
2495             /* Appends the grid id to the beginnig of the value. */
2496             var appendGridId = function(val){
2497               var grid; //Get an instance of the grid if its available
2498               //If its available in the scope then we don't need to try to find it elsewhere
2499               if (scope.grid) {
2500                 grid = scope.grid;
2501               }
2502               //Another possible location to try to find the grid
2503               else if (scope.col && scope.col.grid){
2504                 grid = scope.col.grid;
2505               }
2506               //Last ditch effort: Search through the provided controllers.
2507               else if (!controllers.some( //Go through the controllers till one has the element we need
2508                 function(controller){
2509                   if (controller && controller.grid) {
2510                     grid = controller.grid;
2511                     return true; //We've found the grid
2512                   }
2513               })){
2514                 //We tried our best to find it for you
2515                 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
2516                                  "within the correct scope? Trying to generate id: [gridID]-" + val);
2517                 throw new Error("No valid grid could be found");
2518               }
2519
2520               if (grid){
2521                 var idRegex = new RegExp(grid.id.toString());
2522                 //If the grid id hasn't been appended already in the template declaration
2523                 if (!idRegex.test(val)){
2524                   val = grid.id.toString() + '-' + val;
2525                 }
2526               }
2527               return val;
2528             };
2529
2530             // The watch returns a function to remove itself.
2531             var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2532               if (newV){
2533                 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2534                 if (v.appendGridId) {
2535                   var newIdString = null;
2536                   //Append the id to all of the new ids.
2537                   angular.forEach( newV.split(' '), function(s){
2538                     newIdString = (newIdString ? (newIdString + ' ') : '') +  appendGridId(s);
2539                   });
2540                   newV = newIdString;
2541                 }
2542
2543                 // Append this newValue to the dom element.
2544                 switch (v.method) {
2545                   case 'attr': //The attr method takes two paraams the tag and the value
2546                     if (v.aria) {
2547                       //If it is an aria element then append the aria prefix
2548                       iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
2549                     } else {
2550                       iElement[v.method](v.tag.toLowerCase(),newV);
2551                     }
2552                     break;
2553                   case 'addClass':
2554                     //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2555                     if (angular.isObject(newV) && !angular.isArray(newV)) {
2556                       var results = [];
2557                       var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2558                       angular.forEach(newV, function (value, index) {
2559                         if (value !== null && typeof(value) !== "undefined"){
2560                           nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2561                           if (value) {results.push(index);}
2562                         }
2563                       });
2564                       //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2565                       if (!nonNullFound){
2566                         return; // If not initialized then the watcher should not be removed yet.
2567                       }
2568                       newV = results;
2569                     }
2570
2571                     if (newV) {
2572                       iElement.addClass(angular.isArray(newV) ? newV.join(' ') : newV);
2573                     } else {
2574                       return;
2575                     }
2576                     break;
2577                   default:
2578                     iElement[v.method](newV);
2579                     break;
2580                 }
2581
2582                 //Removes the watcher on itself after the bind
2583                 rmWatcher();
2584               }
2585             // True ensures that equality is determined using angular.equals instead of ===
2586             }, true); //End rm watchers
2587           } //End compile function
2588         }; //End directive return
2589       } // End directive function
2590     ]); //End directive
2591   }); // End angular foreach
2592 })();
2593
2594 (function () {
2595   'use strict';
2596
2597   var module = angular.module('ui.grid');
2598
2599   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2600     function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2601     return {
2602       replace: true,
2603       transclude: true,
2604       templateUrl: 'ui-grid/uiGridRenderContainer',
2605       require: ['^uiGrid', 'uiGridRenderContainer'],
2606       scope: {
2607         containerId: '=',
2608         rowContainerName: '=',
2609         colContainerName: '=',
2610         bindScrollHorizontal: '=',
2611         bindScrollVertical: '=',
2612         enableVerticalScrollbar: '=',
2613         enableHorizontalScrollbar: '='
2614       },
2615       controller: 'uiGridRenderContainer as RenderContainer',
2616       compile: function () {
2617         return {
2618           pre: function prelink($scope, $elm, $attrs, controllers) {
2619
2620             var uiGridCtrl = controllers[0];
2621             var containerCtrl = controllers[1];
2622             var grid = $scope.grid = uiGridCtrl.grid;
2623
2624             // Verify that the render container for this element exists
2625             if (!$scope.rowContainerName) {
2626               throw "No row render container name specified";
2627             }
2628             if (!$scope.colContainerName) {
2629               throw "No column render container name specified";
2630             }
2631
2632             if (!grid.renderContainers[$scope.rowContainerName]) {
2633               throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2634             }
2635             if (!grid.renderContainers[$scope.colContainerName]) {
2636               throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2637             }
2638
2639             var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2640             var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2641
2642             containerCtrl.containerId = $scope.containerId;
2643             containerCtrl.rowContainer = rowContainer;
2644             containerCtrl.colContainer = colContainer;
2645           },
2646           post: function postlink($scope, $elm, $attrs, controllers) {
2647
2648             var uiGridCtrl = controllers[0];
2649             var containerCtrl = controllers[1];
2650
2651             var grid = uiGridCtrl.grid;
2652             var rowContainer = containerCtrl.rowContainer;
2653             var colContainer = containerCtrl.colContainer;
2654             var scrollTop = null;
2655             var scrollLeft = null;
2656
2657
2658             var renderContainer = grid.renderContainers[$scope.containerId];
2659
2660             // Put the container name on this element as a class
2661             $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2662
2663             // Scroll the render container viewport when the mousewheel is used
2664             gridUtil.on.mousewheel($elm, function (event) {
2665               var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2666               if (event.deltaY !== 0) {
2667                 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2668
2669                 scrollTop = containerCtrl.viewport[0].scrollTop;
2670
2671                 // Get the scroll percentage
2672                 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2673                 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2674
2675                 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2676                 //   Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2677                 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2678                   containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2679                 }
2680
2681                 // Keep scrollPercentage within the range 0-1.
2682                 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2683                 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2684
2685                 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2686               }
2687               if (event.deltaX !== 0) {
2688                 var scrollXAmount = event.deltaX * event.deltaFactor;
2689
2690                 // Get the scroll percentage
2691                 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2692                 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2693                 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2694
2695                 // Keep scrollPercentage within the range 0-1.
2696                 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2697                 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2698
2699                 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2700               }
2701
2702               // Let the parent container scroll if the grid is already at the top/bottom
2703               if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2704                   (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2705                 //parent controller scrolls
2706               }
2707               else {
2708                 event.preventDefault();
2709                 event.stopPropagation();
2710                 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2711               }
2712
2713             });
2714
2715             $elm.bind('$destroy', function() {
2716               $elm.unbind('keydown');
2717
2718               ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2719                 $elm.unbind(eventName);
2720               });
2721             });
2722
2723             // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2724             function update() {
2725               var ret = '';
2726
2727               var canvasWidth = colContainer.canvasWidth;
2728               var viewportWidth = colContainer.getViewportWidth();
2729
2730               var canvasHeight = rowContainer.getCanvasHeight();
2731
2732               //add additional height for scrollbar on left and right container
2733               //if ($scope.containerId !== 'body') {
2734               //  canvasHeight -= grid.scrollbarHeight;
2735               //}
2736
2737               var viewportHeight = rowContainer.getViewportHeight();
2738               //shorten the height to make room for a scrollbar placeholder
2739               if (colContainer.needsHScrollbarPlaceholder()) {
2740                 viewportHeight -= grid.scrollbarHeight;
2741               }
2742
2743               var headerViewportWidth,
2744                   footerViewportWidth;
2745               headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2746
2747               // Set canvas dimensions
2748               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2749
2750               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2751
2752               if (renderContainer.explicitHeaderCanvasHeight) {
2753                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2754               }
2755               else {
2756                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2757               }
2758
2759               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2760               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2761
2762               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2763               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2764
2765               return ret;
2766             }
2767
2768             uiGridCtrl.grid.registerStyleComputation({
2769               priority: 6,
2770               func: update
2771             });
2772           }
2773         };
2774       }
2775     };
2776
2777   }]);
2778
2779   module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2780
2781   }]);
2782
2783 })();
2784
2785 (function(){
2786   'use strict';
2787
2788   angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2789     return {
2790       replace: true,
2791       // priority: 2001,
2792       // templateUrl: 'ui-grid/ui-grid-row',
2793       require: ['^uiGrid', '^uiGridRenderContainer'],
2794       scope: {
2795          row: '=uiGridRow',
2796          //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2797          rowRenderIndex: '='
2798       },
2799       compile: function() {
2800         return {
2801           pre: function($scope, $elm, $attrs, controllers) {
2802             var uiGridCtrl = controllers[0];
2803             var containerCtrl = controllers[1];
2804
2805             var grid = uiGridCtrl.grid;
2806
2807             $scope.grid = uiGridCtrl.grid;
2808             $scope.colContainer = containerCtrl.colContainer;
2809
2810             // Function for attaching the template to this scope
2811             var clonedElement, cloneScope;
2812             function compileTemplate() {
2813               $scope.row.getRowTemplateFn.then(function (compiledElementFn) {
2814                 // var compiledElementFn = $scope.row.compiledElementFn;
2815
2816                 // Create a new scope for the contents of this row, so we can destroy it later if need be
2817                 var newScope = $scope.$new();
2818
2819                 compiledElementFn(newScope, function (newElm, scope) {
2820                   // If we already have a cloned element, we need to remove it and destroy its scope
2821                   if (clonedElement) {
2822                     clonedElement.remove();
2823                     cloneScope.$destroy();
2824                   }
2825
2826                   // Empty the row and append the new element
2827                   $elm.empty().append(newElm);
2828
2829                   // Save the new cloned element and scope
2830                   clonedElement = newElm;
2831                   cloneScope = newScope;
2832                 });
2833               });
2834             }
2835
2836             // Initially attach the compiled template to this scope
2837             compileTemplate();
2838
2839             // If the row's compiled element function changes, we need to replace this element's contents with the new compiled template
2840             $scope.$watch('row.getRowTemplateFn', function (newFunc, oldFunc) {
2841               if (newFunc !== oldFunc) {
2842                 compileTemplate();
2843               }
2844             });
2845           },
2846           post: function($scope, $elm, $attrs, controllers) {
2847
2848           }
2849         };
2850       }
2851     };
2852   }]);
2853
2854 })();
2855 (function(){
2856 // 'use strict';
2857
2858   /**
2859    * @ngdoc directive
2860    * @name ui.grid.directive:uiGridStyle
2861    * @element style
2862    * @restrict A
2863    *
2864    * @description
2865    * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2866    *
2867    * @example
2868    <doc:example module="app">
2869    <doc:source>
2870    <script>
2871    var app = angular.module('app', ['ui.grid']);
2872
2873    app.controller('MainCtrl', ['$scope', function ($scope) {
2874           $scope.myStyle = '.blah { border: 1px solid }';
2875         }]);
2876    </script>
2877
2878    <div ng-controller="MainCtrl">
2879    <style ui-grid-style>{{ myStyle }}</style>
2880    <span class="blah">I am in a box.</span>
2881    </div>
2882    </doc:source>
2883    <doc:scenario>
2884       it('should apply the right class to the element', function () {
2885         element(by.css('.blah')).getCssValue('border-top-width')
2886           .then(function(c) {
2887             expect(c).toContain('1px');
2888           });
2889       });
2890    </doc:scenario>
2891    </doc:example>
2892    */
2893
2894
2895   angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
2896     return {
2897       // restrict: 'A',
2898       // priority: 1000,
2899       // require: '?^uiGrid',
2900       link: function($scope, $elm, $attrs, uiGridCtrl) {
2901         // gridUtil.logDebug('ui-grid-style link');
2902         // if (uiGridCtrl === undefined) {
2903         //    gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2904         // }
2905
2906         var interpolateFn = $interpolate($elm.text(), true);
2907
2908         if (interpolateFn) {
2909           $scope.$watch(interpolateFn, function(value) {
2910             $elm.text(value);
2911           });
2912         }
2913
2914           // uiGridCtrl.recalcRowStyles = function() {
2915           //   var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2916           //   var rowHeight = scope.options.rowHeight;
2917
2918           //   var ret = '';
2919           //   var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2920           //   for (var i = 1; i <= rowStyleCount; i++) {
2921           //     ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2922           //     offset = offset + rowHeight;
2923           //   }
2924
2925           //   scope.rowStyles = ret;
2926           // };
2927
2928           // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2929
2930       }
2931     };
2932   }]);
2933
2934 })();
2935
2936 (function(){
2937   'use strict';
2938
2939   angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
2940     function(gridUtil, ScrollEvent, uiGridConstants, $log) {
2941       return {
2942         replace: true,
2943         scope: {},
2944         controllerAs: 'Viewport',
2945         templateUrl: 'ui-grid/uiGridViewport',
2946         require: ['^uiGrid', '^uiGridRenderContainer'],
2947         link: function($scope, $elm, $attrs, controllers) {
2948           // gridUtil.logDebug('viewport post-link');
2949
2950           var uiGridCtrl = controllers[0];
2951           var containerCtrl = controllers[1];
2952
2953           $scope.containerCtrl = containerCtrl;
2954
2955           var rowContainer = containerCtrl.rowContainer;
2956           var colContainer = containerCtrl.colContainer;
2957
2958           var grid = uiGridCtrl.grid;
2959
2960           $scope.grid = uiGridCtrl.grid;
2961
2962           // Put the containers in scope so we can get rows and columns from them
2963           $scope.rowContainer = containerCtrl.rowContainer;
2964           $scope.colContainer = containerCtrl.colContainer;
2965
2966           // Register this viewport with its container
2967           containerCtrl.viewport = $elm;
2968
2969
2970           $elm.on('scroll', scrollHandler);
2971
2972           var ignoreScroll = false;
2973
2974           function scrollHandler(evt) {
2975             //Leaving in this commented code in case it can someday be used
2976             //It does improve performance, but because the horizontal scroll is normalized,
2977             //  using this code will lead to the column header getting slightly out of line with columns
2978             //
2979             //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
2980             //  //don't ask for scrollTop if we just set it
2981             //  ignoreScroll = false;
2982             //  return;
2983             //}
2984             //ignoreScroll = true;
2985
2986             var newScrollTop = $elm[0].scrollTop;
2987             var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
2988
2989             var vertScrollPercentage = rowContainer.scrollVertical(newScrollTop);
2990             var horizScrollPercentage = colContainer.scrollHorizontal(newScrollLeft);
2991
2992             var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
2993             scrollEvent.newScrollLeft = newScrollLeft;
2994             scrollEvent.newScrollTop = newScrollTop;
2995             if ( horizScrollPercentage > -1 ){
2996               scrollEvent.x = { percentage: horizScrollPercentage };
2997             }
2998
2999             if ( vertScrollPercentage > -1 ){
3000               scrollEvent.y = { percentage: vertScrollPercentage };
3001             }
3002
3003             grid.scrollContainers($scope.$parent.containerId, scrollEvent);
3004           }
3005
3006           if ($scope.$parent.bindScrollVertical) {
3007             grid.addVerticalScrollSync($scope.$parent.containerId, syncVerticalScroll);
3008           }
3009
3010           if ($scope.$parent.bindScrollHorizontal) {
3011             grid.addHorizontalScrollSync($scope.$parent.containerId, syncHorizontalScroll);
3012             grid.addHorizontalScrollSync($scope.$parent.containerId + 'header', syncHorizontalHeader);
3013             grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
3014           }
3015
3016           function syncVerticalScroll(scrollEvent){
3017             containerCtrl.prevScrollArgs = scrollEvent;
3018             var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3019             $elm[0].scrollTop = newScrollTop;
3020
3021           }
3022
3023           function syncHorizontalScroll(scrollEvent){
3024             containerCtrl.prevScrollArgs = scrollEvent;
3025             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3026             $elm[0].scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3027           }
3028
3029           function syncHorizontalHeader(scrollEvent){
3030             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3031             if (containerCtrl.headerViewport) {
3032               containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3033             }
3034           }
3035
3036           function syncHorizontalFooter(scrollEvent){
3037             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3038             if (containerCtrl.footerViewport) {
3039               containerCtrl.footerViewport.scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3040             }
3041           }
3042
3043
3044         },
3045         controller: ['$scope', function ($scope) {
3046           this.rowStyle = function (index) {
3047             var rowContainer = $scope.rowContainer;
3048             var colContainer = $scope.colContainer;
3049
3050             var styles = {};
3051
3052             if (index === 0 && rowContainer.currentTopRow !== 0) {
3053               // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
3054               var hiddenRowWidth = (rowContainer.currentTopRow) * rowContainer.grid.options.rowHeight;
3055
3056               // return { 'margin-top': hiddenRowWidth + 'px' };
3057               styles['margin-top'] = hiddenRowWidth + 'px';
3058             }
3059
3060             if (colContainer.currentFirstColumn !== 0) {
3061               if (colContainer.grid.isRTL()) {
3062                 styles['margin-right'] = colContainer.columnOffset + 'px';
3063               }
3064               else {
3065                 styles['margin-left'] = colContainer.columnOffset + 'px';
3066               }
3067             }
3068
3069             return styles;
3070           };
3071         }]
3072       };
3073     }
3074   ]);
3075
3076 })();
3077
3078 (function() {
3079
3080 angular.module('ui.grid')
3081 .directive('uiGridVisible', function uiGridVisibleAction() {
3082   return function ($scope, $elm, $attr) {
3083     $scope.$watch($attr.uiGridVisible, function (visible) {
3084         // $elm.css('visibility', visible ? 'visible' : 'hidden');
3085         $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3086     });
3087   };
3088 });
3089
3090 })();
3091 (function () {
3092   'use strict';
3093
3094   angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3095                     '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3096     function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3097               $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3098       // gridUtil.logDebug('ui-grid controller');
3099
3100       var self = this;
3101
3102       self.grid = gridClassFactory.createGrid($scope.uiGrid);
3103
3104       //assign $scope.$parent if appScope not already assigned
3105       self.grid.appScope = self.grid.appScope || $scope.$parent;
3106
3107       $elm.addClass('grid' + self.grid.id);
3108       self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
3109
3110
3111       // angular.extend(self.grid.options, );
3112
3113       //all properties of grid are available on scope
3114       $scope.grid = self.grid;
3115
3116       if ($attrs.uiGridColumns) {
3117         $attrs.$observe('uiGridColumns', function(value) {
3118           self.grid.options.columnDefs = value;
3119           self.grid.buildColumns()
3120             .then(function(){
3121               self.grid.preCompileCellTemplates();
3122
3123               self.grid.refreshCanvas(true);
3124             });
3125         });
3126       }
3127
3128
3129       // if fastWatch is set we watch only the length and the reference, not every individual object
3130       var deregFunctions = [];
3131       if (self.grid.options.fastWatch) {
3132         self.uiGrid = $scope.uiGrid;
3133         if (angular.isString($scope.uiGrid.data)) {
3134           deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
3135           deregFunctions.push( $scope.$parent.$watch(function() {
3136             if ( self.grid.appScope[$scope.uiGrid.data] ){
3137               return self.grid.appScope[$scope.uiGrid.data].length;
3138             } else {
3139               return undefined;
3140             }
3141           }, dataWatchFunction) );
3142         } else {
3143           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3144           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, dataWatchFunction) );
3145         }
3146         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3147         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, columnDefsWatchFunction) );
3148       } else {
3149         if (angular.isString($scope.uiGrid.data)) {
3150           deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
3151         } else {
3152           deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3153         }
3154         deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3155       }
3156
3157
3158       function columnDefsWatchFunction(n, o) {
3159         if (n && n !== o) {
3160           self.grid.options.columnDefs = n;
3161           self.grid.buildColumns({ orderByColumnDefs: true })
3162             .then(function(){
3163
3164               self.grid.preCompileCellTemplates();
3165
3166               self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3167             });
3168         }
3169       }
3170
3171       var mostRecentData;
3172
3173       function dataWatchFunction(newData) {
3174         // gridUtil.logDebug('dataWatch fired');
3175         var promises = [];
3176
3177         if ( self.grid.options.fastWatch ){
3178           if (angular.isString($scope.uiGrid.data)) {
3179             newData = self.grid.appScope[$scope.uiGrid.data];
3180           } else {
3181             newData = $scope.uiGrid.data;
3182           }
3183         }
3184
3185         mostRecentData = newData;
3186
3187         if (newData) {
3188           // columns length is greater than the number of row header columns, which don't count because they're created automatically
3189           var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
3190
3191           if (
3192             // If we have no columns
3193             !hasColumns &&
3194             // ... and we don't have a ui-grid-columns attribute, which would define columns for us
3195             !$attrs.uiGridColumns &&
3196             // ... and we have no pre-defined columns
3197             self.grid.options.columnDefs.length === 0 &&
3198             // ... but we DO have data
3199             newData.length > 0
3200           ) {
3201             // ... then build the column definitions from the data that we have
3202             self.grid.buildColumnDefsFromData(newData);
3203           }
3204
3205           // If we haven't built columns before and either have some columns defined or some data defined
3206           if (!hasColumns && (self.grid.options.columnDefs.length > 0 || newData.length > 0)) {
3207             // Build the column set, then pre-compile the column cell templates
3208             promises.push(self.grid.buildColumns()
3209               .then(function() {
3210                 self.grid.preCompileCellTemplates();
3211               }));
3212           }
3213
3214           $q.all(promises).then(function() {
3215             // use most recent data, rather than the potentially outdated data passed into watcher handler
3216             self.grid.modifyRows(mostRecentData)
3217               .then(function () {
3218                 // if (self.viewport) {
3219                   self.grid.redrawInPlace(true);
3220                 // }
3221
3222                 $scope.$evalAsync(function() {
3223                   self.grid.refreshCanvas(true);
3224                   self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
3225                 });
3226               });
3227           });
3228         }
3229       }
3230
3231       var styleWatchDereg = $scope.$watch(function () { return self.grid.styleComputations; }, function() {
3232         self.grid.refreshCanvas(true);
3233       });
3234
3235       $scope.$on('$destroy', function() {
3236         deregFunctions.forEach( function( deregFn ){ deregFn(); });
3237         styleWatchDereg();
3238       });
3239
3240       self.fireEvent = function(eventName, args) {
3241         // Add the grid to the event arguments if it's not there
3242         if (typeof(args) === 'undefined' || args === undefined) {
3243           args = {};
3244         }
3245
3246         if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3247           args.grid = self.grid;
3248         }
3249
3250         $scope.$broadcast(eventName, args);
3251       };
3252
3253       self.innerCompile = function innerCompile(elm) {
3254         $compile(elm)($scope);
3255       };
3256
3257     }]);
3258
3259 /**
3260  *  @ngdoc directive
3261  *  @name ui.grid.directive:uiGrid
3262  *  @element div
3263  *  @restrict EA
3264  *  @param {Object} uiGrid Options for the grid to use
3265  *
3266  *  @description Create a very basic grid.
3267  *
3268  *  @example
3269     <example module="app">
3270       <file name="app.js">
3271         var app = angular.module('app', ['ui.grid']);
3272
3273         app.controller('MainCtrl', ['$scope', function ($scope) {
3274           $scope.data = [
3275             { name: 'Bob', title: 'CEO' },
3276             { name: 'Frank', title: 'Lowly Developer' }
3277           ];
3278         }]);
3279       </file>
3280       <file name="index.html">
3281         <div ng-controller="MainCtrl">
3282           <div ui-grid="{ data: data }"></div>
3283         </div>
3284       </file>
3285     </example>
3286  */
3287 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
3288
3289 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3290 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3291   return {
3292     templateUrl: 'ui-grid/ui-grid',
3293     scope: {
3294       uiGrid: '='
3295     },
3296     replace: true,
3297     transclude: true,
3298     controller: 'uiGridController',
3299     compile: function () {
3300       return {
3301         post: function ($scope, $elm, $attrs, uiGridCtrl) {
3302           var grid = uiGridCtrl.grid;
3303           // Initialize scrollbars (TODO: move to controller??)
3304           uiGridCtrl.scrollbars = [];
3305           grid.element = $elm;
3306
3307
3308           // See if the grid has a rendered width, if not, wait a bit and try again
3309           var sizeCheckInterval = 100; // ms
3310           var maxSizeChecks = 20; // 2 seconds total
3311           var sizeChecks = 0;
3312
3313           // Setup (event listeners) the grid
3314           setup();
3315
3316           // And initialize it
3317           init();
3318
3319           // Mark rendering complete so API events can happen
3320           grid.renderingComplete();
3321
3322           // If the grid doesn't have size currently, wait for a bit to see if it gets size
3323           checkSize();
3324
3325           /*-- Methods --*/
3326
3327           function checkSize() {
3328             // If the grid has no width and we haven't checked more than <maxSizeChecks> times, check again in <sizeCheckInterval> milliseconds
3329             if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
3330               setTimeout(checkSize, sizeCheckInterval);
3331               sizeChecks++;
3332             }
3333             else {
3334               $timeout(init);
3335             }
3336           }
3337
3338           // Setup event listeners and watchers
3339           function setup() {
3340             // Bind to window resize events
3341             angular.element($window).on('resize', gridResize);
3342
3343             // Unbind from window resize events when the grid is destroyed
3344             $elm.on('$destroy', function () {
3345               angular.element($window).off('resize', gridResize);
3346             });
3347
3348             // If we add a left container after render, we need to watch and react
3349             $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3350               if (newValue === oldValue) {
3351                 return;
3352               }
3353               grid.refreshCanvas(true);
3354             });
3355
3356             // If we add a right container after render, we need to watch and react
3357             $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3358               if (newValue === oldValue) {
3359                 return;
3360               }
3361               grid.refreshCanvas(true);
3362             });
3363           }
3364
3365           // Initialize the directive
3366           function init() {
3367             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3368
3369             // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3370             grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3371
3372             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3373
3374             // 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
3375             if (grid.gridHeight <= grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3376               autoAdjustHeight();
3377             }
3378
3379             // Run initial canvas refresh
3380             grid.refreshCanvas(true);
3381           }
3382
3383           // Set the grid's height ourselves in the case that its height would be unusably small
3384           function autoAdjustHeight() {
3385             // Figure out the new height
3386             var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3387             var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
3388             var footerHeight = grid.calcFooterHeight();
3389
3390             var scrollbarHeight = 0;
3391             if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3392               scrollbarHeight = gridUtil.getScrollbarWidth();
3393             }
3394
3395             var maxNumberOfFilters = 0;
3396             // Calculates the maximum number of filters in the columns
3397             angular.forEach(grid.options.columnDefs, function(col) {
3398               if (col.hasOwnProperty('filter')) {
3399                 if (maxNumberOfFilters < 1) {
3400                     maxNumberOfFilters = 1;
3401                 }
3402               }
3403               else if (col.hasOwnProperty('filters')) {
3404                 if (maxNumberOfFilters < col.filters.length) {
3405                     maxNumberOfFilters = col.filters.length;
3406                 }
3407               }
3408             });
3409
3410             if (grid.options.enableFiltering) {
3411               var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.every(function(col) {
3412                 return col.enableFiltering === false;
3413               });
3414
3415               if (!allColumnsHaveFilteringTurnedOff) {
3416                 maxNumberOfFilters++;
3417               }
3418             }
3419
3420             var filterHeight = maxNumberOfFilters * headerHeight;
3421
3422             var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3423
3424             $elm.css('height', newHeight + 'px');
3425
3426             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3427           }
3428
3429           // Resize the grid on window resize events
3430           function gridResize($event) {
3431             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3432             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3433
3434             grid.refreshCanvas(true);
3435           }
3436         }
3437       };
3438     }
3439   };
3440 }
3441
3442 })();
3443
3444 (function(){
3445   'use strict';
3446
3447   // TODO: rename this file to ui-grid-pinned-container.js
3448
3449   angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3450     return {
3451       restrict: 'EA',
3452       replace: true,
3453       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>',
3454       scope: {
3455         side: '=uiGridPinnedContainer'
3456       },
3457       require: '^uiGrid',
3458       compile: function compile() {
3459         return {
3460           post: function ($scope, $elm, $attrs, uiGridCtrl) {
3461             // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3462
3463             var grid = uiGridCtrl.grid;
3464
3465             var myWidth = 0;
3466
3467             $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3468
3469             // Monkey-patch the viewport width function
3470             if ($scope.side === 'left' || $scope.side === 'right') {
3471               grid.renderContainers[$scope.side].getViewportWidth = monkeyPatchedGetViewportWidth;
3472             }
3473
3474             function monkeyPatchedGetViewportWidth() {
3475               /*jshint validthis: true */
3476               var self = this;
3477
3478               var viewportWidth = 0;
3479               self.visibleColumnCache.forEach(function (column) {
3480                 viewportWidth += column.drawnWidth;
3481               });
3482
3483               var adjustment = self.getViewportAdjustment();
3484
3485               viewportWidth = viewportWidth + adjustment.width;
3486
3487               return viewportWidth;
3488             }
3489
3490             function updateContainerWidth() {
3491               if ($scope.side === 'left' || $scope.side === 'right') {
3492                 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3493                 var width = 0;
3494                 for (var i = 0; i < cols.length; i++) {
3495                   var col = cols[i];
3496                   width += col.drawnWidth || col.width || 0;
3497                 }
3498
3499                 return width;
3500               }
3501             }
3502
3503             function updateContainerDimensions() {
3504               var ret = '';
3505
3506               // Column containers
3507               if ($scope.side === 'left' || $scope.side === 'right') {
3508                 myWidth = updateContainerWidth();
3509
3510                 // gridUtil.logDebug('myWidth', myWidth);
3511
3512                 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3513                 $elm.attr('style', null);
3514
3515              //   var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3516
3517                 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; } ';
3518               }
3519
3520               return ret;
3521             }
3522
3523             grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3524               myWidth = updateContainerWidth();
3525
3526               // Subtract our own width
3527               adjustment.width -= myWidth;
3528               adjustment.side = $scope.side;
3529
3530               return adjustment;
3531             });
3532
3533             // Register style computation to adjust for columns in `side`'s render container
3534             grid.registerStyleComputation({
3535               priority: 15,
3536               func: updateContainerDimensions
3537             });
3538           }
3539         };
3540       }
3541     };
3542   }]);
3543 })();
3544
3545 (function(){
3546
3547 angular.module('ui.grid')
3548 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
3549     function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout, ScrollEvent) {
3550
3551   /**
3552    * @ngdoc object
3553    * @name ui.grid.core.api:PublicApi
3554    * @description Public Api for the core grid features
3555    *
3556    */
3557
3558   /**
3559    * @ngdoc function
3560    * @name ui.grid.class:Grid
3561    * @description Grid is the main viewModel.  Any properties or methods needed to maintain state are defined in
3562    * this prototype.  One instance of Grid is created per Grid directive instance.
3563    * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3564    */
3565   var Grid = function Grid(options) {
3566     var self = this;
3567     // Get the id out of the options, then remove it
3568     if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3569       if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3570         throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3571       }
3572     }
3573     else {
3574       throw new Error('No ID provided. An ID must be given when creating a grid.');
3575     }
3576
3577     self.id = options.id;
3578     delete options.id;
3579
3580     // Get default options
3581     self.options = GridOptions.initialize( options );
3582
3583     /**
3584      * @ngdoc object
3585      * @name appScope
3586      * @propertyOf ui.grid.class:Grid
3587      * @description reference to the application scope (the parent scope of the ui-grid element).  Assigned in ui-grid controller
3588      * <br/>
3589      * use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
3590      */
3591     self.appScope = self.options.appScopeProvider;
3592
3593     self.headerHeight = self.options.headerRowHeight;
3594
3595
3596     /**
3597      * @ngdoc object
3598      * @name footerHeight
3599      * @propertyOf ui.grid.class:Grid
3600      * @description returns the total footer height gridFooter + columnFooter
3601      */
3602     self.footerHeight = self.calcFooterHeight();
3603
3604
3605     /**
3606      * @ngdoc object
3607      * @name columnFooterHeight
3608      * @propertyOf ui.grid.class:Grid
3609      * @description returns the total column footer height
3610      */
3611     self.columnFooterHeight = self.calcColumnFooterHeight();
3612
3613     self.rtl = false;
3614     self.gridHeight = 0;
3615     self.gridWidth = 0;
3616     self.columnBuilders = [];
3617     self.rowBuilders = [];
3618     self.rowsProcessors = [];
3619     self.columnsProcessors = [];
3620     self.styleComputations = [];
3621     self.viewportAdjusters = [];
3622     self.rowHeaderColumns = [];
3623     self.dataChangeCallbacks = {};
3624     self.verticalScrollSyncCallBackFns = {};
3625     self.horizontalScrollSyncCallBackFns = {};
3626
3627     // self.visibleRowCache = [];
3628
3629     // Set of 'render' containers for self grid, which can render sets of rows
3630     self.renderContainers = {};
3631
3632     // Create a
3633     self.renderContainers.body = new GridRenderContainer('body', self);
3634
3635     self.cellValueGetterCache = {};
3636
3637     // Cached function to use with custom row templates
3638     self.getRowTemplateFn = null;
3639
3640
3641     //representation of the rows on the grid.
3642     //these are wrapped references to the actual data rows (options.data)
3643     self.rows = [];
3644
3645     //represents the columns on the grid
3646     self.columns = [];
3647
3648     /**
3649      * @ngdoc boolean
3650      * @name isScrollingVertically
3651      * @propertyOf ui.grid.class:Grid
3652      * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3653      */
3654     self.isScrollingVertically = false;
3655
3656     /**
3657      * @ngdoc boolean
3658      * @name isScrollingHorizontally
3659      * @propertyOf ui.grid.class:Grid
3660      * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3661      */
3662     self.isScrollingHorizontally = false;
3663
3664     /**
3665      * @ngdoc property
3666      * @name scrollDirection
3667      * @propertyOf ui.grid.class:Grid
3668      * @description set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells
3669      * us which direction we are scrolling. Set to NONE via debounced method
3670      */
3671     self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3672
3673     //if true, grid will not respond to any scroll events
3674     self.disableScrolling = false;
3675
3676
3677     function vertical (scrollEvent) {
3678       self.isScrollingVertically = false;
3679       self.api.core.raise.scrollEnd(scrollEvent);
3680       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3681     }
3682
3683     var debouncedVertical = gridUtil.debounce(vertical, self.options.scrollDebounce);
3684     var debouncedVerticalMinDelay = gridUtil.debounce(vertical, 0);
3685
3686     function horizontal (scrollEvent) {
3687       self.isScrollingHorizontally = false;
3688       self.api.core.raise.scrollEnd(scrollEvent);
3689       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3690     }
3691
3692     var debouncedHorizontal = gridUtil.debounce(horizontal, self.options.scrollDebounce);
3693     var debouncedHorizontalMinDelay = gridUtil.debounce(horizontal, 0);
3694
3695
3696     /**
3697      * @ngdoc function
3698      * @name flagScrollingVertically
3699      * @methodOf ui.grid.class:Grid
3700      * @description sets isScrollingVertically to true and sets it to false in a debounced function
3701      */
3702     self.flagScrollingVertically = function(scrollEvent) {
3703       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3704         self.api.core.raise.scrollBegin(scrollEvent);
3705       }
3706       self.isScrollingVertically = true;
3707       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3708         debouncedVerticalMinDelay(scrollEvent);
3709       }
3710       else {
3711         debouncedVertical(scrollEvent);
3712       }
3713     };
3714
3715     /**
3716      * @ngdoc function
3717      * @name flagScrollingHorizontally
3718      * @methodOf ui.grid.class:Grid
3719      * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3720      */
3721     self.flagScrollingHorizontally = function(scrollEvent) {
3722       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3723         self.api.core.raise.scrollBegin(scrollEvent);
3724       }
3725       self.isScrollingHorizontally = true;
3726       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3727         debouncedHorizontalMinDelay(scrollEvent);
3728       }
3729       else {
3730         debouncedHorizontal(scrollEvent);
3731       }
3732     };
3733
3734     self.scrollbarHeight = 0;
3735     self.scrollbarWidth = 0;
3736     if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3737       self.scrollbarHeight = gridUtil.getScrollbarWidth();
3738     }
3739
3740     if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3741       self.scrollbarWidth = gridUtil.getScrollbarWidth();
3742     }
3743
3744
3745
3746     self.api = new GridApi(self);
3747
3748     /**
3749      * @ngdoc function
3750      * @name refresh
3751      * @methodOf ui.grid.core.api:PublicApi
3752      * @description Refresh the rendered grid on screen.
3753      * The refresh method re-runs both the columnProcessors and the
3754      * rowProcessors, as well as calling refreshCanvas to update all
3755      * the grid sizing.  In general you should prefer to use queueGridRefresh
3756      * instead, which is basically a debounced version of refresh.
3757      *
3758      * If you only want to resize the grid, not regenerate all the rows
3759      * and columns, you should consider directly calling refreshCanvas instead.
3760      *
3761      */
3762     self.api.registerMethod( 'core', 'refresh', this.refresh );
3763
3764     /**
3765      * @ngdoc function
3766      * @name queueGridRefresh
3767      * @methodOf ui.grid.core.api:PublicApi
3768      * @description Request a refresh of the rendered grid on screen, if multiple
3769      * calls to queueGridRefresh are made within a digest cycle only one will execute.
3770      * The refresh method re-runs both the columnProcessors and the
3771      * rowProcessors, as well as calling refreshCanvas to update all
3772      * the grid sizing.  In general you should prefer to use queueGridRefresh
3773      * instead, which is basically a debounced version of refresh.
3774      *
3775      */
3776     self.api.registerMethod( 'core', 'queueGridRefresh', this.queueGridRefresh );
3777
3778     /**
3779      * @ngdoc function
3780      * @name refreshRows
3781      * @methodOf ui.grid.core.api:PublicApi
3782      * @description Runs only the rowProcessors, columns remain as they were.
3783      * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
3784      * @returns {promise} promise that is resolved when render completes?
3785      *
3786      */
3787     self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3788
3789     /**
3790      * @ngdoc function
3791      * @name queueRefresh
3792      * @methodOf ui.grid.core.api:PublicApi
3793      * @description Requests execution of refreshCanvas, if multiple requests are made
3794      * during a digest cycle only one will run.  RefreshCanvas updates the grid sizing.
3795      * @returns {promise} promise that is resolved when render completes?
3796      *
3797      */
3798     self.api.registerMethod( 'core', 'queueRefresh', this.queueRefresh );
3799
3800     /**
3801      * @ngdoc function
3802      * @name handleWindowResize
3803      * @methodOf ui.grid.core.api:PublicApi
3804      * @description Trigger a grid resize, normally this would be picked
3805      * up by a watch on window size, but in some circumstances it is necessary
3806      * to call this manually
3807      * @returns {promise} promise that is resolved when render completes?
3808      *
3809      */
3810     self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3811
3812
3813     /**
3814      * @ngdoc function
3815      * @name addRowHeaderColumn
3816      * @methodOf ui.grid.core.api:PublicApi
3817      * @description adds a row header column to the grid
3818      * @param {object} column def
3819      *
3820      */
3821     self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
3822
3823     /**
3824      * @ngdoc function
3825      * @name scrollToIfNecessary
3826      * @methodOf ui.grid.core.api:PublicApi
3827      * @description Scrolls the grid to make a certain row and column combo visible,
3828      *   in the case that it is not completely visible on the screen already.
3829      * @param {GridRow} gridRow row to make visible
3830      * @param {GridCol} gridCol column to make visible
3831      * @returns {promise} a promise that is resolved when scrolling is complete
3832      *
3833      */
3834     self.api.registerMethod( 'core', 'scrollToIfNecessary', function(gridRow, gridCol) { return self.scrollToIfNecessary(gridRow, gridCol);} );
3835
3836     /**
3837      * @ngdoc function
3838      * @name scrollTo
3839      * @methodOf ui.grid.core.api:PublicApi
3840      * @description Scroll the grid such that the specified
3841      * row and column is in view
3842      * @param {object} rowEntity gridOptions.data[] array instance to make visible
3843      * @param {object} colDef to make visible
3844      * @returns {promise} a promise that is resolved after any scrolling is finished
3845      */
3846     self.api.registerMethod( 'core', 'scrollTo', function (rowEntity, colDef) { return self.scrollTo(rowEntity, colDef);}  );
3847
3848     /**
3849      * @ngdoc function
3850      * @name registerRowsProcessor
3851      * @methodOf ui.grid.core.api:PublicApi
3852      * @description
3853      * Register a "rows processor" function. When the rows are updated,
3854      * the grid calls each registered "rows processor", which has a chance
3855      * to alter the set of rows (sorting, etc) as long as the count is not
3856      * modified.
3857      *
3858      * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
3859      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3860      * return the updated rows list, which is passed to the next processor in the chain
3861      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3862      * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
3863      *
3864      * At present allRowsVisible is running at 50, sort manipulations running at 60-65, filter is running at 100,
3865      * sort is at 200, grouping and treeview at 400-410, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
3866      */
3867     self.api.registerMethod( 'core', 'registerRowsProcessor', this.registerRowsProcessor  );
3868
3869     /**
3870      * @ngdoc function
3871      * @name registerColumnsProcessor
3872      * @methodOf ui.grid.core.api:PublicApi
3873      * @description
3874      * Register a "columns processor" function. When the columns are updated,
3875      * the grid calls each registered "columns processor", which has a chance
3876      * to alter the set of columns as long as the count is not
3877      * modified.
3878      *
3879      * @param {function(renderedColumnsToProcess, rows )} processorFunction columns processor function, which
3880      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3881      * return the updated columns list, which is passed to the next processor in the chain
3882      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3883      * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
3884      *
3885      * 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)
3886      */
3887     self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor  );
3888
3889
3890
3891     /**
3892      * @ngdoc function
3893      * @name sortHandleNulls
3894      * @methodOf ui.grid.core.api:PublicApi
3895      * @description A null handling method that can be used when building custom sort
3896      * functions
3897      * @example
3898      * <pre>
3899      *   mySortFn = function(a, b) {
3900      *   var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3901      *   if ( nulls !== null ){
3902      *     return nulls;
3903      *   } else {
3904      *     // your code for sorting here
3905      *   };
3906      * </pre>
3907      * @param {object} a sort value a
3908      * @param {object} b sort value b
3909      * @returns {number} null if there were no nulls/undefineds, otherwise returns
3910      * a sort value that should be passed back from the sort function
3911      *
3912      */
3913     self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
3914
3915
3916     /**
3917      * @ngdoc function
3918      * @name sortChanged
3919      * @methodOf  ui.grid.core.api:PublicApi
3920      * @description The sort criteria on one or more columns has
3921      * changed.  Provides as parameters the grid and the output of
3922      * getColumnSorting, which is an array of gridColumns
3923      * that have sorting on them, sorted in priority order.
3924      *
3925      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3926      * @param {Function} callBack Will be called when the event is emited. The function passes back an array of columns with
3927      * sorts on them, in priority order.
3928      *
3929      * @example
3930      * <pre>
3931      *      gridApi.core.on.sortChanged( $scope, function(sortColumns){
3932      *        // do something
3933      *      });
3934      * </pre>
3935      */
3936     self.api.registerEvent( 'core', 'sortChanged' );
3937
3938       /**
3939      * @ngdoc function
3940      * @name columnVisibilityChanged
3941      * @methodOf  ui.grid.core.api:PublicApi
3942      * @description The visibility of a column has changed,
3943      * the column itself is passed out as a parameter of the event.
3944      * 
3945      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3946      * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
3947      *
3948      * @example
3949      * <pre>
3950      *      gridApi.core.on.columnVisibilityChanged( $scope, function (column) {
3951      *        // do something
3952      *      } );
3953      * </pre>
3954      */
3955     self.api.registerEvent( 'core', 'columnVisibilityChanged' );
3956
3957     /**
3958      * @ngdoc method
3959      * @name notifyDataChange
3960      * @methodOf ui.grid.core.api:PublicApi
3961      * @description Notify the grid that a data or config change has occurred,
3962      * where that change isn't something the grid was otherwise noticing.  This
3963      * might be particularly relevant where you've changed values within the data
3964      * and you'd like cell classes to be re-evaluated, or changed config within
3965      * the columnDef and you'd like headerCellClasses to be re-evaluated.
3966      * @param {string} type one of the
3967      * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3968      * us which refreshes to fire.
3969      *
3970      */
3971     self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
3972
3973     /**
3974      * @ngdoc method
3975      * @name clearAllFilters
3976      * @methodOf ui.grid.core.api:PublicApi
3977      * @description Clears all filters and optionally refreshes the visible rows.
3978      * @param {object} refreshRows Defaults to true.
3979      * @param {object} clearConditions Defaults to false.
3980      * @param {object} clearFlags Defaults to false.
3981      * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
3982      */
3983     self.api.registerMethod('core', 'clearAllFilters', this.clearAllFilters);
3984
3985     self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
3986     self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
3987     self.registerDataChangeCallback( self.updateFooterHeightCallback, [uiGridConstants.dataChange.OPTIONS]);
3988
3989     self.registerStyleComputation({
3990       priority: 10,
3991       func: self.getFooterStyles
3992     });
3993   };
3994
3995    Grid.prototype.calcFooterHeight = function () {
3996      if (!this.hasFooter()) {
3997        return 0;
3998      }
3999
4000      var height = 0;
4001      if (this.options.showGridFooter) {
4002        height += this.options.gridFooterHeight;
4003      }
4004
4005      height += this.calcColumnFooterHeight();
4006
4007      return height;
4008    };
4009
4010    Grid.prototype.calcColumnFooterHeight = function () {
4011      var height = 0;
4012
4013      if (this.options.showColumnFooter) {
4014        height += this.options.columnFooterHeight;
4015      }
4016
4017      return height;
4018    };
4019
4020    Grid.prototype.getFooterStyles = function () {
4021      var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }';
4022      style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }';
4023      return style;
4024    };
4025
4026   Grid.prototype.hasFooter = function () {
4027    return this.options.showGridFooter || this.options.showColumnFooter;
4028   };
4029
4030   /**
4031    * @ngdoc function
4032    * @name isRTL
4033    * @methodOf ui.grid.class:Grid
4034    * @description Returns true if grid is RightToLeft
4035    */
4036   Grid.prototype.isRTL = function () {
4037     return this.rtl;
4038   };
4039
4040
4041   /**
4042    * @ngdoc function
4043    * @name registerColumnBuilder
4044    * @methodOf ui.grid.class:Grid
4045    * @description When the build creates columns from column definitions, the columnbuilders will be called to add
4046    * additional properties to the column.
4047    * @param {function(colDef, col, gridOptions)} columnBuilder function to be called
4048    */
4049   Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
4050     this.columnBuilders.push(columnBuilder);
4051   };
4052
4053   /**
4054    * @ngdoc function
4055    * @name buildColumnDefsFromData
4056    * @methodOf ui.grid.class:Grid
4057    * @description Populates columnDefs from the provided data
4058    * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
4059    */
4060   Grid.prototype.buildColumnDefsFromData = function (dataRows){
4061     this.options.columnDefs =  gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
4062   };
4063
4064   /**
4065    * @ngdoc function
4066    * @name registerRowBuilder
4067    * @methodOf ui.grid.class:Grid
4068    * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
4069    * additional properties to the row.
4070    * @param {function(row, gridOptions)} rowBuilder function to be called
4071    */
4072   Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
4073     this.rowBuilders.push(rowBuilder);
4074   };
4075
4076
4077   /**
4078    * @ngdoc function
4079    * @name registerDataChangeCallback
4080    * @methodOf ui.grid.class:Grid
4081    * @description When a data change occurs, the data change callbacks of the specified type
4082    * will be called.  The rules are:
4083    *
4084    * - when the data watch fires, that is considered a ROW change (the data watch only notices
4085    *   added or removed rows)
4086    * - when the api is called to inform us of a change, the declared type of that change is used
4087    * - when a cell edit completes, the EDIT callbacks are triggered
4088    * - when the columnDef watch fires, the COLUMN callbacks are triggered
4089    * - when the options watch fires, the OPTIONS callbacks are triggered
4090    *
4091    * For a given event:
4092    * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks
4093    * - ROW calls ROW and ALL callbacks
4094    * - EDIT calls EDIT and ALL callbacks
4095    * - COLUMN calls COLUMN and ALL callbacks
4096    * - OPTIONS calls OPTIONS and ALL callbacks
4097    *
4098    * @param {function(grid)} callback function to be called
4099    * @param {array} types the types of data change you want to be informed of.  Values from
4100    * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ).  Optional and defaults to
4101    * ALL
4102    * @returns {function} deregister function - a function that can be called to deregister this callback
4103    */
4104   Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4105     var uid = gridUtil.nextUid();
4106     if ( !types ){
4107       types = [uiGridConstants.dataChange.ALL];
4108     }
4109     if ( !Array.isArray(types)){
4110       gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
4111     }
4112     this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4113
4114     var self = this;
4115     var deregisterFunction = function() {
4116       delete self.dataChangeCallbacks[uid];
4117     };
4118     return deregisterFunction;
4119   };
4120
4121   /**
4122    * @ngdoc function
4123    * @name callDataChangeCallbacks
4124    * @methodOf ui.grid.class:Grid
4125    * @description Calls the callbacks based on the type of data change that
4126    * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
4127    * event type is matching, or if the type is ALL.
4128    * @param {number} type the type of event that occurred - one of the
4129    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4130    */
4131   Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4132     angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4133       if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
4134            callback.types.indexOf( type ) !== -1 ||
4135            type === uiGridConstants.dataChange.ALL ) {
4136         if (callback._this) {
4137            callback.callback.apply(callback._this,this);
4138         }
4139         else {
4140           callback.callback( this );
4141         }
4142       }
4143     }, this);
4144   };
4145
4146   /**
4147    * @ngdoc function
4148    * @name notifyDataChange
4149    * @methodOf ui.grid.class:Grid
4150    * @description Notifies us that a data change has occurred, used in the public
4151    * api for users to tell us when they've changed data or some other event that
4152    * our watches cannot pick up
4153    * @param {string} type the type of event that occurred - one of the
4154    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4155    */
4156   Grid.prototype.notifyDataChange = function notifyDataChange(type) {
4157     var constants = uiGridConstants.dataChange;
4158     if ( type === constants.ALL ||
4159          type === constants.COLUMN ||
4160          type === constants.EDIT ||
4161          type === constants.ROW ||
4162          type === constants.OPTIONS ){
4163       this.callDataChangeCallbacks( type );
4164     } else {
4165       gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
4166     }
4167   };
4168
4169
4170   /**
4171    * @ngdoc function
4172    * @name columnRefreshCallback
4173    * @methodOf ui.grid.class:Grid
4174    * @description refreshes the grid when a column refresh
4175    * is notified, which triggers handling of the visible flag.
4176    * This is called on uiGridConstants.dataChange.COLUMN, and is
4177    * registered as a dataChangeCallback in grid.js
4178    * @param {string} name column name
4179    */
4180   Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4181     grid.buildColumns();
4182     grid.queueGridRefresh();
4183   };
4184
4185
4186   /**
4187    * @ngdoc function
4188    * @name processRowsCallback
4189    * @methodOf ui.grid.class:Grid
4190    * @description calls the row processors, specifically
4191    * intended to reset the sorting when an edit is called,
4192    * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4193    * @param {string} name column name
4194    */
4195   Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4196     grid.queueGridRefresh();
4197   };
4198
4199
4200   /**
4201    * @ngdoc function
4202    * @name updateFooterHeightCallback
4203    * @methodOf ui.grid.class:Grid
4204    * @description recalculates the footer height,
4205    * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4206    * @param {string} name column name
4207    */
4208   Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4209     grid.footerHeight = grid.calcFooterHeight();
4210     grid.columnFooterHeight = grid.calcColumnFooterHeight();
4211   };
4212
4213
4214   /**
4215    * @ngdoc function
4216    * @name getColumn
4217    * @methodOf ui.grid.class:Grid
4218    * @description returns a grid column for the column name
4219    * @param {string} name column name
4220    */
4221   Grid.prototype.getColumn = function getColumn(name) {
4222     var columns = this.columns.filter(function (column) {
4223       return column.colDef.name === name;
4224     });
4225     return columns.length > 0 ? columns[0] : null;
4226   };
4227
4228   /**
4229    * @ngdoc function
4230    * @name getColDef
4231    * @methodOf ui.grid.class:Grid
4232    * @description returns a grid colDef for the column name
4233    * @param {string} name column.field
4234    */
4235   Grid.prototype.getColDef = function getColDef(name) {
4236     var colDefs = this.options.columnDefs.filter(function (colDef) {
4237       return colDef.name === name;
4238     });
4239     return colDefs.length > 0 ? colDefs[0] : null;
4240   };
4241
4242   /**
4243    * @ngdoc function
4244    * @name assignTypes
4245    * @methodOf ui.grid.class:Grid
4246    * @description uses the first row of data to assign colDef.type for any types not defined.
4247    */
4248   /**
4249    * @ngdoc property
4250    * @name type
4251    * @propertyOf ui.grid.class:GridOptions.columnDef
4252    * @description the type of the column, used in sorting.  If not provided then the
4253    * grid will guess the type.  Add this only if the grid guessing is not to your
4254    * satisfaction.  One of:
4255    * - 'string'
4256    * - 'boolean'
4257    * - 'number'
4258    * - 'date'
4259    * - 'object'
4260    * - 'numberStr'
4261    * Note that if you choose date, your dates should be in a javascript date type
4262    *
4263    */
4264   Grid.prototype.assignTypes = function(){
4265     var self = this;
4266     self.options.columnDefs.forEach(function (colDef, index) {
4267
4268       //Assign colDef type if not specified
4269       if (!colDef.type) {
4270         var col = new GridColumn(colDef, index, self);
4271         var firstRow = self.rows.length > 0 ? self.rows[0] : null;
4272         if (firstRow) {
4273           colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
4274         }
4275         else {
4276           colDef.type = 'string';
4277         }
4278       }
4279     });
4280   };
4281
4282
4283   /**
4284    * @ngdoc function
4285    * @name isRowHeaderColumn
4286    * @methodOf ui.grid.class:Grid
4287    * @description returns true if the column is a row Header
4288    * @param {object} column column
4289    */
4290   Grid.prototype.isRowHeaderColumn = function isRowHeaderColumn(column) {
4291     return this.rowHeaderColumns.indexOf(column) !== -1;
4292   };
4293
4294   /**
4295   * @ngdoc function
4296   * @name addRowHeaderColumn
4297   * @methodOf ui.grid.class:Grid
4298   * @description adds a row header column to the grid
4299   * @param {object} column def
4300   */
4301   Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
4302     var self = this;
4303     var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
4304     rowHeaderCol.isRowHeader = true;
4305     if (self.isRTL()) {
4306       self.createRightContainer();
4307       rowHeaderCol.renderContainer = 'right';
4308     }
4309     else {
4310       self.createLeftContainer();
4311       rowHeaderCol.renderContainer = 'left';
4312     }
4313
4314     // relies on the default column builder being first in array, as it is instantiated
4315     // as part of grid creation
4316     self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4317       .then(function(){
4318         rowHeaderCol.enableFiltering = false;
4319         rowHeaderCol.enableSorting = false;
4320         rowHeaderCol.enableHiding = false;
4321         self.rowHeaderColumns.push(rowHeaderCol);
4322         self.buildColumns()
4323           .then( function() {
4324             self.preCompileCellTemplates();
4325             self.queueGridRefresh();
4326           });
4327       });
4328   };
4329
4330   /**
4331    * @ngdoc function
4332    * @name getOnlyDataColumns
4333    * @methodOf ui.grid.class:Grid
4334    * @description returns all columns except for rowHeader columns
4335    */
4336   Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4337     var self = this;
4338     var cols = [];
4339     self.columns.forEach(function (col) {
4340       if (self.rowHeaderColumns.indexOf(col) === -1) {
4341         cols.push(col);
4342       }
4343     });
4344     return cols;
4345   };
4346
4347   /**
4348    * @ngdoc function
4349    * @name buildColumns
4350    * @methodOf ui.grid.class:Grid
4351    * @description creates GridColumn objects from the columnDefinition.  Calls each registered
4352    * columnBuilder to further process the column
4353    * @param {object} options  An object contains options to use when building columns
4354    *
4355    * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
4356    *
4357    * @returns {Promise} a promise to load any needed column resources
4358    */
4359   Grid.prototype.buildColumns = function buildColumns(opts) {
4360     var options = {
4361       orderByColumnDefs: false
4362     };
4363
4364     angular.extend(options, opts);
4365
4366     // gridUtil.logDebug('buildColumns');
4367     var self = this;
4368     var builderPromises = [];
4369     var headerOffset = self.rowHeaderColumns.length;
4370     var i;
4371
4372     // Remove any columns for which a columnDef cannot be found
4373     // Deliberately don't use forEach, as it doesn't like splice being called in the middle
4374     // Also don't cache columns.length, as it will change during this operation
4375     for (i = 0; i < self.columns.length; i++){
4376       if (!self.getColDef(self.columns[i].name)) {
4377         self.columns.splice(i, 1);
4378         i--;
4379       }
4380     }
4381
4382     //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4383     self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
4384       self.columns.unshift(rowHeaderColumn);
4385     });
4386
4387
4388     // look at each column def, and update column properties to match.  If the column def
4389     // doesn't have a column, then splice in a new gridCol
4390     self.options.columnDefs.forEach(function (colDef, index) {
4391       self.preprocessColDef(colDef);
4392       var col = self.getColumn(colDef.name);
4393
4394       if (!col) {
4395         col = new GridColumn(colDef, gridUtil.nextUid(), self);
4396         self.columns.splice(index + headerOffset, 0, col);
4397       }
4398       else {
4399         // tell updateColumnDef that the column was pre-existing
4400         col.updateColumnDef(colDef, false);
4401       }
4402
4403       self.columnBuilders.forEach(function (builder) {
4404         builderPromises.push(builder.call(self, colDef, col, self.options));
4405       });
4406     });
4407
4408     /*** Reorder columns if necessary ***/
4409     if (!!options.orderByColumnDefs) {
4410       // Create a shallow copy of the columns as a cache
4411       var columnCache = self.columns.slice(0);
4412
4413       // We need to allow for the "row headers" when mapping from the column defs array to the columns array
4414       //   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]
4415
4416       // 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
4417       // columns will be shorter than columnDefs.  In this situation we'll avoid an error, but the user will still get an unexpected result
4418       var len = Math.min(self.options.columnDefs.length, self.columns.length);
4419       for (i = 0; i < len; i++) {
4420         // If the column at this index has a different name than the column at the same index in the column defs...
4421         if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) {
4422           // Replace the one in the cache with the appropriate column
4423           columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name);
4424         }
4425         else {
4426           // Otherwise just copy over the one from the initial columns
4427           columnCache[i + headerOffset] = self.columns[i + headerOffset];
4428         }
4429       }
4430
4431       // Empty out the columns array, non-destructively
4432       self.columns.length = 0;
4433
4434       // And splice in the updated, ordered columns from the cache
4435       Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
4436     }
4437
4438     return $q.all(builderPromises).then(function(){
4439       if (self.rows.length > 0){
4440         self.assignTypes();
4441       }
4442     });
4443   };
4444
4445 /**
4446  * @ngdoc function
4447  * @name preCompileCellTemplates
4448  * @methodOf ui.grid.class:Grid
4449  * @description precompiles all cell templates
4450  */
4451   Grid.prototype.preCompileCellTemplates = function() {
4452     var self = this;
4453
4454     var preCompileTemplate = function( col ) {
4455       var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4456       html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4457
4458       var compiledElementFn = $compile(html);
4459       col.compiledElementFn = compiledElementFn;
4460
4461       if (col.compiledElementFnDefer) {
4462         col.compiledElementFnDefer.resolve(col.compiledElementFn);
4463       }
4464     };
4465
4466     this.columns.forEach(function (col) {
4467       if ( col.cellTemplate ){
4468         preCompileTemplate( col );
4469       } else if ( col.cellTemplatePromise ){
4470         col.cellTemplatePromise.then( function() {
4471           preCompileTemplate( col );
4472         });
4473       }
4474     });
4475   };
4476
4477   /**
4478    * @ngdoc function
4479    * @name getGridQualifiedColField
4480    * @methodOf ui.grid.class:Grid
4481    * @description Returns the $parse-able accessor for a column within its $scope
4482    * @param {GridColumn} col col object
4483    */
4484   Grid.prototype.getQualifiedColField = function (col) {
4485     return 'row.entity.' + gridUtil.preEval(col.field);
4486   };
4487
4488   /**
4489    * @ngdoc function
4490    * @name createLeftContainer
4491    * @methodOf ui.grid.class:Grid
4492    * @description creates the left render container if it doesn't already exist
4493    */
4494   Grid.prototype.createLeftContainer = function() {
4495     if (!this.hasLeftContainer()) {
4496       this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
4497     }
4498   };
4499
4500   /**
4501    * @ngdoc function
4502    * @name createRightContainer
4503    * @methodOf ui.grid.class:Grid
4504    * @description creates the right render container if it doesn't already exist
4505    */
4506   Grid.prototype.createRightContainer = function() {
4507     if (!this.hasRightContainer()) {
4508       this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
4509     }
4510   };
4511
4512   /**
4513    * @ngdoc function
4514    * @name hasLeftContainer
4515    * @methodOf ui.grid.class:Grid
4516    * @description returns true if leftContainer exists
4517    */
4518   Grid.prototype.hasLeftContainer = function() {
4519     return this.renderContainers.left !== undefined;
4520   };
4521
4522   /**
4523    * @ngdoc function
4524    * @name hasRightContainer
4525    * @methodOf ui.grid.class:Grid
4526    * @description returns true if rightContainer exists
4527    */
4528   Grid.prototype.hasRightContainer = function() {
4529     return this.renderContainers.right !== undefined;
4530   };
4531
4532
4533       /**
4534    * undocumented function
4535    * @name preprocessColDef
4536    * @methodOf ui.grid.class:Grid
4537    * @description defaults the name property from field to maintain backwards compatibility with 2.x
4538    * validates that name or field is present
4539    */
4540   Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
4541     var self = this;
4542
4543     if (!colDef.field && !colDef.name) {
4544       throw new Error('colDef.name or colDef.field property is required');
4545     }
4546
4547     //maintain backwards compatibility with 2.x
4548     //field was required in 2.x.  now name is required
4549     if (colDef.name === undefined && colDef.field !== undefined) {
4550       // See if the column name already exists:
4551       var newName = colDef.field,
4552         counter = 2;
4553       while (self.getColumn(newName)) {
4554         newName = colDef.field + counter.toString();
4555         counter++;
4556       }
4557       colDef.name = newName;
4558     }
4559   };
4560
4561   // 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
4562   Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
4563     var self = this;
4564
4565     var t = [];
4566     for (var i = 0; i < n.length; i++) {
4567       var nV = nAccessor ? n[i][nAccessor] : n[i];
4568
4569       var found = false;
4570       for (var j = 0; j < o.length; j++) {
4571         var oV = oAccessor ? o[j][oAccessor] : o[j];
4572         if (self.options.rowEquality(nV, oV)) {
4573           found = true;
4574           break;
4575         }
4576       }
4577       if (!found) {
4578         t.push(nV);
4579       }
4580     }
4581
4582     return t;
4583   };
4584
4585   /**
4586    * @ngdoc function
4587    * @name getRow
4588    * @methodOf ui.grid.class:Grid
4589    * @description returns the GridRow that contains the rowEntity
4590    * @param {object} rowEntity the gridOptions.data array element instance
4591    * @param {array} rows [optional] the rows to look in - if not provided then
4592    * looks in grid.rows
4593    */
4594   Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
4595     var self = this;
4596
4597     lookInRows = typeof(lookInRows) === 'undefined' ? self.rows : lookInRows;
4598
4599     var rows = lookInRows.filter(function (row) {
4600       return self.options.rowEquality(row.entity, rowEntity);
4601     });
4602     return rows.length > 0 ? rows[0] : null;
4603   };
4604
4605
4606   /**
4607    * @ngdoc function
4608    * @name modifyRows
4609    * @methodOf ui.grid.class:Grid
4610    * @description creates or removes GridRow objects from the newRawData array.  Calls each registered
4611    * rowBuilder to further process the row
4612    * @param {array} newRawData Modified set of data
4613    *
4614    * This method aims to achieve three things:
4615    * 1. the resulting rows array is in the same order as the newRawData, we'll call
4616    * rowsProcessors immediately after to sort the data anyway
4617    * 2. if we have row hashing available, we try to use the rowHash to find the row
4618    * 3. no memory leaks - rows that are no longer in newRawData need to be garbage collected
4619    *
4620    * The basic logic flow makes use of the newRawData, oldRows and oldHash, and creates
4621    * the newRows and newHash
4622    *
4623    * ```
4624    * newRawData.forEach newEntity
4625    *   if (hashing enabled)
4626    *     check oldHash for newEntity
4627    *   else
4628    *     look for old row directly in oldRows
4629    *   if !oldRowFound     // must be a new row
4630    *     create newRow
4631    *   append to the newRows and add to newHash
4632    *   run the processors
4633    * ```
4634    * 
4635    * Rows are identified using the hashKey if configured.  If not configured, then rows
4636    * are identified using the gridOptions.rowEquality function
4637    * 
4638    * This method is useful when trying to select rows immediately after loading data without
4639    * using a $timeout/$interval, e.g.:
4640    * 
4641    *   $scope.gridOptions.data =  someData;
4642    *   $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
4643    *   $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4644    * 
4645    * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
4646    * originally selected rows to be re-selected))
4647    */
4648   Grid.prototype.modifyRows = function modifyRows(newRawData) {
4649     var self = this;
4650     var oldRows = self.rows.slice(0);
4651     var oldRowHash = self.rowHashMap || self.createRowHashMap();
4652     self.rowHashMap = self.createRowHashMap();
4653     self.rows.length = 0;
4654
4655     newRawData.forEach( function( newEntity, i ) {
4656       var newRow;
4657       if ( self.options.enableRowHashing ){
4658         // if hashing is enabled, then this row will be in the hash if we already know about it
4659         newRow = oldRowHash.get( newEntity );
4660       } else {
4661         // otherwise, manually search the oldRows to see if we can find this row
4662         newRow = self.getRow(newEntity, oldRows);
4663       }
4664
4665       // if we didn't find the row, it must be new, so create it
4666       if ( !newRow ){
4667         newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
4668       }
4669
4670       self.rows.push( newRow );
4671       self.rowHashMap.put( newEntity, newRow );
4672     });
4673
4674     self.assignTypes();
4675
4676     var p1 = $q.when(self.processRowsProcessors(self.rows))
4677       .then(function (renderableRows) {
4678         return self.setVisibleRows(renderableRows);
4679       });
4680
4681     var p2 = $q.when(self.processColumnsProcessors(self.columns))
4682       .then(function (renderableColumns) {
4683         return self.setVisibleColumns(renderableColumns);
4684       });
4685
4686     return $q.all([p1, p2]);
4687   };
4688
4689
4690   /**
4691    * Private Undocumented Method
4692    * @name addRows
4693    * @methodOf ui.grid.class:Grid
4694    * @description adds the newRawData array of rows to the grid and calls all registered
4695    * rowBuilders. this keyword will reference the grid
4696    */
4697   Grid.prototype.addRows = function addRows(newRawData) {
4698     var self = this;
4699
4700     var existingRowCount = self.rows.length;
4701     for (var i = 0; i < newRawData.length; i++) {
4702       var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4703
4704       if (self.options.enableRowHashing) {
4705         var found = self.rowHashMap.get(newRow.entity);
4706         if (found) {
4707           found.row = newRow;
4708         }
4709       }
4710
4711       self.rows.push(newRow);
4712     }
4713   };
4714
4715   /**
4716    * @ngdoc function
4717    * @name processRowBuilders
4718    * @methodOf ui.grid.class:Grid
4719    * @description processes all RowBuilders for the gridRow
4720    * @param {GridRow} gridRow reference to gridRow
4721    * @returns {GridRow} the gridRow with all additional behavior added
4722    */
4723   Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4724     var self = this;
4725
4726     self.rowBuilders.forEach(function (builder) {
4727       builder.call(self, gridRow, self.options);
4728     });
4729
4730     return gridRow;
4731   };
4732
4733   /**
4734    * @ngdoc function
4735    * @name registerStyleComputation
4736    * @methodOf ui.grid.class:Grid
4737    * @description registered a styleComputation function
4738    *
4739    * If the function returns a value it will be appended into the grid's `<style>` block
4740    * @param {function($scope)} styleComputation function
4741    */
4742   Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4743     this.styleComputations.push(styleComputationInfo);
4744   };
4745
4746
4747   // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4748   // Grid.prototype.registerRowFilter = function(filter) {
4749   //   // TODO(c0bra): validate filter?
4750
4751   //   this.rowFilters.push(filter);
4752   // };
4753
4754   // Grid.prototype.removeRowFilter = function(filter) {
4755   //   var idx = this.rowFilters.indexOf(filter);
4756
4757   //   if (typeof(idx) !== 'undefined' && idx !== undefined) {
4758   //     this.rowFilters.slice(idx, 1);
4759   //   }
4760   // };
4761
4762   // Grid.prototype.processRowFilters = function(rows) {
4763   //   var self = this;
4764   //   self.rowFilters.forEach(function (filter) {
4765   //     filter.call(self, rows);
4766   //   });
4767   // };
4768
4769
4770   /**
4771    * @ngdoc function
4772    * @name registerRowsProcessor
4773    * @methodOf ui.grid.class:Grid
4774    * @description
4775    *
4776    * Register a "rows processor" function. When the rows are updated,
4777    * the grid calls each registered "rows processor", which has a chance
4778    * to alter the set of rows (sorting, etc) as long as the count is not
4779    * modified.
4780    *
4781    * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4782    * is run in the context of the grid (i.e. this for the function will be the grid), and must
4783    * return the updated rows list, which is passed to the next processor in the chain
4784    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4785    * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
4786    *
4787    * 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)
4788    *
4789    */
4790   Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor, priority) {
4791     if (!angular.isFunction(processor)) {
4792       throw 'Attempt to register non-function rows processor: ' + processor;
4793     }
4794
4795     this.rowsProcessors.push({processor: processor, priority: priority});
4796     this.rowsProcessors.sort(function sortByPriority( a, b ){
4797       return a.priority - b.priority;
4798     });
4799   };
4800
4801   /**
4802    * @ngdoc function
4803    * @name removeRowsProcessor
4804    * @methodOf ui.grid.class:Grid
4805    * @param {function(renderableRows)} rows processor function
4806    * @description Remove a registered rows processor
4807    */
4808   Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
4809     var idx = -1;
4810     this.rowsProcessors.forEach(function(rowsProcessor, index){
4811       if ( rowsProcessor.processor === processor ){
4812         idx = index;
4813       }
4814     });
4815
4816     if ( idx !== -1 ) {
4817       this.rowsProcessors.splice(idx, 1);
4818     }
4819   };
4820
4821   /**
4822    * Private Undocumented Method
4823    * @name processRowsProcessors
4824    * @methodOf ui.grid.class:Grid
4825    * @param {Array[GridRow]} The array of "renderable" rows
4826    * @param {Array[GridColumn]} The array of columns
4827    * @description Run all the registered rows processors on the array of renderable rows
4828    */
4829   Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
4830     var self = this;
4831
4832     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4833     var myRenderableRows = renderableRows.slice(0);
4834
4835     // Return myRenderableRows with no processing if we have no rows processors
4836     if (self.rowsProcessors.length === 0) {
4837       return $q.when(myRenderableRows);
4838     }
4839
4840     // Counter for iterating through rows processors
4841     var i = 0;
4842
4843     // Promise for when we're done with all the processors
4844     var finished = $q.defer();
4845
4846     // This function will call the processor in self.rowsProcessors at index 'i', and then
4847     //   when done will call the next processor in the list, using the output from the processor
4848     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4849     //
4850     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4851     //   the result.
4852     function startProcessor(i, renderedRowsToProcess) {
4853       // Get the processor at 'i'
4854       var processor = self.rowsProcessors[i].processor;
4855
4856       // Call the processor, passing in the rows to process and the current columns
4857       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4858       return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
4859         .then(function handleProcessedRows(processedRows) {
4860           // Check for errors
4861           if (!processedRows) {
4862             throw "Processor at index " + i + " did not return a set of renderable rows";
4863           }
4864
4865           if (!angular.isArray(processedRows)) {
4866             throw "Processor at index " + i + " did not return an array";
4867           }
4868
4869           // Processor is done, increment the counter
4870           i++;
4871
4872           // If we're not done with the processors, call the next one
4873           if (i <= self.rowsProcessors.length - 1) {
4874             return startProcessor(i, processedRows);
4875           }
4876           // We're done! Resolve the 'finished' promise
4877           else {
4878             finished.resolve(processedRows);
4879           }
4880         });
4881     }
4882
4883     // Start on the first processor
4884     startProcessor(0, myRenderableRows);
4885
4886     return finished.promise;
4887   };
4888
4889   Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
4890     var self = this;
4891
4892     // Reset all the render container row caches
4893     for (var i in self.renderContainers) {
4894       var container = self.renderContainers[i];
4895
4896       container.canvasHeightShouldUpdate = true;
4897
4898       if ( typeof(container.visibleRowCache) === 'undefined' ){
4899         container.visibleRowCache = [];
4900       } else {
4901         container.visibleRowCache.length = 0;
4902       }
4903     }
4904
4905     // rows.forEach(function (row) {
4906     for (var ri = 0; ri < rows.length; ri++) {
4907       var row = rows[ri];
4908
4909       var targetContainer = (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) ? row.renderContainer : 'body';
4910
4911       // If the row is visible
4912       if (row.visible) {
4913         self.renderContainers[targetContainer].visibleRowCache.push(row);
4914       }
4915     }
4916     self.api.core.raise.rowsRendered(this.api);
4917   };
4918
4919   /**
4920    * @ngdoc function
4921    * @name registerColumnsProcessor
4922    * @methodOf ui.grid.class:Grid
4923    * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
4924    * is run in the context of the grid (i.e. this for the function will be the grid), and
4925    * which must return an updated renderedColumnsToProcess which can be passed to the next processor
4926    * in the chain
4927    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4928    * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
4929    *
4930    * 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)
4931    * @description
4932
4933      Register a "columns processor" function. When the columns are updated,
4934      the grid calls each registered "columns processor", which has a chance
4935      to alter the set of columns, as long as the count is not modified.
4936    */
4937   Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor, priority) {
4938     if (!angular.isFunction(processor)) {
4939       throw 'Attempt to register non-function rows processor: ' + processor;
4940     }
4941
4942     this.columnsProcessors.push({processor: processor, priority: priority});
4943     this.columnsProcessors.sort(function sortByPriority( a, b ){
4944       return a.priority - b.priority;
4945     });
4946   };
4947
4948   Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
4949     var idx = this.columnsProcessors.indexOf(processor);
4950
4951     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4952       this.columnsProcessors.splice(idx, 1);
4953     }
4954   };
4955
4956   Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
4957     var self = this;
4958
4959     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4960     var myRenderableColumns = renderableColumns.slice(0);
4961
4962     // Return myRenderableRows with no processing if we have no rows processors
4963     if (self.columnsProcessors.length === 0) {
4964       return $q.when(myRenderableColumns);
4965     }
4966
4967     // Counter for iterating through rows processors
4968     var i = 0;
4969
4970     // Promise for when we're done with all the processors
4971     var finished = $q.defer();
4972
4973     // This function will call the processor in self.rowsProcessors at index 'i', and then
4974     //   when done will call the next processor in the list, using the output from the processor
4975     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4976     //
4977     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4978     //   the result.
4979     function startProcessor(i, renderedColumnsToProcess) {
4980       // Get the processor at 'i'
4981       var processor = self.columnsProcessors[i].processor;
4982
4983       // Call the processor, passing in the rows to process and the current columns
4984       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4985       return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
4986         .then(function handleProcessedRows(processedColumns) {
4987           // Check for errors
4988           if (!processedColumns) {
4989             throw "Processor at index " + i + " did not return a set of renderable rows";
4990           }
4991
4992           if (!angular.isArray(processedColumns)) {
4993             throw "Processor at index " + i + " did not return an array";
4994           }
4995
4996           // Processor is done, increment the counter
4997           i++;
4998
4999           // If we're not done with the processors, call the next one
5000           if (i <= self.columnsProcessors.length - 1) {
5001             return startProcessor(i, myRenderableColumns);
5002           }
5003           // We're done! Resolve the 'finished' promise
5004           else {
5005             finished.resolve(myRenderableColumns);
5006           }
5007         });
5008     }
5009
5010     // Start on the first processor
5011     startProcessor(0, myRenderableColumns);
5012
5013     return finished.promise;
5014   };
5015
5016   Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
5017     // gridUtil.logDebug('setVisibleColumns');
5018
5019     var self = this;
5020
5021     // Reset all the render container row caches
5022     for (var i in self.renderContainers) {
5023       var container = self.renderContainers[i];
5024
5025       container.visibleColumnCache.length = 0;
5026     }
5027
5028     for (var ci = 0; ci < columns.length; ci++) {
5029       var column = columns[ci];
5030
5031       // If the column is visible
5032       if (column.visible) {
5033         // If the column has a container specified
5034         if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
5035           self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
5036         }
5037         // If not, put it into the body container
5038         else {
5039           self.renderContainers.body.visibleColumnCache.push(column);
5040         }
5041       }
5042     }
5043   };
5044
5045   /**
5046    * @ngdoc function
5047    * @name handleWindowResize
5048    * @methodOf ui.grid.class:Grid
5049    * @description Triggered when the browser window resizes; automatically resizes the grid
5050    * @returns {Promise} A resolved promise once the window resize has completed.
5051    */
5052   Grid.prototype.handleWindowResize = function handleWindowResize($event) {
5053     var self = this;
5054
5055     self.gridWidth = gridUtil.elementWidth(self.element);
5056     self.gridHeight = gridUtil.elementHeight(self.element);
5057
5058     return self.queueRefresh();
5059   };
5060
5061   /**
5062    * @ngdoc function
5063    * @name queueRefresh
5064    * @methodOf ui.grid.class:Grid
5065    * @description queues a grid refreshCanvas, a way of debouncing all the refreshes we might otherwise issue
5066    */
5067   Grid.prototype.queueRefresh = function queueRefresh() {
5068     var self = this;
5069
5070     if (self.refreshCanceller) {
5071       $timeout.cancel(self.refreshCanceller);
5072     }
5073
5074     self.refreshCanceller = $timeout(function () {
5075       self.refreshCanvas(true);
5076     });
5077
5078     self.refreshCanceller.then(function () {
5079       self.refreshCanceller = null;
5080     });
5081
5082     return self.refreshCanceller;
5083   };
5084
5085
5086   /**
5087    * @ngdoc function
5088    * @name queueGridRefresh
5089    * @methodOf ui.grid.class:Grid
5090    * @description queues a grid refresh, a way of debouncing all the refreshes we might otherwise issue
5091    */
5092   Grid.prototype.queueGridRefresh = function queueGridRefresh() {
5093     var self = this;
5094
5095     if (self.gridRefreshCanceller) {
5096       $timeout.cancel(self.gridRefreshCanceller);
5097     }
5098
5099     self.gridRefreshCanceller = $timeout(function () {
5100       self.refresh(true);
5101     });
5102
5103     self.gridRefreshCanceller.then(function () {
5104       self.gridRefreshCanceller = null;
5105     });
5106
5107     return self.gridRefreshCanceller;
5108   };
5109
5110
5111   /**
5112    * @ngdoc function
5113    * @name updateCanvasHeight
5114    * @methodOf ui.grid.class:Grid
5115    * @description flags all render containers to update their canvas height
5116    */
5117   Grid.prototype.updateCanvasHeight = function updateCanvasHeight() {
5118     var self = this;
5119
5120     for (var containerId in self.renderContainers) {
5121       if (self.renderContainers.hasOwnProperty(containerId)) {
5122         var container = self.renderContainers[containerId];
5123         container.canvasHeightShouldUpdate = true;
5124       }
5125     }
5126   };
5127
5128   /**
5129    * @ngdoc function
5130    * @name buildStyles
5131    * @methodOf ui.grid.class:Grid
5132    * @description calls each styleComputation function
5133    */
5134   // TODO: this used to take $scope, but couldn't see that it was used
5135   Grid.prototype.buildStyles = function buildStyles() {
5136     // gridUtil.logDebug('buildStyles');
5137
5138     var self = this;
5139
5140     self.customStyles = '';
5141
5142     self.styleComputations
5143       .sort(function(a, b) {
5144         if (a.priority === null) { return 1; }
5145         if (b.priority === null) { return -1; }
5146         if (a.priority === null && b.priority === null) { return 0; }
5147         return a.priority - b.priority;
5148       })
5149       .forEach(function (compInfo) {
5150         // this used to provide $scope as a second parameter, but I couldn't find any
5151         // style builders that used it, so removed it as part of moving to grid from controller
5152         var ret = compInfo.func.call(self);
5153
5154         if (angular.isString(ret)) {
5155           self.customStyles += '\n' + ret;
5156         }
5157       });
5158   };
5159
5160
5161   Grid.prototype.minColumnsToRender = function minColumnsToRender() {
5162     var self = this;
5163     var viewport = this.getViewportWidth();
5164
5165     var min = 0;
5166     var totalWidth = 0;
5167     self.columns.forEach(function(col, i) {
5168       if (totalWidth < viewport) {
5169         totalWidth += col.drawnWidth;
5170         min++;
5171       }
5172       else {
5173         var currWidth = 0;
5174         for (var j = i; j >= i - min; j--) {
5175           currWidth += self.columns[j].drawnWidth;
5176         }
5177         if (currWidth < viewport) {
5178           min++;
5179         }
5180       }
5181     });
5182
5183     return min;
5184   };
5185
5186   Grid.prototype.getBodyHeight = function getBodyHeight() {
5187     // Start with the viewportHeight
5188     var bodyHeight = this.getViewportHeight();
5189
5190     // Add the horizontal scrollbar height if there is one
5191     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5192     //  bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5193     //}
5194
5195     return bodyHeight;
5196   };
5197
5198   // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
5199   // TODO(c0bra): account for footer height
5200   Grid.prototype.getViewportHeight = function getViewportHeight() {
5201     var self = this;
5202
5203     var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
5204
5205     // Account for native horizontal scrollbar, if present
5206     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5207     //  viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5208     //}
5209
5210     var adjustment = self.getViewportAdjustment();
5211
5212     viewPortHeight = viewPortHeight + adjustment.height;
5213
5214     //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5215
5216     return viewPortHeight;
5217   };
5218
5219   Grid.prototype.getViewportWidth = function getViewportWidth() {
5220     var self = this;
5221
5222     var viewPortWidth = this.gridWidth;
5223
5224     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5225     //  viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5226     //}
5227
5228     var adjustment = self.getViewportAdjustment();
5229
5230     viewPortWidth = viewPortWidth + adjustment.width;
5231
5232     //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5233
5234     return viewPortWidth;
5235   };
5236
5237   Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5238     var viewPortWidth = this.getViewportWidth();
5239
5240     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5241     //  viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5242     //}
5243
5244     return viewPortWidth;
5245   };
5246
5247   Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
5248     this.verticalScrollSyncCallBackFns[containerId] = callBackFn;
5249   };
5250
5251   Grid.prototype.addHorizontalScrollSync = function (containerId, callBackFn) {
5252     this.horizontalScrollSyncCallBackFns[containerId] = callBackFn;
5253   };
5254
5255 /**
5256  * Scroll needed containers by calling their ScrollSyncs
5257  * @param sourceContainerId the containerId that has already set it's top/left.
5258  *         can be empty string which means all containers need to set top/left
5259  * @param scrollEvent
5260  */
5261   Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5262
5263     if (scrollEvent.y) {
5264       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5265       var verts = ['body','left', 'right'];
5266
5267       this.flagScrollingVertically(scrollEvent);
5268
5269       if (sourceContainerId === 'body') {
5270         verts = ['left', 'right'];
5271       }
5272       else if (sourceContainerId === 'left') {
5273         verts = ['body', 'right'];
5274       }
5275       else if (sourceContainerId === 'right') {
5276         verts = ['body', 'left'];
5277       }
5278
5279       for (var i = 0; i < verts.length; i++) {
5280         var id = verts[i];
5281         if (this.verticalScrollSyncCallBackFns[id]) {
5282           this.verticalScrollSyncCallBackFns[id](scrollEvent);
5283         }
5284       }
5285
5286     }
5287
5288     if (scrollEvent.x) {
5289       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5290       var horizs = ['body','bodyheader', 'bodyfooter'];
5291
5292       this.flagScrollingHorizontally(scrollEvent);
5293       if (sourceContainerId === 'body') {
5294         horizs = ['bodyheader', 'bodyfooter'];
5295       }
5296
5297       for (var j = 0; j < horizs.length; j++) {
5298         var idh = horizs[j];
5299         if (this.horizontalScrollSyncCallBackFns[idh]) {
5300           this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
5301         }
5302       }
5303
5304     }
5305
5306   };
5307
5308   Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5309     this.viewportAdjusters.push(func);
5310   };
5311
5312   Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5313     var idx = this.viewportAdjusters.indexOf(func);
5314
5315     if (typeof(idx) !== 'undefined' && idx !== undefined) {
5316       this.viewportAdjusters.splice(idx, 1);
5317     }
5318   };
5319
5320   Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
5321     var self = this;
5322
5323     var adjustment = { height: 0, width: 0 };
5324
5325     self.viewportAdjusters.forEach(function (func) {
5326       adjustment = func.call(this, adjustment);
5327     });
5328
5329     return adjustment;
5330   };
5331
5332   Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5333     // var count = 0;
5334
5335     // this.rows.forEach(function (row) {
5336     //   if (row.visible) {
5337     //     count++;
5338     //   }
5339     // });
5340
5341     // return this.visibleRowCache.length;
5342     return this.renderContainers.body.visibleRowCache.length;
5343   };
5344
5345    Grid.prototype.getVisibleRows = function getVisibleRows() {
5346     return this.renderContainers.body.visibleRowCache;
5347    };
5348
5349   Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5350     // var count = 0;
5351
5352     // this.rows.forEach(function (row) {
5353     //   if (row.visible) {
5354     //     count++;
5355     //   }
5356     // });
5357
5358     // return this.visibleRowCache.length;
5359     return this.renderContainers.body.visibleColumnCache.length;
5360   };
5361
5362
5363   Grid.prototype.searchRows = function searchRows(renderableRows) {
5364     return rowSearcher.search(this, renderableRows, this.columns);
5365   };
5366
5367   Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
5368     return rowSorter.sort(this, renderableRows, this.columns);
5369   };
5370
5371   /**
5372    * @ngdoc function
5373    * @name getCellValue
5374    * @methodOf ui.grid.class:Grid
5375    * @description Gets the value of a cell for a particular row and column
5376    * @param {GridRow} row Row to access
5377    * @param {GridColumn} col Column to access
5378    */
5379   Grid.prototype.getCellValue = function getCellValue(row, col){
5380     if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
5381       return row.entity[ '$$' + col.uid].rendered;
5382     } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5383       return row.entity[col.field];
5384     } else {
5385       if (!col.cellValueGetterCache) {
5386         col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
5387       }
5388
5389       return col.cellValueGetterCache(row);
5390     }
5391   };
5392
5393   /**
5394    * @ngdoc function
5395    * @name getCellDisplayValue
5396    * @methodOf ui.grid.class:Grid
5397    * @description Gets the displayed value of a cell after applying any the `cellFilter`
5398    * @param {GridRow} row Row to access
5399    * @param {GridColumn} col Column to access
5400    */
5401   Grid.prototype.getCellDisplayValue = function getCellDisplayValue(row, col) {
5402     if ( !col.cellDisplayGetterCache ) {
5403       var custom_filter = col.cellFilter ? " | " + col.cellFilter : "";
5404
5405       if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
5406         col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5407       } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5408         col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5409       } else {
5410         col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
5411       }
5412     }
5413
5414     return col.cellDisplayGetterCache(row);
5415   };
5416
5417
5418   Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
5419     var self = this,
5420         p = 0;
5421
5422     self.columns.forEach(function (col) {
5423       if (col.sort && col.sort.priority && col.sort.priority > p) {
5424         p = col.sort.priority;
5425       }
5426     });
5427
5428     return p + 1;
5429   };
5430
5431   /**
5432    * @ngdoc function
5433    * @name resetColumnSorting
5434    * @methodOf ui.grid.class:Grid
5435    * @description Return the columns that the grid is currently being sorted by
5436    * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
5437    */
5438   Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
5439     var self = this;
5440
5441     self.columns.forEach(function (col) {
5442       if (col !== excludeCol && !col.suppressRemoveSort) {
5443         col.sort = {};
5444       }
5445     });
5446   };
5447
5448   /**
5449    * @ngdoc function
5450    * @name getColumnSorting
5451    * @methodOf ui.grid.class:Grid
5452    * @description Return the columns that the grid is currently being sorted by
5453    * @returns {Array[GridColumn]} An array of GridColumn objects
5454    */
5455   Grid.prototype.getColumnSorting = function getColumnSorting() {
5456     var self = this;
5457
5458     var sortedCols = [], myCols;
5459
5460     // Iterate through all the columns, sorted by priority
5461     // Make local copy of column list, because sorting is in-place and we do not want to
5462     // change the original sequence of columns
5463     myCols = self.columns.slice(0);
5464     myCols.sort(rowSorter.prioritySort).forEach(function (col) {
5465       if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
5466         sortedCols.push(col);
5467       }
5468     });
5469
5470     return sortedCols;
5471   };
5472
5473   /**
5474    * @ngdoc function
5475    * @name sortColumn
5476    * @methodOf ui.grid.class:Grid
5477    * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
5478    * Emits the sortChanged event whenever the sort criteria are changed.
5479    * @param {GridColumn} column Column to set the sorting on
5480    * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
5481    *   If not provided, the column will iterate through the sort directions
5482    *   specified in the {@link ui.grid.class:GridOptions.columnDef#sortDirectionCycle sortDirectionCycle} attribute.
5483    * @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
5484    *   by this column only
5485    * @returns {Promise} A resolved promise that supplies the column.
5486    */
5487
5488   Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
5489     var self = this,
5490         direction = null;
5491
5492     if (typeof(column) === 'undefined' || !column) {
5493       throw new Error('No column parameter provided');
5494     }
5495
5496     // Second argument can either be a direction or whether to add this column to the existing sort.
5497     //   If it's a boolean, it's an add, otherwise, it's a direction
5498     if (typeof(directionOrAdd) === 'boolean') {
5499       add = directionOrAdd;
5500     }
5501     else {
5502       direction = directionOrAdd;
5503     }
5504
5505     if (!add) {
5506       self.resetColumnSorting(column);
5507       column.sort.priority = 0;
5508       // Get the actual priority since there may be columns which have suppressRemoveSort set
5509       column.sort.priority = self.getNextColumnSortPriority();
5510     }
5511     else if (!column.sort.priority){
5512       column.sort.priority = self.getNextColumnSortPriority();
5513     }
5514
5515     if (!direction) {
5516       // Find the current position in the cycle (or -1).
5517       var i = column.sortDirectionCycle.indexOf(column.sort.direction ? column.sort.direction : null);
5518       // Proceed to the next position in the cycle (or start at the beginning).
5519       i = (i+1) % column.sortDirectionCycle.length;
5520       // If suppressRemoveSort is set, and the next position in the cycle would
5521       // remove the sort, skip it.
5522       if (column.colDef && column.suppressRemoveSort && !column.sortDirectionCycle[i]) {
5523         i = (i+1) % column.sortDirectionCycle.length;
5524       }
5525
5526       if (column.sortDirectionCycle[i]) {
5527         column.sort.direction = column.sortDirectionCycle[i];
5528       } else {
5529         column.sort = {};
5530       }
5531     }
5532     else {
5533       column.sort.direction = direction;
5534     }
5535
5536     self.api.core.raise.sortChanged( self, self.getColumnSorting() );
5537
5538     return $q.when(column);
5539   };
5540
5541   /**
5542    * communicate to outside world that we are done with initial rendering
5543    */
5544   Grid.prototype.renderingComplete = function(){
5545     if (angular.isFunction(this.options.onRegisterApi)) {
5546       this.options.onRegisterApi(this.api);
5547     }
5548     this.api.core.raise.renderingComplete( this.api );
5549   };
5550
5551   Grid.prototype.createRowHashMap = function createRowHashMap() {
5552     var self = this;
5553
5554     var hashMap = new RowHashMap();
5555     hashMap.grid = self;
5556
5557     return hashMap;
5558   };
5559
5560
5561   /**
5562    * @ngdoc function
5563    * @name refresh
5564    * @methodOf ui.grid.class:Grid
5565    * @description Refresh the rendered grid on screen.
5566    * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed.
5567    */
5568   Grid.prototype.refresh = function refresh(rowsAltered) {
5569     var self = this;
5570
5571     var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
5572       self.setVisibleRows(renderableRows);
5573     });
5574
5575     var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
5576       self.setVisibleColumns(renderableColumns);
5577     });
5578
5579     return $q.all([p1, p2]).then(function () {
5580       self.redrawInPlace(rowsAltered);
5581
5582       self.refreshCanvas(true);
5583     });
5584   };
5585
5586   /**
5587    * @ngdoc function
5588    * @name refreshRows
5589    * @methodOf ui.grid.class:Grid
5590    * @description Refresh the rendered rows on screen?  Note: not functional at present
5591    * @returns {promise} promise that is resolved when render completes?
5592    *
5593    */
5594   Grid.prototype.refreshRows = function refreshRows() {
5595     var self = this;
5596
5597     return self.processRowsProcessors(self.rows)
5598       .then(function (renderableRows) {
5599         self.setVisibleRows(renderableRows);
5600
5601         self.redrawInPlace();
5602
5603         self.refreshCanvas( true );
5604       });
5605   };
5606
5607   /**
5608    * @ngdoc function
5609    * @name refreshCanvas
5610    * @methodOf ui.grid.class:Grid
5611    * @description Builds all styles and recalculates much of the grid sizing
5612    * @param {object} buildStyles optional parameter.  Use TBD
5613    * @returns {promise} promise that is resolved when the canvas
5614    * has been refreshed
5615    *
5616    */
5617   Grid.prototype.refreshCanvas = function(buildStyles) {
5618     var self = this;
5619
5620     if (buildStyles) {
5621       self.buildStyles();
5622     }
5623
5624     var p = $q.defer();
5625
5626     // Get all the header heights
5627     var containerHeadersToRecalc = [];
5628     for (var containerId in self.renderContainers) {
5629       if (self.renderContainers.hasOwnProperty(containerId)) {
5630         var container = self.renderContainers[containerId];
5631
5632         // Skip containers that have no canvasWidth set yet
5633         if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5634           continue;
5635         }
5636
5637         if (container.header || container.headerCanvas) {
5638           container.explicitHeaderHeight = container.explicitHeaderHeight || null;
5639           container.explicitHeaderCanvasHeight = container.explicitHeaderCanvasHeight || null;
5640
5641           containerHeadersToRecalc.push(container);
5642         }
5643       }
5644     }
5645
5646     /*
5647      *
5648      * Here we loop through the headers, measuring each element as well as any header "canvas" it has within it.
5649      *
5650      * If any header is less than the largest header height, it will be resized to that so that we don't have headers
5651      * with different heights, which looks like a rendering problem
5652      *
5653      * We'll do the same thing with the header canvases, and give the header CELLS an explicit height if their canvas
5654      * is smaller than the largest canvas height. That was header cells without extra controls like filtering don't
5655      * appear shorter than other cells.
5656      *
5657      */
5658     if (containerHeadersToRecalc.length > 0) {
5659       // Build the styles without the explicit header heights
5660       if (buildStyles) {
5661         self.buildStyles();
5662       }
5663
5664       // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
5665       $timeout(function() {
5666         // var oldHeaderHeight = self.grid.headerHeight;
5667         // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
5668
5669         var rebuildStyles = false;
5670
5671         // Get all the header heights
5672         var maxHeaderHeight = 0;
5673         var maxHeaderCanvasHeight = 0;
5674         var i, container;
5675         var getHeight = function(oldVal, newVal){
5676           if ( oldVal !== newVal){
5677             rebuildStyles = true;
5678           }
5679           return newVal;
5680         };
5681         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5682           container = containerHeadersToRecalc[i];
5683
5684           // Skip containers that have no canvasWidth set yet
5685           if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5686             continue;
5687           }
5688
5689           if (container.header) {
5690             var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5691
5692             // 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
5693             var topBorder = gridUtil.getBorderSize(container.header, 'top');
5694             var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
5695             var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
5696
5697             innerHeaderHeight  = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
5698
5699             container.innerHeaderHeight = innerHeaderHeight;
5700
5701             // If the header doesn't have an explicit height set, save the largest header height for use later
5702             //   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
5703             if (!container.explicitHeaderHeight && innerHeaderHeight > maxHeaderHeight) {
5704               maxHeaderHeight = innerHeaderHeight;
5705             }
5706           }
5707
5708           if (container.headerCanvas) {
5709             var headerCanvasHeight = container.headerCanvasHeight = getHeight(container.headerCanvasHeight, parseInt(gridUtil.outerElementHeight(container.headerCanvas), 10));
5710
5711
5712             // If the header doesn't have an explicit canvas height, save the largest header canvas height for use later
5713             //   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
5714             if (!container.explicitHeaderCanvasHeight && headerCanvasHeight > maxHeaderCanvasHeight) {
5715               maxHeaderCanvasHeight = headerCanvasHeight;
5716             }
5717           }
5718         }
5719
5720         // Go through all the headers
5721         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5722           container = containerHeadersToRecalc[i];
5723
5724           /* If:
5725               1. We have a max header height
5726               2. This container has a header height defined
5727               3. And either this container has an explicit header height set, OR its header height is less than the max
5728
5729               then:
5730
5731               Give this container's header an explicit height so it will line up with the tallest header
5732           */
5733           if (
5734             maxHeaderHeight > 0 && typeof(container.headerHeight) !== 'undefined' && container.headerHeight !== null &&
5735             (container.explicitHeaderHeight || container.headerHeight < maxHeaderHeight)
5736           ) {
5737             container.explicitHeaderHeight = getHeight(container.explicitHeaderHeight, maxHeaderHeight);
5738           }
5739
5740           // Do the same as above except for the header canvas
5741           if (
5742             maxHeaderCanvasHeight > 0 && typeof(container.headerCanvasHeight) !== 'undefined' && container.headerCanvasHeight !== null &&
5743             (container.explicitHeaderCanvasHeight || container.headerCanvasHeight < maxHeaderCanvasHeight)
5744           ) {
5745             container.explicitHeaderCanvasHeight = getHeight(container.explicitHeaderCanvasHeight, maxHeaderCanvasHeight);
5746           }
5747         }
5748
5749         // Rebuild styles if the header height has changed
5750         //   The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
5751         if (buildStyles && rebuildStyles) {
5752           self.buildStyles();
5753         }
5754
5755         p.resolve();
5756       });
5757     }
5758     else {
5759       // Timeout still needs to be here to trigger digest after styles have been rebuilt
5760       $timeout(function() {
5761         p.resolve();
5762       });
5763     }
5764
5765     return p.promise;
5766   };
5767
5768
5769   /**
5770    * @ngdoc function
5771    * @name redrawCanvas
5772    * @methodOf ui.grid.class:Grid
5773    * @description Redraw the rows and columns based on our current scroll position
5774    * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
5775    *
5776    */
5777   Grid.prototype.redrawInPlace = function redrawInPlace(rowsAdded) {
5778     // gridUtil.logDebug('redrawInPlace');
5779
5780     var self = this;
5781
5782     for (var i in self.renderContainers) {
5783       var container = self.renderContainers[i];
5784
5785       // gridUtil.logDebug('redrawing container', i);
5786
5787       if (rowsAdded) {
5788         container.adjustRows(container.prevScrollTop, null);
5789         container.adjustColumns(container.prevScrollLeft, null);
5790       }
5791       else {
5792         container.adjustRows(null, container.prevScrolltopPercentage);
5793         container.adjustColumns(null, container.prevScrollleftPercentage);
5794       }
5795     }
5796   };
5797
5798     /**
5799      * @ngdoc function
5800      * @name hasLeftContainerColumns
5801      * @methodOf ui.grid.class:Grid
5802      * @description returns true if leftContainer has columns
5803      */
5804     Grid.prototype.hasLeftContainerColumns = function () {
5805       return this.hasLeftContainer() && this.renderContainers.left.renderedColumns.length > 0;
5806     };
5807
5808     /**
5809      * @ngdoc function
5810      * @name hasRightContainerColumns
5811      * @methodOf ui.grid.class:Grid
5812      * @description returns true if rightContainer has columns
5813      */
5814     Grid.prototype.hasRightContainerColumns = function () {
5815       return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
5816     };
5817
5818     /**
5819      * @ngdoc method
5820      * @methodOf  ui.grid.class:Grid
5821      * @name scrollToIfNecessary
5822      * @description Scrolls the grid to make a certain row and column combo visible,
5823      *   in the case that it is not completely visible on the screen already.
5824      * @param {GridRow} gridRow row to make visible
5825      * @param {GridCol} gridCol column to make visible
5826      * @returns {promise} a promise that is resolved when scrolling is complete
5827      */
5828     Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
5829       var self = this;
5830
5831       var scrollEvent = new ScrollEvent(self, 'uiGrid.scrollToIfNecessary');
5832
5833       // Alias the visible row and column caches
5834       var visRowCache = self.renderContainers.body.visibleRowCache;
5835       var visColCache = self.renderContainers.body.visibleColumnCache;
5836
5837       /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
5838
5839       // 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
5840       var topBound = self.renderContainers.body.prevScrollTop + self.headerHeight;
5841
5842       // Don't the let top boundary be less than 0
5843       topBound = (topBound < 0) ? 0 : topBound;
5844
5845       // The left boundary is the current X scroll position
5846       var leftBound = self.renderContainers.body.prevScrollLeft;
5847
5848       // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
5849       //   Basically this is the viewport height added on to the scroll position
5850       var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight -  self.scrollbarWidth;
5851
5852       // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
5853       //if (self.horizontalScrollbarHeight) {
5854       //  bottomBound = bottomBound - self.horizontalScrollbarHeight;
5855       //}
5856
5857       // The right position is the current X scroll position minus the grid width
5858       var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
5859
5860       // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
5861       //if (self.verticalScrollbarWidth) {
5862       //  rightBound = rightBound - self.verticalScrollbarWidth;
5863       //}
5864
5865       // We were given a row to scroll to
5866       if (gridRow !== null) {
5867         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5868         var seekRowIndex = visRowCache.indexOf(gridRow);
5869
5870         // Total vertical scroll length of the grid
5871         var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
5872
5873         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5874         //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
5875         //  scrollLength = scrollLength + self.horizontalScrollbarHeight;
5876         //}
5877
5878         // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
5879         var pixelsToSeeRow = (seekRowIndex * self.options.rowHeight + self.headerHeight);
5880
5881         // Don't let the pixels required to see the row be less than zero
5882         pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
5883
5884         var scrollPixels, percentage;
5885
5886         // 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...
5887         if (pixelsToSeeRow < topBound) {
5888           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5889           //   to get the full position we need
5890           scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
5891
5892           // Turn the scroll position into a percentage and make it an argument for a scroll event
5893           percentage = scrollPixels / scrollLength;
5894           scrollEvent.y = { percentage: percentage  };
5895         }
5896         // 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...
5897         else if (pixelsToSeeRow > bottomBound) {
5898           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5899           //   to get the full position we need
5900           scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
5901
5902           // Turn the scroll position into a percentage and make it an argument for a scroll event
5903           percentage = scrollPixels / scrollLength;
5904           scrollEvent.y = { percentage: percentage  };
5905         }
5906       }
5907
5908       // We were given a column to scroll to
5909       if (gridCol !== null) {
5910         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5911         var seekColumnIndex = visColCache.indexOf(gridCol);
5912
5913         // Total vertical scroll length of the grid
5914         var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
5915
5916         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5917         // if (self.verticalScrollbarWidth && self.verticalScrollbarWidth > 0) {
5918         //   horizScrollLength = horizScrollLength + self.verticalScrollbarWidth;
5919         // }
5920
5921         // This is the minimum amount of pixels we need to scroll vertical in order to see this column
5922         var columnLeftEdge = 0;
5923         for (var i = 0; i < seekColumnIndex; i++) {
5924           var col = visColCache[i];
5925           columnLeftEdge += col.drawnWidth;
5926         }
5927         columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
5928
5929         var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
5930
5931         // Don't let the pixels required to see the column be less than zero
5932         columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
5933
5934         var horizScrollPixels, horizPercentage;
5935
5936         // 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...
5937         if (columnLeftEdge < leftBound) {
5938           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5939           //   to get the full position we need
5940           horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
5941
5942           // Turn the scroll position into a percentage and make it an argument for a scroll event
5943           horizPercentage = horizScrollPixels / horizScrollLength;
5944           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5945           scrollEvent.x = { percentage: horizPercentage  };
5946         }
5947         // 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...
5948         else if (columnRightEdge > rightBound) {
5949           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5950           //   to get the full position we need
5951           horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
5952
5953           // Turn the scroll position into a percentage and make it an argument for a scroll event
5954           horizPercentage = horizScrollPixels / horizScrollLength;
5955           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5956           scrollEvent.x = { percentage: horizPercentage  };
5957         }
5958       }
5959
5960       var deferred = $q.defer();
5961
5962       // If we need to scroll on either the x or y axes, fire a scroll event
5963       if (scrollEvent.y || scrollEvent.x) {
5964         scrollEvent.withDelay = false;
5965         self.scrollContainers('',scrollEvent);
5966         var dereg = self.api.core.on.scrollEnd(null,function() {
5967           deferred.resolve(scrollEvent);
5968           dereg();
5969         });
5970       }
5971       else {
5972         deferred.resolve();
5973       }
5974
5975       return deferred.promise;
5976     };
5977
5978     /**
5979      * @ngdoc method
5980      * @methodOf ui.grid.class:Grid
5981      * @name scrollTo
5982      * @description Scroll the grid such that the specified
5983      * row and column is in view
5984      * @param {object} rowEntity gridOptions.data[] array instance to make visible
5985      * @param {object} colDef to make visible
5986      * @returns {promise} a promise that is resolved after any scrolling is finished
5987      */
5988     Grid.prototype.scrollTo = function (rowEntity, colDef) {
5989       var gridRow = null, gridCol = null;
5990
5991       if (rowEntity !== null && typeof(rowEntity) !== 'undefined' ) {
5992         gridRow = this.getRow(rowEntity);
5993       }
5994
5995       if (colDef !== null && typeof(colDef) !== 'undefined' ) {
5996         gridCol = this.getColumn(colDef.name ? colDef.name : colDef.field);
5997       }
5998       return this.scrollToIfNecessary(gridRow, gridCol);
5999     };
6000
6001   /**
6002    * @ngdoc function
6003    * @name clearAllFilters
6004    * @methodOf ui.grid.class:Grid
6005    * @description Clears all filters and optionally refreshes the visible rows.
6006    * @param {object} refreshRows Defaults to true.
6007    * @param {object} clearConditions Defaults to false.
6008    * @param {object} clearFlags Defaults to false.
6009    * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
6010    */
6011   Grid.prototype.clearAllFilters = function clearAllFilters(refreshRows, clearConditions, clearFlags) {
6012     // Default `refreshRows` to true because it will be the most commonly desired behaviour.
6013     if (refreshRows === undefined) {
6014       refreshRows = true;
6015     }
6016     if (clearConditions === undefined) {
6017       clearConditions = false;
6018     }
6019     if (clearFlags === undefined) {
6020       clearFlags = false;
6021     }
6022
6023     this.columns.forEach(function(column) {
6024       column.filters.forEach(function(filter) {
6025         filter.term = undefined;
6026
6027         if (clearConditions) {
6028           filter.condition = undefined;
6029         }
6030
6031         if (clearFlags) {
6032           filter.flags = undefined;
6033         }
6034       });
6035     });
6036
6037     if (refreshRows) {
6038       return this.refreshRows();
6039     }
6040   };
6041
6042
6043       // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
6044   function RowHashMap() {}
6045
6046   RowHashMap.prototype = {
6047     /**
6048      * Store key value pair
6049      * @param key key to store can be any type
6050      * @param value value to store can be any type
6051      */
6052     put: function(key, value) {
6053       this[this.grid.options.rowIdentity(key)] = value;
6054     },
6055
6056     /**
6057      * @param key
6058      * @returns {Object} the value for the key
6059      */
6060     get: function(key) {
6061       return this[this.grid.options.rowIdentity(key)];
6062     },
6063
6064     /**
6065      * Remove the key/value pair
6066      * @param key
6067      */
6068     remove: function(key) {
6069       var value = this[key = this.grid.options.rowIdentity(key)];
6070       delete this[key];
6071       return value;
6072     }
6073   };
6074
6075
6076
6077   return Grid;
6078
6079 }]);
6080
6081 })();
6082
6083 (function () {
6084
6085   angular.module('ui.grid')
6086     .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6087       function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6088         /**
6089          * @ngdoc function
6090          * @name ui.grid.class:GridApi
6091          * @description GridApi provides the ability to register public methods events inside the grid and allow
6092          * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6093          * <br/>
6094          * To listen to events, you must add a callback to gridOptions.onRegisterApi
6095          * <pre>
6096          *   $scope.gridOptions.onRegisterApi = function(gridApi){
6097          *      gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6098          *          $log.log('navigation event');
6099          *      });
6100          *   };
6101          * </pre>
6102          * @param {object} grid grid that owns api
6103          */
6104         var GridApi = function GridApi(grid) {
6105           this.grid = grid;
6106           this.listeners = [];
6107           
6108           /**
6109            * @ngdoc function
6110            * @name renderingComplete
6111            * @methodOf  ui.grid.core.api:PublicApi
6112            * @description Rendering is complete, called at the same
6113            * time as `onRegisterApi`, but provides a way to obtain
6114            * that same event within features without stopping end
6115            * users from getting at the onRegisterApi method.
6116            * 
6117            * Included in gridApi so that it's always there - otherwise
6118            * there is still a timing problem with when a feature can
6119            * call this. 
6120            * 
6121            * @param {GridApi} gridApi the grid api, as normally 
6122            * returned in the onRegisterApi method
6123            * 
6124            * @example
6125            * <pre>
6126            *      gridApi.core.on.renderingComplete( grid );
6127            * </pre>
6128            */
6129           this.registerEvent( 'core', 'renderingComplete' );
6130
6131           /**
6132            * @ngdoc event
6133            * @name filterChanged
6134            * @eventOf  ui.grid.core.api:PublicApi
6135            * @description  is raised after the filter is changed.  The nature
6136            * of the watch expression doesn't allow notification of what changed,
6137            * so the receiver of this event will need to re-extract the filter 
6138            * conditions from the columns.
6139            * 
6140            */
6141           this.registerEvent( 'core', 'filterChanged' );
6142
6143           /**
6144            * @ngdoc function
6145            * @name setRowInvisible
6146            * @methodOf  ui.grid.core.api:PublicApi
6147            * @description Sets an override on the row to make it always invisible,
6148            * which will override any filtering or other visibility calculations.  
6149            * If the row is currently visible then sets it to invisible and calls
6150            * both grid refresh and emits the rowsVisibleChanged event
6151            * @param {object} rowEntity gridOptions.data[] array instance
6152            */
6153           this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6154       
6155           /**
6156            * @ngdoc function
6157            * @name clearRowInvisible
6158            * @methodOf  ui.grid.core.api:PublicApi
6159            * @description Clears any override on visibility for the row so that it returns to 
6160            * using normal filtering and other visibility calculations.  
6161            * If the row is currently invisible then sets it to visible and calls
6162            * both grid refresh and emits the rowsVisibleChanged event
6163            * TODO: if a filter is active then we can't just set it to visible?
6164            * @param {object} rowEntity gridOptions.data[] array instance
6165            */
6166           this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6167       
6168           /**
6169            * @ngdoc function
6170            * @name getVisibleRows
6171            * @methodOf  ui.grid.core.api:PublicApi
6172            * @description Returns all visible rows
6173            * @param {Grid} grid the grid you want to get visible rows from
6174            * @returns {array} an array of gridRow
6175            */
6176           this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6177           
6178           /**
6179            * @ngdoc event
6180            * @name rowsVisibleChanged
6181            * @eventOf  ui.grid.core.api:PublicApi
6182            * @description  is raised after the rows that are visible
6183            * change.  The filtering is zero-based, so it isn't possible
6184            * to say which rows changed (unlike in the selection feature).
6185            * We can plausibly know which row was changed when setRowInvisible
6186            * is called, but in that situation the user already knows which row
6187            * they changed.  When a filter runs we don't know what changed,
6188            * and that is the one that would have been useful.
6189            *
6190            */
6191           this.registerEvent( 'core', 'rowsVisibleChanged' );
6192
6193           /**
6194            * @ngdoc event
6195            * @name rowsRendered
6196            * @eventOf  ui.grid.core.api:PublicApi
6197            * @description  is raised after the cache of visible rows is changed.
6198            */
6199           this.registerEvent( 'core', 'rowsRendered' );
6200
6201
6202           /**
6203            * @ngdoc event
6204            * @name scrollBegin
6205            * @eventOf  ui.grid.core.api:PublicApi
6206            * @description  is raised when scroll begins.  Is throttled, so won't be raised too frequently
6207            */
6208           this.registerEvent( 'core', 'scrollBegin' );
6209
6210           /**
6211            * @ngdoc event
6212            * @name scrollEnd
6213            * @eventOf  ui.grid.core.api:PublicApi
6214            * @description  is raised when scroll has finished.  Is throttled, so won't be raised too frequently
6215            */
6216           this.registerEvent( 'core', 'scrollEnd' );
6217
6218           /**
6219            * @ngdoc event
6220            * @name canvasHeightChanged
6221            * @eventOf  ui.grid.core.api:PublicApi
6222            * @description  is raised when the canvas height has changed
6223            * <br/>
6224            * arguments: oldHeight, newHeight
6225            */
6226           this.registerEvent( 'core', 'canvasHeightChanged');
6227         };
6228
6229         /**
6230          * @ngdoc function
6231          * @name ui.grid.class:suppressEvents
6232          * @methodOf ui.grid.class:GridApi
6233          * @description Used to execute a function while disabling the specified event listeners.
6234          * Disables the listenerFunctions, executes the callbackFn, and then enables
6235          * the listenerFunctions again
6236          * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
6237          * functions that were used in the .on.eventName method
6238          * @param {object} callBackFn function to execute
6239          * @example
6240          * <pre>
6241          *    var navigate = function (newRowCol, oldRowCol){
6242          *       //do something on navigate
6243          *    }
6244          *
6245          *    gridApi.cellNav.on.navigate(scope,navigate);
6246          *
6247          *
6248          *    //call the scrollTo event and suppress our navigate listener
6249          *    //scrollTo will still raise the event for other listeners
6250          *    gridApi.suppressEvents(navigate, function(){
6251          *       gridApi.cellNav.scrollTo(aRow, aCol);
6252          *    });
6253          *
6254          * </pre>
6255          */
6256         GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6257           var self = this;
6258           var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6259
6260           //find all registered listeners
6261           var foundListeners = self.listeners.filter(function(listener) {
6262             return listeners.some(function(l) {
6263               return listener.handler === l;
6264             });
6265           });
6266
6267           //deregister all the listeners
6268           foundListeners.forEach(function(l){
6269             l.dereg();
6270           });
6271
6272           callBackFn();
6273
6274           //reregister all the listeners
6275           foundListeners.forEach(function(l){
6276               l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
6277           });
6278
6279         };
6280
6281         /**
6282          * @ngdoc function
6283          * @name registerEvent
6284          * @methodOf ui.grid.class:GridApi
6285          * @description Registers a new event for the given feature.  The event will get a
6286          * .raise and .on prepended to it
6287          * <br>
6288          * .raise.eventName() - takes no arguments
6289          * <br/>
6290          * <br/>
6291          * .on.eventName(scope, callBackFn, _this)
6292          * <br/>
6293          * scope - a scope reference to add a deregister call to the scopes .$on('destroy').  Scope is optional and can be a null value,
6294          * but in this case you must deregister it yourself via the returned deregister function
6295          * <br/>
6296          * callBackFn - The function to call
6297          * <br/>
6298          * _this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
6299          * <br/>
6300          * .on.eventName returns a dereg funtion that will remove the listener.  It's not necessary to use it as the listener
6301          * will be removed when the scope is destroyed.
6302          * @param {string} featureName name of the feature that raises the event
6303          * @param {string} eventName  name of the event
6304          */
6305         GridApi.prototype.registerEvent = function (featureName, eventName) {
6306           var self = this;
6307           if (!self[featureName]) {
6308             self[featureName] = {};
6309           }
6310
6311           var feature = self[featureName];
6312           if (!feature.on) {
6313             feature.on = {};
6314             feature.raise = {};
6315           }
6316
6317           var eventId = self.grid.id + featureName + eventName;
6318
6319           // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
6320           feature.raise[eventName] = function () {
6321             $rootScope.$emit.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
6322           };
6323
6324           // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
6325           feature.on[eventName] = function (scope, handler, _this) {
6326             if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6327               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');
6328               return;
6329             }
6330             var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
6331
6332             //track our listener so we can turn off and on
6333             var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6334             self.listeners.push(listener);
6335
6336             var removeListener = function(){
6337               listener.dereg();
6338               var index = self.listeners.indexOf(listener);
6339               self.listeners.splice(index,1);
6340             };
6341
6342             //destroy tracking when scope is destroyed
6343             if (scope) {
6344               scope.$on('$destroy', function() {
6345                 removeListener();
6346               });
6347             }
6348
6349
6350             return removeListener;
6351           };
6352         };
6353
6354         function registerEventWithAngular(eventId, handler, grid, _this) {
6355           return $rootScope.$on(eventId, function (event) {
6356             var args = Array.prototype.slice.call(arguments);
6357             args.splice(0, 1); //remove evt argument
6358             handler.apply(_this ? _this : grid.api, args);
6359           });
6360         }
6361
6362         /**
6363          * @ngdoc function
6364          * @name registerEventsFromObject
6365          * @methodOf ui.grid.class:GridApi
6366          * @description Registers features and events from a simple objectMap.
6367          * eventObjectMap must be in this format (multiple features allowed)
6368          * <pre>
6369          * {featureName:
6370          *        {
6371          *          eventNameOne:function(args){},
6372          *          eventNameTwo:function(args){}
6373          *        }
6374          *  }
6375          * </pre>
6376          * @param {object} eventObjectMap map of feature/event names
6377          */
6378         GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6379           var self = this;
6380           var features = [];
6381           angular.forEach(eventObjectMap, function (featProp, featPropName) {
6382             var feature = {name: featPropName, events: []};
6383             angular.forEach(featProp, function (prop, propName) {
6384               feature.events.push(propName);
6385             });
6386             features.push(feature);
6387           });
6388
6389           features.forEach(function (feature) {
6390             feature.events.forEach(function (event) {
6391               self.registerEvent(feature.name, event);
6392             });
6393           });
6394
6395         };
6396
6397         /**
6398          * @ngdoc function
6399          * @name registerMethod
6400          * @methodOf ui.grid.class:GridApi
6401          * @description Registers a new event for the given feature
6402          * @param {string} featureName name of the feature
6403          * @param {string} methodName  name of the method
6404          * @param {object} callBackFn function to execute
6405          * @param {object} _this binds callBackFn 'this' to _this.  Defaults to gridApi.grid
6406          */
6407         GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, _this) {
6408           if (!this[featureName]) {
6409             this[featureName] = {};
6410           }
6411
6412           var feature = this[featureName];
6413
6414           feature[methodName] = gridUtil.createBoundedWrapper(_this || this.grid, callBackFn);
6415         };
6416
6417         /**
6418          * @ngdoc function
6419          * @name registerMethodsFromObject
6420          * @methodOf ui.grid.class:GridApi
6421          * @description Registers features and methods from a simple objectMap.
6422          * eventObjectMap must be in this format (multiple features allowed)
6423          * <br>
6424          * {featureName:
6425          *        {
6426          *          methodNameOne:function(args){},
6427          *          methodNameTwo:function(args){}
6428          *        }
6429          * @param {object} eventObjectMap map of feature/event names
6430          * @param {object} _this binds this to _this for all functions.  Defaults to gridApi.grid
6431          */
6432         GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
6433           var self = this;
6434           var features = [];
6435           angular.forEach(methodMap, function (featProp, featPropName) {
6436             var feature = {name: featPropName, methods: []};
6437             angular.forEach(featProp, function (prop, propName) {
6438               feature.methods.push({name: propName, fn: prop});
6439             });
6440             features.push(feature);
6441           });
6442
6443           features.forEach(function (feature) {
6444             feature.methods.forEach(function (method) {
6445               self.registerMethod(feature.name, method.name, method.fn, _this);
6446             });
6447           });
6448
6449         };
6450         
6451         return GridApi;
6452
6453       }]);
6454
6455 })();
6456
6457 (function(){
6458
6459 angular.module('ui.grid')
6460 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
6461
6462   /**
6463    * ******************************************************************************************
6464    * PaulL1: Ugly hack here in documentation.  These properties are clearly properties of GridColumn,
6465    * and need to be noted as such for those extending and building ui-grid itself.
6466    * However, from an end-developer perspective, they interact with all these through columnDefs,
6467    * and they really need to be documented there.  I feel like they're relatively static, and
6468    * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
6469    * comment block.  Ugh.
6470    *
6471    */
6472
6473   /**
6474    * @ngdoc property
6475    * @name name
6476    * @propertyOf ui.grid.class:GridColumn
6477    * @description (mandatory) each column should have a name, although for backward
6478    * compatibility with 2.x name can be omitted if field is present
6479    *
6480    */
6481
6482   /**
6483    * @ngdoc property
6484    * @name name
6485    * @propertyOf ui.grid.class:GridOptions.columnDef
6486    * @description (mandatory) each column should have a name, although for backward
6487    * compatibility with 2.x name can be omitted if field is present
6488    *
6489    */
6490
6491   /**
6492    * @ngdoc property
6493    * @name displayName
6494    * @propertyOf ui.grid.class:GridColumn
6495    * @description Column name that will be shown in the header.  If displayName is not
6496    * provided then one is generated using the name.
6497    *
6498    */
6499
6500   /**
6501    * @ngdoc property
6502    * @name displayName
6503    * @propertyOf ui.grid.class:GridOptions.columnDef
6504    * @description Column name that will be shown in the header.  If displayName is not
6505    * provided then one is generated using the name.
6506    *
6507    */
6508
6509   /**
6510    * @ngdoc property
6511    * @name field
6512    * @propertyOf ui.grid.class:GridColumn
6513    * @description field must be provided if you wish to bind to a
6514    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6515    * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
6516    * See the angular docs on binding expressions.
6517    *
6518    */
6519
6520   /**
6521    * @ngdoc property
6522    * @name field
6523    * @propertyOf ui.grid.class:GridOptions.columnDef
6524    * @description field must be provided if you wish to bind to a
6525    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6526    * 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.    *
6527    */
6528
6529   /**
6530    * @ngdoc property
6531    * @name filter
6532    * @propertyOf ui.grid.class:GridColumn
6533    * @description Filter on this column.
6534    * @example
6535    * <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>
6536    *
6537    */
6538
6539   /**
6540    * @ngdoc object
6541    * @name ui.grid.class:GridColumn
6542    * @description Represents the viewModel for each column.  Any state or methods needed for a Grid Column
6543    * are defined on this prototype
6544    * @param {ColumnDef} colDef the column def to associate with this column
6545    * @param {number} uid the unique and immutable uid we'd like to allocate to this column
6546    * @param {Grid} grid the grid we'd like to create this column in
6547    */
6548   function GridColumn(colDef, uid, grid) {
6549     var self = this;
6550
6551     self.grid = grid;
6552     self.uid = uid;
6553
6554     self.updateColumnDef(colDef, true);
6555
6556     self.aggregationValue = undefined;
6557
6558     // The footer cell registers to listen for the rowsRendered event, and calls this.  Needed to be
6559     // in something with a scope so that the dereg would get called
6560     self.updateAggregationValue = function() {
6561
6562      // gridUtil.logDebug('getAggregationValue for Column ' + self.colDef.name);
6563
6564       /**
6565        * @ngdoc property
6566        * @name aggregationType
6567        * @propertyOf ui.grid.class:GridOptions.columnDef
6568        * @description The aggregation that you'd like to show in the columnFooter for this
6569        * column.  Valid values are in uiGridConstants, and currently include `uiGridConstants.aggregationTypes.count`,
6570        * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
6571        * `uiGridConstants.aggregationTypes.max`.
6572        *
6573        * You can also provide a function as the aggregation type, in this case your function needs to accept the full
6574        * set of visible rows, and return a value that should be shown
6575        */
6576       if (!self.aggregationType) {
6577         self.aggregationValue = undefined;
6578         return;
6579       }
6580
6581       var result = 0;
6582       var visibleRows = self.grid.getVisibleRows();
6583
6584       var cellValues = function(){
6585         var values = [];
6586         visibleRows.forEach(function (row) {
6587           var cellValue = self.grid.getCellValue(row, self);
6588           var cellNumber = Number(cellValue);
6589           if (!isNaN(cellNumber)) {
6590             values.push(cellNumber);
6591           }
6592         });
6593         return values;
6594       };
6595
6596       if (angular.isFunction(self.aggregationType)) {
6597         self.aggregationValue = self.aggregationType(visibleRows, self);
6598       }
6599       else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6600         self.aggregationValue = self.grid.getVisibleRowCount();
6601       }
6602       else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6603         cellValues().forEach(function (value) {
6604           result += value;
6605         });
6606         self.aggregationValue = result;
6607       }
6608       else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6609         cellValues().forEach(function (value) {
6610           result += value;
6611         });
6612         result = result / cellValues().length;
6613         self.aggregationValue = result;
6614       }
6615       else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6616         self.aggregationValue = Math.min.apply(null, cellValues());
6617       }
6618       else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6619         self.aggregationValue = Math.max.apply(null, cellValues());
6620       }
6621       else {
6622         self.aggregationValue = '\u00A0';
6623       }
6624     };
6625
6626 //     var throttledUpdateAggregationValue = gridUtil.throttle(updateAggregationValue, self.grid.options.aggregationCalcThrottle, { trailing: true, context: self.name });
6627
6628     /**
6629      * @ngdoc function
6630      * @name getAggregationValue
6631      * @methodOf ui.grid.class:GridColumn
6632      * @description gets the aggregation value based on the aggregation type for this column.
6633      * Debounced using scrollDebounce option setting
6634      */
6635     this.getAggregationValue =  function() {
6636 //      if (!self.grid.isScrollingVertically && !self.grid.isScrollingHorizontally) {
6637 //        throttledUpdateAggregationValue();
6638 //      }
6639
6640       return self.aggregationValue;
6641     };
6642   }
6643
6644   /**
6645    * @ngdoc function
6646    * @name hideColumn
6647    * @methodOf ui.grid.class:GridColumn
6648    * @description Hides the column by setting colDef.visible = false
6649    */
6650   GridColumn.prototype.hideColumn = function() {
6651     this.colDef.visible = false;
6652   };
6653   
6654
6655   /**
6656    * @ngdoc method
6657    * @methodOf ui.grid.class:GridColumn
6658    * @name setPropertyOrDefault
6659    * @description Sets a property on the column using the passed in columnDef, and
6660    * setting the defaultValue if the value cannot be found on the colDef
6661    * @param {ColumnDef} colDef the column def to look in for the property value
6662    * @param {string} propName the property name we'd like to set
6663    * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
6664    */
6665   GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
6666     var self = this;
6667
6668     // Use the column definition filter if we were passed it
6669     if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
6670       self[propName] = colDef[propName];
6671     }
6672     // Otherwise use our own if it's set
6673     else if (typeof(self[propName]) !== 'undefined') {
6674       self[propName] = self[propName];
6675     }
6676     // Default to empty object for the filter
6677     else {
6678       self[propName] = defaultValue ? defaultValue : {};
6679     }
6680   };
6681
6682
6683
6684   /**
6685    * @ngdoc property
6686    * @name width
6687    * @propertyOf ui.grid.class:GridOptions.columnDef
6688    * @description sets the column width.  Can be either
6689    * a number or a percentage, or an * for auto.
6690    * @example
6691    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
6692    *                                          { field: 'field2', width: '20%'},
6693    *                                          { field: 'field3', width: '*' }]; </pre>
6694    *
6695    */
6696
6697   /**
6698    * @ngdoc property
6699    * @name minWidth
6700    * @propertyOf ui.grid.class:GridOptions.columnDef
6701    * @description sets the minimum column width.  Should be a number.
6702    * @example
6703    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
6704    *
6705    */
6706
6707   /**
6708    * @ngdoc property
6709    * @name maxWidth
6710    * @propertyOf ui.grid.class:GridOptions.columnDef
6711    * @description sets the maximum column width.  Should be a number.
6712    * @example
6713    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
6714    *
6715    */
6716
6717   /**
6718    * @ngdoc property
6719    * @name visible
6720    * @propertyOf ui.grid.class:GridOptions.columnDef
6721    * @description sets whether or not the column is visible
6722    * </br>Default is true
6723    * @example
6724    * <pre>  $scope.gridOptions.columnDefs = [
6725    *     { field: 'field1', visible: true},
6726    *     { field: 'field2', visible: false }
6727    *   ]; </pre>
6728    *
6729    */
6730
6731  /**
6732   * @ngdoc property
6733   * @name sort
6734   * @propertyOf ui.grid.class:GridOptions.columnDef
6735   * @description An object of sort information, attributes are:
6736   *
6737   * - direction: values are uiGridConstants.ASC or uiGridConstants.DESC
6738   * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6739   * - priority: says what order to sort the columns in (lower priority gets sorted first).
6740   * @example
6741   * <pre>
6742   *   $scope.gridOptions.columnDefs = [{
6743   *     field: 'field1',
6744   *     sort: {
6745   *       direction: uiGridConstants.ASC,
6746   *       ignoreSort: true,
6747   *       priority: 0
6748   *      }
6749   *   }];
6750   * </pre>
6751   */
6752
6753
6754   /**
6755    * @ngdoc property
6756    * @name sortingAlgorithm
6757    * @propertyOf ui.grid.class:GridOptions.columnDef
6758    * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
6759    * like any normal sorting function with additional 'rowA', 'rowB', and 'direction' parameters
6760    * that are the row objects and the current direction of the sort respectively.
6761    *
6762    */
6763
6764   /**
6765    * @ngdoc array
6766    * @name filters
6767    * @propertyOf ui.grid.class:GridOptions.columnDef
6768    * @description Specify multiple filter fields.
6769    * @example
6770    * <pre>$scope.gridOptions.columnDefs = [
6771    *   {
6772    *     field: 'field1', filters: [
6773    *       {
6774    *         term: 'aa',
6775    *         condition: uiGridConstants.filter.STARTS_WITH,
6776    *         placeholder: 'starts with...',
6777    *         ariaLabel: 'Filter for field1',
6778    *         flags: { caseSensitive: false },
6779    *         type: uiGridConstants.filter.SELECT,
6780    *         selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6781    *       },
6782    *       {
6783    *         condition: uiGridConstants.filter.ENDS_WITH,
6784    *         placeholder: 'ends with...'
6785    *       }
6786    *     ]
6787    *   }
6788    * ]; </pre>
6789    *
6790    *
6791    */
6792
6793   /**
6794    * @ngdoc array
6795    * @name filters
6796    * @propertyOf ui.grid.class:GridColumn
6797    * @description Filters for this column. Includes 'term' property bound to filter input elements.
6798    * @example
6799    * <pre>[
6800    *   {
6801    *     term: 'foo', // ngModel for <input>
6802    *     condition: uiGridConstants.filter.STARTS_WITH,
6803    *     placeholder: 'starts with...',
6804    *     ariaLabel: 'Filter for foo',
6805    *     flags: { caseSensitive: false },
6806    *     type: uiGridConstants.filter.SELECT,
6807    *     selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6808    *   },
6809    *   {
6810    *     term: 'baz',
6811    *     condition: uiGridConstants.filter.ENDS_WITH,
6812    *     placeholder: 'ends with...'
6813    *   }
6814    * ] </pre>
6815    *
6816    *
6817    */
6818
6819   /**
6820    * @ngdoc array
6821    * @name menuItems
6822    * @propertyOf ui.grid.class:GridOptions.columnDef
6823    * @description used to add menu items to a column.  Refer to the tutorial on this
6824    * functionality.  A number of settings are supported:
6825    *
6826    * - title: controls the title that is displayed in the menu
6827    * - icon: the icon shown alongside that title
6828    * - action: the method to call when the menu is clicked
6829    * - shown: a function to evaluate to determine whether or not to show the item
6830    * - active: a function to evaluate to determine whether or not the item is currently selected
6831    * - context: context to pass to the action function, available in this.context in your handler
6832    * - leaveOpen: if set to true, the menu should stay open after the action, defaults to false
6833    * @example
6834    * <pre>  $scope.gridOptions.columnDefs = [
6835    *   { field: 'field1', menuItems: [
6836    *     {
6837    *       title: 'Outer Scope Alert',
6838    *       icon: 'ui-grid-icon-info-circled',
6839    *       action: function($event) {
6840    *         this.context.blargh(); // $scope.blargh() would work too, this is just an example
6841    *       },
6842    *       shown: function() { return true; },
6843    *       active: function() { return true; },
6844    *       context: $scope
6845    *     },
6846    *     {
6847    *       title: 'Grid ID',
6848    *       action: function() {
6849    *         alert('Grid ID: ' + this.grid.id);
6850    *       }
6851    *     }
6852    *   ] }]; </pre>
6853    *
6854    */
6855
6856   /**
6857    * @ngdoc method
6858    * @methodOf ui.grid.class:GridColumn
6859    * @name updateColumnDef
6860    * @description Moves settings from the columnDef down onto the column,
6861    * and sets properties as appropriate
6862    * @param {ColumnDef} colDef the column def to look in for the property value
6863    * @param {boolean} isNew whether the column is being newly created, if not
6864    * we're updating an existing column, and some items such as the sort shouldn't
6865    * be copied down
6866    */
6867   GridColumn.prototype.updateColumnDef = function(colDef, isNew) {
6868     var self = this;
6869
6870     self.colDef = colDef;
6871
6872     if (colDef.name === undefined) {
6873       throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
6874     }
6875
6876     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6877
6878     if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
6879       var colDefWidth = colDef.width;
6880       var parseErrorMsg = "Cannot parse column width '" + colDefWidth + "' for column named '" + colDef.name + "'";
6881       self.hasCustomWidth = false;
6882
6883       if (!angular.isString(colDefWidth) && !angular.isNumber(colDefWidth)) {
6884         self.width = '*';
6885       } else if (angular.isString(colDefWidth)) {
6886         // See if it ends with a percent
6887         if (gridUtil.endsWith(colDefWidth, '%')) {
6888           // If so we should be able to parse the non-percent-sign part to a number
6889           var percentStr = colDefWidth.replace(/%/g, '');
6890           var percent = parseInt(percentStr, 10);
6891           if (isNaN(percent)) {
6892             throw new Error(parseErrorMsg);
6893           }
6894           self.width = colDefWidth;
6895         }
6896         // And see if it's a number string
6897         else if (colDefWidth.match(/^(\d+)$/)) {
6898           self.width = parseInt(colDefWidth.match(/^(\d+)$/)[1], 10);
6899         }
6900         // Otherwise it should be a string of asterisks
6901         else if (colDefWidth.match(/^\*+$/)) {
6902           self.width = colDefWidth;
6903         }
6904         // No idea, throw an Error
6905         else {
6906           throw new Error(parseErrorMsg);
6907         }
6908       }
6909       // Is a number, use it as the width
6910       else {
6911         self.width = colDefWidth;
6912       }
6913     }
6914
6915     ['minWidth', 'maxWidth'].forEach(function (name) {
6916       var minOrMaxWidth = colDef[name];
6917       var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
6918
6919       if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
6920         //Sets default minWidth and maxWidth values
6921         self[name] = ((name === 'minWidth') ? 30 : 9000);
6922       } else if (angular.isString(minOrMaxWidth)) {
6923         if (minOrMaxWidth.match(/^(\d+)$/)) {
6924           self[name] = parseInt(minOrMaxWidth.match(/^(\d+)$/)[1], 10);
6925         } else {
6926           throw new Error(parseErrorMsg);
6927         }
6928       } else {
6929         self[name] = minOrMaxWidth;
6930       }
6931     });
6932
6933     //use field if it is defined; name if it is not
6934     self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
6935
6936     if ( typeof( self.field ) !== 'string' ){
6937       gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
6938     }
6939
6940     self.name = colDef.name;
6941
6942     // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
6943     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6944
6945     //self.originalIndex = index;
6946
6947     self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
6948     self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
6949
6950     /**
6951      * @ngdoc property
6952      * @name cellTooltip
6953      * @propertyOf ui.grid.class:GridOptions.columnDef
6954      * @description Whether or not to show a tooltip when a user hovers over the cell.
6955      * If set to false, no tooltip.  If true, the cell value is shown in the tooltip (useful
6956      * if you have long values in your cells), if a function then that function is called
6957      * passing in the row and the col `cellTooltip( row, col )`, and the return value is shown in the tooltip,
6958      * if it is a static string then displays that static string.
6959      *
6960      * Defaults to false
6961      *
6962      */
6963     if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
6964       self.cellTooltip = false;
6965     } else if ( colDef.cellTooltip === true ){
6966       self.cellTooltip = function(row, col) {
6967         return self.grid.getCellValue( row, col );
6968       };
6969     } else if (typeof(colDef.cellTooltip) === 'function' ){
6970       self.cellTooltip = colDef.cellTooltip;
6971     } else {
6972       self.cellTooltip = function ( row, col ){
6973         return col.colDef.cellTooltip;
6974       };
6975     }
6976
6977     /**
6978      * @ngdoc property
6979      * @name headerTooltip
6980      * @propertyOf ui.grid.class:GridOptions.columnDef
6981      * @description Whether or not to show a tooltip when a user hovers over the header cell.
6982      * If set to false, no tooltip.  If true, the displayName is shown in the tooltip (useful
6983      * if you have long values in your headers), if a function then that function is called
6984      * passing in the row and the col `headerTooltip( col )`, and the return value is shown in the tooltip,
6985      * if a static string then shows that static string.
6986      *
6987      * Defaults to false
6988      *
6989      */
6990     if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
6991       self.headerTooltip = false;
6992     } else if ( colDef.headerTooltip === true ){
6993       self.headerTooltip = function(col) {
6994         return col.displayName;
6995       };
6996     } else if (typeof(colDef.headerTooltip) === 'function' ){
6997       self.headerTooltip = colDef.headerTooltip;
6998     } else {
6999       self.headerTooltip = function ( col ) {
7000         return col.colDef.headerTooltip;
7001       };
7002     }
7003
7004
7005     /**
7006      * @ngdoc property
7007      * @name footerCellClass
7008      * @propertyOf ui.grid.class:GridOptions.columnDef
7009      * @description footerCellClass can be a string specifying the class to append to a cell
7010      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7011      *
7012      */
7013     self.footerCellClass = colDef.footerCellClass;
7014
7015     /**
7016      * @ngdoc property
7017      * @name cellClass
7018      * @propertyOf ui.grid.class:GridOptions.columnDef
7019      * @description cellClass can be a string specifying the class to append to a cell
7020      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7021      *
7022      */
7023     self.cellClass = colDef.cellClass;
7024
7025     /**
7026      * @ngdoc property
7027      * @name headerCellClass
7028      * @propertyOf ui.grid.class:GridOptions.columnDef
7029      * @description headerCellClass can be a string specifying the class to append to a cell
7030      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7031      *
7032      */
7033     self.headerCellClass = colDef.headerCellClass;
7034
7035     /**
7036      * @ngdoc property
7037      * @name cellFilter
7038      * @propertyOf ui.grid.class:GridOptions.columnDef
7039      * @description cellFilter is a filter to apply to the content of each cell
7040      * @example
7041      * <pre>
7042      *   gridOptions.columnDefs[0].cellFilter = 'date'
7043      *
7044      */
7045     self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
7046
7047     /**
7048      * @ngdoc boolean
7049      * @name sortCellFiltered
7050      * @propertyOf ui.grid.class:GridOptions.columnDef
7051      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7052      * sorting the data. Note that when using this option uiGrid will assume that the displayed value is
7053      * a string, and use the {@link ui.grid.class:RowSorter#sortAlpha sortAlpha} `sortFn`. It is possible
7054      * 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}
7055      * for the column which hanldes the returned type. You may specify one of the `sortingAlgorithms`
7056      * found in the {@link ui.grid.RowSorter rowSorter} service.
7057      */
7058     self.sortCellFiltered = colDef.sortCellFiltered ? true : false;
7059
7060     /**
7061      * @ngdoc boolean
7062      * @name filterCellFiltered
7063      * @propertyOf ui.grid.class:GridOptions.columnDef
7064      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7065      * applying "search" `filters`.
7066      */
7067     self.filterCellFiltered = colDef.filterCellFiltered ? true : false;
7068
7069     /**
7070      * @ngdoc property
7071      * @name headerCellFilter
7072      * @propertyOf ui.grid.class:GridOptions.columnDef
7073      * @description headerCellFilter is a filter to apply to the content of the column header
7074      * @example
7075      * <pre>
7076      *   gridOptions.columnDefs[0].headerCellFilter = 'translate'
7077      *
7078      */
7079     self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
7080
7081     /**
7082      * @ngdoc property
7083      * @name footerCellFilter
7084      * @propertyOf ui.grid.class:GridOptions.columnDef
7085      * @description footerCellFilter is a filter to apply to the content of the column footer
7086      * @example
7087      * <pre>
7088      *   gridOptions.columnDefs[0].footerCellFilter = 'date'
7089      *
7090      */
7091     self.footerCellFilter = colDef.footerCellFilter ? colDef.footerCellFilter : "";
7092
7093     self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
7094
7095     self.headerClass = colDef.headerClass;
7096     //self.cursor = self.sortable ? 'pointer' : 'default';
7097
7098     // Turn on sorting by default
7099     self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
7100     self.sortingAlgorithm = colDef.sortingAlgorithm;
7101
7102     /**
7103      * @ngdoc property
7104      * @name sortDirectionCycle
7105      * @propertyOf ui.grid.class:GridOptions.columnDef
7106      * @description (optional) An array of sort directions, specifying the order that they
7107      * should cycle through as the user repeatedly clicks on the column heading.
7108      * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
7109      * refers to the unsorted state. This does not affect the initial sort
7110      * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
7111      * property for that. If
7112      * {@link ui.grid.class:GridOptions.columnDef#suppressRemoveSort suppressRemoveSort}
7113      * is also set, the unsorted state will be skipped even if it is listed here.
7114      * Each direction may not appear in the list more than once (e.g. `[ASC,
7115      * DESC, DESC]` is not allowed), and the list may not be empty.
7116      */
7117     self.sortDirectionCycle = typeof(colDef.sortDirectionCycle) !== 'undefined' ?
7118       colDef.sortDirectionCycle :
7119       [null, uiGridConstants.ASC, uiGridConstants.DESC];
7120
7121     /**
7122      * @ngdoc boolean
7123      * @name suppressRemoveSort
7124      * @propertyOf ui.grid.class:GridOptions.columnDef
7125      * @description (optional) False by default. When enabled, this setting hides the removeSort option
7126      * in the menu, and prevents users from manually removing the sort
7127      */
7128     if ( typeof(self.suppressRemoveSort) === 'undefined'){
7129       self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
7130     }
7131
7132     /**
7133      * @ngdoc property
7134      * @name enableFiltering
7135      * @propertyOf ui.grid.class:GridOptions.columnDef
7136      * @description turn off filtering for an individual column, where
7137      * you've turned on filtering for the overall grid
7138      * @example
7139      * <pre>
7140      *   gridOptions.columnDefs[0].enableFiltering = false;
7141      *
7142      */
7143     // Turn on filtering by default (it's disabled by default at the Grid level)
7144     self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
7145
7146     // self.menuItems = colDef.menuItems;
7147     self.setPropertyOrDefault(colDef, 'menuItems', []);
7148
7149     // Use the column definition sort if we were passed it, but only if this is a newly added column
7150     if ( isNew ){
7151       self.setPropertyOrDefault(colDef, 'sort');
7152     }
7153
7154     // Set up default filters array for when one is not provided.
7155     //   In other words, this (in column def):
7156     //
7157     //       filter: { term: 'something', flags: {}, condition: [CONDITION] }
7158     //
7159     //   is just shorthand for this:
7160     //
7161     //       filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
7162     //
7163     var defaultFilters = [];
7164     if (colDef.filter) {
7165       defaultFilters.push(colDef.filter);
7166     }
7167     else if ( colDef.filters ){
7168       defaultFilters = colDef.filters;
7169     } else {
7170       // Add an empty filter definition object, which will
7171       // translate to a guessed condition and no pre-populated
7172       // value for the filter <input>.
7173       defaultFilters.push({});
7174     }
7175
7176     /**
7177      * @ngdoc property
7178      * @name filter
7179      * @propertyOf ui.grid.class:GridOptions.columnDef
7180      * @description Specify a single filter field on this column.
7181      *
7182      * A filter consists of a condition, a term, and a placeholder:
7183      *
7184      * - condition defines how rows are chosen as matching the filter term. This can be set to
7185      * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
7186      * that gets passed the following arguments: [searchTerm, cellValue, row, column].
7187      * - term: If set, the filter field will be pre-populated
7188      * with this value.
7189      * - placeholder: String that will be set to the `<input>.placeholder` attribute.
7190      * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
7191      * - noTerm: set this to true if you have defined a custom function in condition, and
7192      * your custom function doesn't require a term (so it can run even when the term is null)
7193      * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
7194      * case sensitive matching
7195      * - type: defaults to uiGridConstants.filter.INPUT, which gives a text box.  If set to uiGridConstants.filter.SELECT
7196      * then a select box will be shown with options selectOptions
7197      * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`.  No i18n filter is provided, you need
7198      * to perform the i18n on the values before you provide them
7199      * - disableCancelFilterButton: defaults to false. If set to true then the 'x' button that cancels/clears the filter
7200      * will not be shown.
7201      * @example
7202      * <pre>$scope.gridOptions.columnDefs = [
7203      *   {
7204      *     field: 'field1',
7205      *     filter: {
7206      *       term: 'xx',
7207      *       condition: uiGridConstants.filter.STARTS_WITH,
7208      *       placeholder: 'starts with...',
7209      *       ariaLabel: 'Starts with filter for field1',
7210      *       flags: { caseSensitive: false },
7211      *       type: uiGridConstants.filter.SELECT,
7212      *       selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ],
7213      *       disableCancelFilterButton: true
7214      *     }
7215      *   }
7216      * ]; </pre>
7217      *
7218      */
7219
7220     /*
7221
7222
7223     /*
7224
7225       self.filters = [
7226         {
7227           term: 'search term'
7228           condition: uiGridConstants.filter.CONTAINS,
7229           placeholder: 'my placeholder',
7230           ariaLabel: 'Starts with filter for field1',
7231           flags: {
7232             caseSensitive: true
7233           }
7234         }
7235       ]
7236
7237     */
7238
7239     // Only set filter if this is a newly added column, if we're updating an existing
7240     // column then we don't want to put the default filter back if the user may have already
7241     // removed it.
7242     // However, we do want to keep the settings if they change, just not the term
7243     if ( isNew ) {
7244       self.setPropertyOrDefault(colDef, 'filter');
7245       self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
7246     } else if ( self.filters.length === defaultFilters.length ) {
7247       self.filters.forEach( function( filter, index ){
7248         if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
7249           filter.placeholder = defaultFilters[index].placeholder;
7250         }
7251         if (typeof(defaultFilters[index].ariaLabel) !== 'undefined') {
7252           filter.ariaLabel = defaultFilters[index].ariaLabel;
7253         }
7254         if (typeof(defaultFilters[index].flags) !== 'undefined') {
7255           filter.flags = defaultFilters[index].flags;
7256         }
7257         if (typeof(defaultFilters[index].type) !== 'undefined') {
7258           filter.type = defaultFilters[index].type;
7259         }
7260         if (typeof(defaultFilters[index].selectOptions) !== 'undefined') {
7261           filter.selectOptions = defaultFilters[index].selectOptions;
7262         }
7263       });
7264     }
7265   };
7266
7267   /**
7268    * @ngdoc function
7269    * @name unsort
7270    * @methodOf ui.grid.class:GridColumn
7271    * @description Removes column from the grid sorting
7272    */
7273   GridColumn.prototype.unsort = function () {
7274     this.sort = {};
7275     this.grid.api.core.raise.sortChanged( this.grid, this.grid.getColumnSorting() );
7276   };
7277
7278
7279   /**
7280    * @ngdoc function
7281    * @name getColClass
7282    * @methodOf ui.grid.class:GridColumn
7283    * @description Returns the class name for the column
7284    * @param {bool} prefixDot  if true, will return .className instead of className
7285    */
7286   GridColumn.prototype.getColClass = function (prefixDot) {
7287     var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
7288
7289     return prefixDot ? '.' + cls : cls;
7290   };
7291
7292     /**
7293      * @ngdoc function
7294      * @name isPinnedLeft
7295      * @methodOf ui.grid.class:GridColumn
7296      * @description Returns true if column is in the left render container
7297      */
7298     GridColumn.prototype.isPinnedLeft = function () {
7299       return this.renderContainer === 'left';
7300     };
7301
7302     /**
7303      * @ngdoc function
7304      * @name isPinnedRight
7305      * @methodOf ui.grid.class:GridColumn
7306      * @description Returns true if column is in the right render container
7307      */
7308     GridColumn.prototype.isPinnedRight = function () {
7309       return this.renderContainer === 'right';
7310     };
7311
7312
7313     /**
7314    * @ngdoc function
7315    * @name getColClassDefinition
7316    * @methodOf ui.grid.class:GridColumn
7317    * @description Returns the class definition for th column
7318    */
7319   GridColumn.prototype.getColClassDefinition = function () {
7320     return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { min-width: ' + this.drawnWidth + 'px; max-width: ' + this.drawnWidth + 'px; }';
7321   };
7322
7323   /**
7324    * @ngdoc function
7325    * @name getRenderContainer
7326    * @methodOf ui.grid.class:GridColumn
7327    * @description Returns the render container object that this column belongs to.
7328    *
7329    * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
7330    */
7331   GridColumn.prototype.getRenderContainer = function getRenderContainer() {
7332     var self = this;
7333
7334     var containerId = self.renderContainer;
7335
7336     if (containerId === null || containerId === '' || containerId === undefined) {
7337       containerId = 'body';
7338     }
7339
7340     return self.grid.renderContainers[containerId];
7341   };
7342
7343   /**
7344    * @ngdoc function
7345    * @name showColumn
7346    * @methodOf ui.grid.class:GridColumn
7347    * @description Makes the column visible by setting colDef.visible = true
7348    */
7349   GridColumn.prototype.showColumn = function() {
7350       this.colDef.visible = true;
7351   };
7352
7353
7354   /**
7355    * @ngdoc property
7356    * @name aggregationHideLabel
7357    * @propertyOf ui.grid.class:GridOptions.columnDef
7358    * @description defaults to false, if set to true hides the label text
7359    * in the aggregation footer, so only the value is displayed.
7360    *
7361    */
7362   /**
7363    * @ngdoc function
7364    * @name getAggregationText
7365    * @methodOf ui.grid.class:GridColumn
7366    * @description Gets the aggregation label from colDef.aggregationLabel if
7367    * specified or by using i18n, including deciding whether or not to display
7368    * based on colDef.aggregationHideLabel.
7369    *
7370    * @param {string} label the i18n lookup value to use for the column label
7371    *
7372    */
7373   GridColumn.prototype.getAggregationText = function () {
7374     var self = this;
7375     if ( self.colDef.aggregationHideLabel ){
7376       return '';
7377     }
7378     else if ( self.colDef.aggregationLabel ) {
7379       return self.colDef.aggregationLabel;
7380     }
7381     else {
7382       switch ( self.colDef.aggregationType ){
7383         case uiGridConstants.aggregationTypes.count:
7384           return i18nService.getSafeText('aggregation.count');
7385         case uiGridConstants.aggregationTypes.sum:
7386           return i18nService.getSafeText('aggregation.sum');
7387         case uiGridConstants.aggregationTypes.avg:
7388           return i18nService.getSafeText('aggregation.avg');
7389         case uiGridConstants.aggregationTypes.min:
7390           return i18nService.getSafeText('aggregation.min');
7391         case uiGridConstants.aggregationTypes.max:
7392           return i18nService.getSafeText('aggregation.max');
7393         default:
7394           return '';
7395       }
7396     }
7397   };
7398
7399   GridColumn.prototype.getCellTemplate = function () {
7400     var self = this;
7401
7402     return self.cellTemplatePromise;
7403   };
7404
7405   GridColumn.prototype.getCompiledElementFn = function () {
7406     var self = this;
7407
7408     return self.compiledElementFnDefer.promise;
7409   };
7410
7411   return GridColumn;
7412 }]);
7413
7414 })();
7415
7416   (function(){
7417
7418 angular.module('ui.grid')
7419 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7420
7421   /**
7422    * @ngdoc function
7423    * @name ui.grid.class:GridOptions
7424    * @description Default GridOptions class.  GridOptions are defined by the application developer and overlaid
7425    * over this object.  Setting gridOptions within your controller is the most common method for an application
7426    * developer to configure the behaviour of their ui-grid
7427    *
7428    * @example To define your gridOptions within your controller:
7429    * <pre>$scope.gridOptions = {
7430    *   data: $scope.myData,
7431    *   columnDefs: [
7432    *     { name: 'field1', displayName: 'pretty display name' },
7433    *     { name: 'field2', visible: false }
7434    *  ]
7435    * };</pre>
7436    *
7437    * You can then use this within your html template, when you define your grid:
7438    * <pre>&lt;div ui-grid="gridOptions"&gt;&lt;/div&gt;</pre>
7439    *
7440    * To provide default options for all of the grids within your application, use an angular
7441    * decorator to modify the GridOptions factory.
7442    * <pre>
7443    * app.config(function($provide){
7444    *   $provide.decorator('GridOptions',function($delegate){
7445    *     var gridOptions;
7446    *     gridOptions = angular.copy($delegate);
7447    *     gridOptions.initialize = function(options) {
7448    *       var initOptions;
7449    *       initOptions = $delegate.initialize(options);
7450    *       initOptions.enableColumnMenus = false;
7451    *       return initOptions;
7452    *     };
7453    *     return gridOptions;
7454    *   });
7455    * });
7456    * </pre>
7457    */
7458   return {
7459     initialize: function( baseOptions ){
7460       /**
7461        * @ngdoc function
7462        * @name onRegisterApi
7463        * @propertyOf ui.grid.class:GridOptions
7464        * @description A callback that returns the gridApi once the grid is instantiated, which is
7465        * then used to interact with the grid programatically.
7466        *
7467        * Note that the gridApi.core.renderingComplete event is identical to this
7468        * callback, but has the advantage that it can be called from multiple places
7469        * if needed
7470        *
7471        * @example
7472        * <pre>
7473        *   $scope.gridOptions.onRegisterApi = function ( gridApi ) {
7474        *     $scope.gridApi = gridApi;
7475        *     $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid );
7476        *   };
7477        * </pre>
7478        *
7479        */
7480       baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
7481
7482       /**
7483        * @ngdoc object
7484        * @name data
7485        * @propertyOf ui.grid.class:GridOptions
7486        * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
7487        * the grid.
7488        *
7489        * Most commonly the data is an array of objects, where each object has a number of attributes.
7490        * Each attribute automatically becomes a column in your grid.  This array could, for example, be sourced from
7491        * an angularJS $resource query request.  The array can also contain complex objects, refer the binding tutorial
7492        * for examples of that.
7493        *
7494        * The most flexible usage is to set your data on $scope:
7495        *
7496        * `$scope.data = data;`
7497        *
7498        * And then direct the grid to resolve whatever is in $scope.data:
7499        *
7500        * `$scope.gridOptions.data = 'data';`
7501        *
7502        * This is the most flexible approach as it allows you to replace $scope.data whenever you feel like it without
7503        * getting pointer issues.
7504        *
7505        * Alternatively you can directly set the data array:
7506        *
7507        * `$scope.gridOptions.data = [ ];`
7508        * or
7509        *
7510        * `$http.get('/data/100.json')
7511        * .success(function(data) {
7512        *   $scope.myData = data;
7513        *   $scope.gridOptions.data = $scope.myData;
7514        *  });`
7515        *
7516        * Where you do this, you need to take care in updating the data - you can't just update `$scope.myData` to some other
7517        * array, you need to update $scope.gridOptions.data to point to that new array as well.
7518        *
7519        */
7520       baseOptions.data = baseOptions.data || [];
7521
7522       /**
7523        * @ngdoc array
7524        * @name columnDefs
7525        * @propertyOf  ui.grid.class:GridOptions
7526        * @description Array of columnDef objects.  Only required property is name.
7527        * The individual options available in columnDefs are documented in the
7528        * {@link ui.grid.class:GridOptions.columnDef columnDef} section
7529        * </br>_field property can be used in place of name for backwards compatibility with 2.x_
7530        *  @example
7531        *
7532        * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7533        *
7534        */
7535       baseOptions.columnDefs = baseOptions.columnDefs || [];
7536
7537       /**
7538        * @ngdoc object
7539        * @name ui.grid.class:GridOptions.columnDef
7540        * @description Definition / configuration of an individual column, which would typically be
7541        * one of many column definitions within the gridOptions.columnDefs array
7542        * @example
7543        * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7544        *
7545        */
7546
7547
7548       /**
7549        * @ngdoc array
7550        * @name excludeProperties
7551        * @propertyOf  ui.grid.class:GridOptions
7552        * @description Array of property names in data to ignore when auto-generating column names.  Provides the
7553        * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
7554        * to exclude.
7555        *
7556        * If columnDefs is defined, this will be ignored.
7557        *
7558        * Defaults to ['$$hashKey']
7559        */
7560
7561       baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
7562
7563       /**
7564        * @ngdoc boolean
7565        * @name enableRowHashing
7566        * @propertyOf ui.grid.class:GridOptions
7567        * @description True by default. When enabled, this setting allows uiGrid to add
7568        * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
7569        * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
7570        *
7571        * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
7572        * 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
7573        * and are altering the data set often.
7574        */
7575       baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
7576
7577       /**
7578        * @ngdoc function
7579        * @name rowIdentity
7580        * @methodOf ui.grid.class:GridOptions
7581        * @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).
7582        *
7583        * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
7584        */
7585       baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
7586         return gridUtil.hashKey(row);
7587       };
7588
7589       /**
7590        * @ngdoc function
7591        * @name getRowIdentity
7592        * @methodOf ui.grid.class:GridOptions
7593        * @description This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
7594        *
7595        * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
7596        */
7597       baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
7598         return row.$$hashKey;
7599       };
7600
7601       /**
7602        * @ngdoc property
7603        * @name flatEntityAccess
7604        * @propertyOf ui.grid.class:GridOptions
7605        * @description Set to true if your columns are all related directly to fields in a flat object structure - i.e.
7606        * each of your columns associate directly with a property on each of the entities in your data array.
7607        *
7608        * In that situation we can avoid all the logic associated with complex binding to functions or to properties of sub-objects,
7609        * which can provide a significant speed improvement with large data sets when filtering or sorting.
7610        *
7611        * By default false
7612        */
7613       baseOptions.flatEntityAccess = baseOptions.flatEntityAccess === true;
7614
7615       /**
7616        * @ngdoc property
7617        * @name showHeader
7618        * @propertyOf ui.grid.class:GridOptions
7619        * @description True by default. When set to false, this setting will replace the
7620        * standard header template with '<div></div>', resulting in no header being shown.
7621        */
7622       baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
7623
7624       /* (NOTE): Don't show this in the docs. We only use it internally
7625        * @ngdoc property
7626        * @name headerRowHeight
7627        * @propertyOf ui.grid.class:GridOptions
7628        * @description The height of the header in pixels, defaults to 30
7629        *
7630        */
7631       if (!baseOptions.showHeader) {
7632         baseOptions.headerRowHeight = 0;
7633       }
7634       else {
7635         baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
7636       }
7637
7638       /**
7639        * @ngdoc property
7640        * @name rowHeight
7641        * @propertyOf ui.grid.class:GridOptions
7642        * @description The height of the row in pixels, defaults to 30
7643        *
7644        */
7645       baseOptions.rowHeight = baseOptions.rowHeight || 30;
7646
7647       /**
7648        * @ngdoc integer
7649        * @name minRowsToShow
7650        * @propertyOf ui.grid.class:GridOptions
7651        * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
7652        */
7653       baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
7654
7655       /**
7656        * @ngdoc property
7657        * @name showGridFooter
7658        * @propertyOf ui.grid.class:GridOptions
7659        * @description Whether or not to show the footer, defaults to false
7660        * The footer display Total Rows and Visible Rows (filtered rows)
7661        */
7662       baseOptions.showGridFooter = baseOptions.showGridFooter === true;
7663
7664       /**
7665        * @ngdoc property
7666        * @name showColumnFooter
7667        * @propertyOf ui.grid.class:GridOptions
7668        * @description Whether or not to show the column footer, defaults to false
7669        * The column footer displays column aggregates
7670        */
7671       baseOptions.showColumnFooter = baseOptions.showColumnFooter === true;
7672
7673       /**
7674        * @ngdoc property
7675        * @name columnFooterHeight
7676        * @propertyOf ui.grid.class:GridOptions
7677        * @description The height of the footer rows (column footer and grid footer) in pixels
7678        *
7679        */
7680       baseOptions.columnFooterHeight = typeof(baseOptions.columnFooterHeight) !== "undefined" ? baseOptions.columnFooterHeight : 30;
7681       baseOptions.gridFooterHeight = typeof(baseOptions.gridFooterHeight) !== "undefined" ? baseOptions.gridFooterHeight : 30;
7682
7683       baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
7684
7685       /**
7686        * @ngdoc property
7687        * @name maxVisibleColumnCount
7688        * @propertyOf ui.grid.class:GridOptions
7689        * @description Defaults to 200
7690        *
7691        */
7692       baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
7693
7694       /**
7695        * @ngdoc property
7696        * @name virtualizationThreshold
7697        * @propertyOf ui.grid.class:GridOptions
7698        * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
7699        */
7700       baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
7701
7702       /**
7703        * @ngdoc property
7704        * @name columnVirtualizationThreshold
7705        * @propertyOf ui.grid.class:GridOptions
7706        * @description Turn virtualization on when number of columns goes over this number, defaults to 10
7707        */
7708       baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
7709
7710       /**
7711        * @ngdoc property
7712        * @name excessRows
7713        * @propertyOf ui.grid.class:GridOptions
7714        * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
7715        * Defaults to 4
7716        */
7717       baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7718       /**
7719        * @ngdoc property
7720        * @name scrollThreshold
7721        * @propertyOf ui.grid.class:GridOptions
7722        * @description Defaults to 4
7723        */
7724       baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
7725
7726       /**
7727        * @ngdoc property
7728        * @name excessColumns
7729        * @propertyOf ui.grid.class:GridOptions
7730        * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
7731        * Defaults to 4
7732        */
7733       baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7734       /**
7735        * @ngdoc property
7736        * @name horizontalScrollThreshold
7737        * @propertyOf ui.grid.class:GridOptions
7738        * @description Defaults to 4
7739        */
7740       baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
7741
7742
7743       /**
7744        * @ngdoc property
7745        * @name aggregationCalcThrottle
7746        * @propertyOf ui.grid.class:GridOptions
7747        * @description Default time in milliseconds to throttle aggregation calcuations, defaults to 500ms
7748        */
7749       baseOptions.aggregationCalcThrottle = typeof(baseOptions.aggregationCalcThrottle) !== "undefined" ? baseOptions.aggregationCalcThrottle : 500;
7750
7751       /**
7752        * @ngdoc property
7753        * @name wheelScrollThrottle
7754        * @propertyOf ui.grid.class:GridOptions
7755        * @description Default time in milliseconds to throttle scroll events to, defaults to 70ms
7756        */
7757       baseOptions.wheelScrollThrottle = typeof(baseOptions.wheelScrollThrottle) !== "undefined" ? baseOptions.wheelScrollThrottle : 70;
7758
7759
7760       /**
7761        * @ngdoc property
7762        * @name scrollDebounce
7763        * @propertyOf ui.grid.class:GridOptions
7764        * @description Default time in milliseconds to debounce scroll events, defaults to 300ms
7765        */
7766       baseOptions.scrollDebounce = typeof(baseOptions.scrollDebounce) !== "undefined" ? baseOptions.scrollDebounce : 300;
7767
7768       /**
7769        * @ngdoc boolean
7770        * @name enableSorting
7771        * @propertyOf ui.grid.class:GridOptions
7772        * @description True by default. When enabled, this setting adds sort
7773        * widgets to the column headers, allowing sorting of the data for the entire grid.
7774        * Sorting can then be disabled on individual columns using the columnDefs.
7775        */
7776       baseOptions.enableSorting = baseOptions.enableSorting !== false;
7777
7778       /**
7779        * @ngdoc boolean
7780        * @name enableFiltering
7781        * @propertyOf ui.grid.class:GridOptions
7782        * @description False by default. When enabled, this setting adds filter
7783        * boxes to each column header, allowing filtering within the column for the entire grid.
7784        * Filtering can then be disabled on individual columns using the columnDefs.
7785        */
7786       baseOptions.enableFiltering = baseOptions.enableFiltering === true;
7787
7788       /**
7789        * @ngdoc boolean
7790        * @name enableColumnMenus
7791        * @propertyOf ui.grid.class:GridOptions
7792        * @description True by default. When enabled, this setting displays a column
7793        * menu within each column.
7794        */
7795       baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
7796
7797       /**
7798        * @ngdoc boolean
7799        * @name enableVerticalScrollbar
7800        * @propertyOf ui.grid.class:GridOptions
7801        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
7802        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7803        */
7804       baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7805
7806       /**
7807        * @ngdoc boolean
7808        * @name enableHorizontalScrollbar
7809        * @propertyOf ui.grid.class:GridOptions
7810        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
7811        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7812        */
7813       baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7814
7815       /**
7816        * @ngdoc boolean
7817        * @name enableMinHeightCheck
7818        * @propertyOf ui.grid.class:GridOptions
7819        * @description True by default. When enabled, a newly initialized grid will check to see if it is tall enough to display
7820        * at least one row of data.  If the grid is not tall enough, it will resize the DOM element to display minRowsToShow number
7821        * of rows.
7822        */
7823        baseOptions.enableMinHeightCheck = baseOptions.enableMinHeightCheck !== false;
7824
7825       /**
7826        * @ngdoc boolean
7827        * @name minimumColumnSize
7828        * @propertyOf ui.grid.class:GridOptions
7829        * @description Columns can't be smaller than this, defaults to 10 pixels
7830        */
7831       baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
7832
7833       /**
7834        * @ngdoc function
7835        * @name rowEquality
7836        * @methodOf ui.grid.class:GridOptions
7837        * @description By default, rows are compared using object equality.  This option can be overridden
7838        * to compare on any data item property or function
7839        * @param {object} entityA First Data Item to compare
7840        * @param {object} entityB Second Data Item to compare
7841        */
7842       baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
7843         return entityA === entityB;
7844       };
7845
7846       /**
7847        * @ngdoc string
7848        * @name headerTemplate
7849        * @propertyOf ui.grid.class:GridOptions
7850        * @description Null by default. When provided, this setting uses a custom header
7851        * template, rather than the default template. Can be set to either the name of a template file:
7852        * <pre>  $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
7853        * inline html
7854        * <pre>  $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
7855        * or the id of a precompiled template (TBD how to use this).
7856        * </br>Refer to the custom header tutorial for more information.
7857        * If you want no header at all, you can set to an empty div:
7858        * <pre>  $scope.gridOptions.headerTemplate = '<div></div>';</pre>
7859        *
7860        * If you want to only have a static header, then you can set to static content.  If
7861        * you want to tailor the existing column headers, then you should look at the
7862        * current 'ui-grid-header.html' template in github as your starting point.
7863        *
7864        */
7865       baseOptions.headerTemplate = baseOptions.headerTemplate || null;
7866
7867       /**
7868        * @ngdoc string
7869        * @name footerTemplate
7870        * @propertyOf ui.grid.class:GridOptions
7871        * @description (optional) ui-grid/ui-grid-footer by default.  This footer shows the per-column
7872        * aggregation totals.
7873        * 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
7874        * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
7875        * of a precompiled template (TBD how to use this).  Refer to the custom footer tutorial for more information.
7876        */
7877       baseOptions.footerTemplate = baseOptions.footerTemplate || 'ui-grid/ui-grid-footer';
7878
7879       /**
7880        * @ngdoc string
7881        * @name gridFooterTemplate
7882        * @propertyOf ui.grid.class:GridOptions
7883        * @description (optional) ui-grid/ui-grid-grid-footer by default. This template by default shows the
7884        * total items at the bottom of the grid, and the selected items if selection is enabled.
7885        */
7886       baseOptions.gridFooterTemplate = baseOptions.gridFooterTemplate || 'ui-grid/ui-grid-grid-footer';
7887
7888       /**
7889        * @ngdoc string
7890        * @name rowTemplate
7891        * @propertyOf ui.grid.class:GridOptions
7892        * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
7893        * custom row template.  Can be set to either the name of a template file:
7894        * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
7895        * inline html
7896        * <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>
7897        * or the id of a precompiled template (TBD how to use this) can be provided.
7898        * </br>Refer to the custom row template tutorial for more information.
7899        */
7900       baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
7901
7902       /**
7903        * @ngdoc object
7904        * @name appScopeProvider
7905        * @propertyOf ui.grid.class:GridOptions
7906        * @description by default, the parent scope of the ui-grid element will be assigned to grid.appScope
7907        * this property allows you to assign any reference you want to grid.appScope
7908        */
7909       baseOptions.appScopeProvider = baseOptions.appScopeProvider || null;
7910
7911       return baseOptions;
7912     }
7913   };
7914
7915
7916 }]);
7917
7918 })();
7919
7920 (function(){
7921
7922 angular.module('ui.grid')
7923
7924   /**
7925    * @ngdoc function
7926    * @name ui.grid.class:GridRenderContainer
7927    * @description The grid has render containers, allowing the ability to have pinned columns.  If the grid
7928    * is right-to-left then there may be a right render container, if left-to-right then there may
7929    * be a left render container.  There is always a body render container.
7930    * @param {string} name The name of the render container ('body', 'left', or 'right')
7931    * @param {Grid} grid the grid the render container is in
7932    * @param {object} options the render container options
7933    */
7934 .factory('GridRenderContainer', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
7935   function GridRenderContainer(name, grid, options) {
7936     var self = this;
7937
7938     // if (gridUtil.type(grid) !== 'Grid') {
7939     //   throw new Error('Grid argument is not a Grid object');
7940     // }
7941
7942     self.name = name;
7943
7944     self.grid = grid;
7945
7946     // self.rowCache = [];
7947     // self.columnCache = [];
7948
7949     self.visibleRowCache = [];
7950     self.visibleColumnCache = [];
7951
7952     self.renderedRows = [];
7953     self.renderedColumns = [];
7954
7955     self.prevScrollTop = 0;
7956     self.prevScrolltopPercentage = 0;
7957     self.prevRowScrollIndex = 0;
7958
7959     self.prevScrollLeft = 0;
7960     self.prevScrollleftPercentage = 0;
7961     self.prevColumnScrollIndex = 0;
7962
7963     self.columnStyles = "";
7964
7965     self.viewportAdjusters = [];
7966
7967     /**
7968      *  @ngdoc boolean
7969      *  @name hasHScrollbar
7970      *  @propertyOf  ui.grid.class:GridRenderContainer
7971      *  @description flag to signal that container has a horizontal scrollbar
7972      */
7973     self.hasHScrollbar = false;
7974
7975     /**
7976      *  @ngdoc boolean
7977      *  @name hasVScrollbar
7978      *  @propertyOf  ui.grid.class:GridRenderContainer
7979      *  @description flag to signal that container has a vertical scrollbar
7980      */
7981     self.hasVScrollbar = false;
7982
7983     /**
7984      *  @ngdoc boolean
7985      *  @name canvasHeightShouldUpdate
7986      *  @propertyOf  ui.grid.class:GridRenderContainer
7987      *  @description flag to signal that container should recalculate the canvas size
7988      */
7989     self.canvasHeightShouldUpdate = true;
7990
7991     /**
7992      *  @ngdoc boolean
7993      *  @name canvasHeight
7994      *  @propertyOf  ui.grid.class:GridRenderContainer
7995      *  @description last calculated canvas height value
7996      */
7997     self.$$canvasHeight = 0;
7998
7999     if (options && angular.isObject(options)) {
8000       angular.extend(self, options);
8001     }
8002
8003     grid.registerStyleComputation({
8004       priority: 5,
8005       func: function () {
8006         self.updateColumnWidths();
8007         return self.columnStyles;
8008       }
8009     });
8010   }
8011
8012
8013   GridRenderContainer.prototype.reset = function reset() {
8014     // this.rowCache.length = 0;
8015     // this.columnCache.length = 0;
8016
8017     this.visibleColumnCache.length = 0;
8018     this.visibleRowCache.length = 0;
8019
8020     this.renderedRows.length = 0;
8021     this.renderedColumns.length = 0;
8022   };
8023
8024   // TODO(c0bra): calculate size?? Should this be in a stackable directive?
8025
8026
8027   GridRenderContainer.prototype.containsColumn = function (col) {
8028      return this.visibleColumnCache.indexOf(col) !== -1;
8029   };
8030
8031   GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
8032     var self = this;
8033     var minRows = 0;
8034     var rowAddedHeight = 0;
8035     var viewPortHeight = self.getViewportHeight();
8036     for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
8037       rowAddedHeight += self.visibleRowCache[i].height;
8038       minRows++;
8039     }
8040     return minRows;
8041   };
8042
8043   GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
8044     var self = this;
8045     var viewportWidth = this.getViewportWidth();
8046
8047     var min = 0;
8048     var totalWidth = 0;
8049     // self.columns.forEach(function(col, i) {
8050     for (var i = 0; i < self.visibleColumnCache.length; i++) {
8051       var col = self.visibleColumnCache[i];
8052
8053       if (totalWidth < viewportWidth) {
8054         totalWidth += col.drawnWidth ? col.drawnWidth : 0;
8055         min++;
8056       }
8057       else {
8058         var currWidth = 0;
8059         for (var j = i; j >= i - min; j--) {
8060           currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
8061         }
8062         if (currWidth < viewportWidth) {
8063           min++;
8064         }
8065       }
8066     }
8067
8068     return min;
8069   };
8070
8071   GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
8072     return this.visibleRowCache.length;
8073   };
8074
8075   /**
8076    * @ngdoc function
8077    * @name registerViewportAdjuster
8078    * @methodOf ui.grid.class:GridRenderContainer
8079    * @description Registers an adjuster to the render container's available width or height.  Adjusters are used
8080    * to tell the render container that there is something else consuming space, and to adjust it's size
8081    * appropriately.
8082    * @param {function} func the adjuster function we want to register
8083    */
8084
8085   GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
8086     this.viewportAdjusters.push(func);
8087   };
8088
8089   /**
8090    * @ngdoc function
8091    * @name removeViewportAdjuster
8092    * @methodOf ui.grid.class:GridRenderContainer
8093    * @description Removes an adjuster, should be used when your element is destroyed
8094    * @param {function} func the adjuster function we want to remove
8095    */
8096   GridRenderContainer.prototype.removeViewportAdjuster = function removeViewportAdjuster(func) {
8097     var idx = this.viewportAdjusters.indexOf(func);
8098
8099     if (idx > -1) {
8100       this.viewportAdjusters.splice(idx, 1);
8101     }
8102   };
8103
8104   /**
8105    * @ngdoc function
8106    * @name getViewportAdjustment
8107    * @methodOf ui.grid.class:GridRenderContainer
8108    * @description Gets the adjustment based on the viewportAdjusters.
8109    * @returns {object} a hash of { height: x, width: y }.  Usually the values will be negative
8110    */
8111   GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
8112     var self = this;
8113
8114     var adjustment = { height: 0, width: 0 };
8115
8116     self.viewportAdjusters.forEach(function (func) {
8117       adjustment = func.call(this, adjustment);
8118     });
8119
8120     return adjustment;
8121   };
8122
8123   GridRenderContainer.prototype.getMargin = function getMargin(side) {
8124     var self = this;
8125
8126     var amount = 0;
8127
8128     self.viewportAdjusters.forEach(function (func) {
8129       var adjustment = func.call(this, { height: 0, width: 0 });
8130
8131       if (adjustment.side && adjustment.side === side) {
8132         amount += adjustment.width * -1;
8133       }
8134     });
8135
8136     return amount;
8137   };
8138
8139   GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
8140     var self = this;
8141
8142     var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
8143
8144     var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
8145
8146
8147     var adjustment = self.getViewportAdjustment();
8148
8149     viewPortHeight = viewPortHeight + adjustment.height;
8150
8151     return viewPortHeight;
8152   };
8153
8154   GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
8155     var self = this;
8156
8157     var viewportWidth = self.grid.gridWidth;
8158
8159     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8160     //  viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8161     //}
8162
8163     // var viewportWidth = 0;\
8164     // self.visibleColumnCache.forEach(function (column) {
8165     //   viewportWidth += column.drawnWidth;
8166     // });
8167
8168     var adjustment = self.getViewportAdjustment();
8169
8170     viewportWidth = viewportWidth + adjustment.width;
8171
8172     return viewportWidth;
8173   };
8174
8175   GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8176     var self = this;
8177
8178     var viewportWidth = this.getViewportWidth();
8179
8180     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8181     //  viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8182     //}
8183
8184     // var adjustment = self.getViewportAdjustment();
8185     // viewPortWidth = viewPortWidth + adjustment.width;
8186
8187     return viewportWidth;
8188   };
8189
8190
8191   /**
8192    * @ngdoc function
8193    * @name getCanvasHeight
8194    * @methodOf ui.grid.class:GridRenderContainer
8195    * @description Returns the total canvas height.   Only recalculates if canvasHeightShouldUpdate = false
8196    * @returns {number} total height of all the visible rows in the container
8197    */
8198   GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
8199     var self = this;
8200
8201     if (!self.canvasHeightShouldUpdate) {
8202       return self.$$canvasHeight;
8203     }
8204
8205     var oldCanvasHeight = self.$$canvasHeight;
8206
8207     self.$$canvasHeight =  0;
8208
8209     self.visibleRowCache.forEach(function(row){
8210       self.$$canvasHeight += row.height;
8211     });
8212
8213
8214     self.canvasHeightShouldUpdate = false;
8215
8216     self.grid.api.core.raise.canvasHeightChanged(oldCanvasHeight, self.$$canvasHeight);
8217
8218     return self.$$canvasHeight;
8219   };
8220
8221   GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8222     return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight;
8223   };
8224
8225   GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
8226     var self = this;
8227
8228     var ret = self.canvasWidth;
8229
8230     return ret;
8231   };
8232
8233   GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
8234     this.renderedRows.length = newRows.length;
8235     for (var i = 0; i < newRows.length; i++) {
8236       this.renderedRows[i] = newRows[i];
8237     }
8238   };
8239
8240   GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8241     var self = this;
8242
8243     // OLD:
8244     this.renderedColumns.length = newColumns.length;
8245     for (var i = 0; i < newColumns.length; i++) {
8246       this.renderedColumns[i] = newColumns[i];
8247     }
8248
8249     this.updateColumnOffset();
8250   };
8251
8252   GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
8253     // Calculate the width of the columns on the left side that are no longer rendered.
8254     //  That will be the offset for the columns as we scroll horizontally.
8255     var hiddenColumnsWidth = 0;
8256     for (var i = 0; i < this.currentFirstColumn; i++) {
8257       hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
8258     }
8259
8260     this.columnOffset = hiddenColumnsWidth;
8261   };
8262
8263   GridRenderContainer.prototype.scrollVertical = function (newScrollTop) {
8264     var vertScrollPercentage = -1;
8265
8266     if (newScrollTop !== this.prevScrollTop) {
8267       var yDiff = newScrollTop - this.prevScrollTop;
8268
8269       if (yDiff > 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; }
8270       if (yDiff < 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.UP; }
8271
8272       var vertScrollLength = this.getVerticalScrollLength();
8273
8274       vertScrollPercentage = newScrollTop / vertScrollLength;
8275
8276       // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8277
8278       if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
8279       if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
8280
8281       this.adjustScrollVertical(newScrollTop, vertScrollPercentage);
8282       return vertScrollPercentage;
8283     }
8284   };
8285
8286   GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8287     var horizScrollPercentage = -1;
8288
8289     // Handle RTL here
8290
8291     if (newScrollLeft !== this.prevScrollLeft) {
8292       var xDiff = newScrollLeft - this.prevScrollLeft;
8293
8294       if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
8295       if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
8296
8297       var horizScrollLength = (this.canvasWidth - this.getViewportWidth());
8298       if (horizScrollLength !== 0) {
8299         horizScrollPercentage = newScrollLeft / horizScrollLength;
8300       }
8301       else {
8302         horizScrollPercentage = 0;
8303       }
8304
8305       this.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
8306       return horizScrollPercentage;
8307     }
8308   };
8309
8310   GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
8311     if (this.prevScrollTop === scrollTop && !force) {
8312       return;
8313     }
8314
8315     if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
8316       scrollTop = (this.getCanvasHeight() - this.getViewportHeight()) * scrollPercentage;
8317     }
8318
8319     this.adjustRows(scrollTop, scrollPercentage, false);
8320
8321     this.prevScrollTop = scrollTop;
8322     this.prevScrolltopPercentage = scrollPercentage;
8323
8324     this.grid.queueRefresh();
8325   };
8326
8327   GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
8328     if (this.prevScrollLeft === scrollLeft && !force) {
8329       return;
8330     }
8331
8332     if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
8333       scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
8334     }
8335
8336     this.adjustColumns(scrollLeft, scrollPercentage);
8337
8338     this.prevScrollLeft = scrollLeft;
8339     this.prevScrollleftPercentage = scrollPercentage;
8340
8341     this.grid.queueRefresh();
8342   };
8343
8344   GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) {
8345     var self = this;
8346
8347     var minRows = self.minRowsToRender();
8348
8349     var rowCache = self.visibleRowCache;
8350
8351     var maxRowIndex = rowCache.length - minRows;
8352
8353     // console.log('scroll%1', scrollPercentage);
8354
8355     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
8356     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
8357       scrollPercentage = scrollTop / self.getVerticalScrollLength();
8358     }
8359
8360     var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8361
8362     // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
8363
8364     // Define a max row index that we can't scroll past
8365     if (rowIndex > maxRowIndex) {
8366       rowIndex = maxRowIndex;
8367     }
8368
8369     var newRange = [];
8370     if (rowCache.length > self.grid.options.virtualizationThreshold) {
8371       if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) {
8372         // Have we hit the threshold going down?
8373         if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8374           return;
8375         }
8376         //Have we hit the threshold going up?
8377         if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8378           return;
8379         }
8380       }
8381       var rangeStart = {};
8382       var rangeEnd = {};
8383
8384       rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8385       rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8386
8387       newRange = [rangeStart, rangeEnd];
8388     }
8389     else {
8390       var maxLen = self.visibleRowCache.length;
8391       newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
8392     }
8393
8394     self.updateViewableRowRange(newRange);
8395
8396     self.prevRowScrollIndex = rowIndex;
8397   };
8398
8399   GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
8400     var self = this;
8401
8402     var minCols = self.minColumnsToRender();
8403
8404     var columnCache = self.visibleColumnCache;
8405     var maxColumnIndex = columnCache.length - minCols;
8406
8407     // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
8408     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8409       var horizScrollLength = (self.getCanvasWidth() - self.getViewportWidth());
8410       scrollPercentage = scrollLeft / horizScrollLength;
8411     }
8412
8413     var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
8414
8415     // Define a max row index that we can't scroll past
8416     if (colIndex > maxColumnIndex) {
8417       colIndex = maxColumnIndex;
8418     }
8419
8420     var newRange = [];
8421     if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8422       /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8423        * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8424       // Have we hit the threshold going down?
8425       if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8426         return;
8427       }
8428       //Have we hit the threshold going up?
8429       if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8430         return;
8431       }*/
8432
8433       var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
8434       var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
8435
8436       newRange = [rangeStart, rangeEnd];
8437     }
8438     else {
8439       var maxLen = self.visibleColumnCache.length;
8440
8441       newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
8442     }
8443
8444     self.updateViewableColumnRange(newRange);
8445
8446     self.prevColumnScrollIndex = colIndex;
8447   };
8448
8449   // Method for updating the visible rows
8450   GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
8451     // Slice out the range of rows from the data
8452     // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
8453     var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
8454
8455     // Define the top-most rendered row
8456     this.currentTopRow = renderedRange[0];
8457
8458     this.setRenderedRows(rowArr);
8459   };
8460
8461   // Method for updating the visible columns
8462   GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
8463     // Slice out the range of rows from the data
8464     // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
8465     var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
8466
8467     // Define the left-most rendered columns
8468     this.currentFirstColumn = renderedRange[0];
8469
8470     this.setRenderedColumns(columnArr);
8471   };
8472
8473   GridRenderContainer.prototype.headerCellWrapperStyle = function () {
8474     var self = this;
8475
8476     if (self.currentFirstColumn !== 0) {
8477       var offset = self.columnOffset;
8478
8479       if (self.grid.isRTL()) {
8480         return { 'margin-right': offset + 'px' };
8481       }
8482       else {
8483         return { 'margin-left': offset + 'px' };
8484       }
8485     }
8486
8487     return null;
8488   };
8489
8490     /**
8491      *  @ngdoc boolean
8492      *  @name updateColumnWidths
8493      *  @propertyOf  ui.grid.class:GridRenderContainer
8494      *  @description Determine the appropriate column width of each column across all render containers.
8495      *
8496      *  Column width is easy when each column has a specified width.  When columns are variable width (i.e.
8497      *  have an * or % of the viewport) then we try to calculate so that things fit in.  The problem is that
8498      *  we have multiple render containers, and we don't want one render container to just take the whole viewport
8499      *  when it doesn't need to - we want things to balance out across the render containers.
8500      *
8501      *  To do this, we use this method to calculate all the renderContainers, recognising that in a given render
8502      *  cycle it'll get called once per render container, so it needs to return the same values each time.
8503      *
8504      *  The constraints on this method are therefore:
8505      *  - must return the same value when called multiple times, to do this it needs to rely on properties of the
8506      *    columns, but not properties that change when this is called (so it shouldn't rely on drawnWidth)
8507      *
8508      *  The general logic of this method is:
8509      *  - calculate our total available width
8510      *  - look at all the columns across all render containers, and work out which have widths and which have
8511      *    constraints such as % or * or something else
8512      *  - for those with *, count the total number of * we see and add it onto a running total, add this column to an * array
8513      *  - for those with a %, allocate the % as a percentage of the viewport, having consideration of min and max
8514      *  - for those with manual width (in pixels) we set the drawnWidth to the specified width
8515      *  - we end up with an asterisks array still to process
8516      *  - we look at our remaining width.  If it's greater than zero, we divide it up among the asterisk columns, then process
8517      *    them for min and max width constraints
8518      *  - if it's zero or less, we set the asterisk columns to their minimum widths
8519      *  - we use parseInt quite a bit, as we try to make all our column widths integers
8520      */
8521   GridRenderContainer.prototype.updateColumnWidths = function () {
8522     var self = this;
8523
8524     var asterisksArray = [],
8525         asteriskNum = 0,
8526         usedWidthSum = 0,
8527         ret = '';
8528
8529     // Get the width of the viewport
8530     var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
8531
8532     // get all the columns across all render containers, we have to calculate them all or one render container
8533     // could consume the whole viewport
8534     var columnCache = [];
8535     angular.forEach(self.grid.renderContainers, function( container, name){
8536       columnCache = columnCache.concat(container.visibleColumnCache);
8537     });
8538
8539     // look at each column, process any manual values or %, put the * into an array to look at later
8540     columnCache.forEach(function(column, i) {
8541       var width = 0;
8542       // Skip hidden columns
8543       if (!column.visible) { return; }
8544
8545       if (angular.isNumber(column.width)) {
8546         // pixel width, set to this value
8547         width = parseInt(column.width, 10);
8548         usedWidthSum = usedWidthSum + width;
8549         column.drawnWidth = width;
8550
8551       } else if (gridUtil.endsWith(column.width, "%")) {
8552         // percentage width, set to percentage of the viewport
8553         width = parseInt(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8554
8555         if ( width > column.maxWidth ){
8556           width = column.maxWidth;
8557         }
8558
8559         if ( width < column.minWidth ){
8560           width = column.minWidth;
8561         }
8562
8563         usedWidthSum = usedWidthSum + width;
8564         column.drawnWidth = width;
8565       } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
8566         // is an asterisk column, the gridColumn already checked the string consists only of '****'
8567         asteriskNum = asteriskNum + column.width.length;
8568         asterisksArray.push(column);
8569       }
8570     });
8571
8572     // Get the remaining width (available width subtracted by the used widths sum)
8573     var remainingWidth = availableWidth - usedWidthSum;
8574
8575     var i, column, colWidth;
8576
8577     if (asterisksArray.length > 0) {
8578       // the width that each asterisk value would be assigned (this can be negative)
8579       var asteriskVal = remainingWidth / asteriskNum;
8580
8581       asterisksArray.forEach(function( column ){
8582         var width = parseInt(column.width.length * asteriskVal, 10);
8583
8584         if ( width > column.maxWidth ){
8585           width = column.maxWidth;
8586         }
8587
8588         if ( width < column.minWidth ){
8589           width = column.minWidth;
8590         }
8591
8592         usedWidthSum = usedWidthSum + width;
8593         column.drawnWidth = width;
8594       });
8595     }
8596
8597     // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8598     // calculated widths would have the grid narrower than the available space,
8599     // dole the remainder out one by one to make everything fit
8600     var processColumnUpwards = function(column){
8601       if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8602         column.drawnWidth++;
8603         usedWidthSum++;
8604         leftoverWidth--;
8605         columnsToChange = true;
8606       }
8607     };
8608
8609     var leftoverWidth = availableWidth - usedWidthSum;
8610     var columnsToChange = true;
8611
8612     while (leftoverWidth > 0 && columnsToChange) {
8613       columnsToChange = false;
8614       asterisksArray.forEach(processColumnUpwards);
8615     }
8616
8617     // We can end up with too much width even though some columns aren't at their max width, in this situation
8618     // we can trim the columns a little
8619     var processColumnDownwards = function(column){
8620       if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8621         column.drawnWidth--;
8622         usedWidthSum--;
8623         excessWidth--;
8624         columnsToChange = true;
8625       }
8626     };
8627
8628     var excessWidth =  usedWidthSum - availableWidth;
8629     columnsToChange = true;
8630
8631     while (excessWidth > 0 && columnsToChange) {
8632       columnsToChange = false;
8633       asterisksArray.forEach(processColumnDownwards);
8634     }
8635
8636
8637     // all that was across all the renderContainers, now we need to work out what that calculation decided for
8638     // our renderContainer
8639     var canvasWidth = 0;
8640     self.visibleColumnCache.forEach(function(column){
8641       if ( column.visible ){
8642         canvasWidth = canvasWidth + column.drawnWidth;
8643       }
8644     });
8645
8646     // Build the CSS
8647     columnCache.forEach(function (column) {
8648       ret = ret + column.getColClassDefinition();
8649     });
8650
8651     self.canvasWidth = canvasWidth;
8652
8653     // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
8654     // return ret;
8655
8656     // Set this render container's column styles so they can be used in style computation
8657     this.columnStyles = ret;
8658   };
8659
8660   GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8661     return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8662   };
8663
8664   GridRenderContainer.prototype.getViewportStyle = function () {
8665     var self = this;
8666     var styles = {};
8667
8668     self.hasHScrollbar = false;
8669     self.hasVScrollbar = false;
8670
8671     if (self.grid.disableScrolling) {
8672       styles['overflow-x'] = 'hidden';
8673       styles['overflow-y'] = 'hidden';
8674       return styles;
8675     }
8676
8677     if (self.name === 'body') {
8678       self.hasHScrollbar = self.grid.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER;
8679       if (!self.grid.isRTL()) {
8680         if (!self.grid.hasRightContainerColumns()) {
8681           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8682         }
8683       }
8684       else {
8685         if (!self.grid.hasLeftContainerColumns()) {
8686           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8687         }
8688       }
8689     }
8690     else if (self.name === 'left') {
8691       self.hasVScrollbar = self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8692     }
8693     else {
8694       self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8695     }
8696
8697     styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8698     styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8699
8700
8701     return styles;
8702
8703
8704   };
8705
8706   return GridRenderContainer;
8707 }]);
8708
8709 })();
8710
8711 (function(){
8712
8713 angular.module('ui.grid')
8714 .factory('GridRow', ['gridUtil', function(gridUtil) {
8715
8716    /**
8717    * @ngdoc function
8718    * @name ui.grid.class:GridRow
8719    * @description GridRow is the viewModel for one logical row on the grid.  A grid Row is not necessarily a one-to-one
8720    * relation to gridOptions.data.
8721    * @param {object} entity the array item from GridOptions.data
8722    * @param {number} index the current position of the row in the array
8723    * @param {Grid} reference to the parent grid
8724    */
8725   function GridRow(entity, index, grid) {
8726
8727      /**
8728       *  @ngdoc object
8729       *  @name grid
8730       *  @propertyOf  ui.grid.class:GridRow
8731       *  @description A reference back to the grid
8732       */
8733      this.grid = grid;
8734
8735      /**
8736       *  @ngdoc object
8737       *  @name entity
8738       *  @propertyOf  ui.grid.class:GridRow
8739       *  @description A reference to an item in gridOptions.data[]
8740       */
8741     this.entity = entity;
8742
8743      /**
8744       *  @ngdoc object
8745       *  @name uid
8746       *  @propertyOf  ui.grid.class:GridRow
8747       *  @description  UniqueId of row
8748       */
8749      this.uid = gridUtil.nextUid();
8750
8751      /**
8752       *  @ngdoc object
8753       *  @name visible
8754       *  @propertyOf  ui.grid.class:GridRow
8755       *  @description If true, the row will be rendered
8756       */
8757     // Default to true
8758     this.visible = true;
8759
8760
8761     this.$$height = grid.options.rowHeight;
8762
8763   }
8764
8765     /**
8766      *  @ngdoc object
8767      *  @name height
8768      *  @propertyOf  ui.grid.class:GridRow
8769      *  @description height of each individual row. changing the height will flag all
8770      *  row renderContainers to recalculate their canvas height
8771      */
8772     Object.defineProperty(GridRow.prototype, 'height', {
8773       get: function() {
8774         return this.$$height;
8775       },
8776       set: function(height) {
8777         if (height !== this.$$height) {
8778           this.grid.updateCanvasHeight();
8779           this.$$height = height;
8780         }
8781       }
8782     });
8783
8784   /**
8785    * @ngdoc function
8786    * @name getQualifiedColField
8787    * @methodOf ui.grid.class:GridRow
8788    * @description returns the qualified field name as it exists on scope
8789    * ie: row.entity.fieldA
8790    * @param {GridCol} col column instance
8791    * @returns {string} resulting name that can be evaluated on scope
8792    */
8793     GridRow.prototype.getQualifiedColField = function(col) {
8794       return 'row.' + this.getEntityQualifiedColField(col);
8795     };
8796
8797     /**
8798      * @ngdoc function
8799      * @name getEntityQualifiedColField
8800      * @methodOf ui.grid.class:GridRow
8801      * @description returns the qualified field name minus the row path
8802      * ie: entity.fieldA
8803      * @param {GridCol} col column instance
8804      * @returns {string} resulting name that can be evaluated against a row
8805      */
8806   GridRow.prototype.getEntityQualifiedColField = function(col) {
8807     return gridUtil.preEval('entity.' + col.field);
8808   };
8809   
8810   
8811   /**
8812    * @ngdoc function
8813    * @name setRowInvisible
8814    * @methodOf  ui.grid.class:GridRow
8815    * @description Sets an override on the row that forces it to always
8816    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8817    * 
8818    * This method can be called from the api, passing in the gridRow we want
8819    * altered.  It should really work by calling gridRow.setRowInvisible, but that's
8820    * not the way I coded it, and too late to change now.  Changed to just call
8821    * the internal function row.setThisRowInvisible().
8822    * 
8823    * @param {GridRow} row the row we want to set to invisible
8824    * 
8825    */
8826   GridRow.prototype.setRowInvisible = function ( row ) {
8827     if (row && row.setThisRowInvisible){
8828       row.setThisRowInvisible( 'user' );
8829     }
8830   };
8831   
8832   
8833   /**
8834    * @ngdoc function
8835    * @name clearRowInvisible
8836    * @methodOf  ui.grid.class:GridRow
8837    * @description Clears an override on the row that forces it to always
8838    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8839    * 
8840    * This method can be called from the api, passing in the gridRow we want
8841    * altered.  It should really work by calling gridRow.clearRowInvisible, but that's
8842    * not the way I coded it, and too late to change now.  Changed to just call
8843    * the internal function row.clearThisRowInvisible().
8844    * 
8845    * @param {GridRow} row the row we want to clear the invisible flag
8846    * 
8847    */
8848   GridRow.prototype.clearRowInvisible = function ( row ) {
8849     if (row && row.clearThisRowInvisible){
8850       row.clearThisRowInvisible( 'user' );
8851     }
8852   };
8853   
8854   
8855   /**
8856    * @ngdoc function
8857    * @name setThisRowInvisible
8858    * @methodOf  ui.grid.class:GridRow
8859    * @description Sets an override on the row that forces it to always
8860    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility
8861    *
8862    * @param {string} reason the reason (usually the module) for the row to be invisible.
8863    * E.g. grouping, user, filter
8864    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8865    */
8866   GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
8867     if ( !this.invisibleReason ){
8868       this.invisibleReason = {};
8869     }
8870     this.invisibleReason[reason] = true;
8871     this.evaluateRowVisibility( fromRowsProcessor);
8872   };
8873
8874
8875   /**
8876    * @ngdoc function
8877    * @name clearRowInvisible
8878    * @methodOf ui.grid.class:GridRow
8879    * @description Clears any override on the row visibility, returning it 
8880    * to normal visibility calculations.  Emits the rowsVisibleChanged
8881    * event
8882    * 
8883    * @param {string} reason the reason (usually the module) for the row to be invisible.
8884    * E.g. grouping, user, filter
8885    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8886    */
8887   GridRow.prototype.clearThisRowInvisible = function ( reason, fromRowsProcessor ) {
8888     if (typeof(this.invisibleReason) !== 'undefined' ) {
8889       delete this.invisibleReason[reason];
8890     }
8891     this.evaluateRowVisibility( fromRowsProcessor );
8892   };
8893
8894
8895   /**
8896    * @ngdoc function
8897    * @name evaluateRowVisibility
8898    * @methodOf ui.grid.class:GridRow
8899    * @description Determines whether the row should be visible based on invisibleReason, 
8900    * and if it changes the row visibility, then emits the rowsVisibleChanged event.
8901    * 
8902    * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
8903    * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
8904    * row processor does that already
8905    */
8906   GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
8907     var newVisibility = true;
8908     if ( typeof(this.invisibleReason) !== 'undefined' ){
8909       angular.forEach(this.invisibleReason, function( value, key ){
8910         if ( value ){
8911           newVisibility = false;
8912         }
8913       });
8914     }
8915     
8916     if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
8917       this.visible = newVisibility;
8918       if ( !fromRowProcessor ){
8919         this.grid.queueGridRefresh();
8920         this.grid.api.core.raise.rowsVisibleChanged(this);
8921       }
8922     }
8923   };
8924   
8925
8926   return GridRow;
8927 }]);
8928
8929 })();
8930
8931 (function(){
8932   'use strict';
8933   /**
8934    * @ngdoc object
8935    * @name ui.grid.class:GridRowColumn
8936    * @param {GridRow} row The row for this pair
8937    * @param {GridColumn} column The column for this pair
8938    * @description A row and column pair that represents the intersection of these two entities.
8939    * Must be instantiated as a constructor using the `new` keyword.
8940    */
8941   angular.module('ui.grid')
8942   .factory('GridRowColumn', ['$parse', '$filter',
8943     function GridRowColumnFactory($parse, $filter){
8944       var GridRowColumn = function GridRowColumn(row, col) {
8945         if ( !(this instanceof GridRowColumn)){
8946           throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
8947         }
8948
8949         /**
8950          * @ngdoc object
8951          * @name row
8952          * @propertyOf ui.grid.class:GridRowColumn
8953          * @description {@link ui.grid.class:GridRow }
8954          */
8955         this.row = row;
8956         /**
8957          * @ngdoc object
8958          * @name col
8959          * @propertyOf ui.grid.class:GridRowColumn
8960          * @description {@link ui.grid.class:GridColumn }
8961          */
8962         this.col = col;
8963       };
8964
8965       /**
8966        * @ngdoc function
8967        * @name getIntersectionValueRaw
8968        * @methodOf ui.grid.class:GridRowColumn
8969        * @description Gets the intersection of where the row and column meet.
8970        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8971        *          If the column has a cellFilter this will NOT return the filtered value.
8972        */
8973       GridRowColumn.prototype.getIntersectionValueRaw = function(){
8974         var getter = $parse(this.row.getEntityQualifiedColField(this.col));
8975         var context = this.row;
8976         return getter(context);
8977       };
8978       /**
8979        * @ngdoc function
8980        * @name getIntersectionValueFiltered
8981        * @methodOf ui.grid.class:GridRowColumn
8982        * @description Gets the intersection of where the row and column meet.
8983        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8984        *          If the column has a cellFilter this will also apply the filter to it and return the value that the filter displays.
8985        */
8986       GridRowColumn.prototype.getIntersectionValueFiltered = function(){
8987         var value = this.getIntersectionValueRaw();
8988         if (this.col.cellFilter && this.col.cellFilter !== ''){
8989           var getFilterIfExists = function(filterName){
8990             try {
8991               return $filter(filterName);
8992             } catch (e){
8993               return null;
8994             }
8995           };
8996           var filter = getFilterIfExists(this.col.cellFilter);
8997           if (filter) { // Check if this is filter name or a filter string
8998             value = filter(value);
8999           } else { // We have the template version of a filter so we need to parse it apart
9000             // Get the filter params out using a regex
9001             // Test out this regex here https://regex101.com/r/rC5eR5/2
9002             var re = /([^:]*):([^:]*):?([\s\S]+)?/;
9003             var matches;
9004             if ((matches = re.exec(this.col.cellFilter)) !== null) {
9005                 // View your result using the matches-variable.
9006                 // eg matches[0] etc.
9007                 value = $filter(matches[1])(value, matches[2], matches[3]);
9008             }
9009           }
9010         }
9011         return value;
9012       };
9013       return GridRowColumn;
9014     }
9015   ]);
9016 })();
9017
9018 (function () {
9019   angular.module('ui.grid')
9020     .factory('ScrollEvent', ['gridUtil', function (gridUtil) {
9021
9022       /**
9023        * @ngdoc function
9024        * @name ui.grid.class:ScrollEvent
9025        * @description Model for all scrollEvents
9026        * @param {Grid} grid that owns the scroll event
9027        * @param {GridRenderContainer} sourceRowContainer that owns the scroll event. Can be null
9028        * @param {GridRenderContainer} sourceColContainer that owns the scroll event. Can be null
9029        * @param {string} source the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
9030        */
9031       function ScrollEvent(grid, sourceRowContainer, sourceColContainer, source) {
9032         var self = this;
9033         if (!grid) {
9034           throw new Error("grid argument is required");
9035         }
9036
9037         /**
9038          *  @ngdoc object
9039          *  @name grid
9040          *  @propertyOf  ui.grid.class:ScrollEvent
9041          *  @description A reference back to the grid
9042          */
9043          self.grid = grid;
9044
9045
9046
9047         /**
9048          *  @ngdoc object
9049          *  @name source
9050          *  @propertyOf  ui.grid.class:ScrollEvent
9051          *  @description the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
9052          */
9053         self.source = source;
9054
9055
9056         /**
9057          *  @ngdoc object
9058          *  @name noDelay
9059          *  @propertyOf  ui.grid.class:ScrollEvent
9060          *  @description most scroll events from the mouse or trackpad require delay to operate properly
9061          *  set to false to eliminate delay.  Useful for scroll events that the grid causes, such as scrolling to make a row visible.
9062          */
9063         self.withDelay = true;
9064
9065         self.sourceRowContainer = sourceRowContainer;
9066         self.sourceColContainer = sourceColContainer;
9067
9068         self.newScrollLeft = null;
9069         self.newScrollTop = null;
9070         self.x = null;
9071         self.y = null;
9072
9073         self.verticalScrollLength = -9999999;
9074         self.horizontalScrollLength = -999999;
9075
9076
9077         /**
9078          *  @ngdoc function
9079          *  @name fireThrottledScrollingEvent
9080          *  @methodOf  ui.grid.class:ScrollEvent
9081          *  @description fires a throttled event using grid.api.core.raise.scrollEvent
9082          */
9083         self.fireThrottledScrollingEvent = gridUtil.throttle(function(sourceContainerId) {
9084           self.grid.scrollContainers(sourceContainerId, self);
9085         }, self.grid.options.wheelScrollThrottle, {trailing: true});
9086
9087       }
9088
9089
9090       /**
9091        *  @ngdoc function
9092        *  @name getNewScrollLeft
9093        *  @methodOf  ui.grid.class:ScrollEvent
9094        *  @description returns newScrollLeft property if available; calculates a new value if it isn't
9095        */
9096       ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9097         var self = this;
9098
9099         if (!self.newScrollLeft){
9100           var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
9101
9102           var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
9103
9104           var scrollXPercentage;
9105           if (typeof(self.x.percentage) !== 'undefined' && self.x.percentage !== undefined) {
9106             scrollXPercentage = self.x.percentage;
9107           }
9108           else if (typeof(self.x.pixels) !== 'undefined' && self.x.pixels !== undefined) {
9109             scrollXPercentage = self.x.percentage = (oldScrollLeft + self.x.pixels) / scrollWidth;
9110           }
9111           else {
9112             throw new Error("No percentage or pixel value provided for scroll event X axis");
9113           }
9114
9115           return Math.max(0, scrollXPercentage * scrollWidth);
9116         }
9117
9118         return self.newScrollLeft;
9119       };
9120
9121
9122       /**
9123        *  @ngdoc function
9124        *  @name getNewScrollTop
9125        *  @methodOf  ui.grid.class:ScrollEvent
9126        *  @description returns newScrollTop property if available; calculates a new value if it isn't
9127        */
9128       ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9129         var self = this;
9130
9131
9132         if (!self.newScrollTop){
9133           var scrollLength = rowContainer.getVerticalScrollLength();
9134
9135           var oldScrollTop = viewport[0].scrollTop;
9136
9137           var scrollYPercentage;
9138           if (typeof(self.y.percentage) !== 'undefined' && self.y.percentage !== undefined) {
9139             scrollYPercentage = self.y.percentage;
9140           }
9141           else if (typeof(self.y.pixels) !== 'undefined' && self.y.pixels !== undefined) {
9142             scrollYPercentage = self.y.percentage = (oldScrollTop + self.y.pixels) / scrollLength;
9143           }
9144           else {
9145             throw new Error("No percentage or pixel value provided for scroll event Y axis");
9146           }
9147
9148           return Math.max(0, scrollYPercentage * scrollLength);
9149         }
9150
9151         return self.newScrollTop;
9152       };
9153
9154       ScrollEvent.prototype.atTop = function(scrollTop) {
9155         return (this.y && (this.y.percentage === 0 || this.verticalScrollLength < 0) && scrollTop === 0);
9156       };
9157
9158       ScrollEvent.prototype.atBottom = function(scrollTop) {
9159         return (this.y && (this.y.percentage === 1 || this.verticalScrollLength === 0) && scrollTop > 0);
9160       };
9161
9162       ScrollEvent.prototype.atLeft = function(scrollLeft) {
9163         return (this.x && (this.x.percentage === 0 || this.horizontalScrollLength < 0) && scrollLeft === 0);
9164       };
9165
9166       ScrollEvent.prototype.atRight = function(scrollLeft) {
9167         return (this.x && (this.x.percentage === 1 || this.horizontalScrollLength ===0) && scrollLeft > 0);
9168       };
9169
9170
9171       ScrollEvent.Sources = {
9172         ViewPortScroll: 'ViewPortScroll',
9173         RenderContainerMouseWheel: 'RenderContainerMouseWheel',
9174         RenderContainerTouchMove: 'RenderContainerTouchMove',
9175         Other: 99
9176       };
9177
9178       return ScrollEvent;
9179     }]);
9180
9181
9182
9183 })();
9184
9185 (function () {
9186   'use strict';
9187   /**
9188    *  @ngdoc object
9189    *  @name ui.grid.service:gridClassFactory
9190    *
9191    *  @description factory to return dom specific instances of a grid
9192    *
9193    */
9194   angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
9195     function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
9196
9197       var service = {
9198         /**
9199          * @ngdoc method
9200          * @name createGrid
9201          * @methodOf ui.grid.service:gridClassFactory
9202          * @description Creates a new grid instance. Each instance will have a unique id
9203          * @param {object} options An object map of options to pass into the created grid instance.
9204          * @returns {Grid} grid
9205          */
9206         createGrid : function(options) {
9207           options = (typeof(options) !== 'undefined') ? options : {};
9208           options.id = gridUtil.newId();
9209           var grid = new Grid(options);
9210
9211           // NOTE/TODO: rowTemplate should always be defined...
9212           if (grid.options.rowTemplate) {
9213             var rowTemplateFnPromise = $q.defer();
9214             grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9215             
9216             gridUtil.getTemplate(grid.options.rowTemplate)
9217               .then(
9218                 function (template) {
9219                   var rowTemplateFn = $compile(template);
9220                   rowTemplateFnPromise.resolve(rowTemplateFn);
9221                 },
9222                 function (res) {
9223                   // Todo handle response error here?
9224                   throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9225                 });
9226           }
9227
9228           grid.registerColumnBuilder(service.defaultColumnBuilder);
9229
9230           // Row builder for custom row templates
9231           grid.registerRowBuilder(service.rowTemplateAssigner);
9232
9233           // Reset all rows to visible initially
9234           grid.registerRowsProcessor(function allRowsVisible(rows) {
9235             rows.forEach(function (row) {
9236               row.evaluateRowVisibility( true );
9237             }, 50);
9238
9239             return rows;
9240           });
9241
9242           grid.registerColumnsProcessor(function allColumnsVisible(columns) {
9243             columns.forEach(function (column) {
9244               column.visible = true;
9245             });
9246
9247             return columns;
9248           }, 50);
9249
9250           grid.registerColumnsProcessor(function(renderableColumns) {
9251               renderableColumns.forEach(function (column) {
9252                   if (column.colDef.visible === false) {
9253                       column.visible = false;
9254                   }
9255               });
9256
9257               return renderableColumns;
9258           }, 50);
9259
9260
9261           grid.registerRowsProcessor(grid.searchRows, 100);
9262
9263           // Register the default row processor, it sorts rows by selected columns
9264           if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
9265             grid.registerRowsProcessor(grid.options.externalSort, 200);
9266           }
9267           else {
9268             grid.registerRowsProcessor(grid.sortByColumn, 200);
9269           }
9270
9271           return grid;
9272         },
9273
9274         /**
9275          * @ngdoc function
9276          * @name defaultColumnBuilder
9277          * @methodOf ui.grid.service:gridClassFactory
9278          * @description Processes designTime column definitions and applies them to col for the
9279          *              core grid features
9280          * @param {object} colDef reference to column definition
9281          * @param {GridColumn} col reference to gridCol
9282          * @param {object} gridOptions reference to grid options
9283          */
9284         defaultColumnBuilder: function (colDef, col, gridOptions) {
9285
9286           var templateGetPromises = [];
9287
9288           // Abstracts the standard template processing we do for every template type.
9289           var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9290             if ( !colDef[templateType] ){
9291               col[providedType] = defaultTemplate;
9292             } else {
9293               col[providedType] = colDef[templateType];
9294             }
9295  
9296              templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9297                 .then(
9298                 function (template) {
9299                   if ( angular.isFunction(template) ) { template = template(); }
9300                   var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9301                   if ( tooltipType && col[tooltipType] === false ){
9302                     template = template.replace(uiGridConstants.TOOLTIP, '');
9303                   } else if ( tooltipType && col[tooltipType] ){
9304                     template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
9305                   }
9306
9307                   if ( filterType ){
9308                     col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
9309                       return col[filterType] ? "|" + col[filterType] : "";
9310                     });
9311                   } else {
9312                     col[templateType] = template;
9313                   }
9314                 },
9315                 function (res) {
9316                   throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9317                 })
9318             );
9319
9320           };
9321
9322
9323           /**
9324            * @ngdoc property
9325            * @name cellTemplate
9326            * @propertyOf ui.grid.class:GridOptions.columnDef
9327            * @description a custom template for each cell in this column.  The default
9328            * is ui-grid/uiGridCell.  If you are using the cellNav feature, this template
9329            * must contain a div that can receive focus.
9330            *
9331            */
9332           processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9333           col.cellTemplatePromise = templateGetPromises[0];
9334
9335           /**
9336            * @ngdoc property
9337            * @name headerCellTemplate
9338            * @propertyOf ui.grid.class:GridOptions.columnDef
9339            * @description a custom template for the header for this column.  The default
9340            * is ui-grid/uiGridHeaderCell
9341            *
9342            */
9343           processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9344
9345           /**
9346            * @ngdoc property
9347            * @name footerCellTemplate
9348            * @propertyOf ui.grid.class:GridOptions.columnDef
9349            * @description a custom template for the footer for this column.  The default
9350            * is ui-grid/uiGridFooterCell
9351            *
9352            */
9353           processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9354
9355           /**
9356            * @ngdoc property
9357            * @name filterHeaderTemplate
9358            * @propertyOf ui.grid.class:GridOptions.columnDef
9359            * @description a custom template for the filter input.  The default is ui-grid/ui-grid-filter
9360            *
9361            */
9362           processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9363
9364           // Create a promise for the compiled element function
9365           col.compiledElementFnDefer = $q.defer();
9366
9367           return $q.all(templateGetPromises);
9368         },
9369         
9370
9371         rowTemplateAssigner: function rowTemplateAssigner(row) {
9372           var grid = this;
9373
9374           // Row has no template assigned to it
9375           if (!row.rowTemplate) {
9376             // Use the default row template from the grid
9377             row.rowTemplate = grid.options.rowTemplate;
9378
9379             // Use the grid's function for fetching the compiled row template function
9380             row.getRowTemplateFn = grid.getRowTemplateFn;
9381           }
9382           // Row has its own template assigned
9383           else {
9384             // Create a promise for the compiled row template function
9385             var perRowTemplateFnPromise = $q.defer();
9386             row.getRowTemplateFn = perRowTemplateFnPromise.promise;
9387
9388             // Get the row template
9389             gridUtil.getTemplate(row.rowTemplate)
9390               .then(function (template) {
9391                 // Compile the template
9392                 var rowTemplateFn = $compile(template);
9393                 
9394                 // Resolve the compiled template function promise
9395                 perRowTemplateFnPromise.resolve(rowTemplateFn);
9396               },
9397               function (res) {
9398                 // Todo handle response error here?
9399                 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
9400               });
9401           }
9402
9403           return row.getRowTemplateFn;
9404         }
9405       };
9406
9407       //class definitions (moved to separate factories)
9408
9409       return service;
9410     }]);
9411
9412 })();
9413
9414 (function() {
9415
9416 var module = angular.module('ui.grid');
9417
9418 function escapeRegExp(str) {
9419   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
9420 }
9421
9422
9423 /**
9424  *  @ngdoc service
9425  *  @name ui.grid.service:rowSearcher
9426  *
9427  *  @description Service for searching/filtering rows based on column value conditions.
9428  */
9429 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
9430   var defaultCondition = uiGridConstants.filter.CONTAINS;
9431
9432   var rowSearcher = {};
9433
9434   /**
9435    * @ngdoc function
9436    * @name getTerm
9437    * @methodOf ui.grid.service:rowSearcher
9438    * @description Get the term from a filter
9439    * Trims leading and trailing whitespace
9440    * @param {object} filter object to use
9441    * @returns {object} Parsed term
9442    */
9443   rowSearcher.getTerm = function getTerm(filter) {
9444     if (typeof(filter.term) === 'undefined') { return filter.term; }
9445     
9446     var term = filter.term;
9447
9448     // Strip leading and trailing whitespace if the term is a string
9449     if (typeof(term) === 'string') {
9450       term = term.trim();
9451     }
9452
9453     return term;
9454   };
9455
9456   /**
9457    * @ngdoc function
9458    * @name stripTerm
9459    * @methodOf ui.grid.service:rowSearcher
9460    * @description Remove leading and trailing asterisk (*) from the filter's term
9461    * @param {object} filter object to use
9462    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9463    */
9464   rowSearcher.stripTerm = function stripTerm(filter) {
9465     var term = rowSearcher.getTerm(filter);
9466
9467     if (typeof(term) === 'string') {
9468       return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
9469     }
9470     else {
9471       return term;
9472     }
9473   };
9474   
9475
9476   /**
9477    * @ngdoc function
9478    * @name guessCondition
9479    * @methodOf ui.grid.service:rowSearcher
9480    * @description Guess the condition for a filter based on its term
9481    * <br>
9482    * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
9483    * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
9484    * @param {object} filter object to use
9485    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9486    */
9487   rowSearcher.guessCondition = function guessCondition(filter) {
9488     if (typeof(filter.term) === 'undefined' || !filter.term) {
9489       return defaultCondition;
9490     }
9491
9492     var term = rowSearcher.getTerm(filter);
9493     
9494     if (/\*/.test(term)) {
9495       var regexpFlags = '';
9496       if (!filter.flags || !filter.flags.caseSensitive) {
9497         regexpFlags += 'i';
9498       }
9499
9500       var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
9501       return new RegExp('^' + reText + '$', regexpFlags);
9502     }
9503     // Otherwise default to default condition
9504     else {
9505       return defaultCondition;
9506     }
9507   };
9508   
9509   
9510   /**
9511    * @ngdoc function
9512    * @name setupFilters
9513    * @methodOf ui.grid.service:rowSearcher
9514    * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
9515    * do all the parsing and pre-processing and store that data into a new filters object.  The object
9516    * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9517    * 
9518    * We could use a forEach in here, since it's much less performance sensitive, but since we're using 
9519    * for loops everywhere else in this module...
9520    * 
9521    * @param {array} filters the filters from the column (col.filters or [col.filter])
9522    * @returns {array} An array of parsed/preprocessed filters
9523    */
9524   rowSearcher.setupFilters = function setupFilters( filters ){
9525     var newFilters = [];
9526     
9527     var filtersLength = filters.length;
9528     for ( var i = 0; i < filtersLength; i++ ){
9529       var filter = filters[i];
9530       
9531       if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9532         var newFilter = {};
9533         
9534         var regexpFlags = '';
9535         if (!filter.flags || !filter.flags.caseSensitive) {
9536           regexpFlags += 'i';
9537         }
9538     
9539         if ( !gridUtil.isNullOrUndefined(filter.term) ){
9540           // it is possible to have noTerm.  We don't need to copy that across, it was just a flag to avoid
9541           // getting the filter ignored if the filter was a function that didn't use a term
9542           newFilter.term = rowSearcher.stripTerm(filter);
9543         }
9544         
9545         if ( filter.condition ){
9546           newFilter.condition = filter.condition;
9547         } else {
9548           newFilter.condition = rowSearcher.guessCondition(filter);
9549         }
9550
9551         newFilter.flags = angular.extend( { caseSensitive: false, date: false }, filter.flags );
9552
9553         if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
9554           newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
9555         }
9556         
9557          if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
9558           newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
9559         }
9560
9561         if (newFilter.condition === uiGridConstants.filter.CONTAINS) {
9562           newFilter.containsRE = new RegExp(newFilter.term, regexpFlags);
9563         }
9564
9565         if (newFilter.condition === uiGridConstants.filter.EXACT) {
9566           newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
9567         }
9568         
9569         newFilters.push(newFilter);
9570       }
9571     }
9572     return newFilters;
9573   };
9574   
9575
9576   /**
9577    * @ngdoc function
9578    * @name runColumnFilter
9579    * @methodOf ui.grid.service:rowSearcher
9580    * @description Runs a single pre-parsed filter against a cell, returning true
9581    * if the cell matches that one filter.
9582    * 
9583    * @param {Grid} grid the grid we're working against
9584    * @param {GridRow} row the row we're matching against
9585    * @param {GridCol} column the column that we're working against
9586    * @param {object} filter the specific, preparsed, filter that we want to test
9587    * @returns {boolean} true if we match (row stays visible)
9588    */
9589   rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, filter) {
9590     // Cache typeof condition
9591     var conditionType = typeof(filter.condition);
9592
9593     // Term to search for.
9594     var term = filter.term;
9595
9596     // Get the column value for this row
9597     var value;
9598     if ( column.filterCellFiltered ){
9599       value = grid.getCellDisplayValue(row, column);
9600     } else {
9601       value = grid.getCellValue(row, column);
9602     }
9603
9604
9605     // If the filter's condition is a RegExp, then use it
9606     if (filter.condition instanceof RegExp) {
9607       return filter.condition.test(value);
9608     }
9609
9610     // If the filter's condition is a function, run it
9611     if (conditionType === 'function') {
9612       return filter.condition(term, value, row, column);
9613     }
9614
9615     if (filter.startswithRE) {
9616       return filter.startswithRE.test(value);
9617     }
9618
9619     if (filter.endswithRE) {
9620       return filter.endswithRE.test(value);
9621     }
9622
9623     if (filter.containsRE) {
9624       return filter.containsRE.test(value);
9625     }
9626
9627     if (filter.exactRE) {
9628       return filter.exactRE.test(value);
9629     }
9630
9631     if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
9632       var regex = new RegExp('^' + term + '$');
9633       return !regex.exec(value);
9634     }
9635
9636     if (typeof(value) === 'number' && typeof(term) === 'string' ){
9637       // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
9638       // the same for negative numbers
9639       // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
9640       var tempFloat = parseFloat(term.replace(/\\\./,'.').replace(/\\\-/,'-'));
9641       if (!isNaN(tempFloat)) {
9642         term = tempFloat;
9643       }
9644     }
9645
9646     if (filter.flags.date === true) {
9647       value = new Date(value);
9648       // If the term has a dash in it, it comes through as '\-' -- we need to take out the '\'.
9649       term = new Date(term.replace(/\\/g, ''));
9650     }
9651
9652     if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
9653       return (value > term);
9654     }
9655
9656     if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
9657       return (value >= term);
9658     }
9659
9660     if (filter.condition === uiGridConstants.filter.LESS_THAN) {
9661       return (value < term);
9662     }
9663
9664     if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
9665       return (value <= term);
9666     }
9667
9668     return true;
9669   };
9670
9671
9672   /**
9673    * @ngdoc boolean
9674    * @name useExternalFiltering
9675    * @propertyOf ui.grid.class:GridOptions
9676    * @description False by default. When enabled, this setting suppresses the internal filtering.
9677    * All UI logic will still operate, allowing filter conditions to be set and modified.
9678    * 
9679    * The external filter logic can listen for the `filterChange` event, which fires whenever
9680    * a filter has been adjusted.
9681    */
9682   /**
9683    * @ngdoc function
9684    * @name searchColumn
9685    * @methodOf ui.grid.service:rowSearcher
9686    * @description Process provided filters on provided column against a given row. If the row meets 
9687    * the conditions on all the filters, return true.
9688    * @param {Grid} grid Grid to search in
9689    * @param {GridRow} row Row to search on
9690    * @param {GridCol} column Column with the filters to use
9691    * @param {array} filters array of pre-parsed/preprocessed filters to apply
9692    * @returns {boolean} Whether the column matches or not.
9693    */
9694   rowSearcher.searchColumn = function searchColumn(grid, row, column, filters) {
9695     if (grid.options.useExternalFiltering) {
9696       return true;
9697     }
9698
9699     var filtersLength = filters.length;
9700     for (var i = 0; i < filtersLength; i++) {
9701       var filter = filters[i];
9702
9703       var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9704       if (!ret) {
9705         return false;
9706       }
9707     }
9708
9709     return true;
9710   };
9711
9712
9713   /**
9714    * @ngdoc function
9715    * @name search
9716    * @methodOf ui.grid.service:rowSearcher
9717    * @description Run a search across the given rows and columns, marking any rows that don't 
9718    * match the stored col.filters or col.filter as invisible.
9719    * @param {Grid} grid Grid instance to search inside
9720    * @param {Array[GridRow]} rows GridRows to filter
9721    * @param {Array[GridColumn]} columns GridColumns with filters to process
9722    */
9723   rowSearcher.search = function search(grid, rows, columns) {
9724     /*
9725      * Added performance optimisations into this code base, as this logic creates deeply nested
9726      * loops and is therefore very performance sensitive.  In particular, avoiding forEach as
9727      * this impacts some browser optimisers (particularly Chrome), using iterators instead
9728      */
9729
9730     // Don't do anything if we weren't passed any rows
9731     if (!rows) {
9732       return;
9733     }
9734
9735     // don't filter if filtering currently disabled
9736     if (!grid.options.enableFiltering){
9737       return rows;
9738     }
9739
9740     // Build list of filters to apply
9741     var filterData = [];
9742
9743     var colsLength = columns.length;
9744
9745     var hasTerm = function( filters ) {
9746       var hasTerm = false;
9747
9748       filters.forEach( function (filter) {
9749         if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9750           hasTerm = true;
9751         }
9752       });
9753
9754       return hasTerm;
9755     };
9756
9757     for (var i = 0; i < colsLength; i++) {
9758       var col = columns[i];
9759
9760       if (typeof(col.filters) !== 'undefined' && hasTerm(col.filters) ) {
9761         filterData.push( { col: col, filters: rowSearcher.setupFilters(col.filters) } );
9762       }
9763     }
9764
9765     if (filterData.length > 0) {
9766       // define functions outside the loop, performance optimisation
9767       var foreachRow = function(grid, row, col, filters){
9768         if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
9769           row.visible = false;
9770         }
9771       };
9772
9773       var foreachFilterCol = function(grid, filterData){
9774         var rowsLength = rows.length;
9775         for ( var i = 0; i < rowsLength; i++){
9776           foreachRow(grid, rows[i], filterData.col, filterData.filters);  
9777         }
9778       };
9779
9780       // nested loop itself - foreachFilterCol, which in turn calls foreachRow
9781       var filterDataLength = filterData.length;
9782       for ( var j = 0; j < filterDataLength; j++){
9783         foreachFilterCol( grid, filterData[j] );  
9784       }
9785
9786       if (grid.api.core.raise.rowsVisibleChanged) {
9787         grid.api.core.raise.rowsVisibleChanged();
9788       }
9789
9790       // drop any invisible rows
9791       // 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
9792       // rows = rows.filter(function(row){ return row.visible; });
9793
9794     }
9795
9796     return rows;
9797   };
9798
9799   return rowSearcher;
9800 }]);
9801
9802 })();
9803
9804 (function() {
9805
9806 var module = angular.module('ui.grid');
9807
9808 /**
9809  * @ngdoc object
9810  * @name ui.grid.class:RowSorter
9811  * @description RowSorter provides the default sorting mechanisms, 
9812  * including guessing column types and applying appropriate sort 
9813  * algorithms
9814  * 
9815  */ 
9816
9817 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
9818   var currencyRegexStr = 
9819     '(' +
9820     uiGridConstants.CURRENCY_SYMBOLS
9821       .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
9822       .join('|') + // Join all the symbols together with |s
9823     ')?';
9824
9825   // /^[-+]?[£$¤¥]?[\d,.]+%?$/
9826   var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
9827
9828   var rowSorter = {
9829     // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
9830     //   this takes a piece of data from the cell and tries to determine its type and what sorting
9831     //   function to use for it
9832     colSortFnCache: {}
9833   };
9834
9835
9836   /**
9837    * @ngdoc method
9838    * @methodOf ui.grid.class:RowSorter
9839    * @name guessSortFn
9840    * @description Assigns a sort function to use based on the itemType in the column
9841    * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'.  And
9842    * error will be thrown for any other type.
9843    * @returns {function} a sort function that will sort that type
9844    */
9845   rowSorter.guessSortFn = function guessSortFn(itemType) {
9846     switch (itemType) {
9847       case "number":
9848         return rowSorter.sortNumber;
9849       case "numberStr":
9850         return rowSorter.sortNumberStr;
9851       case "boolean":
9852         return rowSorter.sortBool;
9853       case "string":
9854         return rowSorter.sortAlpha;
9855       case "date":
9856         return rowSorter.sortDate;
9857       case "object":
9858         return rowSorter.basicSort;
9859       default:
9860         throw new Error('No sorting function found for type:' + itemType);
9861     }
9862   };
9863
9864
9865   /**
9866    * @ngdoc method
9867    * @methodOf ui.grid.class:RowSorter
9868    * @name handleNulls
9869    * @description Sorts nulls and undefined to the bottom (top when
9870    * descending).  Called by each of the internal sorters before
9871    * attempting to sort.  Note that this method is available on the core api
9872    * via gridApi.core.sortHandleNulls
9873    * @param {object} a sort value a
9874    * @param {object} b sort value b
9875    * @returns {number} null if there were no nulls/undefineds, otherwise returns
9876    * a sort value that should be passed back from the sort function
9877    */
9878   rowSorter.handleNulls = function handleNulls(a, b) {
9879     // We want to allow zero values and false values to be evaluated in the sort function
9880     if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
9881       // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
9882       if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
9883         return 0;
9884       }
9885       else if (!a && a !== 0 && a !== false) {
9886         return 1;
9887       }
9888       else if (!b && b !== 0 && b !== false) {
9889         return -1;
9890       }
9891     }
9892     return null;
9893   };
9894
9895
9896   /**
9897    * @ngdoc method
9898    * @methodOf ui.grid.class:RowSorter
9899    * @name basicSort
9900    * @description Sorts any values that provide the < method, including strings
9901    * or numbers.  Handles nulls and undefined through calling handleNulls 
9902    * @param {object} a sort value a
9903    * @param {object} b sort value b
9904    * @returns {number} normal sort function, returns -ve, 0, +ve
9905    */
9906   rowSorter.basicSort = function basicSort(a, b) {
9907     var nulls = rowSorter.handleNulls(a, b);
9908     if ( nulls !== null ){
9909       return nulls;
9910     } else {
9911       if (a === b) {
9912         return 0;
9913       }
9914       if (a < b) {
9915         return -1;
9916       }
9917       return 1;
9918     }
9919   };
9920
9921
9922   /**
9923    * @ngdoc method
9924    * @methodOf ui.grid.class:RowSorter
9925    * @name sortNumber
9926    * @description Sorts numerical values.  Handles nulls and undefined through calling handleNulls 
9927    * @param {object} a sort value a
9928    * @param {object} b sort value b
9929    * @returns {number} normal sort function, returns -ve, 0, +ve
9930    */
9931   rowSorter.sortNumber = function sortNumber(a, b) {
9932     var nulls = rowSorter.handleNulls(a, b);
9933     if ( nulls !== null ){
9934       return nulls;
9935     } else {
9936       return a - b;
9937     }
9938   };
9939
9940
9941   /**
9942    * @ngdoc method
9943    * @methodOf ui.grid.class:RowSorter
9944    * @name sortNumberStr
9945    * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).  
9946    * Handles nulls and undefined through calling handleNulls 
9947    * @param {object} a sort value a
9948    * @param {object} b sort value b
9949    * @returns {number} normal sort function, returns -ve, 0, +ve
9950    */
9951   rowSorter.sortNumberStr = function sortNumberStr(a, b) {
9952     var nulls = rowSorter.handleNulls(a, b);
9953     if ( nulls !== null ){
9954       return nulls;
9955     } else {
9956       var numA, // The parsed number form of 'a'
9957           numB, // The parsed number form of 'b'
9958           badA = false,
9959           badB = false;
9960   
9961       // Try to parse 'a' to a float
9962       numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
9963   
9964       // If 'a' couldn't be parsed to float, flag it as bad
9965       if (isNaN(numA)) {
9966           badA = true;
9967       }
9968   
9969       // Try to parse 'b' to a float
9970       numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
9971   
9972       // If 'b' couldn't be parsed to float, flag it as bad
9973       if (isNaN(numB)) {
9974           badB = true;
9975       }
9976   
9977       // We want bad ones to get pushed to the bottom... which effectively is "greater than"
9978       if (badA && badB) {
9979           return 0;
9980       }
9981   
9982       if (badA) {
9983           return 1;
9984       }
9985   
9986       if (badB) {
9987           return -1;
9988       }
9989   
9990       return numA - numB;
9991     }
9992   };
9993
9994
9995   /**
9996    * @ngdoc method
9997    * @methodOf ui.grid.class:RowSorter
9998    * @name sortAlpha
9999    * @description Sorts string values. Handles nulls and undefined through calling handleNulls 
10000    * @param {object} a sort value a
10001    * @param {object} b sort value b
10002    * @returns {number} normal sort function, returns -ve, 0, +ve
10003    */
10004   rowSorter.sortAlpha = function sortAlpha(a, b) {
10005     var nulls = rowSorter.handleNulls(a, b);
10006     if ( nulls !== null ){
10007       return nulls;
10008     } else {
10009       var strA = a.toString().toLowerCase(),
10010           strB = b.toString().toLowerCase();
10011   
10012       return strA === strB ? 0 : strA.localeCompare(strB);
10013     }
10014   };
10015
10016
10017   /**
10018    * @ngdoc method
10019    * @methodOf ui.grid.class:RowSorter
10020    * @name sortDate
10021    * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
10022    * Handles date strings by converting to Date object if not already an instance of Date
10023    * @param {object} a sort value a
10024    * @param {object} b sort value b
10025    * @returns {number} normal sort function, returns -ve, 0, +ve
10026    */
10027   rowSorter.sortDate = function sortDate(a, b) {
10028     var nulls = rowSorter.handleNulls(a, b);
10029     if ( nulls !== null ){
10030       return nulls;
10031     } else {
10032       if (!(a instanceof Date)) {
10033         a = new Date(a);
10034       }
10035       if (!(b instanceof Date)){
10036         b = new Date(b);
10037       }
10038       var timeA = a.getTime(),
10039           timeB = b.getTime();
10040   
10041       return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
10042     }
10043   };
10044
10045
10046   /**
10047    * @ngdoc method
10048    * @methodOf ui.grid.class:RowSorter
10049    * @name sortBool
10050    * @description Sorts boolean values, true is considered larger than false. 
10051    * Handles nulls and undefined through calling handleNulls 
10052    * @param {object} a sort value a
10053    * @param {object} b sort value b
10054    * @returns {number} normal sort function, returns -ve, 0, +ve
10055    */
10056   rowSorter.sortBool = function sortBool(a, b) {
10057     var nulls = rowSorter.handleNulls(a, b);
10058     if ( nulls !== null ){
10059       return nulls;
10060     } else {
10061       if (a && b) {
10062         return 0;
10063       }
10064   
10065       if (!a && !b) {
10066         return 0;
10067       }
10068       else {
10069         return a ? 1 : -1;
10070       }
10071     }
10072   };
10073
10074
10075   /**
10076    * @ngdoc method
10077    * @methodOf ui.grid.class:RowSorter
10078    * @name getSortFn
10079    * @description Get the sort function for the column.  Looks first in 
10080    * rowSorter.colSortFnCache using the column name, failing that it
10081    * looks at col.sortingAlgorithm (and puts it in the cache), failing that
10082    * it guesses the sort algorithm based on the data type.
10083    * 
10084    * The cache currently seems a bit pointless, as none of the work we do is
10085    * processor intensive enough to need caching.  Presumably in future we might
10086    * inspect the row data itself to guess the sort function, and in that case
10087    * it would make sense to have a cache, the infrastructure is in place to allow
10088    * that.
10089    * 
10090    * @param {Grid} grid the grid to consider
10091    * @param {GridCol} col the column to find a function for
10092    * @param {array} rows an array of grid rows.  Currently unused, but presumably in future
10093    * we might inspect the rows themselves to decide what sort of data might be there
10094    * @returns {function} the sort function chosen for the column
10095    */
10096   rowSorter.getSortFn = function getSortFn(grid, col, rows) {
10097     var sortFn, item;
10098
10099     // See if we already figured out what to use to sort the column and have it in the cache
10100     if (rowSorter.colSortFnCache[col.colDef.name]) {
10101       sortFn = rowSorter.colSortFnCache[col.colDef.name];
10102     }
10103     // If the column has its OWN sorting algorithm, use that
10104     else if (col.sortingAlgorithm !== undefined) {
10105       sortFn = col.sortingAlgorithm;
10106       rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
10107     }
10108     // Always default to sortAlpha when sorting after a cellFilter
10109     else if ( col.sortCellFiltered && col.cellFilter ){
10110       sortFn = rowSorter.sortAlpha;
10111       rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10112     }
10113     // Try and guess what sort function to use
10114     else {
10115       // Guess the sort function
10116       sortFn = rowSorter.guessSortFn(col.colDef.type);
10117
10118       // If we found a sort function, cache it
10119       if (sortFn) {
10120         rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10121       }
10122       else {
10123         // We assign the alpha sort because anything that is null/undefined will never get passed to
10124         // the actual sorting function. It will get caught in our null check and returned to be sorted
10125         // down to the bottom
10126         sortFn = rowSorter.sortAlpha;
10127       }
10128     }
10129
10130     return sortFn;
10131   };
10132
10133
10134
10135   /**
10136    * @ngdoc method
10137    * @methodOf ui.grid.class:RowSorter
10138    * @name prioritySort
10139    * @description Used where multiple columns are present in the sort criteria,
10140    * we determine which column should take precedence in the sort by sorting
10141    * the columns based on their sort.priority
10142    * 
10143    * @param {gridColumn} a column a
10144    * @param {gridColumn} b column b
10145    * @returns {number} normal sort function, returns -ve, 0, +ve
10146    */
10147   rowSorter.prioritySort = function (a, b) {
10148     // Both columns have a sort priority
10149     if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
10150       // A is higher priority
10151       if (a.sort.priority < b.sort.priority) {
10152         return -1;
10153       }
10154       // Equal
10155       else if (a.sort.priority === b.sort.priority) {
10156         return 0;
10157       }
10158       // B is higher
10159       else {
10160         return 1;
10161       }
10162     }
10163     // Only A has a priority
10164     else if (a.sort.priority || a.sort.priority === 0) {
10165       return -1;
10166     }
10167     // Only B has a priority
10168     else if (b.sort.priority || b.sort.priority === 0) {
10169       return 1;
10170     }
10171     // Neither has a priority
10172     else {
10173       return 0;
10174     }
10175   };
10176
10177
10178   /**
10179    * @ngdoc object
10180    * @name useExternalSorting
10181    * @propertyOf ui.grid.class:GridOptions
10182    * @description Prevents the internal sorting from executing.  Events will
10183    * still be fired when the sort changes, and the sort information on
10184    * the columns will be updated, allowing an external sorter (for example,
10185    * server sorting) to be implemented.  Defaults to false. 
10186    * 
10187    */
10188   /**
10189    * @ngdoc method
10190    * @methodOf ui.grid.class:RowSorter
10191    * @name sort
10192    * @description sorts the grid 
10193    * @param {Object} grid the grid itself
10194    * @param {array} rows the rows to be sorted
10195    * @param {array} columns the columns in which to look
10196    * for sort criteria
10197    * @returns {array} sorted rows
10198    */
10199   rowSorter.sort = function rowSorterSort(grid, rows, columns) {
10200     // first make sure we are even supposed to do work
10201     if (!rows) {
10202       return;
10203     }
10204     
10205     if (grid.options.useExternalSorting){
10206       return rows;
10207     }
10208
10209     // Build the list of columns to sort by
10210     var sortCols = [];
10211     columns.forEach(function (col) {
10212       if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10213         sortCols.push(col);
10214       }
10215     });
10216
10217     // Sort the "sort columns" by their sort priority
10218     sortCols = sortCols.sort(rowSorter.prioritySort);
10219
10220     // Now rows to sort by, maintain original order
10221     if (sortCols.length === 0) {
10222       return rows;
10223     }
10224
10225     // Re-usable variables
10226     var col, direction;
10227
10228     // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10229     var setIndex = function( row, idx ){
10230       row.entity.$$uiGridIndex = idx;
10231     };
10232     rows.forEach(setIndex);
10233
10234     // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
10235     // var d = data.slice(0);
10236     var r = rows.slice(0);
10237
10238     // Now actually sort the data
10239     var rowSortFn = function (rowA, rowB) {
10240       var tem = 0,
10241           idx = 0,
10242           sortFn;
10243
10244       while (tem === 0 && idx < sortCols.length) {
10245         // grab the metadata for the rest of the logic
10246         col = sortCols[idx];
10247         direction = sortCols[idx].sort.direction;
10248
10249         sortFn = rowSorter.getSortFn(grid, col, r);
10250
10251         var propA, propB;
10252
10253         if ( col.sortCellFiltered ){
10254           propA = grid.getCellDisplayValue(rowA, col);
10255           propB = grid.getCellDisplayValue(rowB, col);
10256         } else {
10257           propA = grid.getCellValue(rowA, col);
10258           propB = grid.getCellValue(rowB, col);
10259         }
10260
10261         tem = sortFn(propA, propB, rowA, rowB, direction);
10262
10263         idx++;
10264       }
10265
10266       // Chrome doesn't implement a stable sort function.  If our sort returns 0 
10267       // (i.e. the items are equal), and we're at the last sort column in the list,
10268       // then return the previous order using our custom
10269       // index variable
10270       if (tem === 0 ) {
10271         return rowA.entity.$$uiGridIndex - rowB.entity.$$uiGridIndex;
10272       }
10273
10274       // Made it this far, we don't have to worry about null & undefined
10275       if (direction === uiGridConstants.ASC) {
10276         return tem;
10277       } else {
10278         return 0 - tem;
10279       }
10280     };
10281
10282     var newRows = rows.sort(rowSortFn);
10283     
10284     // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10285     var clearIndex = function( row, idx ){
10286        delete row.entity.$$uiGridIndex;
10287     };
10288     rows.forEach(clearIndex);
10289     
10290     return newRows;
10291   };
10292
10293   return rowSorter;
10294 }]);
10295
10296 })();
10297
10298 (function() {
10299
10300 var module = angular.module('ui.grid');
10301
10302 var bindPolyfill;
10303 if (typeof Function.prototype.bind !== "function") {
10304   bindPolyfill = function() {
10305     var slice = Array.prototype.slice;
10306     return function(context) {
10307       var fn = this,
10308         args = slice.call(arguments, 1);
10309       if (args.length) {
10310         return function() {
10311           return arguments.length ? fn.apply(context, args.concat(slice.call(arguments))) : fn.apply(context, args);
10312         };
10313       }
10314       return function() {
10315         return arguments.length ? fn.apply(context, arguments) : fn.call(context);
10316       };
10317     };
10318   };
10319 }
10320
10321 function getStyles (elem) {
10322   var e = elem;
10323   if (typeof(e.length) !== 'undefined' && e.length) {
10324     e = elem[0];
10325   }
10326
10327   return e.ownerDocument.defaultView.getComputedStyle(e, null);
10328 }
10329
10330 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
10331     // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
10332     // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
10333     rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
10334     cssShow = { position: "absolute", visibility: "hidden", display: "block" };
10335
10336 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
10337   var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
10338           // If we already have the right measurement, avoid augmentation
10339           4 :
10340           // Otherwise initialize for horizontal or vertical properties
10341           name === 'width' ? 1 : 0,
10342
10343           val = 0;
10344
10345   var sides = ['Top', 'Right', 'Bottom', 'Left'];
10346
10347   for ( ; i < 4; i += 2 ) {
10348     var side = sides[i];
10349     // dump('side', side);
10350
10351     // both box models exclude margin, so add it if we want it
10352     if ( extra === 'margin' ) {
10353       var marg = parseFloat(styles[extra + side]);
10354       if (!isNaN(marg)) {
10355         val += marg;
10356       }
10357     }
10358     // dump('val1', val);
10359
10360     if ( isBorderBox ) {
10361       // border-box includes padding, so remove it if we want content
10362       if ( extra === 'content' ) {
10363         var padd = parseFloat(styles['padding' + side]);
10364         if (!isNaN(padd)) {
10365           val -= padd;
10366           // dump('val2', val);
10367         }
10368       }
10369
10370       // at this point, extra isn't border nor margin, so remove border
10371       if ( extra !== 'margin' ) {
10372         var bordermarg = parseFloat(styles['border' + side + 'Width']);
10373         if (!isNaN(bordermarg)) {
10374           val -= bordermarg;
10375           // dump('val3', val);
10376         }
10377       }
10378     }
10379     else {
10380       // at this point, extra isn't content, so add padding
10381       var nocontentPad = parseFloat(styles['padding' + side]);
10382       if (!isNaN(nocontentPad)) {
10383         val += nocontentPad;
10384         // dump('val4', val);
10385       }
10386
10387       // at this point, extra isn't content nor padding, so add border
10388       if ( extra !== 'padding') {
10389         var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
10390         if (!isNaN(nocontentnopad)) {
10391           val += nocontentnopad;
10392           // dump('val5', val);
10393         }
10394       }
10395     }
10396   }
10397
10398   // dump('augVal', val);
10399
10400   return val;
10401 }
10402
10403 function getWidthOrHeight( elem, name, extra ) {
10404   // Start with offset property, which is equivalent to the border-box value
10405   var valueIsBorderBox = true,
10406           val, // = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
10407           styles = getStyles(elem),
10408           isBorderBox = styles['boxSizing'] === 'border-box';
10409
10410   // some non-html elements return undefined for offsetWidth, so check for null/undefined
10411   // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
10412   // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
10413   if ( val <= 0 || val == null ) {
10414     // Fall back to computed then uncomputed css if necessary
10415     val = styles[name];
10416     if ( val < 0 || val == null ) {
10417       val = elem.style[ name ];
10418     }
10419
10420     // Computed unit is not pixels. Stop here and return.
10421     if ( rnumnonpx.test(val) ) {
10422       return val;
10423     }
10424
10425     // we need the check for style in case a browser which returns unreliable values
10426     // for getComputedStyle silently falls back to the reliable elem.style
10427     valueIsBorderBox = isBorderBox &&
10428             ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
10429
10430     // Normalize "", auto, and prepare for extra
10431     val = parseFloat( val ) || 0;
10432   }
10433
10434   // use the active box-sizing model to add/subtract irrelevant styles
10435   var ret = ( val +
10436     augmentWidthOrHeight(
10437       elem,
10438       name,
10439       extra || ( isBorderBox ? "border" : "content" ),
10440       valueIsBorderBox,
10441       styles
10442     )
10443   );
10444
10445   // dump('ret', ret, val);
10446   return ret;
10447 }
10448
10449 function getLineHeight(elm) {
10450   elm = angular.element(elm)[0];
10451   var parent = elm.parentElement;
10452
10453   if (!parent) {
10454     parent = document.getElementsByTagName('body')[0];
10455   }
10456
10457   return parseInt( getStyles(parent).fontSize ) || parseInt( getStyles(elm).fontSize ) || 16;
10458 }
10459
10460 var uid = ['0', '0', '0', '0'];
10461 var uidPrefix = 'uiGrid-';
10462
10463 /**
10464  *  @ngdoc service
10465  *  @name ui.grid.service:GridUtil
10466  *
10467  *  @description Grid utility functions
10468  */
10469 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$interval', '$injector', '$q', '$interpolate', 'uiGridConstants',
10470   function ($log, $window, $document, $http, $templateCache, $timeout, $interval, $injector, $q, $interpolate, uiGridConstants) {
10471   var s = {
10472
10473     augmentWidthOrHeight: augmentWidthOrHeight,
10474
10475     getStyles: getStyles,
10476
10477     /**
10478      * @ngdoc method
10479      * @name createBoundedWrapper
10480      * @methodOf ui.grid.service:GridUtil
10481      *
10482      * @param {object} Object to bind 'this' to
10483      * @param {method} Method to bind
10484      * @returns {Function} The wrapper that performs the binding
10485      *
10486      * @description
10487      * Binds given method to given object.
10488      *
10489      * By means of a wrapper, ensures that ``method`` is always bound to
10490      * ``object`` regardless of its calling environment.
10491      * Iow, inside ``method``, ``this`` always points to ``object``.
10492      *
10493      * See http://alistapart.com/article/getoutbindingsituations
10494      *
10495      */
10496     createBoundedWrapper: function(object, method) {
10497         return function() {
10498             return method.apply(object, arguments);
10499         };
10500     },
10501
10502
10503     /**
10504      * @ngdoc method
10505      * @name readableColumnName
10506      * @methodOf ui.grid.service:GridUtil
10507      *
10508      * @param {string} columnName Column name as a string
10509      * @returns {string} Column name appropriately capitalized and split apart
10510      *
10511        @example
10512        <example module="app">
10513         <file name="app.js">
10514           var app = angular.module('app', ['ui.grid']);
10515
10516           app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
10517             $scope.name = 'firstName';
10518             $scope.columnName = function(name) {
10519               return gridUtil.readableColumnName(name);
10520             };
10521           }]);
10522         </file>
10523         <file name="index.html">
10524           <div ng-controller="MainCtrl">
10525             <strong>Column name:</strong> <input ng-model="name" />
10526             <br>
10527             <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
10528           </div>
10529         </file>
10530       </example>
10531      */
10532     readableColumnName: function (columnName) {
10533       // Convert underscores to spaces
10534       if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
10535
10536       if (typeof(columnName) !== 'string') {
10537         columnName = String(columnName);
10538       }
10539
10540       return columnName.replace(/_+/g, ' ')
10541         // Replace a completely all-capsed word with a first-letter-capitalized version
10542         .replace(/^[A-Z]+$/, function (match) {
10543           return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10544         })
10545         // Capitalize the first letter of words
10546         .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10547           return angular.uppercase(match.charAt(0)) + match.slice(1);
10548         })
10549         // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
10550         // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
10551         // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
10552         .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
10553     },
10554
10555     /**
10556      * @ngdoc method
10557      * @name getColumnsFromData
10558      * @methodOf ui.grid.service:GridUtil
10559      * @description Return a list of column names, given a data set
10560      *
10561      * @param {string} data Data array for grid
10562      * @returns {Object} Column definitions with field accessor and column name
10563      *
10564      * @example
10565        <pre>
10566          var data = [
10567            { firstName: 'Bob', lastName: 'Jones' },
10568            { firstName: 'Frank', lastName: 'Smith' }
10569          ];
10570
10571          var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
10572
10573          columnDefs == [
10574           {
10575             field: 'firstName',
10576             name: 'First Name'
10577           },
10578           {
10579             field: 'lastName',
10580             name: 'Last Name'
10581           }
10582          ];
10583        </pre>
10584      */
10585     getColumnsFromData: function (data, excludeProperties) {
10586       var columnDefs = [];
10587
10588       if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
10589       if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
10590
10591       var item = data[0];
10592
10593       angular.forEach(item,function (prop, propName) {
10594         if ( excludeProperties.indexOf(propName) === -1){
10595           columnDefs.push({
10596             name: propName
10597           });
10598         }
10599       });
10600
10601       return columnDefs;
10602     },
10603
10604     /**
10605      * @ngdoc method
10606      * @name newId
10607      * @methodOf ui.grid.service:GridUtil
10608      * @description Return a unique ID string
10609      *
10610      * @returns {string} Unique string
10611      *
10612      * @example
10613        <pre>
10614         var id = GridUtil.newId();
10615
10616         # 1387305700482;
10617        </pre>
10618      */
10619     newId: (function() {
10620       var seedId = new Date().getTime();
10621       return function() {
10622           return seedId += 1;
10623       };
10624     })(),
10625
10626
10627     /**
10628      * @ngdoc method
10629      * @name getTemplate
10630      * @methodOf ui.grid.service:GridUtil
10631      * @description Get's template from cache / element / url
10632      *
10633      * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10634      *   an jQuery/Angualr element, or a promise that returns the template contents to use.
10635      * @returns {object} a promise resolving to template contents
10636      *
10637      * @example
10638      <pre>
10639      GridUtil.getTemplate(url).then(function (contents) {
10640           alert(contents);
10641         })
10642      </pre>
10643      */
10644     getTemplate: function (template) {
10645       // Try to fetch the template out of the templateCache
10646       if ($templateCache.get(template)) {
10647         return s.postProcessTemplate($templateCache.get(template));
10648       }
10649
10650       // See if the template is itself a promise
10651       if (template.hasOwnProperty('then')) {
10652         return template.then(s.postProcessTemplate);
10653       }
10654
10655       // If the template is an element, return the element
10656       try {
10657         if (angular.element(template).length > 0) {
10658           return $q.when(template).then(s.postProcessTemplate);
10659         }
10660       }
10661       catch (err){
10662         //do nothing; not valid html
10663       }
10664
10665       s.logDebug('fetching url', template);
10666
10667       // Default to trying to fetch the template as a url with $http
10668       return $http({ method: 'GET', url: template})
10669         .then(
10670           function (result) {
10671             var templateHtml = result.data.trim();
10672             //put in templateCache for next call
10673             $templateCache.put(template, templateHtml);
10674             return templateHtml;
10675           },
10676           function (err) {
10677             throw new Error("Could not get template " + template + ": " + err);
10678           }
10679         )
10680         .then(s.postProcessTemplate);
10681     },
10682
10683     //
10684     postProcessTemplate: function (template) {
10685       var startSym = $interpolate.startSymbol(),
10686           endSym = $interpolate.endSymbol();
10687
10688       // If either of the interpolation symbols have been changed, we need to alter this template
10689       if (startSym !== '{{' || endSym !== '}}') {
10690         template = template.replace(/\{\{/g, startSym);
10691         template = template.replace(/\}\}/g, endSym);
10692       }
10693
10694       return $q.when(template);
10695     },
10696
10697     /**
10698      * @ngdoc method
10699      * @name guessType
10700      * @methodOf ui.grid.service:GridUtil
10701      * @description guesses the type of an argument
10702      *
10703      * @param {string/number/bool/object} item variable to examine
10704      * @returns {string} one of the following
10705      * - 'string'
10706      * - 'boolean'
10707      * - 'number'
10708      * - 'date'
10709      * - 'object'
10710      */
10711     guessType : function (item) {
10712       var itemType = typeof(item);
10713
10714       // Check for numbers and booleans
10715       switch (itemType) {
10716         case "number":
10717         case "boolean":
10718         case "string":
10719           return itemType;
10720         default:
10721           if (angular.isDate(item)) {
10722             return "date";
10723           }
10724           return "object";
10725       }
10726     },
10727
10728
10729   /**
10730     * @ngdoc method
10731     * @name elementWidth
10732     * @methodOf ui.grid.service:GridUtil
10733     *
10734     * @param {element} element DOM element
10735     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10736     *
10737     * @returns {number} Element width in pixels, accounting for any borders, etc.
10738     */
10739     elementWidth: function (elem) {
10740
10741     },
10742
10743     /**
10744     * @ngdoc method
10745     * @name elementHeight
10746     * @methodOf ui.grid.service:GridUtil
10747     *
10748     * @param {element} element DOM element
10749     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10750     *
10751     * @returns {number} Element height in pixels, accounting for any borders, etc.
10752     */
10753     elementHeight: function (elem) {
10754
10755     },
10756
10757     // Thanks to http://stackoverflow.com/a/13382873/888165
10758     getScrollbarWidth: function() {
10759         var outer = document.createElement("div");
10760         outer.style.visibility = "hidden";
10761         outer.style.width = "100px";
10762         outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
10763
10764         document.body.appendChild(outer);
10765
10766         var widthNoScroll = outer.offsetWidth;
10767         // force scrollbars
10768         outer.style.overflow = "scroll";
10769
10770         // add innerdiv
10771         var inner = document.createElement("div");
10772         inner.style.width = "100%";
10773         outer.appendChild(inner);
10774
10775         var widthWithScroll = inner.offsetWidth;
10776
10777         // remove divs
10778         outer.parentNode.removeChild(outer);
10779
10780         return widthNoScroll - widthWithScroll;
10781     },
10782
10783     swap: function( elem, options, callback, args ) {
10784       var ret, name,
10785               old = {};
10786
10787       // Remember the old values, and insert the new ones
10788       for ( name in options ) {
10789         old[ name ] = elem.style[ name ];
10790         elem.style[ name ] = options[ name ];
10791       }
10792
10793       ret = callback.apply( elem, args || [] );
10794
10795       // Revert the old values
10796       for ( name in options ) {
10797         elem.style[ name ] = old[ name ];
10798       }
10799
10800       return ret;
10801     },
10802
10803     fakeElement: function( elem, options, callback, args ) {
10804       var ret, name,
10805           newElement = angular.element(elem).clone()[0];
10806
10807       for ( name in options ) {
10808         newElement.style[ name ] = options[ name ];
10809       }
10810
10811       angular.element(document.body).append(newElement);
10812
10813       ret = callback.call( newElement, newElement );
10814
10815       angular.element(newElement).remove();
10816
10817       return ret;
10818     },
10819
10820     /**
10821     * @ngdoc method
10822     * @name normalizeWheelEvent
10823     * @methodOf ui.grid.service:GridUtil
10824     *
10825     * @param {event} event A mouse wheel event
10826     *
10827     * @returns {event} A normalized event
10828     *
10829     * @description
10830     * Given an event from this list:
10831     *
10832     * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
10833     *
10834     * "normalize" it
10835     * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
10836     */
10837     normalizeWheelEvent: function (event) {
10838       // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
10839       // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
10840       var lowestDelta, lowestDeltaXY;
10841
10842       var orgEvent   = event || window.event,
10843           args       = [].slice.call(arguments, 1),
10844           delta      = 0,
10845           deltaX     = 0,
10846           deltaY     = 0,
10847           absDelta   = 0,
10848           absDeltaXY = 0,
10849           fn;
10850
10851       // event = $.event.fix(orgEvent);
10852       // event.type = 'mousewheel';
10853
10854       // NOTE: jQuery masks the event and stores it in the event as originalEvent
10855       if (orgEvent.originalEvent) {
10856         orgEvent = orgEvent.originalEvent;
10857       }
10858
10859       // Old school scrollwheel delta
10860       if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
10861       if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
10862
10863       // At a minimum, setup the deltaY to be delta
10864       deltaY = delta;
10865
10866       // Firefox < 17 related to DOMMouseScroll event
10867       if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
10868           deltaY = 0;
10869           deltaX = delta * -1;
10870       }
10871
10872       // New school wheel delta (wheel event)
10873       if ( orgEvent.deltaY ) {
10874           deltaY = orgEvent.deltaY * -1;
10875           delta  = deltaY;
10876       }
10877       if ( orgEvent.deltaX ) {
10878           deltaX = orgEvent.deltaX;
10879           delta  = deltaX * -1;
10880       }
10881
10882       // Webkit
10883       if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
10884       if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
10885
10886       // Look for lowest delta to normalize the delta values
10887       absDelta = Math.abs(delta);
10888       if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
10889       absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
10890       if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
10891
10892       // Get a whole value for the deltas
10893       fn     = delta > 0 ? 'floor' : 'ceil';
10894       delta  = Math[fn](delta  / lowestDelta);
10895       deltaX = Math[fn](deltaX / lowestDeltaXY);
10896       deltaY = Math[fn](deltaY / lowestDeltaXY);
10897
10898       return {
10899         delta: delta,
10900         deltaX: deltaX,
10901         deltaY: deltaY
10902       };
10903     },
10904
10905     // Stolen from Modernizr
10906     // TODO: make this, and everythign that flows from it, robust
10907     //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
10908     isTouchEnabled: function() {
10909       var bool;
10910
10911       if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
10912         bool = true;
10913       }
10914
10915       return bool;
10916     },
10917
10918     isNullOrUndefined: function(obj) {
10919       if (obj === undefined || obj === null) {
10920         return true;
10921       }
10922       return false;
10923     },
10924
10925     endsWith: function(str, suffix) {
10926       if (!str || !suffix || typeof str !== "string") {
10927         return false;
10928       }
10929       return str.indexOf(suffix, str.length - suffix.length) !== -1;
10930     },
10931
10932     arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
10933         var found = false;
10934         angular.forEach(array, function (object) {
10935             if (object[propertyName] === propertyValue) {
10936                 found = true;
10937             }
10938         });
10939         return found;
10940     },
10941
10942     //// Shim requestAnimationFrame
10943     //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
10944     //                       $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
10945     //                       function(fn) {
10946     //                         return $timeout(fn, 10, false);
10947     //                       },
10948
10949     numericAndNullSort: function (a, b) {
10950       if (a === null) { return 1; }
10951       if (b === null) { return -1; }
10952       if (a === null && b === null) { return 0; }
10953       return a - b;
10954     },
10955
10956     // Disable ngAnimate animations on an element
10957     disableAnimations: function (element) {
10958       var $animate;
10959       try {
10960         $animate = $injector.get('$animate');
10961         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10962         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10963           $animate.enabled(element, false);
10964         } else {
10965           $animate.enabled(false, element);
10966         }
10967       }
10968       catch (e) {}
10969     },
10970
10971     enableAnimations: function (element) {
10972       var $animate;
10973       try {
10974         $animate = $injector.get('$animate');
10975         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10976         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10977           $animate.enabled(element, true);
10978         } else {
10979           $animate.enabled(true, element);
10980         }
10981         return $animate;
10982       }
10983       catch (e) {}
10984     },
10985
10986     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
10987     nextUid: function nextUid() {
10988       var index = uid.length;
10989       var digit;
10990
10991       while (index) {
10992         index--;
10993         digit = uid[index].charCodeAt(0);
10994         if (digit === 57 /*'9'*/) {
10995           uid[index] = 'A';
10996           return uidPrefix + uid.join('');
10997         }
10998         if (digit === 90  /*'Z'*/) {
10999           uid[index] = '0';
11000         } else {
11001           uid[index] = String.fromCharCode(digit + 1);
11002           return uidPrefix + uid.join('');
11003         }
11004       }
11005       uid.unshift('0');
11006
11007       return uidPrefix + uid.join('');
11008     },
11009
11010     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11011     hashKey: function hashKey(obj) {
11012       var objType = typeof obj,
11013           key;
11014
11015       if (objType === 'object' && obj !== null) {
11016         if (typeof (key = obj.$$hashKey) === 'function') {
11017           // must invoke on object to keep the right this
11018           key = obj.$$hashKey();
11019         }
11020         else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
11021           key = obj.$$hashKey;
11022         }
11023         else if (key === undefined) {
11024           key = obj.$$hashKey = s.nextUid();
11025         }
11026       }
11027       else {
11028         key = obj;
11029       }
11030
11031       return objType + ':' + key;
11032     },
11033
11034     resetUids: function () {
11035       uid = ['0', '0', '0'];
11036     },
11037
11038     /**
11039      * @ngdoc method
11040      * @methodOf ui.grid.service:GridUtil
11041      * @name logError
11042      * @description wraps the $log method, allowing us to choose different
11043      * treatment within ui-grid if we so desired.  At present we only log
11044      * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
11045      * @param {string} logMessage message to be logged to the console
11046      *
11047      */
11048     logError: function( logMessage ){
11049       if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11050         $log.error( logMessage );
11051       }
11052     },
11053
11054     /**
11055      * @ngdoc method
11056      * @methodOf ui.grid.service:GridUtil
11057      * @name logWarn
11058      * @description wraps the $log method, allowing us to choose different
11059      * treatment within ui-grid if we so desired.  At present we only log
11060      * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
11061      * @param {string} logMessage message to be logged to the console
11062      *
11063      */
11064     logWarn: function( logMessage ){
11065       if ( uiGridConstants.LOG_WARN_MESSAGES ){
11066         $log.warn( logMessage );
11067       }
11068     },
11069
11070     /**
11071      * @ngdoc method
11072      * @methodOf ui.grid.service:GridUtil
11073      * @name logDebug
11074      * @description wraps the $log method, allowing us to choose different
11075      * treatment within ui-grid if we so desired.  At present we only log
11076      * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
11077      *
11078      */
11079     logDebug: function() {
11080       if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11081         $log.debug.apply($log, arguments);
11082       }
11083     }
11084
11085   };
11086
11087   /**
11088    * @ngdoc object
11089    * @name focus
11090    * @propertyOf ui.grid.service:GridUtil
11091    * @description Provies a set of methods to set the document focus inside the grid.
11092    * See {@link ui.grid.service:GridUtil.focus} for more information.
11093    */
11094
11095   /**
11096    * @ngdoc object
11097    * @name ui.grid.service:GridUtil.focus
11098    * @description Provies a set of methods to set the document focus inside the grid.
11099    * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
11100    * e.g. click events that need to run before the focus or
11101    * inputs elements that are in a disabled state but are enabled when those events
11102    * are triggered.
11103    */
11104   s.focus = {
11105     queue: [],
11106     //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11107     /**
11108      * @ngdoc method
11109      * @methodOf ui.grid.service:GridUtil.focus
11110      * @name byId
11111      * @description Sets the focus of the document to the given id value.
11112      * If provided with the grid object it will automatically append the grid id.
11113      * This is done to encourage unique dom id's as it allows for multiple grids on a
11114      * page.
11115      * @param {String} id the id of the dom element to set the focus on
11116      * @param {Object=} Grid the grid object for this grid instance. See: {@link ui.grid.class:Grid}
11117      * @param {Number} Grid.id the unique id for this grid. Already set on an initialized grid object.
11118      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11119      * then the promise will fail with the `'canceled'` reason.
11120      */
11121     byId: function (id, Grid) {
11122       this._purgeQueue();
11123       var promise = $timeout(function() {
11124         var elementID = (Grid && Grid.id ? Grid.id + '-' : '') + id;
11125         var element = $window.document.getElementById(elementID);
11126         if (element) {
11127           element.focus();
11128         } else {
11129           s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
11130         }
11131       });
11132       this.queue.push(promise);
11133       return promise;
11134     },
11135
11136     /**
11137      * @ngdoc method
11138      * @methodOf ui.grid.service:GridUtil.focus
11139      * @name byElement
11140      * @description Sets the focus of the document to the given dom element.
11141      * @param {(element|angular.element)} element the DOM element to set the focus on
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     byElement: function(element){
11146       if (!angular.isElement(element)){
11147         s.logWarn("Trying to focus on an element that isn\'t an element.");
11148         return $q.reject('not-element');
11149       }
11150       element = angular.element(element);
11151       this._purgeQueue();
11152       var promise = $timeout(function(){
11153         if (element){
11154           element[0].focus();
11155         }
11156       });
11157       this.queue.push(promise);
11158       return promise;
11159     },
11160     /**
11161      * @ngdoc method
11162      * @methodOf ui.grid.service:GridUtil.focus
11163      * @name bySelector
11164      * @description Sets the focus of the document to the given dom element.
11165      * @param {(element|angular.element)} parentElement the parent/ancestor of the dom element that you are selecting using the query selector
11166      * @param {String} querySelector finds the dom element using the {@link http://www.w3schools.com/jsref/met_document_queryselector.asp querySelector}
11167      * @param {boolean} [aSync=false] If true then the selector will be querried inside of a timeout. Otherwise the selector will be querried imidately
11168      * then the focus will be called.
11169      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11170      * then the promise will fail with the `'canceled'` reason.
11171      */
11172     bySelector: function(parentElement, querySelector, aSync){
11173       var self = this;
11174       if (!angular.isElement(parentElement)){
11175         throw new Error("The parent element is not an element.");
11176       }
11177       // Ensure that this is an angular element.
11178       // It is fine if this is already an angular element.
11179       parentElement = angular.element(parentElement);
11180       var focusBySelector = function(){
11181         var element = parentElement[0].querySelector(querySelector);
11182         return self.byElement(element);
11183       };
11184       this._purgeQueue();
11185       if (aSync){ //Do this asynchronysly
11186         var promise = $timeout(focusBySelector);
11187         this.queue.push($timeout(focusBySelector));
11188         return promise;
11189       } else {
11190         return focusBySelector();
11191       }
11192     },
11193     _purgeQueue: function(){
11194       this.queue.forEach(function(element){
11195         $timeout.cancel(element);
11196       });
11197       this.queue = [];
11198     }
11199   };
11200
11201
11202   ['width', 'height'].forEach(function (name) {
11203     var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11204     s['element' + capsName] = function (elem, extra) {
11205       var e = elem;
11206       if (e && typeof(e.length) !== 'undefined' && e.length) {
11207         e = elem[0];
11208       }
11209
11210       if (e) {
11211         var styles = getStyles(e);
11212         return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11213                   s.swap(e, cssShow, function() {
11214                     return getWidthOrHeight(e, name, extra );
11215                   }) :
11216                   getWidthOrHeight( e, name, extra );
11217       }
11218       else {
11219         return null;
11220       }
11221     };
11222
11223     s['outerElement' + capsName] = function (elem, margin) {
11224       return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
11225     };
11226   });
11227
11228   // http://stackoverflow.com/a/24107550/888165
11229   s.closestElm = function closestElm(el, selector) {
11230     if (typeof(el.length) !== 'undefined' && el.length) {
11231       el = el[0];
11232     }
11233
11234     var matchesFn;
11235
11236     // find vendor prefix
11237     ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
11238         if (typeof document.body[fn] === 'function') {
11239             matchesFn = fn;
11240             return true;
11241         }
11242         return false;
11243     });
11244
11245     // traverse parents
11246     var parent;
11247     while (el !== null) {
11248       parent = el.parentElement;
11249       if (parent !== null && parent[matchesFn](selector)) {
11250           return parent;
11251       }
11252       el = parent;
11253     }
11254
11255     return null;
11256   };
11257
11258   s.type = function (obj) {
11259     var text = Function.prototype.toString.call(obj.constructor);
11260     return text.match(/function (.*?)\(/)[1];
11261   };
11262
11263   s.getBorderSize = function getBorderSize(elem, borderType) {
11264     if (typeof(elem.length) !== 'undefined' && elem.length) {
11265       elem = elem[0];
11266     }
11267
11268     var styles = getStyles(elem);
11269
11270     // If a specific border is supplied, like 'top', read the 'borderTop' style property
11271     if (borderType) {
11272       borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
11273     }
11274     else {
11275       borderType = 'border';
11276     }
11277
11278     borderType += 'Width';
11279
11280     var val = parseInt(styles[borderType], 10);
11281
11282     if (isNaN(val)) {
11283       return 0;
11284     }
11285     else {
11286       return val;
11287     }
11288   };
11289
11290   // http://stackoverflow.com/a/22948274/888165
11291   // TODO: Opera? Mobile?
11292   s.detectBrowser = function detectBrowser() {
11293     var userAgent = $window.navigator.userAgent;
11294
11295     var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
11296
11297     for (var key in browsers) {
11298       if (browsers[key].test(userAgent)) {
11299         return key;
11300       }
11301     }
11302
11303     return 'unknown';
11304   };
11305
11306   // Borrowed from https://github.com/othree/jquery.rtl-scroll-type
11307   // Determine the scroll "type" this browser is using for RTL
11308   s.rtlScrollType = function rtlScrollType() {
11309     if (rtlScrollType.type) {
11310       return rtlScrollType.type;
11311     }
11312
11313     var definer = angular.element('<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>')[0],
11314         type = 'reverse';
11315
11316     document.body.appendChild(definer);
11317
11318     if (definer.scrollLeft > 0) {
11319       type = 'default';
11320     }
11321     else {
11322       definer.scrollLeft = 1;
11323       if (definer.scrollLeft === 0) {
11324         type = 'negative';
11325       }
11326     }
11327
11328     angular.element(definer).remove();
11329     rtlScrollType.type = type;
11330
11331     return type;
11332   };
11333
11334     /**
11335      * @ngdoc method
11336      * @name normalizeScrollLeft
11337      * @methodOf ui.grid.service:GridUtil
11338      *
11339      * @param {element} element The element to get the `scrollLeft` from.
11340      * @param {grid} grid -  grid used to normalize (uses the rtl property)
11341      *
11342      * @returns {number} A normalized scrollLeft value for the current browser.
11343      *
11344      * @description
11345      * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
11346      */
11347   s.normalizeScrollLeft = function normalizeScrollLeft(element, grid) {
11348     if (typeof(element.length) !== 'undefined' && element.length) {
11349       element = element[0];
11350     }
11351
11352     var scrollLeft = element.scrollLeft;
11353
11354     if (grid.isRTL()) {
11355       switch (s.rtlScrollType()) {
11356         case 'default':
11357           return element.scrollWidth - scrollLeft - element.clientWidth;
11358         case 'negative':
11359           return Math.abs(scrollLeft);
11360         case 'reverse':
11361           return scrollLeft;
11362       }
11363     }
11364
11365     return scrollLeft;
11366   };
11367
11368   /**
11369   * @ngdoc method
11370   * @name denormalizeScrollLeft
11371   * @methodOf ui.grid.service:GridUtil
11372   *
11373   * @param {element} element The element to normalize the `scrollLeft` value for
11374   * @param {number} scrollLeft The `scrollLeft` value to denormalize.
11375   * @param {grid} grid The grid that owns the scroll event.
11376   *
11377   * @returns {number} A normalized scrollLeft value for the current browser.
11378   *
11379   * @description
11380   * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
11381   */
11382   s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft, grid) {
11383     if (typeof(element.length) !== 'undefined' && element.length) {
11384       element = element[0];
11385     }
11386
11387     if (grid.isRTL()) {
11388       switch (s.rtlScrollType()) {
11389         case 'default':
11390           // Get the max scroll for the element
11391           var maxScrollLeft = element.scrollWidth - element.clientWidth;
11392
11393           // Subtract the current scroll amount from the max scroll
11394           return maxScrollLeft - scrollLeft;
11395         case 'negative':
11396           return scrollLeft * -1;
11397         case 'reverse':
11398           return scrollLeft;
11399       }
11400     }
11401
11402     return scrollLeft;
11403   };
11404
11405     /**
11406      * @ngdoc method
11407      * @name preEval
11408      * @methodOf ui.grid.service:GridUtil
11409      *
11410      * @param {string} path Path to evaluate
11411      *
11412      * @returns {string} A path that is normalized.
11413      *
11414      * @description
11415      * Takes a field path and converts it to bracket notation to allow for special characters in path
11416      * @example
11417      * <pre>
11418      * gridUtil.preEval('property') == 'property'
11419      * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
11420      * </pre>
11421      */
11422   s.preEval = function (path) {
11423     var m = uiGridConstants.BRACKET_REGEXP.exec(path);
11424     if (m) {
11425       return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
11426     } else {
11427       path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
11428       var parts = path.split(uiGridConstants.DOT_REGEXP);
11429       var preparsed = [parts.shift()];    // first item must be var notation, thus skip
11430       angular.forEach(parts, function (part) {
11431         preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
11432       });
11433       return preparsed.join('[\'');
11434     }
11435   };
11436
11437   /**
11438    * @ngdoc method
11439    * @name debounce
11440    * @methodOf ui.grid.service:GridUtil
11441    *
11442    * @param {function} func function to debounce
11443    * @param {number} wait milliseconds to delay
11444    * @param {boolean} immediate execute before delay
11445    *
11446    * @returns {function} A function that can be executed as debounced function
11447    *
11448    * @description
11449    * Copied from https://github.com/shahata/angular-debounce
11450    * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
11451    * @example
11452    * <pre>
11453    * var debouncedFunc =  gridUtil.debounce(function(){alert('debounced');}, 500);
11454    * debouncedFunc();
11455    * debouncedFunc();
11456    * debouncedFunc();
11457    * </pre>
11458    */
11459   s.debounce =  function (func, wait, immediate) {
11460     var timeout, args, context, result;
11461     function debounce() {
11462       /* jshint validthis:true */
11463       context = this;
11464       args = arguments;
11465       var later = function () {
11466         timeout = null;
11467         if (!immediate) {
11468           result = func.apply(context, args);
11469         }
11470       };
11471       var callNow = immediate && !timeout;
11472       if (timeout) {
11473         $timeout.cancel(timeout);
11474       }
11475       timeout = $timeout(later, wait);
11476       if (callNow) {
11477         result = func.apply(context, args);
11478       }
11479       return result;
11480     }
11481     debounce.cancel = function () {
11482       $timeout.cancel(timeout);
11483       timeout = null;
11484     };
11485     return debounce;
11486   };
11487
11488   /**
11489    * @ngdoc method
11490    * @name throttle
11491    * @methodOf ui.grid.service:GridUtil
11492    *
11493    * @param {function} func function to throttle
11494    * @param {number} wait milliseconds to delay after first trigger
11495    * @param {Object} params to use in throttle.
11496    *
11497    * @returns {function} A function that can be executed as throttled function
11498    *
11499    * @description
11500    * Adapted from debounce function (above)
11501    * Potential keys for Params Object are:
11502    *    trailing (bool) - whether to trigger after throttle time ends if called multiple times
11503    * Updated to use $interval rather than $timeout, as protractor (e2e tests) is able to work with $interval,
11504    * but not with $timeout
11505    *
11506    * Note that when using throttle, you need to use throttle to create a new function upfront, then use the function
11507    * return from that call each time you need to call throttle.  If you call throttle itself repeatedly, the lastCall
11508    * variable will get overwritten and the throttling won't work
11509    *
11510    * @example
11511    * <pre>
11512    * var throttledFunc =  gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11513    * throttledFunc(); //=> logs throttled
11514    * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
11515    * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
11516    * </pre>
11517    */
11518   s.throttle = function(func, wait, options){
11519     options = options || {};
11520     var lastCall = 0, queued = null, context, args;
11521
11522     function runFunc(endDate){
11523       lastCall = +new Date();
11524       func.apply(context, args);
11525       $interval(function(){ queued = null; }, 0, 1);
11526     }
11527
11528     return function(){
11529       /* jshint validthis:true */
11530       context = this;
11531       args = arguments;
11532       if (queued === null){
11533         var sinceLast = +new Date() - lastCall;
11534         if (sinceLast > wait){
11535           runFunc();
11536         }
11537         else if (options.trailing){
11538           queued = $interval(runFunc, wait - sinceLast, 1);
11539         }
11540       }
11541     };
11542   };
11543
11544   s.on = {};
11545   s.off = {};
11546   s._events = {};
11547
11548   s.addOff = function (eventName) {
11549     s.off[eventName] = function (elm, fn) {
11550       var idx = s._events[eventName].indexOf(fn);
11551       if (idx > 0) {
11552         s._events[eventName].removeAt(idx);
11553       }
11554     };
11555   };
11556
11557   var mouseWheeltoBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
11558       nullLowestDeltaTimeout,
11559       lowestDelta;
11560
11561   s.on.mousewheel = function (elm, fn) {
11562     if (!elm || !fn) { return; }
11563
11564     var $elm = angular.element(elm);
11565
11566     // Store the line height and page height for this particular element
11567     $elm.data('mousewheel-line-height', getLineHeight($elm));
11568     $elm.data('mousewheel-page-height', s.elementHeight($elm));
11569     if (!$elm.data('mousewheel-callbacks')) { $elm.data('mousewheel-callbacks', {}); }
11570
11571     var cbs = $elm.data('mousewheel-callbacks');
11572     cbs[fn] = (Function.prototype.bind || bindPolyfill).call(mousewheelHandler, $elm[0], fn);
11573
11574     // Bind all the mousew heel events
11575     for ( var i = mouseWheeltoBind.length; i; ) {
11576       $elm.on(mouseWheeltoBind[--i], cbs[fn]);
11577     }
11578   };
11579   s.off.mousewheel = function (elm, fn) {
11580     var $elm = angular.element(elm);
11581
11582     var cbs = $elm.data('mousewheel-callbacks');
11583     var handler = cbs[fn];
11584
11585     if (handler) {
11586       for ( var i = mouseWheeltoBind.length; i; ) {
11587         $elm.off(mouseWheeltoBind[--i], handler);
11588       }
11589     }
11590
11591     delete cbs[fn];
11592
11593     if (Object.keys(cbs).length === 0) {
11594       $elm.removeData('mousewheel-line-height');
11595       $elm.removeData('mousewheel-page-height');
11596       $elm.removeData('mousewheel-callbacks');
11597     }
11598   };
11599
11600   function mousewheelHandler(fn, event) {
11601     var $elm = angular.element(this);
11602
11603     var delta      = 0,
11604         deltaX     = 0,
11605         deltaY     = 0,
11606         absDelta   = 0,
11607         offsetX    = 0,
11608         offsetY    = 0;
11609
11610     // jQuery masks events
11611     if (event.originalEvent) { event = event.originalEvent; }
11612
11613     if ( 'detail'      in event ) { deltaY = event.detail * -1;      }
11614     if ( 'wheelDelta'  in event ) { deltaY = event.wheelDelta;       }
11615     if ( 'wheelDeltaY' in event ) { deltaY = event.wheelDeltaY;      }
11616     if ( 'wheelDeltaX' in event ) { deltaX = event.wheelDeltaX * -1; }
11617
11618     // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
11619     if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
11620       deltaX = deltaY * -1;
11621       deltaY = 0;
11622     }
11623
11624     // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
11625     delta = deltaY === 0 ? deltaX : deltaY;
11626
11627     // New school wheel delta (wheel event)
11628     if ( 'deltaY' in event ) {
11629       deltaY = event.deltaY * -1;
11630       delta  = deltaY;
11631     }
11632     if ( 'deltaX' in event ) {
11633       deltaX = event.deltaX;
11634       if ( deltaY === 0 ) { delta  = deltaX * -1; }
11635     }
11636
11637     // No change actually happened, no reason to go any further
11638     if ( deltaY === 0 && deltaX === 0 ) { return; }
11639
11640     // Need to convert lines and pages to pixels if we aren't already in pixels
11641     // There are three delta modes:
11642     //   * deltaMode 0 is by pixels, nothing to do
11643     //   * deltaMode 1 is by lines
11644     //   * deltaMode 2 is by pages
11645     if ( event.deltaMode === 1 ) {
11646         var lineHeight = $elm.data('mousewheel-line-height');
11647         delta  *= lineHeight;
11648         deltaY *= lineHeight;
11649         deltaX *= lineHeight;
11650     }
11651     else if ( event.deltaMode === 2 ) {
11652         var pageHeight = $elm.data('mousewheel-page-height');
11653         delta  *= pageHeight;
11654         deltaY *= pageHeight;
11655         deltaX *= pageHeight;
11656     }
11657
11658     // Store lowest absolute delta to normalize the delta values
11659     absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
11660
11661     if ( !lowestDelta || absDelta < lowestDelta ) {
11662       lowestDelta = absDelta;
11663
11664       // Adjust older deltas if necessary
11665       if ( shouldAdjustOldDeltas(event, absDelta) ) {
11666         lowestDelta /= 40;
11667       }
11668     }
11669
11670     // Get a whole, normalized value for the deltas
11671     delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
11672     deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
11673     deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
11674
11675     event.deltaMode = 0;
11676
11677     // Normalise offsetX and offsetY properties
11678     // if ($elm[0].getBoundingClientRect ) {
11679     //   var boundingRect = $(elm)[0].getBoundingClientRect();
11680     //   offsetX = event.clientX - boundingRect.left;
11681     //   offsetY = event.clientY - boundingRect.top;
11682     // }
11683
11684     // event.deltaX = deltaX;
11685     // event.deltaY = deltaY;
11686     // event.deltaFactor = lowestDelta;
11687
11688     var newEvent = {
11689       originalEvent: event,
11690       deltaX: deltaX,
11691       deltaY: deltaY,
11692       deltaFactor: lowestDelta,
11693       preventDefault: function () { event.preventDefault(); },
11694       stopPropagation: function () { event.stopPropagation(); }
11695     };
11696
11697     // Clearout lowestDelta after sometime to better
11698     // handle multiple device types that give
11699     // a different lowestDelta
11700     // Ex: trackpad = 3 and mouse wheel = 120
11701     if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
11702     nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
11703
11704     fn.call($elm[0], newEvent);
11705   }
11706
11707   function nullLowestDelta() {
11708     lowestDelta = null;
11709   }
11710
11711   function shouldAdjustOldDeltas(orgEvent, absDelta) {
11712     // If this is an older event and the delta is divisable by 120,
11713     // then we are assuming that the browser is treating this as an
11714     // older mouse wheel event and that we should divide the deltas
11715     // by 40 to try and get a more usable deltaFactor.
11716     // Side note, this actually impacts the reported scroll distance
11717     // in older browsers and can cause scrolling to be slower than native.
11718     // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
11719     return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
11720   }
11721
11722   return s;
11723 }]);
11724
11725 // Add 'px' to the end of a number string if it doesn't have it already
11726 module.filter('px', function() {
11727   return function(str) {
11728     if (str.match(/^[\d\.]+$/)) {
11729       return str + 'px';
11730     }
11731     else {
11732       return str;
11733     }
11734   };
11735 });
11736
11737 })();
11738
11739 (function () {
11740   angular.module('ui.grid').config(['$provide', function($provide) {
11741     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11742       var lang = {
11743               aggregate: {
11744                   label: 'položky'
11745               },
11746               groupPanel: {
11747                   description: 'Přesuňte záhlaví zde pro vytvoření skupiny dle sloupce.'
11748               },
11749               search: {
11750                   placeholder: 'Hledat...',
11751                   showingItems: 'Zobrazuji položky:',
11752                   selectedItems: 'Vybrané položky:',
11753                   totalItems: 'Celkem položek:',
11754                   size: 'Velikost strany:',
11755                   first: 'První strana',
11756                   next: 'Další strana',
11757                   previous: 'Předchozí strana',
11758                   last: 'Poslední strana'
11759               },
11760               menu: {
11761                   text: 'Vyberte sloupec:'
11762               },
11763               sort: {
11764                   ascending: 'Seřadit od A-Z',
11765                   descending: 'Seřadit od Z-A',
11766                   remove: 'Odebrat seřazení'
11767               },
11768               column: {
11769                   hide: 'Schovat sloupec'
11770               },
11771               aggregation: {
11772                   count: 'celkem řádků: ',
11773                   sum: 'celkem: ',
11774                   avg: 'avg: ',
11775                   min: 'min.: ',
11776                   max: 'max.: '
11777               },
11778               pinning: {
11779                   pinLeft: 'Zamknout vlevo',
11780                   pinRight: 'Zamknout vpravo',
11781                   unpin: 'Odemknout'
11782               },
11783               gridMenu: {
11784                   columns: 'Sloupce:',
11785                   importerTitle: 'Importovat soubor',
11786                   exporterAllAsCsv: 'Exportovat všechna data do csv',
11787                   exporterVisibleAsCsv: 'Exportovat viditelná data do csv',
11788                   exporterSelectedAsCsv: 'Exportovat vybraná data do csv',
11789                   exporterAllAsPdf: 'Exportovat všechna data do pdf',
11790                   exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
11791                   exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
11792                   clearAllFilters: 'Odstranit všechny filtry'
11793               },
11794               importer: {
11795                   noHeaders: 'Názvy sloupců se nepodařilo získat, obsahuje soubor záhlaví?',
11796                   noObjects: 'Data se nepodařilo zpracovat, obsahuje soubor řádky mimo záhlaví?',
11797                   invalidCsv: 'Soubor nelze zpracovat, jedná se o CSV?',
11798                   invalidJson: 'Soubor nelze zpracovat, je to JSON?',
11799                   jsonNotArray: 'Soubor musí obsahovat json. Ukončuji..'
11800               },
11801               pagination: {
11802                   sizes: 'položek na stránku',
11803                   totalItems: 'položek'
11804               },
11805               grouping: {
11806                   group: 'Seskupit',
11807                   ungroup: 'Odebrat seskupení',
11808                   aggregate_count: 'Agregace: Count',
11809                   aggregate_sum: 'Agregace: Sum',
11810                   aggregate_max: 'Agregace: Max',
11811                   aggregate_min: 'Agregace: Min',
11812                   aggregate_avg: 'Agregace: Avg',
11813                   aggregate_remove: 'Agregace: Odebrat'
11814               }
11815           };
11816
11817           // support varianty of different czech keys.
11818           $delegate.add('cs', lang);
11819           $delegate.add('cz', lang);
11820           $delegate.add('cs-cz', lang);
11821           $delegate.add('cs-CZ', lang);
11822       return $delegate;
11823     }]);
11824   }]);
11825 })();
11826
11827 (function(){
11828   angular.module('ui.grid').config(['$provide', function($provide) {
11829     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11830       $delegate.add('da', {
11831         aggregate:{
11832           label: 'artikler'
11833         },
11834         groupPanel:{
11835           description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
11836         },
11837         search:{
11838           placeholder: 'Søg...',
11839           showingItems: 'Viste rækker:',
11840           selectedItems: 'Valgte rækker:',
11841           totalItems: 'Rækker totalt:',
11842           size: 'Side størrelse:',
11843           first: 'Første side',
11844           next: 'Næste side',
11845           previous: 'Forrige side',
11846           last: 'Sidste side'
11847         },
11848         menu:{
11849           text: 'Vælg kolonner:'
11850         },
11851         sort: {
11852           ascending: 'Sorter stigende',
11853           descending: 'Sorter faldende',
11854           none: 'Sorter ingen',
11855           remove: 'Fjern sortering'
11856         },
11857         column: {
11858           hide: 'Skjul kolonne'
11859         },
11860         aggregation: {
11861           count: 'antal rækker: ',
11862           sum: 'sum: ',
11863           avg: 'gns: ',
11864           min: 'min: ',
11865           max: 'max: '
11866         },
11867         gridMenu: {
11868           columns: 'Columns:',
11869           importerTitle: 'Import file',
11870           exporterAllAsCsv: 'Export all data as csv',
11871           exporterVisibleAsCsv: 'Export visible data as csv',
11872           exporterSelectedAsCsv: 'Export selected data as csv',
11873           exporterAllAsPdf: 'Export all data as pdf',
11874           exporterVisibleAsPdf: 'Export visible data as pdf',
11875           exporterSelectedAsPdf: 'Export selected data as pdf',
11876           clearAllFilters: 'Clear all filters'
11877         },
11878         importer: {
11879           noHeaders: 'Column names were unable to be derived, does the file have a header?',
11880           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
11881           invalidCsv: 'File was unable to be processed, is it valid CSV?',
11882           invalidJson: 'File was unable to be processed, is it valid Json?',
11883           jsonNotArray: 'Imported json file must contain an array, aborting.'
11884         }
11885       });
11886       return $delegate;
11887     }]);
11888   }]);
11889 })();
11890
11891 (function () {
11892   angular.module('ui.grid').config(['$provide', function ($provide) {
11893     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
11894       $delegate.add('de', {
11895         headerCell: {
11896           aria: {
11897             defaultFilterLabel: 'Filter für Spalte',
11898             removeFilter: 'Filter löschen',
11899             columnMenuButtonLabel: 'Spaltenmenü'
11900           },
11901           priority: 'Priorität:',
11902           filterLabel: "Filter für Spalte: "
11903         },
11904         aggregate: {
11905           label: 'Eintrag'
11906         },
11907         groupPanel: {
11908           description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
11909         },
11910         search: {
11911           placeholder: 'Suche...',
11912           showingItems: 'Zeige Einträge:',
11913           selectedItems: 'Ausgewählte Einträge:',
11914           totalItems: 'Einträge gesamt:',
11915           size: 'Einträge pro Seite:',
11916           first: 'Erste Seite',
11917           next: 'Nächste Seite',
11918           previous: 'Vorherige Seite',
11919           last: 'Letzte Seite'
11920         },
11921         menu: {
11922           text: 'Spalten auswählen:'
11923         },
11924         sort: {
11925           ascending: 'aufsteigend sortieren',
11926           descending: 'absteigend sortieren',
11927           none: 'keine Sortierung',
11928           remove: 'Sortierung zurücksetzen'
11929         },
11930         column: {
11931           hide: 'Spalte ausblenden'
11932         },
11933         aggregation: {
11934           count: 'Zeilen insgesamt: ',
11935           sum: 'gesamt: ',
11936           avg: 'Durchschnitt: ',
11937           min: 'min: ',
11938           max: 'max: '
11939         },
11940         pinning: {
11941             pinLeft: 'Links anheften',
11942             pinRight: 'Rechts anheften',
11943             unpin: 'Lösen'
11944         },
11945         columnMenu: {
11946           close: 'Schließen'
11947         },
11948         gridMenu: {
11949           aria: {
11950             buttonLabel: 'Tabellenmenü'
11951           },
11952           columns: 'Spalten:',
11953           importerTitle: 'Datei importieren',
11954           exporterAllAsCsv: 'Alle Daten als CSV exportieren',
11955           exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
11956           exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
11957           exporterAllAsPdf: 'Alle Daten als PDF exportieren',
11958           exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
11959           exporterSelectedAsPdf: 'markierte Daten als CSV exportieren',
11960           clearAllFilters: 'Alle Filter zurücksetzen'
11961         },
11962         importer: {
11963           noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
11964           noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
11965           invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
11966           invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
11967           jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
11968         },
11969         pagination: {
11970           aria: {
11971             pageToFirst: 'Zum Anfang',
11972             pageBack: 'Seite zurück',
11973             pageSelected: 'Ausgwählte Seite',
11974             pageForward: 'Seite vor',
11975             pageToLast: 'Zum Ende'
11976           },
11977           sizes: 'Einträge pro Seite',
11978           totalItems: 'Einträge',
11979           through: 'bis',
11980           of: 'von'
11981         },
11982         grouping: {
11983             group: 'Gruppieren',
11984             ungroup: 'Gruppierung aufheben',
11985             aggregate_count: 'Agg: Anzahl',
11986             aggregate_sum: 'Agg: Summe',
11987             aggregate_max: 'Agg: Maximum',
11988             aggregate_min: 'Agg: Minimum',
11989             aggregate_avg: 'Agg: Mittelwert',
11990             aggregate_remove: 'Aggregation entfernen'
11991         }
11992       });
11993       return $delegate;
11994     }]);
11995   }]);
11996 })();
11997
11998 (function () {
11999   angular.module('ui.grid').config(['$provide', function($provide) {
12000     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12001       $delegate.add('en', {
12002         headerCell: {
12003           aria: {
12004             defaultFilterLabel: 'Filter for column',
12005             removeFilter: 'Remove Filter',
12006             columnMenuButtonLabel: 'Column Menu'
12007           },
12008           priority: 'Priority:',
12009           filterLabel: "Filter for column: "
12010         },
12011         aggregate: {
12012           label: 'items'
12013         },
12014         groupPanel: {
12015           description: 'Drag a column header here and drop it to group by that column.'
12016         },
12017         search: {
12018           placeholder: 'Search...',
12019           showingItems: 'Showing Items:',
12020           selectedItems: 'Selected Items:',
12021           totalItems: 'Total Items:',
12022           size: 'Page Size:',
12023           first: 'First Page',
12024           next: 'Next Page',
12025           previous: 'Previous Page',
12026           last: 'Last Page'
12027         },
12028         menu: {
12029           text: 'Choose Columns:'
12030         },
12031         sort: {
12032           ascending: 'Sort Ascending',
12033           descending: 'Sort Descending',
12034           none: 'Sort None',
12035           remove: 'Remove Sort'
12036         },
12037         column: {
12038           hide: 'Hide Column'
12039         },
12040         aggregation: {
12041           count: 'total rows: ',
12042           sum: 'total: ',
12043           avg: 'avg: ',
12044           min: 'min: ',
12045           max: 'max: '
12046         },
12047         pinning: {
12048           pinLeft: 'Pin Left',
12049           pinRight: 'Pin Right',
12050           unpin: 'Unpin'
12051         },
12052         columnMenu: {
12053           close: 'Close'
12054         },
12055         gridMenu: {
12056           aria: {
12057             buttonLabel: 'Grid Menu'
12058           },
12059           columns: 'Columns:',
12060           importerTitle: 'Import file',
12061           exporterAllAsCsv: 'Export all data as csv',
12062           exporterVisibleAsCsv: 'Export visible data as csv',
12063           exporterSelectedAsCsv: 'Export selected data as csv',
12064           exporterAllAsPdf: 'Export all data as pdf',
12065           exporterVisibleAsPdf: 'Export visible data as pdf',
12066           exporterSelectedAsPdf: 'Export selected data as pdf',
12067           clearAllFilters: 'Clear all filters'
12068         },
12069         importer: {
12070           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12071           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12072           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12073           invalidJson: 'File was unable to be processed, is it valid Json?',
12074           jsonNotArray: 'Imported json file must contain an array, aborting.'
12075         },
12076         pagination: {
12077           aria: {
12078             pageToFirst: 'Page to first',
12079             pageBack: 'Page back',
12080             pageSelected: 'Selected page',
12081             pageForward: 'Page forward',
12082             pageToLast: 'Page to last'
12083           },
12084           sizes: 'items per page',
12085           totalItems: 'items',
12086           through: 'through',
12087           of: 'of'
12088         },
12089         grouping: {
12090           group: 'Group',
12091           ungroup: 'Ungroup',
12092           aggregate_count: 'Agg: Count',
12093           aggregate_sum: 'Agg: Sum',
12094           aggregate_max: 'Agg: Max',
12095           aggregate_min: 'Agg: Min',
12096           aggregate_avg: 'Agg: Avg',
12097           aggregate_remove: 'Agg: Remove'
12098         },
12099         validate: {
12100           error: 'Error:',
12101           minLength: 'Value should be at least THRESHOLD characters long.',
12102           maxLength: 'Value should be at most THRESHOLD characters long.',
12103           required: 'A value is needed.'
12104         }
12105       });
12106       return $delegate;
12107     }]);
12108   }]);
12109 })();
12110
12111 (function () {
12112   angular.module('ui.grid').config(['$provide', function($provide) {
12113     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12114       $delegate.add('es', {
12115         aggregate: {
12116           label: 'Artículos'
12117         },
12118         groupPanel: {
12119           description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
12120         },
12121         search: {
12122           placeholder: 'Buscar...',
12123           showingItems: 'Artículos Mostrados:',
12124           selectedItems: 'Artículos Seleccionados:',
12125           totalItems: 'Artículos Totales:',
12126           size: 'Tamaño de Página:',
12127           first: 'Primera Página',
12128           next: 'Página Siguiente',
12129           previous: 'Página Anterior',
12130           last: 'Última Página'
12131         },
12132         menu: {
12133           text: 'Elegir columnas:'
12134         },
12135         sort: {
12136           ascending: 'Orden Ascendente',
12137           descending: 'Orden Descendente',
12138           remove: 'Sin Ordenar'
12139         },
12140         column: {
12141           hide: 'Ocultar la columna'
12142         },
12143         aggregation: {
12144           count: 'filas totales: ',
12145           sum: 'total: ',
12146           avg: 'media: ',
12147           min: 'min: ',
12148           max: 'max: '
12149         },
12150         pinning: {
12151           pinLeft: 'Fijar a la Izquierda',
12152           pinRight: 'Fijar a la Derecha',
12153           unpin: 'Quitar Fijación'
12154         },
12155         gridMenu: {
12156           columns: 'Columnas:',
12157           importerTitle: 'Importar archivo',
12158           exporterAllAsCsv: 'Exportar todo como csv',
12159           exporterVisibleAsCsv: 'Exportar vista como csv',
12160           exporterSelectedAsCsv: 'Exportar selección como csv',
12161           exporterAllAsPdf: 'Exportar todo como pdf',
12162           exporterVisibleAsPdf: 'Exportar vista como pdf',
12163           exporterSelectedAsPdf: 'Exportar selección como pdf',
12164           clearAllFilters: 'Limpiar todos los filtros'
12165         },
12166         importer: {
12167           noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
12168           noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
12169           invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
12170           invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
12171           jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
12172         },
12173         pagination: {
12174           sizes: 'registros por página',
12175           totalItems: 'registros',
12176           of: 'de'
12177         },
12178         grouping: {
12179           group: 'Agrupar',
12180           ungroup: 'Desagrupar',
12181           aggregate_count: 'Agr: Cont',
12182           aggregate_sum: 'Agr: Sum',
12183           aggregate_max: 'Agr: Máx',
12184           aggregate_min: 'Agr: Min',
12185           aggregate_avg: 'Agr: Prom',
12186           aggregate_remove: 'Agr: Quitar'
12187         }
12188       });
12189       return $delegate;
12190     }]);
12191 }]);
12192 })();
12193
12194 /**
12195  * Translated by: R. Salarmehr
12196  *                M. Hosseynzade
12197  *                Using Vajje.com online dictionary.
12198  */
12199 (function () {
12200   angular.module('ui.grid').config(['$provide', function ($provide) {
12201     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12202       $delegate.add('fa', {
12203         aggregate: {
12204           label: 'قلم'
12205         },
12206         groupPanel: {
12207           description: 'عنوان یک ستون را بگیر و به گروهی از آن ستون رها کن.'
12208         },
12209         search: {
12210           placeholder: 'جستجو...',
12211           showingItems: 'نمایش اقلام:',
12212           selectedItems: 'قلم\u200cهای انتخاب شده:',
12213           totalItems: 'مجموع اقلام:',
12214           size: 'اندازه\u200cی صفحه:',
12215           first: 'اولین صفحه',
12216           next: 'صفحه\u200cی\u200cبعدی',
12217           previous: 'صفحه\u200cی\u200c قبلی',
12218           last: 'آخرین صفحه'
12219         },
12220         menu: {
12221           text: 'ستون\u200cهای انتخابی:'
12222         },
12223         sort: {
12224           ascending: 'ترتیب صعودی',
12225           descending: 'ترتیب نزولی',
12226           remove: 'حذف مرتب کردن'
12227         },
12228         column: {
12229           hide: 'پنهان\u200cکردن ستون'
12230         },
12231         aggregation: {
12232           count: 'تعداد: ',
12233           sum: 'مجموع: ',
12234           avg: 'میانگین: ',
12235           min: 'کمترین: ',
12236           max: 'بیشترین: '
12237         },
12238         pinning: {
12239           pinLeft: 'پین کردن سمت چپ',
12240           pinRight: 'پین کردن سمت راست',
12241           unpin: 'حذف پین'
12242         },
12243         gridMenu: {
12244           columns: 'ستون\u200cها:',
12245           importerTitle: 'وارد کردن فایل',
12246           exporterAllAsCsv: 'خروجی تمام داده\u200cها در فایل csv',
12247           exporterVisibleAsCsv: 'خروجی داده\u200cهای قابل مشاهده در فایل csv',
12248           exporterSelectedAsCsv: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل csv',
12249           exporterAllAsPdf: 'خروجی تمام داده\u200cها در فایل pdf',
12250           exporterVisibleAsPdf: 'خروجی داده\u200cهای قابل مشاهده در فایل pdf',
12251           exporterSelectedAsPdf: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل pdf',
12252           clearAllFilters: 'پاک کردن تمام فیلتر'
12253         },
12254         importer: {
12255           noHeaders: 'نام ستون قابل استخراج نیست. آیا فایل عنوان دارد؟',
12256           noObjects: 'اشیا قابل استخراج نیستند. آیا به جز عنوان\u200cها در فایل داده وجود دارد؟',
12257           invalidCsv: 'فایل قابل پردازش نیست. آیا فرمت  csv  معتبر است؟',
12258           invalidJson: 'فایل قابل پردازش نیست. آیا فرمت json   معتبر است؟',
12259           jsonNotArray: 'فایل json وارد شده باید حاوی آرایه باشد. عملیات ساقط شد.'
12260         },
12261         pagination: {
12262           sizes: 'اقلام در هر صفحه',
12263           totalItems: 'اقلام',
12264           of: 'از'
12265         },
12266         grouping: {
12267           group: 'گروه\u200cبندی',
12268           ungroup: 'حذف گروه\u200cبندی',
12269           aggregate_count: 'Agg: تعداد',
12270           aggregate_sum: 'Agg: جمع',
12271           aggregate_max: 'Agg: بیشینه',
12272           aggregate_min: 'Agg: کمینه',
12273           aggregate_avg: 'Agg: میانگین',
12274           aggregate_remove: 'Agg: حذف'
12275         }
12276       });
12277       return $delegate;
12278     }]);
12279   }]);
12280 })();
12281
12282 (function () {
12283   angular.module('ui.grid').config(['$provide', function($provide) {
12284     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12285       $delegate.add('fi', {
12286         aggregate: {
12287           label: 'rivit'
12288         },
12289         groupPanel: {
12290           description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
12291         },
12292         search: {
12293           placeholder: 'Hae...',
12294           showingItems: 'Näytetään rivejä:',
12295           selectedItems: 'Valitut rivit:',
12296           totalItems: 'Rivejä yht.:',
12297           size: 'Näytä:',
12298           first: 'Ensimmäinen sivu',
12299           next: 'Seuraava sivu',
12300           previous: 'Edellinen sivu',
12301           last: 'Viimeinen sivu'
12302         },
12303         menu: {
12304           text: 'Valitse sarakkeet:'
12305         },
12306         sort: {
12307           ascending: 'Järjestä nouseva',
12308           descending: 'Järjestä laskeva',
12309           remove: 'Poista järjestys'
12310         },
12311         column: {
12312           hide: 'Piilota sarake'
12313         },
12314         aggregation: {
12315           count: 'Rivejä yht.: ',
12316           sum: 'Summa: ',
12317           avg: 'K.a.: ',
12318           min: 'Min: ',
12319           max: 'Max: '
12320         },
12321         pinning: {
12322          pinLeft: 'Lukitse vasemmalle',
12323           pinRight: 'Lukitse oikealle',
12324           unpin: 'Poista lukitus'
12325         },
12326         gridMenu: {
12327           columns: 'Sarakkeet:',
12328           importerTitle: 'Tuo tiedosto',
12329           exporterAllAsCsv: 'Vie tiedot csv-muodossa',
12330           exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
12331           exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
12332           exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
12333           exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
12334           exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12335           clearAllFilters: 'Puhdista kaikki suodattimet'
12336         },
12337         importer: {
12338           noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
12339           noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
12340           invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
12341           invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
12342           jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
12343         }
12344       });
12345       return $delegate;
12346     }]);
12347   }]);
12348 })();
12349
12350 (function () {
12351   angular.module('ui.grid').config(['$provide', function($provide) {
12352     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12353       $delegate.add('fr', {
12354         aggregate: {
12355           label: 'éléments'
12356         },
12357         groupPanel: {
12358           description: 'Faites glisser une en-tête de colonne ici pour créer un groupe de colonnes.'
12359         },
12360         search: {
12361           placeholder: 'Recherche...',
12362           showingItems: 'Affichage des éléments :',
12363           selectedItems: 'Éléments sélectionnés :',
12364           totalItems: 'Nombre total d\'éléments:',
12365           size: 'Taille de page:',
12366           first: 'Première page',
12367           next: 'Page Suivante',
12368           previous: 'Page précédente',
12369           last: 'Dernière page'
12370         },
12371         menu: {
12372           text: 'Choisir des colonnes :'
12373         },
12374         sort: {
12375           ascending: 'Trier par ordre croissant',
12376           descending: 'Trier par ordre décroissant',
12377           remove: 'Enlever le tri'
12378         },
12379         column: {
12380           hide: 'Cacher la colonne'
12381         },
12382         aggregation: {
12383           count: 'lignes totales: ',
12384           sum: 'total: ',
12385           avg: 'moy: ',
12386           min: 'min: ',
12387           max: 'max: '
12388         },
12389         pinning: {
12390           pinLeft: 'Épingler à gauche',
12391           pinRight: 'Épingler à droite',
12392           unpin: 'Détacher'
12393         },
12394         gridMenu: {
12395           columns: 'Colonnes:',
12396           importerTitle: 'Importer un fichier',
12397           exporterAllAsCsv: 'Exporter toutes les données en CSV',
12398           exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
12399           exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
12400           exporterAllAsPdf: 'Exporter toutes les données en PDF',
12401           exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
12402           exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
12403           clearAllFilters: 'Nettoyez tous les filtres'
12404         },
12405         importer: {
12406           noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il une en-tête ?',
12407           noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
12408           invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
12409           invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
12410           jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
12411         },
12412         pagination: {
12413           sizes: 'éléments par page',
12414           totalItems: 'éléments',
12415           of: 'sur'
12416         },
12417         grouping: {
12418           group: 'Grouper',
12419           ungroup: 'Dégrouper',
12420           aggregate_count: 'Agg: Compte',
12421           aggregate_sum: 'Agg: Somme',
12422           aggregate_max: 'Agg: Max',
12423           aggregate_min: 'Agg: Min',
12424           aggregate_avg: 'Agg: Moy',
12425           aggregate_remove: 'Agg: Retirer'
12426         }
12427       });
12428       return $delegate;
12429     }]);
12430   }]);
12431 })();
12432
12433 (function () {
12434   angular.module('ui.grid').config(['$provide', function ($provide) {
12435     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12436       $delegate.add('he', {
12437         aggregate: {
12438           label: 'items'
12439         },
12440         groupPanel: {
12441           description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
12442         },
12443         search: {
12444           placeholder: 'חפש...',
12445           showingItems: 'מציג:',
12446           selectedItems: 'סה"כ נבחרו:',
12447           totalItems: 'סה"כ רשומות:',
12448           size: 'תוצאות בדף:',
12449           first: 'דף ראשון',
12450           next: 'דף הבא',
12451           previous: 'דף קודם',
12452           last: 'דף אחרון'
12453         },
12454         menu: {
12455           text: 'בחר עמודות:'
12456         },
12457         sort: {
12458           ascending: 'סדר עולה',
12459           descending: 'סדר יורד',
12460           remove: 'בטל'
12461         },
12462         column: {
12463           hide: 'טור הסתר'
12464         },
12465         aggregation: {
12466           count: 'total rows: ',
12467           sum: 'total: ',
12468           avg: 'avg: ',
12469           min: 'min: ',
12470           max: 'max: '
12471         },
12472         gridMenu: {
12473           columns: 'Columns:',
12474           importerTitle: 'Import file',
12475           exporterAllAsCsv: 'Export all data as csv',
12476           exporterVisibleAsCsv: 'Export visible data as csv',
12477           exporterSelectedAsCsv: 'Export selected data as csv',
12478           exporterAllAsPdf: 'Export all data as pdf',
12479           exporterVisibleAsPdf: 'Export visible data as pdf',
12480           exporterSelectedAsPdf: 'Export selected data as pdf',
12481           clearAllFilters: 'Clean all filters'
12482         },
12483         importer: {
12484           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12485           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12486           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12487           invalidJson: 'File was unable to be processed, is it valid Json?',
12488           jsonNotArray: 'Imported json file must contain an array, aborting.'
12489         }
12490       });
12491       return $delegate;
12492     }]);
12493   }]);
12494 })();
12495
12496 (function () {
12497   angular.module('ui.grid').config(['$provide', function($provide) {
12498     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12499       $delegate.add('hy', {
12500         aggregate: {
12501           label: 'տվյալներ'
12502         },
12503         groupPanel: {
12504           description: 'Ըստ սյան խմբավորելու համար քաշեք և գցեք վերնագիրն այստեղ։'
12505         },
12506         search: {
12507           placeholder: 'Փնտրում...',
12508           showingItems: 'Ցուցադրված տվյալներ՝',
12509           selectedItems: 'Ընտրված:',
12510           totalItems: 'Ընդամենը՝',
12511           size: 'Տողերի քանակը էջում՝',
12512           first: 'Առաջին էջ',
12513           next: 'Հաջորդ էջ',
12514           previous: 'Նախորդ էջ',
12515           last: 'Վերջին էջ'
12516         },
12517         menu: {
12518           text: 'Ընտրել սյուները:'
12519         },
12520         sort: {
12521           ascending: 'Աճման կարգով',
12522           descending: 'Նվազման կարգով',
12523           remove: 'Հանել '
12524         },
12525         column: {
12526           hide: 'Թաքցնել սյունը'
12527         },
12528         aggregation: {
12529           count: 'ընդամենը տող՝ ',
12530           sum: 'ընդամենը՝ ',
12531           avg: 'միջին՝ ',
12532           min: 'մին՝ ',
12533           max: 'մաքս՝ '
12534         },
12535         pinning: {
12536           pinLeft: 'Կպցնել ձախ կողմում',
12537           pinRight: 'Կպցնել աջ կողմում',
12538           unpin: 'Արձակել'
12539         },
12540         gridMenu: {
12541           columns: 'Սյուներ:',
12542           importerTitle: 'Ներմուծել ֆայլ',
12543           exporterAllAsCsv: 'Արտահանել ամբողջը CSV',
12544           exporterVisibleAsCsv: 'Արտահանել երևացող տվյալները CSV',
12545           exporterSelectedAsCsv: 'Արտահանել ընտրված տվյալները CSV',
12546           exporterAllAsPdf: 'Արտահանել PDF',
12547           exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
12548           exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
12549           clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
12550         },
12551         importer: {
12552           noHeaders: 'Հնարավոր չեղավ որոշել սյան վերնագրերը։ Արդյո՞ք ֆայլը ունի վերնագրեր։',
12553           noObjects: 'Հնարավոր չեղավ կարդալ տվյալները։ Արդյո՞ք ֆայլում կան տվյալներ։',
12554           invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
12555           invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
12556           jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
12557         }
12558       });
12559       return $delegate;
12560     }]);
12561   }]);
12562 })();
12563
12564 (function () {
12565   angular.module('ui.grid').config(['$provide', function($provide) {
12566     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12567       $delegate.add('it', {
12568         aggregate: {
12569           label: 'elementi'
12570         },
12571         groupPanel: {
12572           description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
12573         },
12574         search: {
12575           placeholder: 'Ricerca...',
12576           showingItems: 'Mostra:',
12577           selectedItems: 'Selezionati:',
12578           totalItems: 'Totali:',
12579           size: 'Tot Pagine:',
12580           first: 'Prima',
12581           next: 'Prossima',
12582           previous: 'Precedente',
12583           last: 'Ultima'
12584         },
12585         menu: {
12586           text: 'Scegli le colonne:'
12587         },
12588         sort: {
12589           ascending: 'Asc.',
12590           descending: 'Desc.',
12591           remove: 'Annulla ordinamento'
12592         },
12593         column: {
12594           hide: 'Nascondi'
12595         },
12596         aggregation: {
12597           count: 'righe totali: ',
12598           sum: 'tot: ',
12599           avg: 'media: ',
12600           min: 'minimo: ',
12601           max: 'massimo: '
12602         },
12603         pinning: {
12604          pinLeft: 'Blocca a sx',
12605           pinRight: 'Blocca a dx',
12606           unpin: 'Blocca in alto'
12607         },
12608         gridMenu: {
12609           columns: 'Colonne:',
12610           importerTitle: 'Importa',
12611           exporterAllAsCsv: 'Esporta tutti i dati in CSV',
12612           exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
12613           exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
12614           exporterAllAsPdf: 'Esporta tutti i dati in PDF',
12615           exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
12616           exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
12617           clearAllFilters: 'Pulire tutti i filtri'
12618         },
12619         importer: {
12620           noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
12621           noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
12622           invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
12623           invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
12624           jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
12625         },
12626         grouping: {
12627           group: 'Raggruppa',
12628           ungroup: 'Separa',
12629           aggregate_count: 'Agg: N. Elem.',
12630           aggregate_sum: 'Agg: Somma',
12631           aggregate_max: 'Agg: Massimo',
12632           aggregate_min: 'Agg: Minimo',
12633           aggregate_avg: 'Agg: Media',
12634           aggregate_remove: 'Agg: Rimuovi'
12635         },
12636         validate: {
12637           error: 'Errore:',
12638           minLength: 'Lunghezza minima pari a THRESHOLD caratteri.',
12639           maxLength: 'Lunghezza massima pari a THRESHOLD caratteri.',
12640           required: 'Necessario inserire un valore.'
12641         }
12642       });
12643       return $delegate;
12644     }]);
12645   }]);
12646 })();
12647
12648 (function() {
12649   angular.module('ui.grid').config(['$provide', function($provide) {
12650     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12651       $delegate.add('ja', {
12652         aggregate: {
12653           label: '項目'
12654         },
12655         groupPanel: {
12656           description: 'ここに列ヘッダをドラッグアンドドロップして、その列でグループ化します。'
12657         },
12658         search: {
12659           placeholder: '検索...',
12660           showingItems: '表示中の項目:',
12661           selectedItems: '選択した項目:',
12662           totalItems: '項目の総数:',
12663           size: 'ページサイズ:',
12664           first: '最初のページ',
12665           next: '次のページ',
12666           previous: '前のページ',
12667           last: '前のページ'
12668         },
12669         menu: {
12670           text: '列の選択:'
12671         },
12672         sort: {
12673           ascending: '昇順に並べ替え',
12674           descending: '降順に並べ替え',
12675           remove: '並べ替えの解除'
12676         },
12677         column: {
12678           hide: '列の非表示'
12679         },
12680         aggregation: {
12681           count: '合計行数: ',
12682           sum: '合計: ',
12683           avg: '平均: ',
12684           min: '最小: ',
12685           max: '最大: '
12686         },
12687         pinning: {
12688           pinLeft: '左に固定',
12689           pinRight: '右に固定',
12690           unpin: '固定解除'
12691         },
12692         gridMenu: {
12693           columns: '列:',
12694           importerTitle: 'ファイルのインポート',
12695           exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
12696           exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
12697           exporterSelectedAsCsv: '選択したデータをCSV形式でエクスポート',
12698           exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
12699           exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
12700           exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12701           clearAllFilters: 'すべてのフィルタを清掃してください'
12702         },
12703         importer: {
12704           noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
12705           noObjects: 'オブジェクトを取得できません。ファイルにヘッダ以外のデータが含まれていることを確認してください。',
12706           invalidCsv: 'ファイルを処理できません。ファイルが有効なCSV形式であることを確認してください。',
12707           invalidJson: 'ファイルを処理できません。ファイルが有効なJSON形式であることを確認してください。',
12708           jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
12709         },
12710         pagination: {
12711           sizes: '項目/ページ',
12712           totalItems: '項目'
12713         }
12714       });
12715       return $delegate;
12716     }]);
12717   }]);
12718 })();
12719
12720 (function () {
12721   angular.module('ui.grid').config(['$provide', function($provide) {
12722     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12723       $delegate.add('ko', {
12724         aggregate: {
12725           label: '아이템'
12726         },
12727         groupPanel: {
12728           description: '컬럼으로 그룹핑하기 위해서는 컬럼 헤더를 끌어 떨어뜨려 주세요.'
12729         },
12730         search: {
12731           placeholder: '검색...',
12732           showingItems: '항목 보여주기:',
12733           selectedItems: '선택 항목:',
12734           totalItems: '전체 항목:',
12735           size: '페이지 크기:',
12736           first: '첫번째 페이지',
12737           next: '다음 페이지',
12738           previous: '이전 페이지',
12739           last: '마지막 페이지'
12740         },
12741         menu: {
12742           text: '컬럼을 선택하세요:'
12743         },
12744         sort: {
12745           ascending: '오름차순 정렬',
12746           descending: '내림차순 정렬',
12747           remove: '소팅 제거'
12748         },
12749         column: {
12750           hide: '컬럼 제거'
12751         },
12752         aggregation: {
12753           count: '전체 갯수: ',
12754           sum: '전체: ',
12755           avg: '평균: ',
12756           min: '최소: ',
12757           max: '최대: '
12758         },
12759         pinning: {
12760          pinLeft: '왼쪽 핀',
12761           pinRight: '오른쪽 핀',
12762           unpin: '핀 제거'
12763         },
12764         gridMenu: {
12765           columns: '컬럼:',
12766           importerTitle: '파일 가져오기',
12767           exporterAllAsCsv: 'csv로 모든 데이터 내보내기',
12768           exporterVisibleAsCsv: 'csv로 보이는 데이터 내보내기',
12769           exporterSelectedAsCsv: 'csv로 선택된 데이터 내보내기',
12770           exporterAllAsPdf: 'pdf로 모든 데이터 내보내기',
12771           exporterVisibleAsPdf: 'pdf로 보이는 데이터 내보내기',
12772           exporterSelectedAsPdf: 'pdf로 선택 데이터 내보내기',
12773           clearAllFilters: '모든 필터를 청소'
12774         },
12775         importer: {
12776           noHeaders: '컬럼명이 지정되어 있지 않습니다. 파일에 헤더가 명시되어 있는지 확인해 주세요.',
12777           noObjects: '데이터가 지정되어 있지 않습니다. 데이터가 파일에 있는지 확인해 주세요.',
12778           invalidCsv: '파일을 처리할 수 없습니다. 올바른 csv인지 확인해 주세요.',
12779           invalidJson: '파일을 처리할 수 없습니다. 올바른 json인지 확인해 주세요.',
12780           jsonNotArray: 'json 파일은 배열을 포함해야 합니다.'
12781         },
12782         pagination: {
12783           sizes: '페이지당 항목',
12784           totalItems: '전체 항목'
12785         }
12786       });
12787       return $delegate;
12788     }]);
12789   }]);
12790 })();
12791
12792 (function () {
12793   angular.module('ui.grid').config(['$provide', function($provide) {
12794     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12795       $delegate.add('nl', {
12796         aggregate: {
12797           label: 'items'
12798         },
12799         groupPanel: {
12800           description: 'Sleep hier een kolomnaam heen om op te groeperen.'
12801         },
12802         search: {
12803           placeholder: 'Zoeken...',
12804           showingItems: 'Getoonde items:',
12805           selectedItems: 'Geselecteerde items:',
12806           totalItems: 'Totaal aantal items:',
12807           size: 'Items per pagina:',
12808           first: 'Eerste pagina',
12809           next: 'Volgende pagina',
12810           previous: 'Vorige pagina',
12811           last: 'Laatste pagina'
12812         },
12813         menu: {
12814           text: 'Kies kolommen:'
12815         },
12816         sort: {
12817           ascending: 'Sorteer oplopend',
12818           descending: 'Sorteer aflopend',
12819           remove: 'Verwijder sortering'
12820         },
12821         column: {
12822           hide: 'Verberg kolom'
12823         },
12824         aggregation: {
12825           count: 'Aantal rijen: ',
12826           sum: 'Som: ',
12827           avg: 'Gemiddelde: ',
12828           min: 'Min: ',
12829           max: 'Max: '
12830         },
12831         pinning: {
12832           pinLeft: 'Zet links vast',
12833           pinRight: 'Zet rechts vast',
12834           unpin: 'Maak los'
12835         },
12836         gridMenu: {
12837           columns: 'Kolommen:',
12838           importerTitle: 'Importeer bestand',
12839           exporterAllAsCsv: 'Exporteer alle data als csv',
12840           exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
12841           exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
12842           exporterAllAsPdf: 'Exporteer alle data als pdf',
12843           exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
12844           exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
12845           clearAllFilters: 'Reinig alle filters'
12846         },
12847         importer: {
12848           noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
12849           noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
12850           invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
12851           invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
12852           jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
12853         },
12854         pagination: {
12855             sizes: 'items per pagina',
12856             totalItems: 'items',
12857             of: 'van de'
12858         },
12859         grouping: {
12860             group: 'Groepeer',
12861             ungroup: 'Groepering opheffen',
12862             aggregate_count: 'Agg: Aantal',
12863             aggregate_sum: 'Agg: Som',
12864             aggregate_max: 'Agg: Max',
12865             aggregate_min: 'Agg: Min',
12866             aggregate_avg: 'Agg: Gem',
12867             aggregate_remove: 'Agg: Verwijder'
12868         }
12869       });
12870       return $delegate;
12871     }]);
12872   }]);
12873 })();
12874
12875 (function () {
12876   angular.module('ui.grid').config(['$provide', function($provide) {
12877     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12878       $delegate.add('pl', {
12879         headerCell: {
12880           aria: {
12881             defaultFilterLabel: 'Filter dla kolumny',
12882             removeFilter: 'Usuń filter',
12883             columnMenuButtonLabel: 'Menu kolumny'
12884           },
12885           priority: 'Prioritet:',
12886           filterLabel: "Filtr dla kolumny: "
12887         },
12888         aggregate: {
12889           label: 'pozycji'
12890         },
12891         groupPanel: {
12892           description: 'Przeciągnij nagłówek kolumny tutaj, aby pogrupować według niej.'
12893         },
12894         search: {
12895           placeholder: 'Szukaj...',
12896           showingItems: 'Widoczne pozycje:',
12897           selectedItems: 'Zaznaczone pozycje:',
12898           totalItems: 'Wszystkich pozycji:',
12899           size: 'Rozmiar strony:',
12900           first: 'Pierwsza strona',
12901           next: 'Następna strona',
12902           previous: 'Poprzednia strona',
12903           last: 'Ostatnia strona'
12904         },
12905         menu: {
12906           text: 'Wybierz kolumny:'
12907         },
12908         sort: {
12909           ascending: 'Sortuj rosnąco',
12910           descending: 'Sortuj malejąco',
12911           none: 'Brak sortowania',
12912           remove: 'Wyłącz sortowanie'
12913         },
12914         column: {
12915           hide: 'Ukryj kolumne'
12916         },
12917         aggregation: {
12918           count: 'Razem pozycji: ',
12919             sum: 'Razem: ',
12920             avg: 'Średnia: ',
12921             min: 'Min: ',
12922             max: 'Max: '
12923         },
12924         pinning: {
12925           pinLeft: 'Przypnij do lewej',
12926           pinRight: 'Przypnij do prawej',
12927           unpin: 'Odepnij'
12928         },
12929         columnMenu: {
12930           close: 'Zamknij'
12931         },
12932         gridMenu: {
12933           aria: {
12934             buttonLabel: 'Menu Grida'
12935           },
12936           columns: 'Kolumny:',
12937           importerTitle: 'Importuj plik',
12938           exporterAllAsCsv: 'Eksportuj wszystkie dane do csv',
12939           exporterVisibleAsCsv: 'Eksportuj widoczne dane do csv',
12940           exporterSelectedAsCsv: 'Eksportuj zaznaczone dane do csv',
12941           exporterAllAsPdf: 'Eksportuj wszystkie dane do pdf',
12942           exporterVisibleAsPdf: 'Eksportuj widoczne dane do pdf',
12943           exporterSelectedAsPdf: 'Eksportuj zaznaczone dane do pdf',
12944           clearAllFilters: 'Wyczyść filtry'
12945         },
12946         importer: {
12947           noHeaders: 'Nie udało się wczytać nazw kolumn. Czy plik posiada nagłówek?',
12948           noObjects: 'Nie udalo się wczytać pozycji. Czy plik zawiera dane??',
12949           invalidCsv: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik CSV??',
12950           invalidJson: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik Json?',
12951           jsonNotArray: 'Importowany plik json musi zawierać tablicę, importowanie przerwane.'
12952         },
12953         pagination: {
12954           aria: {
12955             pageToFirst: 'Pierwsza strona',
12956             pageBack: 'Poprzednia strona',
12957             pageSelected: 'Wybrana strona',
12958             pageForward: 'Następna strona',
12959             pageToLast: 'Ostatnia strona'
12960           },
12961           sizes: 'pozycji na stronę',
12962           totalItems: 'pozycji',
12963           through: 'do',
12964           of: 'z'
12965         },
12966         grouping: {
12967           group: 'Grupuj',
12968           ungroup: 'Rozgrupuj',
12969           aggregate_count: 'Zbiorczo: Razem',
12970           aggregate_sum: 'Zbiorczo: Suma',
12971           aggregate_max: 'Zbiorczo: Max',
12972           aggregate_min: 'Zbiorczo: Min',
12973           aggregate_avg: 'Zbiorczo: Średnia',
12974           aggregate_remove: 'Zbiorczo: Usuń'
12975         }
12976       });
12977       return $delegate;
12978     }]);
12979   }]);
12980 })();
12981
12982 (function () {
12983   angular.module('ui.grid').config(['$provide', function($provide) {
12984     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12985       $delegate.add('pt-br', {
12986         headerCell: {
12987           aria: {
12988             defaultFilterLabel: 'Filtro por coluna',
12989             removeFilter: 'Remover filtro',
12990             columnMenuButtonLabel: 'Menu coluna'
12991           },
12992           priority: 'Prioridade:',
12993           filterLabel: "Filtro por coluna: "
12994         },
12995         aggregate: {
12996           label: 'itens'
12997         },
12998         groupPanel: {
12999           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13000         },
13001         search: {
13002           placeholder: 'Procurar...',
13003           showingItems: 'Mostrando os Itens:',
13004           selectedItems: 'Items Selecionados:',
13005           totalItems: 'Total de Itens:',
13006           size: 'Tamanho da Página:',
13007           first: 'Primeira Página',
13008           next: 'Próxima Página',
13009           previous: 'Página Anterior',
13010           last: 'Última Página'
13011         },
13012         menu: {
13013           text: 'Selecione as colunas:'
13014         },
13015         sort: {
13016           ascending: 'Ordenar Ascendente',
13017           descending: 'Ordenar Descendente',
13018           none: 'Nenhuma Ordem',
13019           remove: 'Remover Ordenação'
13020         },
13021         column: {
13022           hide: 'Esconder coluna'
13023         },
13024         aggregation: {
13025           count: 'total de linhas: ',
13026           sum: 'total: ',
13027           avg: 'med: ',
13028           min: 'min: ',
13029           max: 'max: '
13030         },
13031         pinning: {
13032           pinLeft: 'Fixar Esquerda',
13033           pinRight: 'Fixar Direita',
13034           unpin: 'Desprender'
13035         },
13036         columnMenu: {
13037           close: 'Fechar'
13038         },
13039         gridMenu: {
13040           aria: {
13041             buttonLabel: 'Menu Grid'
13042           },
13043           columns: 'Colunas:',
13044           importerTitle: 'Importar arquivo',
13045           exporterAllAsCsv: 'Exportar todos os dados como csv',
13046           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13047           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13048           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13049           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13050           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13051           clearAllFilters: 'Limpar todos os filtros'
13052         },
13053         importer: {
13054           noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
13055           noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
13056           invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
13057           invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
13058           jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
13059         },
13060         pagination: {
13061           aria: {
13062             pageToFirst: 'Primeira página',
13063             pageBack: 'Página anterior',
13064             pageSelected: 'Página Selecionada',
13065             pageForward: 'Proxima',
13066             pageToLast: 'Anterior'
13067           },
13068           sizes: 'itens por página',
13069           totalItems: 'itens',
13070           through: 'através dos',
13071           of: 'de'
13072         },
13073         grouping: {
13074           group: 'Agrupar',
13075           ungroup: 'Desagrupar',
13076           aggregate_count: 'Agr: Contar',
13077           aggregate_sum: 'Agr: Soma',
13078           aggregate_max: 'Agr: Max',
13079           aggregate_min: 'Agr: Min',
13080           aggregate_avg: 'Agr: Med',
13081           aggregate_remove: 'Agr: Remover'
13082         }
13083       });
13084       return $delegate;
13085     }]);
13086 }]);
13087 })();
13088
13089 (function () {
13090   angular.module('ui.grid').config(['$provide', function($provide) {
13091     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13092       $delegate.add('pt', {
13093         headerCell: {
13094           aria: {
13095             defaultFilterLabel: 'Filtro por coluna',
13096             removeFilter: 'Remover filtro',
13097             columnMenuButtonLabel: 'Menu coluna'
13098           },
13099           priority: 'Prioridade:',
13100           filterLabel: "Filtro por coluna: "
13101         },
13102         aggregate: {
13103           label: 'itens'
13104         },
13105         groupPanel: {
13106           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13107         },
13108         search: {
13109           placeholder: 'Procurar...',
13110           showingItems: 'Mostrando os Itens:',
13111           selectedItems: 'Itens Selecionados:',
13112           totalItems: 'Total de Itens:',
13113           size: 'Tamanho da Página:',
13114           first: 'Primeira Página',
13115           next: 'Próxima Página',
13116           previous: 'Página Anterior',
13117           last: 'Última Página'
13118         },
13119         menu: {
13120           text: 'Selecione as colunas:'
13121         },
13122         sort: {
13123           ascending: 'Ordenar Ascendente',
13124           descending: 'Ordenar Descendente',
13125           none: 'Nenhuma Ordem',
13126           remove: 'Remover Ordenação'
13127         },
13128         column: {
13129           hide: 'Esconder coluna'
13130         },
13131         aggregation: {
13132           count: 'total de linhas: ',
13133           sum: 'total: ',
13134           avg: 'med: ',
13135           min: 'min: ',
13136           max: 'max: '
13137         },
13138         pinning: {
13139           pinLeft: 'Fixar Esquerda',
13140           pinRight: 'Fixar Direita',
13141           unpin: 'Desprender'
13142         },
13143         columnMenu: {
13144           close: 'Fechar'
13145         },
13146         gridMenu: {
13147           aria: {
13148             buttonLabel: 'Menu Grid'
13149           },
13150           columns: 'Colunas:',
13151           importerTitle: 'Importar ficheiro',
13152           exporterAllAsCsv: 'Exportar todos os dados como csv',
13153           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13154           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13155           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13156           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13157           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13158           clearAllFilters: 'Limpar todos os filtros'
13159         },
13160         importer: {
13161           noHeaders: 'Nomes de colunas não puderam ser derivados. O ficheiro tem um cabeçalho?',
13162           noObjects: 'Objetos não puderam ser derivados. Havia dados no ficheiro, além dos cabeçalhos?',
13163           invalidCsv: 'Ficheiro não pode ser processado. É um CSV válido?',
13164           invalidJson: 'Ficheiro não pode ser processado. É um Json válido?',
13165           jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
13166         },
13167         pagination: {
13168           aria: {
13169             pageToFirst: 'Primeira página',
13170             pageBack: 'Página anterior',
13171             pageSelected: 'Página Selecionada',
13172             pageForward: 'Próxima',
13173             pageToLast: 'Anterior'
13174           },
13175           sizes: 'itens por página',
13176           totalItems: 'itens',
13177           through: 'através dos',
13178           of: 'de'
13179         },
13180         grouping: {
13181           group: 'Agrupar',
13182           ungroup: 'Desagrupar',
13183           aggregate_count: 'Agr: Contar',
13184           aggregate_sum: 'Agr: Soma',
13185           aggregate_max: 'Agr: Max',
13186           aggregate_min: 'Agr: Min',
13187           aggregate_avg: 'Agr: Med',
13188           aggregate_remove: 'Agr: Remover'
13189         }
13190       });
13191       return $delegate;
13192     }]);
13193 }]);
13194 })();
13195
13196 (function () {
13197   angular.module('ui.grid').config(['$provide', function($provide) {
13198     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13199       $delegate.add('ro', {
13200         headerCell: {
13201           aria: {
13202             defaultFilterLabel: 'Filtru pentru coloana',
13203             removeFilter: 'Sterge filtru',
13204             columnMenuButtonLabel: 'Column Menu'
13205           },
13206           priority: 'Prioritate:',
13207           filterLabel: "Filtru pentru coloana:"
13208         },
13209         aggregate: {
13210           label: 'Elemente'
13211         },
13212         groupPanel: {
13213           description: 'Trage un cap de coloana aici pentru a grupa elementele dupa coloana respectiva'
13214         },
13215         search: {
13216           placeholder: 'Cauta...',
13217           showingItems: 'Arata elementele:',
13218           selectedItems: 'Elementele selectate:',
13219           totalItems: 'Total elemente:',
13220           size: 'Marime pagina:',
13221           first: 'Prima pagina',
13222           next: 'Pagina urmatoare',
13223           previous: 'Pagina anterioara',
13224           last: 'Ultima pagina'
13225         },
13226         menu: {
13227           text: 'Alege coloane:'
13228         },
13229         sort: {
13230           ascending: 'Ordoneaza crescator',
13231           descending: 'Ordoneaza descrescator',
13232           none: 'Fara ordonare',
13233           remove: 'Sterge ordonarea'
13234         },
13235         column: {
13236           hide: 'Ascunde coloana'
13237         },
13238         aggregation: {
13239           count: 'total linii: ',
13240           sum: 'total: ',
13241           avg: 'medie: ',
13242           min: 'min: ',
13243           max: 'max: '
13244         },
13245         pinning: {
13246           pinLeft: 'Pin la stanga',
13247           pinRight: 'Pin la dreapta',
13248           unpin: 'Sterge pinul'
13249         },
13250         columnMenu: {
13251           close: 'Inchide'
13252         },
13253         gridMenu: {
13254           aria: {
13255             buttonLabel: 'Grid Menu'
13256           },
13257           columns: 'Coloane:',
13258           importerTitle: 'Incarca fisier',
13259           exporterAllAsCsv: 'Exporta toate datele ca csv',
13260           exporterVisibleAsCsv: 'Exporta datele vizibile ca csv',
13261           exporterSelectedAsCsv: 'Exporta datele selectate ca csv',
13262           exporterAllAsPdf: 'Exporta toate datele ca pdf',
13263           exporterVisibleAsPdf: 'Exporta datele vizibile ca pdf',
13264           exporterSelectedAsPdf: 'Exporta datele selectate ca csv pdf',
13265           clearAllFilters: 'Sterge toate filtrele'
13266         },
13267         importer: {
13268           noHeaders: 'Numele coloanelor nu a putut fi incarcat, acest fisier are un header?',
13269           noObjects: 'Datele nu au putut fi incarcate, exista date in fisier in afara numelor de coloane?',
13270           invalidCsv: 'Fisierul nu a putut fi procesat, ati incarcat un CSV valid ?',
13271           invalidJson: 'Fisierul nu a putut fi procesat, ati incarcat un Json valid?',
13272           jsonNotArray: 'Json-ul incarcat trebuie sa contina un array, inchidere.'
13273         },
13274         pagination: {
13275           aria: {
13276             pageToFirst: 'Prima pagina',
13277             pageBack: 'O pagina inapoi',
13278             pageSelected: 'Pagina selectata',
13279             pageForward: 'O pagina inainte',
13280             pageToLast: 'Ultima pagina'
13281           },
13282           sizes: 'Elemente per pagina',
13283           totalItems: 'elemente',
13284           through: 'prin',
13285           of: 'of'
13286         },
13287         grouping: {
13288           group: 'Grupeaza',
13289           ungroup: 'Opreste gruparea',
13290           aggregate_count: 'Agg: Count',
13291           aggregate_sum: 'Agg: Sum',
13292           aggregate_max: 'Agg: Max',
13293           aggregate_min: 'Agg: Min',
13294           aggregate_avg: 'Agg: Avg',
13295           aggregate_remove: 'Agg: Remove'
13296         }
13297       });
13298       return $delegate;
13299     }]);
13300   }]);
13301 })();
13302
13303 (function () {
13304   angular.module('ui.grid').config(['$provide', function($provide) {
13305     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13306       $delegate.add('ru', {
13307         headerCell: {
13308           aria: {
13309             defaultFilterLabel: 'Фильтр столбца',
13310             removeFilter: 'Удалить фильтр',
13311             columnMenuButtonLabel: 'Меню столбца'
13312           },
13313           priority: 'Приоритет:',
13314           filterLabel: "Фильтр столбца: "
13315         },
13316         aggregate: {
13317           label: 'элементы'
13318         },
13319         groupPanel: {
13320           description: 'Для группировки по столбцу перетащите сюда его название.'
13321         },
13322         search: {
13323           placeholder: 'Поиск...',
13324           showingItems: 'Показать элементы:',
13325           selectedItems: 'Выбранные элементы:',
13326           totalItems: 'Всего элементов:',
13327           size: 'Размер страницы:',
13328           first: 'Первая страница',
13329           next: 'Следующая страница',
13330           previous: 'Предыдущая страница',
13331           last: 'Последняя страница'
13332         },
13333         menu: {
13334           text: 'Выбрать столбцы:'
13335         },
13336         sort: {
13337           ascending: 'По возрастанию',
13338           descending: 'По убыванию',
13339           none: 'Без сортировки',
13340           remove: 'Убрать сортировку'
13341         },
13342         column: {
13343           hide: 'Спрятать столбец'
13344         },
13345         aggregation: {
13346           count: 'всего строк: ',
13347           sum: 'итого: ',
13348           avg: 'среднее: ',
13349           min: 'мин: ',
13350           max: 'макс: '
13351         },
13352                                 pinning: {
13353                                         pinLeft: 'Закрепить слева',
13354                                         pinRight: 'Закрепить справа',
13355                                         unpin: 'Открепить'
13356                                 },
13357         columnMenu: {
13358           close: 'Закрыть'
13359         },
13360         gridMenu: {
13361           aria: {
13362             buttonLabel: 'Меню'
13363           },
13364           columns: 'Столбцы:',
13365           importerTitle: 'Импортировать файл',
13366           exporterAllAsCsv: 'Экспортировать всё в CSV',
13367           exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
13368           exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
13369           exporterAllAsPdf: 'Экспортировать всё в PDF',
13370           exporterVisibleAsPdf: 'Экспортировать видимые данные в PDF',
13371           exporterSelectedAsPdf: 'Экспортировать выбранные данные в PDF',
13372           clearAllFilters: 'Очистите все фильтры'
13373         },
13374         importer: {
13375           noHeaders: 'Не удалось получить названия столбцов, есть ли в файле заголовок?',
13376           noObjects: 'Не удалось получить данные, есть ли в файле строки кроме заголовка?',
13377           invalidCsv: 'Не удалось обработать файл, это правильный CSV-файл?',
13378           invalidJson: 'Не удалось обработать файл, это правильный JSON?',
13379           jsonNotArray: 'Импортируемый JSON-файл должен содержать массив, операция отменена.'
13380         },
13381         pagination: {
13382           aria: {
13383             pageToFirst: 'Первая страница',
13384             pageBack: 'Предыдущая страница',
13385             pageSelected: 'Выбранная страница',
13386             pageForward: 'Следующая страница',
13387             pageToLast: 'Последняя страница'
13388           },
13389           sizes: 'строк на страницу',
13390           totalItems: 'строк',
13391           through: 'по',
13392           of: 'из'
13393         },
13394         grouping: {
13395           group: 'Группировать',
13396           ungroup: 'Разгруппировать',
13397           aggregate_count: 'Группировать: Count',
13398           aggregate_sum: 'Для группы: Сумма',
13399           aggregate_max: 'Для группы: Максимум',
13400           aggregate_min: 'Для группы: Минимум',
13401           aggregate_avg: 'Для группы: Среднее',
13402           aggregate_remove: 'Для группы: Пусто'
13403         }
13404       });
13405       return $delegate;
13406     }]);
13407   }]);
13408 })();
13409
13410 (function () {
13411   angular.module('ui.grid').config(['$provide', function($provide) {
13412     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13413       $delegate.add('sk', {
13414         aggregate: {
13415           label: 'items'
13416         },
13417         groupPanel: {
13418           description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13419         },
13420         search: {
13421           placeholder: 'Hľadaj...',
13422           showingItems: 'Zobrazujem položky:',
13423           selectedItems: 'Vybraté položky:',
13424           totalItems: 'Počet položiek:',
13425           size: 'Počet:',
13426           first: 'Prvá strana',
13427           next: 'Ďalšia strana',
13428           previous: 'Predchádzajúca strana',
13429           last: 'Posledná strana'
13430         },
13431         menu: {
13432           text: 'Vyberte stĺpce:'
13433         },
13434         sort: {
13435           ascending: 'Zotriediť vzostupne',
13436           descending: 'Zotriediť zostupne',
13437           remove: 'Vymazať triedenie'
13438         },
13439         aggregation: {
13440           count: 'total rows: ',
13441           sum: 'total: ',
13442           avg: 'avg: ',
13443           min: 'min: ',
13444           max: 'max: '
13445         },
13446         gridMenu: {
13447           columns: 'Columns:',
13448           importerTitle: 'Import file',
13449           exporterAllAsCsv: 'Export all data as csv',
13450           exporterVisibleAsCsv: 'Export visible data as csv',
13451           exporterSelectedAsCsv: 'Export selected data as csv',
13452           exporterAllAsPdf: 'Export all data as pdf',
13453           exporterVisibleAsPdf: 'Export visible data as pdf',
13454           exporterSelectedAsPdf: 'Export selected data as pdf',
13455           clearAllFilters: 'Clear all filters'
13456         },
13457         importer: {
13458           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13459           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13460           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13461           invalidJson: 'File was unable to be processed, is it valid Json?',
13462           jsonNotArray: 'Imported json file must contain an array, aborting.'
13463         }
13464       });
13465       return $delegate;
13466     }]);
13467   }]);
13468 })();
13469
13470 (function () {
13471   angular.module('ui.grid').config(['$provide', function($provide) {
13472     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13473       $delegate.add('sv', {
13474         aggregate: {
13475           label: 'Artiklar'
13476         },
13477         groupPanel: {
13478           description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
13479         },
13480         search: {
13481           placeholder: 'Sök...',
13482           showingItems: 'Visar artiklar:',
13483           selectedItems: 'Valda artiklar:',
13484           totalItems: 'Antal artiklar:',
13485           size: 'Sidstorlek:',
13486           first: 'Första sidan',
13487           next: 'Nästa sida',
13488           previous: 'Föregående sida',
13489           last: 'Sista sidan'
13490         },
13491         menu: {
13492           text: 'Välj kolumner:'
13493         },
13494         sort: {
13495           ascending: 'Sortera stigande',
13496           descending: 'Sortera fallande',
13497           remove: 'Inaktivera sortering'
13498         },
13499         column: {
13500           hide: 'Göm kolumn'
13501         },
13502         aggregation: {
13503           count: 'Antal rader: ',
13504           sum: 'Summa: ',
13505           avg: 'Genomsnitt: ',
13506           min: 'Min: ',
13507           max: 'Max: '
13508         },
13509         pinning: {
13510           pinLeft: 'Fäst vänster',
13511           pinRight: 'Fäst höger',
13512           unpin: 'Lösgör'
13513         },
13514         gridMenu: {
13515           columns: 'Kolumner:',
13516           importerTitle: 'Importera fil',
13517           exporterAllAsCsv: 'Exportera all data som CSV',
13518           exporterVisibleAsCsv: 'Exportera synlig data som CSV',
13519           exporterSelectedAsCsv: 'Exportera markerad data som CSV',
13520           exporterAllAsPdf: 'Exportera all data som PDF',
13521           exporterVisibleAsPdf: 'Exportera synlig data som PDF',
13522           exporterSelectedAsPdf: 'Exportera markerad data som PDF',
13523           clearAllFilters: 'Rengör alla filter'
13524         },
13525         importer: {
13526           noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
13527           noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
13528           invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
13529           invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
13530           jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
13531         },
13532         pagination: {
13533           sizes: 'Artiklar per sida',
13534           totalItems: 'Artiklar'
13535         }
13536       });
13537       return $delegate;
13538     }]);
13539   }]);
13540 })();
13541
13542 (function () {
13543   angular.module('ui.grid').config(['$provide', function($provide) {
13544     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13545       $delegate.add('ta', {
13546         aggregate: {
13547           label: 'உருப்படிகள்'
13548         },
13549         groupPanel: {
13550           description: 'ஒரு பத்தியை குழுவாக அமைக்க அப்பத்தியின் தலைப்பை இங்கே  இழுத்து வரவும் '
13551         },
13552         search: {
13553           placeholder: 'தேடல் ...',
13554           showingItems: 'உருப்படிகளை காண்பித்தல்:',
13555           selectedItems: 'தேர்ந்தெடுக்கப்பட்ட  உருப்படிகள்:',
13556           totalItems: 'மொத்த உருப்படிகள்:',
13557           size: 'பக்க அளவு: ',
13558           first: 'முதல் பக்கம்',
13559           next: 'அடுத்த பக்கம்',
13560           previous: 'முந்தைய பக்கம் ',
13561           last: 'இறுதி பக்கம்'
13562         },
13563         menu: {
13564           text: 'பத்திகளை தேர்ந்தெடு:'
13565         },
13566         sort: {
13567           ascending: 'மேலிருந்து கீழாக',
13568           descending: 'கீழிருந்து மேலாக',
13569           remove: 'வரிசையை நீக்கு'
13570         },
13571         column: {
13572           hide: 'பத்தியை மறைத்து வை '
13573         },
13574         aggregation: {
13575           count: 'மொத்த வரிகள்:',
13576           sum: 'மொத்தம்: ',
13577           avg: 'சராசரி: ',
13578           min: 'குறைந்தபட்ச: ',
13579           max: 'அதிகபட்ச: '
13580         },
13581         pinning: {
13582          pinLeft: 'இடதுபுறமாக தைக்க ',
13583           pinRight: 'வலதுபுறமாக தைக்க',
13584           unpin: 'பிரி'
13585         },
13586         gridMenu: {
13587           columns: 'பத்திகள்:',
13588           importerTitle: 'கோப்பு : படித்தல்',
13589           exporterAllAsCsv: 'எல்லா தரவுகளையும் கோப்பாக்கு: csv',
13590           exporterVisibleAsCsv: 'இருக்கும் தரவுகளை கோப்பாக்கு: csv',
13591           exporterSelectedAsCsv: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: csv',
13592           exporterAllAsPdf: 'எல்லா தரவுகளையும் கோப்பாக்கு: pdf',
13593           exporterVisibleAsPdf: 'இருக்கும் தரவுகளை கோப்பாக்கு: pdf',
13594           exporterSelectedAsPdf: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: pdf',
13595           clearAllFilters: 'Clear all filters'
13596         },
13597         importer: {
13598           noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
13599           noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
13600           invalidCsv:   'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
13601           invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
13602           jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
13603         },
13604         pagination: {
13605           sizes         : 'உருப்படிகள் / பக்கம்',
13606           totalItems    : 'உருப்படிகள் '
13607         },
13608         grouping: {
13609           group : 'குழு',
13610           ungroup : 'பிரி',
13611           aggregate_count       : 'மதிப்பீட்டு : எண்ணு',
13612           aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
13613           aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
13614           aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
13615           aggregate_avg : 'மதிப்பீட்டு : சராசரி',
13616           aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
13617         }
13618       });
13619       return $delegate;
13620     }]);
13621   }]);
13622 })();
13623
13624 (function () {
13625   angular.module('ui.grid').config(['$provide', function($provide) {
13626     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13627       $delegate.add('tr', {
13628         headerCell: {
13629           aria: {
13630             defaultFilterLabel: 'Sütun için filtre',
13631             removeFilter: 'Filtreyi Kaldır',
13632             columnMenuButtonLabel: 'Sütun Menüsü'
13633           },
13634           priority: 'Öncelik:',
13635           filterLabel: "Sütun için filtre: "
13636         },
13637         aggregate: {
13638           label: 'kayıtlar'
13639         },
13640         groupPanel: {
13641           description: 'Sütuna göre gruplamak için sütun başlığını buraya sürükleyin ve bırakın.'
13642         },
13643         search: {
13644           placeholder: 'Arama...',
13645           showingItems: 'Gösterilen Kayıt:',
13646           selectedItems: 'Seçili Kayıt:',
13647           totalItems: 'Toplam Kayıt:',
13648           size: 'Sayfa Boyutu:',
13649           first: 'İlk Sayfa',
13650           next: 'Sonraki Sayfa',
13651           previous: 'Önceki Sayfa',
13652           last: 'Son Sayfa'
13653         },
13654         menu: {
13655           text: 'Sütunları Seç:'
13656         },
13657         sort: {
13658           ascending: 'Artan Sırada Sırala',
13659           descending: 'Azalan Sırada Sırala',
13660           none: 'Sıralama Yapma',
13661           remove: 'Sıralamayı Kaldır'
13662         },
13663         column: {
13664           hide: 'Sütunu Gizle'
13665         },
13666         aggregation: {
13667           count: 'toplam satır: ',
13668           sum: 'toplam: ',
13669           avg: 'ort: ',
13670           min: 'min: ',
13671           max: 'maks: '
13672         },
13673         pinning: {
13674           pinLeft: 'Sola Sabitle',
13675           pinRight: 'Sağa Sabitle',
13676           unpin: 'Sabitlemeyi Kaldır'
13677         },
13678         columnMenu: {
13679           close: 'Kapat'
13680         },
13681         gridMenu: {
13682           aria: {
13683             buttonLabel: 'Tablo Menü'
13684           },
13685           columns: 'Sütunlar:',
13686           importerTitle: 'Dosya içeri aktar',
13687           exporterAllAsCsv: 'Bütün veriyi CSV olarak dışarı aktar',
13688           exporterVisibleAsCsv: 'Görünen veriyi CSV olarak dışarı aktar',
13689           exporterSelectedAsCsv: 'Seçili veriyi CSV olarak dışarı aktar',
13690           exporterAllAsPdf: 'Bütün veriyi PDF olarak dışarı aktar',
13691           exporterVisibleAsPdf: 'Görünen veriyi PDF olarak dışarı aktar',
13692           exporterSelectedAsPdf: 'Seçili veriyi PDF olarak dışarı aktar',
13693           clearAllFilters: 'Bütün filtreleri kaldır'
13694         },
13695         importer: {
13696           noHeaders: 'Sütun isimleri üretilemiyor, dosyanın bir başlığı var mı?',
13697           noObjects: 'Nesneler üretilemiyor, dosyada başlıktan başka bir veri var mı?',
13698           invalidCsv: 'Dosya işlenemedi, geçerli bir CSV dosyası mı?',
13699           invalidJson: 'Dosya işlenemedi, geçerli bir Json dosyası mı?',
13700           jsonNotArray: 'Alınan Json dosyasında bir dizi bulunmalıdır, işlem iptal ediliyor.'
13701         },
13702         pagination: {
13703           aria: {
13704             pageToFirst: 'İlk sayfaya',
13705             pageBack: 'Geri git',
13706             pageSelected: 'Seçili sayfa',
13707             pageForward: 'İleri git',
13708             pageToLast: 'Sona git'
13709           },
13710           sizes: 'Sayfadaki nesne sayısı',
13711           totalItems: 'kayıtlar',
13712           through: '', //note(fsw) : turkish dont have this preposition 
13713           of: '' //note(fsw) : turkish dont have this preposition
13714         },
13715         grouping: {
13716           group: 'Grupla',
13717           ungroup: 'Gruplama',
13718           aggregate_count: 'Yekun: Sayı',
13719           aggregate_sum: 'Yekun: Toplam',
13720           aggregate_max: 'Yekun: Maks',
13721           aggregate_min: 'Yekun: Min',
13722           aggregate_avg: 'Yekun: Ort',
13723           aggregate_remove: 'Yekun: Sil'
13724         }
13725       });
13726       return $delegate;
13727     }]);
13728   }]);
13729 })();
13730 /**
13731  * @ngdoc overview
13732  * @name ui.grid.i18n
13733  * @description
13734  *
13735  *  # ui.grid.i18n
13736  * This module provides i18n functions to ui.grid and any application that wants to use it
13737
13738  *
13739  * <div doc-module-components="ui.grid.i18n"></div>
13740  */
13741
13742 (function () {
13743   var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
13744   var FILTER_ALIASES = ['t', 'uiTranslate'];
13745
13746   var module = angular.module('ui.grid.i18n');
13747
13748
13749   /**
13750    *  @ngdoc object
13751    *  @name ui.grid.i18n.constant:i18nConstants
13752    *
13753    *  @description constants available in i18n module
13754    */
13755   module.constant('i18nConstants', {
13756     MISSING: '[MISSING]',
13757     UPDATE_EVENT: '$uiI18n',
13758
13759     LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
13760     // default to english
13761     DEFAULT_LANG: 'en'
13762   });
13763
13764 //    module.config(['$provide', function($provide) {
13765 //        $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
13766
13767   /**
13768    *  @ngdoc service
13769    *  @name ui.grid.i18n.service:i18nService
13770    *
13771    *  @description Services for i18n
13772    */
13773   module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
13774     function ($log, i18nConstants, $rootScope) {
13775
13776       var langCache = {
13777         _langs: {},
13778         current: null,
13779         get: function (lang) {
13780           return this._langs[lang.toLowerCase()];
13781         },
13782         add: function (lang, strings) {
13783           var lower = lang.toLowerCase();
13784           if (!this._langs[lower]) {
13785             this._langs[lower] = {};
13786           }
13787           angular.extend(this._langs[lower], strings);
13788         },
13789         getAllLangs: function () {
13790           var langs = [];
13791           if (!this._langs) {
13792             return langs;
13793           }
13794
13795           for (var key in this._langs) {
13796             langs.push(key);
13797           }
13798
13799           return langs;
13800         },
13801         setCurrent: function (lang) {
13802           this.current = lang.toLowerCase();
13803         },
13804         getCurrentLang: function () {
13805           return this.current;
13806         }
13807       };
13808
13809       var service = {
13810
13811         /**
13812          * @ngdoc service
13813          * @name add
13814          * @methodOf ui.grid.i18n.service:i18nService
13815          * @description  Adds the languages and strings to the cache. Decorate this service to
13816          * add more translation strings
13817          * @param {string} lang language to add
13818          * @param {object} stringMaps of strings to add grouped by property names
13819          * @example
13820          * <pre>
13821          *      i18nService.add('en', {
13822          *         aggregate: {
13823          *                 label1: 'items',
13824          *                 label2: 'some more items'
13825          *                 }
13826          *         },
13827          *         groupPanel: {
13828          *              description: 'Drag a column header here and drop it to group by that column.'
13829          *           }
13830          *      }
13831          * </pre>
13832          */
13833         add: function (langs, stringMaps) {
13834           if (typeof(langs) === 'object') {
13835             angular.forEach(langs, function (lang) {
13836               if (lang) {
13837                 langCache.add(lang, stringMaps);
13838               }
13839             });
13840           } else {
13841             langCache.add(langs, stringMaps);
13842           }
13843         },
13844
13845         /**
13846          * @ngdoc service
13847          * @name getAllLangs
13848          * @methodOf ui.grid.i18n.service:i18nService
13849          * @description  return all currently loaded languages
13850          * @returns {array} string
13851          */
13852         getAllLangs: function () {
13853           return langCache.getAllLangs();
13854         },
13855
13856         /**
13857          * @ngdoc service
13858          * @name get
13859          * @methodOf ui.grid.i18n.service:i18nService
13860          * @description  return all currently loaded languages
13861          * @param {string} lang to return.  If not specified, returns current language
13862          * @returns {object} the translation string maps for the language
13863          */
13864         get: function (lang) {
13865           var language = lang ? lang : service.getCurrentLang();
13866           return langCache.get(language);
13867         },
13868
13869         /**
13870          * @ngdoc service
13871          * @name getSafeText
13872          * @methodOf ui.grid.i18n.service:i18nService
13873          * @description  returns the text specified in the path or a Missing text if text is not found
13874          * @param {string} path property path to use for retrieving text from string map
13875          * @param {string} lang to return.  If not specified, returns current language
13876          * @returns {object} the translation for the path
13877          * @example
13878          * <pre>
13879          * i18nService.getSafeText('sort.ascending')
13880          * </pre>
13881          */
13882         getSafeText: function (path, lang) {
13883           var language = lang ? lang : service.getCurrentLang();
13884           var trans = langCache.get(language);
13885
13886           if (!trans) {
13887             return i18nConstants.MISSING;
13888           }
13889
13890           var paths = path.split('.');
13891           var current = trans;
13892
13893           for (var i = 0; i < paths.length; ++i) {
13894             if (current[paths[i]] === undefined || current[paths[i]] === null) {
13895               return i18nConstants.MISSING;
13896             } else {
13897               current = current[paths[i]];
13898             }
13899           }
13900
13901           return current;
13902
13903         },
13904
13905         /**
13906          * @ngdoc service
13907          * @name setCurrentLang
13908          * @methodOf ui.grid.i18n.service:i18nService
13909          * @description sets the current language to use in the application
13910          * $broadcasts the Update_Event on the $rootScope
13911          * @param {string} lang to set
13912          * @example
13913          * <pre>
13914          * i18nService.setCurrentLang('fr');
13915          * </pre>
13916          */
13917
13918         setCurrentLang: function (lang) {
13919           if (lang) {
13920             langCache.setCurrent(lang);
13921             $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
13922           }
13923         },
13924
13925         /**
13926          * @ngdoc service
13927          * @name getCurrentLang
13928          * @methodOf ui.grid.i18n.service:i18nService
13929          * @description returns the current language used in the application
13930          */
13931         getCurrentLang: function () {
13932           var lang = langCache.getCurrentLang();
13933           if (!lang) {
13934             lang = i18nConstants.DEFAULT_LANG;
13935             langCache.setCurrent(lang);
13936           }
13937           return lang;
13938         }
13939
13940       };
13941
13942       return service;
13943
13944     }]);
13945
13946   var localeDirective = function (i18nService, i18nConstants) {
13947     return {
13948       compile: function () {
13949         return {
13950           pre: function ($scope, $elm, $attrs) {
13951             var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
13952             // check for watchable property
13953             var lang = $scope.$eval($attrs[alias]);
13954             if (lang) {
13955               $scope.$watch($attrs[alias], function () {
13956                 i18nService.setCurrentLang(lang);
13957               });
13958             } else if ($attrs.$$observers) {
13959               $attrs.$observe(alias, function () {
13960                 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
13961               });
13962             }
13963           }
13964         };
13965       }
13966     };
13967   };
13968
13969   module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
13970
13971   // directive syntax
13972   var uitDirective = function ($parse, i18nService, i18nConstants) {
13973     return {
13974       restrict: 'EA',
13975       compile: function () {
13976         return {
13977           pre: function ($scope, $elm, $attrs) {
13978             var alias1 = DIRECTIVE_ALIASES[0],
13979               alias2 = DIRECTIVE_ALIASES[1];
13980             var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
13981             var missing = i18nConstants.MISSING + token;
13982             var observer;
13983             if ($attrs.$$observers) {
13984               var prop = $attrs[alias1] ? alias1 : alias2;
13985               observer = $attrs.$observe(prop, function (result) {
13986                 if (result) {
13987                   $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
13988                 }
13989               });
13990             }
13991             var getter = $parse(token);
13992             var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
13993               if (observer) {
13994                 observer($attrs[alias1] || $attrs[alias2]);
13995               } else {
13996                 // set text based on i18n current language
13997                 $elm.html(getter(i18nService.get()) || missing);
13998               }
13999             });
14000             $scope.$on('$destroy', listener);
14001
14002             $elm.html(getter(i18nService.get()) || missing);
14003           }
14004         };
14005       }
14006     };
14007   };
14008
14009   angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
14010     module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
14011   } );
14012
14013   // optional filter syntax
14014   var uitFilter = function ($parse, i18nService, i18nConstants) {
14015     return function (data) {
14016       var getter = $parse(data);
14017       // set text based on i18n current language
14018       return getter(i18nService.get()) || i18nConstants.MISSING + data;
14019     };
14020   };
14021
14022   angular.forEach( FILTER_ALIASES, function ( alias ) {
14023     module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
14024   } );
14025
14026
14027 })();
14028 (function() {
14029   angular.module('ui.grid').config(['$provide', function($provide) {
14030     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14031       $delegate.add('zh-cn', {
14032         headerCell: {
14033           aria: {
14034             defaultFilterLabel: '列过滤器',
14035             removeFilter: '移除过滤器',
14036             columnMenuButtonLabel: '列菜单'
14037           },
14038           priority: '优先级:',
14039           filterLabel: "列过滤器: "
14040         },
14041         aggregate: {
14042           label: '行'
14043         },
14044         groupPanel: {
14045           description: '拖曳表头到此处进行分组'
14046         },
14047         search: {
14048           placeholder: '查找',
14049           showingItems: '已显示行数:',
14050           selectedItems: '已选择行数:',
14051           totalItems: '总行数:',
14052           size: '每页显示行数:',
14053           first: '首页',
14054           next: '下一页',
14055           previous: '上一页',
14056           last: '末页'
14057         },
14058         menu: {
14059           text: '选择列:'
14060         },
14061         sort: {
14062           ascending: '升序',
14063           descending: '降序',
14064           none: '无序',
14065           remove: '取消排序'
14066         },
14067         column: {
14068           hide: '隐藏列'
14069         },
14070         aggregation: {
14071           count: '计数:',
14072           sum: '求和:',
14073           avg: '均值:',
14074           min: '最小值:',
14075           max: '最大值:'
14076         },
14077         pinning: {
14078           pinLeft: '左侧固定',
14079           pinRight: '右侧固定',
14080           unpin: '取消固定'
14081         },
14082         columnMenu: {
14083           close: '关闭'
14084         },
14085         gridMenu: {
14086           aria: {
14087             buttonLabel: '表格菜单'
14088           },
14089           columns: '列:',
14090           importerTitle: '导入文件',
14091           exporterAllAsCsv: '导出全部数据到CSV',
14092           exporterVisibleAsCsv: '导出可见数据到CSV',
14093           exporterSelectedAsCsv: '导出已选数据到CSV',
14094           exporterAllAsPdf: '导出全部数据到PDF',
14095           exporterVisibleAsPdf: '导出可见数据到PDF',
14096           exporterSelectedAsPdf: '导出已选数据到PDF',
14097           clearAllFilters: '清除所有过滤器'
14098         },
14099         importer: {
14100           noHeaders: '无法获取列名,确定文件包含表头?',
14101           noObjects: '无法获取数据,确定文件包含数据?',
14102           invalidCsv: '无法处理文件,确定是合法的CSV文件?',
14103           invalidJson: '无法处理文件,确定是合法的JSON文件?',
14104           jsonNotArray: '导入的文件不是JSON数组!'
14105         },
14106         pagination: {
14107           aria: {
14108             pageToFirst: '第一页',
14109             pageBack: '上一页',
14110             pageSelected: '当前页',
14111             pageForward: '下一页',
14112             pageToLast: '最后一页'
14113           },
14114           sizes: '行每页',
14115           totalItems: '行',
14116           through: '至',
14117           of: '共'
14118         },
14119         grouping: {
14120           group: '分组',
14121           ungroup: '取消分组',
14122           aggregate_count: '合计: 计数',
14123           aggregate_sum: '合计: 求和',
14124           aggregate_max: '合计: 最大',
14125           aggregate_min: '合计: 最小',
14126           aggregate_avg: '合计: 平均',
14127           aggregate_remove: '合计: 移除'
14128         }
14129       });
14130       return $delegate;
14131     }]);
14132   }]);
14133 })();
14134
14135 (function() {
14136   angular.module('ui.grid').config(['$provide', function($provide) {
14137     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14138       $delegate.add('zh-tw', {
14139         aggregate: {
14140           label: '行'
14141         },
14142         groupPanel: {
14143           description: '拖曳表頭到此處進行分組'
14144         },
14145         search: {
14146           placeholder: '查找',
14147           showingItems: '已顯示行數:',
14148           selectedItems: '已選擇行數:',
14149           totalItems: '總行數:',
14150           size: '每頁顯示行數:',
14151           first: '首頁',
14152           next: '下壹頁',
14153           previous: '上壹頁',
14154           last: '末頁'
14155         },
14156         menu: {
14157           text: '選擇列:'
14158         },
14159         sort: {
14160           ascending: '升序',
14161           descending: '降序',
14162           remove: '取消排序'
14163         },
14164         column: {
14165           hide: '隱藏列'
14166         },
14167         aggregation: {
14168           count: '計數:',
14169           sum: '求和:',
14170           avg: '均值:',
14171           min: '最小值:',
14172           max: '最大值:'
14173         },
14174         pinning: {
14175           pinLeft: '左側固定',
14176           pinRight: '右側固定',
14177           unpin: '取消固定'
14178         },
14179         gridMenu: {
14180           columns: '列:',
14181           importerTitle: '導入文件',
14182           exporterAllAsCsv: '導出全部數據到CSV',
14183           exporterVisibleAsCsv: '導出可見數據到CSV',
14184           exporterSelectedAsCsv: '導出已選數據到CSV',
14185           exporterAllAsPdf: '導出全部數據到PDF',
14186           exporterVisibleAsPdf: '導出可見數據到PDF',
14187           exporterSelectedAsPdf: '導出已選數據到PDF',
14188           clearAllFilters: '清除所有过滤器'
14189         },
14190         importer: {
14191           noHeaders: '無法獲取列名,確定文件包含表頭?',
14192           noObjects: '無法獲取數據,確定文件包含數據?',
14193           invalidCsv: '無法處理文件,確定是合法的CSV文件?',
14194           invalidJson: '無法處理文件,確定是合法的JSON文件?',
14195           jsonNotArray: '導入的文件不是JSON數組!'
14196         },
14197         pagination: {
14198           sizes: '行每頁',
14199           totalItems: '行'
14200         }
14201       });
14202       return $delegate;
14203     }]);
14204   }]);
14205 })();
14206
14207 (function() {
14208   'use strict';
14209   /**
14210    *  @ngdoc overview
14211    *  @name ui.grid.autoResize
14212    *
14213    *  @description
14214    *
14215    *  #ui.grid.autoResize
14216    *
14217    *  <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>
14218    *
14219    *  This module provides auto-resizing functionality to UI-Grid.
14220    */
14221   var module = angular.module('ui.grid.autoResize', ['ui.grid']);
14222
14223
14224   module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
14225     return {
14226       require: 'uiGrid',
14227       scope: false,
14228       link: function ($scope, $elm, $attrs, uiGridCtrl) {
14229         var prevGridWidth, prevGridHeight;
14230
14231         function getDimensions() {
14232           prevGridHeight = gridUtil.elementHeight($elm);
14233           prevGridWidth = gridUtil.elementWidth($elm);
14234         }
14235
14236         // Initialize the dimensions
14237         getDimensions();
14238
14239         var resizeTimeoutId;
14240         function startTimeout() {
14241           clearTimeout(resizeTimeoutId);
14242
14243           resizeTimeoutId = setTimeout(function () {
14244             var newGridHeight = gridUtil.elementHeight($elm);
14245             var newGridWidth = gridUtil.elementWidth($elm);
14246
14247             if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
14248               uiGridCtrl.grid.gridHeight = newGridHeight;
14249               uiGridCtrl.grid.gridWidth = newGridWidth;
14250
14251               $scope.$apply(function () {
14252                 uiGridCtrl.grid.refresh()
14253                   .then(function () {
14254                     getDimensions();
14255
14256                     startTimeout();
14257                   });
14258               });
14259             }
14260             else {
14261               startTimeout();
14262             }
14263           }, 250);
14264         }
14265
14266         startTimeout();
14267
14268         $scope.$on('$destroy', function() {
14269           clearTimeout(resizeTimeoutId);
14270         });
14271       }
14272     };
14273   }]);
14274 })();
14275
14276 (function () {
14277   'use strict';
14278
14279   /**
14280    *  @ngdoc overview
14281    *  @name ui.grid.cellNav
14282    *
14283    *  @description
14284
14285       #ui.grid.cellNav
14286
14287       <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>
14288
14289       This module provides auto-resizing functionality to UI-Grid.
14290    */
14291   var module = angular.module('ui.grid.cellNav', ['ui.grid']);
14292
14293   /**
14294    *  @ngdoc object
14295    *  @name ui.grid.cellNav.constant:uiGridCellNavConstants
14296    *
14297    *  @description constants available in cellNav
14298    */
14299   module.constant('uiGridCellNavConstants', {
14300     FEATURE_NAME: 'gridCellNav',
14301     CELL_NAV_EVENT: 'cellNav',
14302     direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
14303     EVENT_TYPE: {
14304       KEYDOWN: 0,
14305       CLICK: 1,
14306       CLEAR: 2
14307     }
14308   });
14309
14310
14311   module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
14312     function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
14313       /**
14314        *  @ngdoc object
14315        *  @name ui.grid.cellNav.object:CellNav
14316        *  @description returns a CellNav prototype function
14317        *  @param {object} rowContainer container for rows
14318        *  @param {object} colContainer parent column container
14319        *  @param {object} leftColContainer column container to the left of parent
14320        *  @param {object} rightColContainer column container to the right of parent
14321        */
14322       var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
14323         this.rows = rowContainer.visibleRowCache;
14324         this.columns = colContainer.visibleColumnCache;
14325         this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
14326         this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
14327         this.bodyContainer = rowContainer;
14328       };
14329
14330       /** returns focusable columns of all containers */
14331       UiGridCellNav.prototype.getFocusableCols = function () {
14332         var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
14333
14334         return allColumns.filter(function (col) {
14335           return col.colDef.allowCellFocus;
14336         });
14337       };
14338
14339       /**
14340        *  @ngdoc object
14341        *  @name ui.grid.cellNav.api:GridRow
14342        *
14343        *  @description GridRow settings for cellNav feature, these are available to be
14344        *  set only internally (for example, by other features)
14345        */
14346
14347       /**
14348        *  @ngdoc object
14349        *  @name allowCellFocus
14350        *  @propertyOf  ui.grid.cellNav.api:GridRow
14351        *  @description Enable focus on a cell within this row.  If set to false then no cells
14352        *  in this row can be focused - group header rows as an example would set this to false.
14353        *  <br/>Defaults to true
14354        */
14355       /** returns focusable rows */
14356       UiGridCellNav.prototype.getFocusableRows = function () {
14357         return this.rows.filter(function(row) {
14358           return row.allowCellFocus !== false;
14359         });
14360       };
14361
14362       UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
14363         switch (direction) {
14364           case uiGridCellNavConstants.direction.LEFT:
14365             return this.getRowColLeft(curRow, curCol);
14366           case uiGridCellNavConstants.direction.RIGHT:
14367             return this.getRowColRight(curRow, curCol);
14368           case uiGridCellNavConstants.direction.UP:
14369             return this.getRowColUp(curRow, curCol);
14370           case uiGridCellNavConstants.direction.DOWN:
14371             return this.getRowColDown(curRow, curCol);
14372           case uiGridCellNavConstants.direction.PG_UP:
14373             return this.getRowColPageUp(curRow, curCol);
14374           case uiGridCellNavConstants.direction.PG_DOWN:
14375             return this.getRowColPageDown(curRow, curCol);
14376         }
14377
14378       };
14379
14380       UiGridCellNav.prototype.initializeSelection = function () {
14381         var focusableCols = this.getFocusableCols();
14382         var focusableRows = this.getFocusableRows();
14383         if (focusableCols.length === 0 || focusableRows.length === 0) {
14384           return null;
14385         }
14386
14387         var curRowIndex = 0;
14388         var curColIndex = 0;
14389         return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
14390       };
14391
14392       UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
14393         var focusableCols = this.getFocusableCols();
14394         var focusableRows = this.getFocusableRows();
14395         var curColIndex = focusableCols.indexOf(curCol);
14396         var curRowIndex = focusableRows.indexOf(curRow);
14397
14398         //could not find column in focusable Columns so set it to 1
14399         if (curColIndex === -1) {
14400           curColIndex = 1;
14401         }
14402
14403         var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
14404
14405         //get column to left
14406         if (nextColIndex > curColIndex) {
14407           // On the first row
14408           // if (curRowIndex === 0 && curColIndex === 0) {
14409           //   return null;
14410           // }
14411           if (curRowIndex === 0) {
14412             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14413           }
14414           else {
14415             //up one row and far right column
14416             return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
14417           }
14418         }
14419         else {
14420           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14421         }
14422       };
14423
14424
14425
14426       UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
14427         var focusableCols = this.getFocusableCols();
14428         var focusableRows = this.getFocusableRows();
14429         var curColIndex = focusableCols.indexOf(curCol);
14430         var curRowIndex = focusableRows.indexOf(curRow);
14431
14432         //could not find column in focusable Columns so set it to 0
14433         if (curColIndex === -1) {
14434           curColIndex = 0;
14435         }
14436         var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
14437
14438         if (nextColIndex < curColIndex) {
14439           if (curRowIndex === focusableRows.length - 1) {
14440             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14441           }
14442           else {
14443             //down one row and far left column
14444             return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
14445           }
14446         }
14447         else {
14448           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14449         }
14450       };
14451
14452       UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
14453         var focusableCols = this.getFocusableCols();
14454         var focusableRows = this.getFocusableRows();
14455         var curColIndex = focusableCols.indexOf(curCol);
14456         var curRowIndex = focusableRows.indexOf(curRow);
14457
14458         //could not find column in focusable Columns so set it to 0
14459         if (curColIndex === -1) {
14460           curColIndex = 0;
14461         }
14462
14463         if (curRowIndex === focusableRows.length - 1) {
14464           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14465         }
14466         else {
14467           //down one row
14468           return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
14469         }
14470       };
14471
14472       UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
14473         var focusableCols = this.getFocusableCols();
14474         var focusableRows = this.getFocusableRows();
14475         var curColIndex = focusableCols.indexOf(curCol);
14476         var curRowIndex = focusableRows.indexOf(curRow);
14477
14478         //could not find column in focusable Columns so set it to 0
14479         if (curColIndex === -1) {
14480           curColIndex = 0;
14481         }
14482
14483         var pageSize = this.bodyContainer.minRowsToRender();
14484         if (curRowIndex >= focusableRows.length - pageSize) {
14485           return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
14486         }
14487         else {
14488           //down one page
14489           return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
14490         }
14491       };
14492
14493       UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
14494         var focusableCols = this.getFocusableCols();
14495         var focusableRows = this.getFocusableRows();
14496         var curColIndex = focusableCols.indexOf(curCol);
14497         var curRowIndex = focusableRows.indexOf(curRow);
14498
14499         //could not find column in focusable Columns so set it to 0
14500         if (curColIndex === -1) {
14501           curColIndex = 0;
14502         }
14503
14504         if (curRowIndex === 0) {
14505           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14506         }
14507         else {
14508           //up one row
14509           return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
14510         }
14511       };
14512
14513       UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
14514         var focusableCols = this.getFocusableCols();
14515         var focusableRows = this.getFocusableRows();
14516         var curColIndex = focusableCols.indexOf(curCol);
14517         var curRowIndex = focusableRows.indexOf(curRow);
14518
14519         //could not find column in focusable Columns so set it to 0
14520         if (curColIndex === -1) {
14521           curColIndex = 0;
14522         }
14523
14524         var pageSize = this.bodyContainer.minRowsToRender();
14525         if (curRowIndex - pageSize < 0) {
14526           return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
14527         }
14528         else {
14529           //up one page
14530           return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
14531         }
14532       };
14533       return UiGridCellNav;
14534     }]);
14535
14536   /**
14537    *  @ngdoc service
14538    *  @name ui.grid.cellNav.service:uiGridCellNavService
14539    *
14540    *  @description Services for cell navigation features. If you don't like the key maps we use,
14541    *  or the direction cells navigation, override with a service decorator (see angular docs)
14542    */
14543   module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
14544     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
14545
14546       var service = {
14547
14548         initializeGrid: function (grid) {
14549           grid.registerColumnBuilder(service.cellNavColumnBuilder);
14550
14551
14552           /**
14553            *  @ngdoc object
14554            *  @name ui.grid.cellNav:Grid.cellNav
14555            * @description cellNav properties added to grid class
14556            */
14557           grid.cellNav = {};
14558           grid.cellNav.lastRowCol = null;
14559           grid.cellNav.focusedCells = [];
14560
14561           service.defaultGridOptions(grid.options);
14562
14563           /**
14564            *  @ngdoc object
14565            *  @name ui.grid.cellNav.api:PublicApi
14566            *
14567            *  @description Public Api for cellNav feature
14568            */
14569           var publicApi = {
14570             events: {
14571               cellNav: {
14572                 /**
14573                  * @ngdoc event
14574                  * @name navigate
14575                  * @eventOf  ui.grid.cellNav.api:PublicApi
14576                  * @description raised when the active cell is changed
14577                  * <pre>
14578                  *      gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
14579                  * </pre>
14580                  * @param {object} newRowCol new position
14581                  * @param {object} oldRowCol old position
14582                  */
14583                 navigate: function (newRowCol, oldRowCol) {},
14584                 /**
14585                  * @ngdoc event
14586                  * @name viewPortKeyDown
14587                  * @eventOf  ui.grid.cellNav.api:PublicApi
14588                  * @description  is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
14589                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14590                  * event whenever you need a keydown event on a cell
14591                  * <br/>
14592                  * @param {object} event keydown event
14593                  * @param {object} rowCol current rowCol position
14594                  */
14595                 viewPortKeyDown: function (event, rowCol) {},
14596
14597                 /**
14598                  * @ngdoc event
14599                  * @name viewPortKeyPress
14600                  * @eventOf  ui.grid.cellNav.api:PublicApi
14601                  * @description  is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
14602                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14603                  * event whenever you need a keypress event on a cell
14604                  * <br/>
14605                  * @param {object} event keypress event
14606                  * @param {object} rowCol current rowCol position
14607                  */
14608                 viewPortKeyPress: function (event, rowCol) {}
14609               }
14610             },
14611             methods: {
14612               cellNav: {
14613                 /**
14614                  * @ngdoc function
14615                  * @name scrollToFocus
14616                  * @methodOf  ui.grid.cellNav.api:PublicApi
14617                  * @description brings the specified row and column into view, and sets focus
14618                  * to that cell
14619                  * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
14620                  * @param {object} colDef to make visible and set focus
14621                  * @returns {promise} a promise that is resolved after any scrolling is finished
14622                  */
14623                 scrollToFocus: function (rowEntity, colDef) {
14624                   return service.scrollToFocus(grid, rowEntity, colDef);
14625                 },
14626
14627                 /**
14628                  * @ngdoc function
14629                  * @name getFocusedCell
14630                  * @methodOf  ui.grid.cellNav.api:PublicApi
14631                  * @description returns the current (or last if Grid does not have focus) focused row and column
14632                  * <br> value is null if no selection has occurred
14633                  */
14634                 getFocusedCell: function () {
14635                   return grid.cellNav.lastRowCol;
14636                 },
14637
14638                 /**
14639                  * @ngdoc function
14640                  * @name getCurrentSelection
14641                  * @methodOf  ui.grid.cellNav.api:PublicApi
14642                  * @description returns an array containing the current selection
14643                  * <br> array is empty if no selection has occurred
14644                  */
14645                 getCurrentSelection: function () {
14646                   return grid.cellNav.focusedCells;
14647                 },
14648
14649                 /**
14650                  * @ngdoc function
14651                  * @name rowColSelectIndex
14652                  * @methodOf  ui.grid.cellNav.api:PublicApi
14653                  * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
14654                  * isn't selected
14655                  * @param {object} rowCol the rowCol to evaluate
14656                  */
14657                 rowColSelectIndex: function (rowCol) {
14658                   //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
14659                   var index = -1;
14660                   for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
14661                     if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
14662                       grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
14663                       index = i;
14664                       break;
14665                     }
14666                   }
14667                   return index;
14668                 }
14669               }
14670             }
14671           };
14672
14673           grid.api.registerEventsFromObject(publicApi.events);
14674
14675           grid.api.registerMethodsFromObject(publicApi.methods);
14676
14677         },
14678
14679         defaultGridOptions: function (gridOptions) {
14680           /**
14681            *  @ngdoc object
14682            *  @name ui.grid.cellNav.api:GridOptions
14683            *
14684            *  @description GridOptions for cellNav feature, these are available to be
14685            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14686            */
14687
14688           /**
14689            *  @ngdoc object
14690            *  @name modifierKeysToMultiSelectCells
14691            *  @propertyOf  ui.grid.cellNav.api:GridOptions
14692            *  @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
14693            *  <br/>Defaults to false
14694            */
14695           gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
14696
14697         },
14698
14699         /**
14700          * @ngdoc service
14701          * @name decorateRenderContainers
14702          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14703          * @description  decorates grid renderContainers with cellNav functions
14704          */
14705         decorateRenderContainers: function (grid) {
14706
14707           var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
14708           var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
14709
14710           if (leftContainer !== null) {
14711             grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
14712           }
14713           if (rightContainer !== null) {
14714             grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
14715           }
14716
14717           grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
14718         },
14719
14720         /**
14721          * @ngdoc service
14722          * @name getDirection
14723          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14724          * @description  determines which direction to for a given keyDown event
14725          * @returns {uiGridCellNavConstants.direction} direction
14726          */
14727         getDirection: function (evt) {
14728           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
14729             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
14730             return uiGridCellNavConstants.direction.LEFT;
14731           }
14732           if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
14733             evt.keyCode === uiGridConstants.keymap.TAB) {
14734             return uiGridCellNavConstants.direction.RIGHT;
14735           }
14736
14737           if (evt.keyCode === uiGridConstants.keymap.UP ||
14738             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
14739             return uiGridCellNavConstants.direction.UP;
14740           }
14741
14742           if (evt.keyCode === uiGridConstants.keymap.PG_UP){
14743             return uiGridCellNavConstants.direction.PG_UP;
14744           }
14745
14746           if (evt.keyCode === uiGridConstants.keymap.DOWN ||
14747             evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
14748             return uiGridCellNavConstants.direction.DOWN;
14749           }
14750
14751           if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
14752             return uiGridCellNavConstants.direction.PG_DOWN;
14753           }
14754
14755           return null;
14756         },
14757
14758         /**
14759          * @ngdoc service
14760          * @name cellNavColumnBuilder
14761          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14762          * @description columnBuilder function that adds cell navigation properties to grid column
14763          * @returns {promise} promise that will load any needed templates when resolved
14764          */
14765         cellNavColumnBuilder: function (colDef, col, gridOptions) {
14766           var promises = [];
14767
14768           /**
14769            *  @ngdoc object
14770            *  @name ui.grid.cellNav.api:ColumnDef
14771            *
14772            *  @description Column Definitions for cellNav feature, these are available to be
14773            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
14774            */
14775
14776           /**
14777            *  @ngdoc object
14778            *  @name allowCellFocus
14779            *  @propertyOf  ui.grid.cellNav.api:ColumnDef
14780            *  @description Enable focus on a cell within this column.
14781            *  <br/>Defaults to true
14782            */
14783           colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
14784
14785           return $q.all(promises);
14786         },
14787
14788         /**
14789          * @ngdoc method
14790          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14791          * @name scrollToFocus
14792          * @description Scroll the grid such that the specified
14793          * row and column is in view, and set focus to the cell in that row and column
14794          * @param {Grid} grid the grid you'd like to act upon, usually available
14795          * from gridApi.grid
14796          * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
14797          * @param {object} colDef to make visible and set focus to
14798          * @returns {promise} a promise that is resolved after any scrolling is finished
14799          */
14800         scrollToFocus: function (grid, rowEntity, colDef) {
14801           var gridRow = null, gridCol = null;
14802
14803           if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
14804             gridRow = grid.getRow(rowEntity);
14805           }
14806
14807           if (typeof(colDef) !== 'undefined' && colDef !== null) {
14808             gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
14809           }
14810           return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
14811             var rowCol = { row: gridRow, col: gridCol };
14812
14813             // Broadcast the navigation
14814             if (gridRow !== null && gridCol !== null) {
14815               grid.cellNav.broadcastCellNav(rowCol);
14816             }
14817           });
14818
14819
14820
14821         },
14822
14823
14824         /**
14825          * @ngdoc method
14826          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14827          * @name getLeftWidth
14828          * @description Get the current drawn width of the columns in the
14829          * grid up to the numbered column, and add an apportionment for the
14830          * column that we're on.  So if we are on column 0, we want to scroll
14831          * 0% (i.e. exclude this column from calc).  If we're on the last column
14832          * we want to scroll to 100% (i.e. include this column in the calc). So
14833          * we include (thisColIndex / totalNumberCols) % of this column width
14834          * @param {Grid} grid the grid you'd like to act upon, usually available
14835          * from gridApi.grid
14836          * @param {gridCol} upToCol the column to total up to and including
14837          */
14838         getLeftWidth: function (grid, upToCol) {
14839           var width = 0;
14840
14841           if (!upToCol) {
14842             return width;
14843           }
14844
14845           var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
14846
14847           // total column widths up-to but not including the passed in column
14848           grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
14849             if ( index < lastIndex ){
14850               width += col.drawnWidth;
14851             }
14852           });
14853
14854           // pro-rata the final column based on % of total columns.
14855           var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
14856           width += upToCol.drawnWidth * percentage;
14857
14858           return width;
14859         }
14860       };
14861
14862       return service;
14863     }]);
14864
14865   /**
14866    *  @ngdoc directive
14867    *  @name ui.grid.cellNav.directive:uiCellNav
14868    *  @element div
14869    *  @restrict EA
14870    *
14871    *  @description Adds cell navigation features to the grid columns
14872    *
14873    *  @example
14874    <example module="app">
14875    <file name="app.js">
14876    var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
14877
14878    app.controller('MainCtrl', ['$scope', function ($scope) {
14879       $scope.data = [
14880         { name: 'Bob', title: 'CEO' },
14881             { name: 'Frank', title: 'Lowly Developer' }
14882       ];
14883
14884       $scope.columnDefs = [
14885         {name: 'name'},
14886         {name: 'title'}
14887       ];
14888     }]);
14889    </file>
14890    <file name="index.html">
14891    <div ng-controller="MainCtrl">
14892    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
14893    </div>
14894    </file>
14895    </example>
14896    */
14897   module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
14898     function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
14899       return {
14900         replace: true,
14901         priority: -150,
14902         require: '^uiGrid',
14903         scope: false,
14904         controller: function () {},
14905         compile: function () {
14906           return {
14907             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14908               var _scope = $scope;
14909
14910               var grid = uiGridCtrl.grid;
14911               uiGridCellNavService.initializeGrid(grid);
14912
14913               uiGridCtrl.cellNav = {};
14914
14915               //Ensure that the object has all of the methods we expect it to
14916               uiGridCtrl.cellNav.makeRowCol = function (obj) {
14917                 if (!(obj instanceof GridRowColumn)) {
14918                   obj = new GridRowColumn(obj.row, obj.col);
14919                 }
14920                 return obj;
14921               };
14922
14923               uiGridCtrl.cellNav.getActiveCell = function () {
14924                 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
14925                 if (elms.length > 0){
14926                   return elms[0];
14927                 }
14928
14929                 return undefined;
14930               };
14931
14932               uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
14933                 modifierDown = !(modifierDown === undefined || !modifierDown);
14934
14935                 newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
14936
14937                 uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
14938                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
14939               };
14940
14941               uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
14942                 grid.cellNav.focusedCells = [];
14943                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
14944               };
14945
14946               uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
14947                 modifierDown = !(modifierDown === undefined || !modifierDown);
14948
14949                 rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
14950
14951                 var row = rowCol.row,
14952                   col = rowCol.col;
14953
14954                 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
14955
14956                 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
14957                   var newRowCol = new GridRowColumn(row, col);
14958
14959                   if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col){
14960                     grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
14961                     grid.cellNav.lastRowCol = newRowCol;  
14962                   }
14963                   if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
14964                     grid.cellNav.focusedCells.push(rowCol);
14965                   } else {
14966                     grid.cellNav.focusedCells = [rowCol];
14967                   }
14968                 } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
14969                   rowColSelectIndex >= 0) {
14970
14971                   grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
14972                 }
14973               };
14974
14975               uiGridCtrl.cellNav.handleKeyDown = function (evt) {
14976                 var direction = uiGridCellNavService.getDirection(evt);
14977                 if (direction === null) {
14978                   return null;
14979                 }
14980
14981                 var containerId = 'body';
14982                 if (evt.uiGridTargetRenderContainerId) {
14983                   containerId = evt.uiGridTargetRenderContainerId;
14984                 }
14985
14986                 // Get the last-focused row+col combo
14987                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14988                 if (lastRowCol) {
14989                   // Figure out which new row+combo we're navigating to
14990                   var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
14991                   var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
14992                   var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
14993                   // Shift+tab on top-left cell should exit cellnav on render container
14994                   if (
14995                     // Navigating left
14996                     direction === uiGridCellNavConstants.direction.LEFT &&
14997                     // New col is last col (i.e. wrap around)
14998                     rowCol.col === focusableCols[focusableCols.length - 1] &&
14999                     // Staying on same row, which means we're at first row
15000                     rowCol.row === lastRowCol.row &&
15001                     evt.keyCode === uiGridConstants.keymap.TAB &&
15002                     evt.shiftKey
15003                   ) {
15004                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15005                     uiGridCtrl.cellNav.clearFocus();
15006                     return true;
15007                   }
15008                   // Tab on bottom-right cell should exit cellnav on render container
15009                   else if (
15010                     direction === uiGridCellNavConstants.direction.RIGHT &&
15011                     // New col is first col (i.e. wrap around)
15012                     rowCol.col === focusableCols[0] &&
15013                     // Staying on same row, which means we're at first row
15014                     rowCol.row === lastRowCol.row &&
15015                     evt.keyCode === uiGridConstants.keymap.TAB &&
15016                     !evt.shiftKey
15017                   ) {
15018                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15019                     uiGridCtrl.cellNav.clearFocus();
15020                     return true;
15021                   }
15022
15023                   // Scroll to the new cell, if it's not completely visible within the render container's viewport
15024                   grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
15025                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15026                   });
15027
15028
15029                   evt.stopPropagation();
15030                   evt.preventDefault();
15031
15032                   return false;
15033                 }
15034               };
15035             },
15036             post: function ($scope, $elm, $attrs, uiGridCtrl) {
15037               var _scope = $scope;
15038               var grid = uiGridCtrl.grid;
15039
15040               function addAriaLiveRegion(){
15041                 // Thanks to google docs for the inspiration behind how to do this
15042                 // XXX: Why is this entire mess nessasary?
15043                 // Because browsers take a lot of coercing to get them to read out live regions
15044                 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
15045                 var ariaNotifierDomElt = '<div ' +
15046                                            'id="' + grid.id +'-aria-speakable" ' +
15047                                            'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
15048                                            'aria-live="assertive" ' +
15049                                            'role="region" ' +
15050                                            'aria-atomic="true" ' +
15051                                            'aria-hidden="false" ' +
15052                                            'aria-relevant="additions" ' +
15053                                            '>' +
15054                                            '&nbsp;' +
15055                                          '</div>';
15056
15057                 var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
15058                 $elm.prepend(ariaNotifier);
15059                 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
15060                   /*
15061                    * If the cell nav event was because of a focus event then we don't want to
15062                    * change the notifier text.
15063                    * Reasoning: Voice Over fires a focus events when moving arround the grid.
15064                    * If the screen reader is handing the grid nav properly then we don't need to
15065                    * use the alert to notify the user of the movement.
15066                    * In all other cases we do want a notification event.
15067                    */
15068                   if (originEvt && originEvt.type === 'focus'){return;}
15069
15070                   function setNotifyText(text){
15071                     if (text === ariaNotifier.text()){return;}
15072                     ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
15073                     /*
15074                      * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
15075                      */
15076                     ariaNotifier[0].innerHTML = "";
15077                     ariaNotifier[0].style.visibility = 'hidden';
15078                     ariaNotifier[0].style.visibility = 'visible';
15079                     if (text !== ''){
15080                       ariaNotifier[0].style.clip = 'auto';
15081                       /*
15082                        * The space after the text is something that google docs does.
15083                        */
15084                       ariaNotifier[0].appendChild(document.createTextNode(text + " "));
15085                       ariaNotifier[0].style.visibility = 'hidden';
15086                       ariaNotifier[0].style.visibility = 'visible';
15087                     }
15088                   }
15089
15090                   var values = [];
15091                   var currentSelection = grid.api.cellNav.getCurrentSelection();
15092                   for (var i = 0; i < currentSelection.length; i++) {
15093                     values.push(currentSelection[i].getIntersectionValueFiltered());
15094                   }
15095                   var cellText = values.toString();
15096                   setNotifyText(cellText);
15097
15098                 });
15099               }
15100               addAriaLiveRegion();
15101             }
15102           };
15103         }
15104       };
15105     }]);
15106
15107   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
15108     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
15109       return {
15110         replace: true,
15111         priority: -99999, //this needs to run very last
15112         require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
15113         scope: false,
15114         compile: function () {
15115           return {
15116             post: function ($scope, $elm, $attrs, controllers) {
15117               var uiGridCtrl = controllers[0],
15118                  renderContainerCtrl = controllers[1],
15119                  uiGridCellnavCtrl = controllers[2];
15120
15121               // Skip attaching cell-nav specific logic if the directive is not attached above us
15122               if (!uiGridCtrl.grid.api.cellNav) { return; }
15123
15124               var containerId = renderContainerCtrl.containerId;
15125
15126               var grid = uiGridCtrl.grid;
15127
15128               //run each time a render container is created
15129               uiGridCellNavService.decorateRenderContainers(grid);
15130
15131               // focusser only created for body
15132               if (containerId !== 'body') {
15133                 return;
15134               }
15135
15136
15137
15138               if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
15139                 $elm.attr('aria-multiselectable', true);
15140               } else {
15141                 $elm.attr('aria-multiselectable', false);
15142               }
15143
15144               //add an element with no dimensions that can be used to set focus and capture keystrokes
15145               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);
15146               $elm.append(focuser);
15147
15148               focuser.on('focus', function (evt) {
15149                 evt.uiGridTargetRenderContainerId = containerId;
15150                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15151                 if (rowCol === null) {
15152                   rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
15153                   if (rowCol.row && rowCol.col) {
15154                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15155                   }
15156                 }
15157               });
15158
15159               uiGridCellnavCtrl.setAriaActivedescendant = function(id){
15160                 $elm.attr('aria-activedescendant', id);
15161               };
15162
15163               uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
15164                 if ($elm.attr('aria-activedescendant') === id){
15165                   $elm.attr('aria-activedescendant', '');
15166                 }
15167               };
15168
15169
15170               uiGridCtrl.focus = function () {
15171                 gridUtil.focus.byElement(focuser[0]);
15172                 //allow for first time grid focus
15173               };
15174
15175               var viewPortKeyDownWasRaisedForRowCol = null;
15176               // Bind to keydown events in the render container
15177               focuser.on('keydown', function (evt) {
15178                 evt.uiGridTargetRenderContainerId = containerId;
15179                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15180                 var result = uiGridCtrl.cellNav.handleKeyDown(evt);
15181                 if (result === null) {
15182                   uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
15183                   viewPortKeyDownWasRaisedForRowCol = rowCol;
15184                 }
15185               });
15186               //Bind to keypress events in the render container
15187               //keypress events are needed by edit function so the key press
15188               //that initiated an edit is not lost
15189               //must fire the event in a timeout so the editor can
15190               //initialize and subscribe to the event on another event loop
15191               focuser.on('keypress', function (evt) {
15192                 if (viewPortKeyDownWasRaisedForRowCol) {
15193                   $timeout(function () {
15194                     uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
15195                   },4);
15196
15197                   viewPortKeyDownWasRaisedForRowCol = null;
15198                 }
15199               });
15200
15201               $scope.$on('$destroy', function(){
15202                 //Remove all event handlers associated with this focuser.
15203                 focuser.off();
15204               });
15205
15206             }
15207           };
15208         }
15209       };
15210     }]);
15211
15212   module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
15213     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
15214       return {
15215         replace: true,
15216         priority: -99999, //this needs to run very last
15217         require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
15218         scope: false,
15219         compile: function () {
15220           return {
15221             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15222             },
15223             post: function ($scope, $elm, $attrs, controllers) {
15224               var uiGridCtrl = controllers[0],
15225                 renderContainerCtrl = controllers[1];
15226
15227               // Skip attaching cell-nav specific logic if the directive is not attached above us
15228               if (!uiGridCtrl.grid.api.cellNav) { return; }
15229
15230               var containerId = renderContainerCtrl.containerId;
15231               //no need to process for other containers
15232               if (containerId !== 'body') {
15233                 return;
15234               }
15235
15236               var grid = uiGridCtrl.grid;
15237
15238               grid.api.core.on.scrollBegin($scope, function (args) {
15239
15240                 // Skip if there's no currently-focused cell
15241                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15242                 if (lastRowCol === null) {
15243                   return;
15244                 }
15245
15246                 //if not in my container, move on
15247                 //todo: worry about horiz scroll
15248                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15249                   return;
15250                 }
15251
15252                 uiGridCtrl.cellNav.clearFocus();
15253
15254               });
15255
15256               grid.api.core.on.scrollEnd($scope, function (args) {
15257                 // Skip if there's no currently-focused cell
15258                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15259                 if (lastRowCol === null) {
15260                   return;
15261                 }
15262
15263                 //if not in my container, move on
15264                 //todo: worry about horiz scroll
15265                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15266                   return;
15267                 }
15268
15269                 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
15270
15271               });
15272
15273               grid.api.cellNav.on.navigate($scope, function () {
15274                 //focus again because it can be lost
15275                  uiGridCtrl.focus();
15276               });
15277
15278             }
15279           };
15280         }
15281       };
15282     }]);
15283
15284   /**
15285    *  @ngdoc directive
15286    *  @name ui.grid.cellNav.directive:uiGridCell
15287    *  @element div
15288    *  @restrict A
15289    *  @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
15290    */
15291   module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
15292     function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
15293       return {
15294         priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
15295         restrict: 'A',
15296         require: ['^uiGrid', '?^uiGridCellnav'],
15297         scope: false,
15298         link: function ($scope, $elm, $attrs, controllers) {
15299           var uiGridCtrl = controllers[0],
15300               uiGridCellnavCtrl = controllers[1];
15301           // Skip attaching cell-nav specific logic if the directive is not attached above us
15302           if (!uiGridCtrl.grid.api.cellNav) { return; }
15303
15304           if (!$scope.col.colDef.allowCellFocus) {
15305             return;
15306           }
15307
15308           //Convinience local variables
15309           var grid = uiGridCtrl.grid;
15310           $scope.focused = false;
15311
15312           // Make this cell focusable but only with javascript/a mouse click
15313           $elm.attr('tabindex', -1);
15314
15315           // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
15316           $elm.find('div').on('click', function (evt) {
15317             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
15318
15319             evt.stopPropagation();
15320             $scope.$apply();
15321           });
15322
15323
15324           /*
15325            * XXX Hack for screen readers.
15326            * This allows the grid to focus using only the screen reader cursor.
15327            * Since the focus event doesn't include key press information we can't use it
15328            * as our primary source of the event.
15329            */
15330           $elm.on('mousedown', preventMouseDown);
15331
15332           //turn on and off for edit events
15333           if (uiGridCtrl.grid.api.edit) {
15334             uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
15335               $elm.off('mousedown', preventMouseDown);
15336             });
15337
15338             uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
15339               $elm.on('mousedown', preventMouseDown);
15340             });
15341
15342             uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
15343               $elm.on('mousedown', preventMouseDown);
15344             });
15345           }
15346
15347           function preventMouseDown(evt) {
15348             //Prevents the foucus event from firing if the click event is already going to fire.
15349             //If both events fire it will cause bouncing behavior.
15350             evt.preventDefault();
15351           }
15352
15353           //You can only focus on elements with a tabindex value
15354           $elm.on('focus', function (evt) {
15355             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
15356             evt.stopPropagation();
15357             $scope.$apply();
15358           });
15359
15360           // This event is fired for all cells.  If the cell matches, then focus is set
15361           $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown) {
15362             var isFocused = grid.cellNav.focusedCells.some(function(focusedRowCol, index){
15363               return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
15364             });
15365             if (isFocused){
15366               setFocused();
15367             } else {
15368               clearFocus();
15369             }
15370           });
15371
15372           function setFocused() {
15373             if (!$scope.focused){
15374               var div = $elm.find('div');
15375               div.addClass('ui-grid-cell-focus');
15376               $elm.attr('aria-selected', true);
15377               uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
15378               $scope.focused = true;
15379             }
15380           }
15381
15382           function clearFocus() {
15383             if ($scope.focused){
15384               var div = $elm.find('div');
15385               div.removeClass('ui-grid-cell-focus');
15386               $elm.attr('aria-selected', false);
15387               uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
15388               $scope.focused = false;
15389             }
15390           }
15391
15392           $scope.$on('$destroy', function () {
15393             //.off withouth paramaters removes all handlers
15394             $elm.find('div').off();
15395             $elm.off();
15396           });
15397         }
15398       };
15399     }]);
15400
15401 })();
15402
15403 (function () {
15404   'use strict';
15405
15406   /**
15407    * @ngdoc overview
15408    * @name ui.grid.edit
15409    * @description
15410    *
15411    * # ui.grid.edit
15412    *
15413    * <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>
15414    *
15415    * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
15416    * a keyboard.
15417    * <br/>
15418    * <br/>
15419    * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
15420    * user to key data and then tab, arrow, or enter to the cells beside or below.
15421    *
15422    * <div doc-module-components="ui.grid.edit"></div>
15423    */
15424
15425   var module = angular.module('ui.grid.edit', ['ui.grid']);
15426
15427   /**
15428    *  @ngdoc object
15429    *  @name ui.grid.edit.constant:uiGridEditConstants
15430    *
15431    *  @description constants available in edit module
15432    */
15433   module.constant('uiGridEditConstants', {
15434     EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
15435     //must be lowercase because template bulder converts to lower
15436     EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
15437     events: {
15438       BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
15439       END_CELL_EDIT: 'uiGridEventEndCellEdit',
15440       CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
15441     }
15442   });
15443
15444   /**
15445    *  @ngdoc service
15446    *  @name ui.grid.edit.service:uiGridEditService
15447    *
15448    *  @description Services for editing features
15449    */
15450   module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
15451     function ($q, uiGridConstants, gridUtil) {
15452
15453       var service = {
15454
15455         initializeGrid: function (grid) {
15456
15457           service.defaultGridOptions(grid.options);
15458
15459           grid.registerColumnBuilder(service.editColumnBuilder);
15460           grid.edit = {};
15461
15462           /**
15463            *  @ngdoc object
15464            *  @name ui.grid.edit.api:PublicApi
15465            *
15466            *  @description Public Api for edit feature
15467            */
15468           var publicApi = {
15469             events: {
15470               edit: {
15471                 /**
15472                  * @ngdoc event
15473                  * @name afterCellEdit
15474                  * @eventOf  ui.grid.edit.api:PublicApi
15475                  * @description raised when cell editing is complete
15476                  * <pre>
15477                  *      gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
15478                  * </pre>
15479                  * @param {object} rowEntity the options.data element that was edited
15480                  * @param {object} colDef the column that was edited
15481                  * @param {object} newValue new value
15482                  * @param {object} oldValue old value
15483                  */
15484                 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
15485                 },
15486                 /**
15487                  * @ngdoc event
15488                  * @name beginCellEdit
15489                  * @eventOf  ui.grid.edit.api:PublicApi
15490                  * @description raised when cell editing starts on a cell
15491                  * <pre>
15492                  *      gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
15493                  * </pre>
15494                  * @param {object} rowEntity the options.data element that was edited
15495                  * @param {object} colDef the column that was edited
15496                  * @param {object} triggerEvent the event that triggered the edit.  Useful to prevent losing keystrokes on some
15497                  *                 complex editors
15498                  */
15499                 beginCellEdit: function (rowEntity, colDef, triggerEvent) {
15500                 },
15501                 /**
15502                  * @ngdoc event
15503                  * @name cancelCellEdit
15504                  * @eventOf  ui.grid.edit.api:PublicApi
15505                  * @description raised when cell editing is cancelled on a cell
15506                  * <pre>
15507                  *      gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
15508                  * </pre>
15509                  * @param {object} rowEntity the options.data element that was edited
15510                  * @param {object} colDef the column that was edited
15511                  */
15512                 cancelCellEdit: function (rowEntity, colDef) {
15513                 }
15514               }
15515             },
15516             methods: {
15517               edit: { }
15518             }
15519           };
15520
15521           grid.api.registerEventsFromObject(publicApi.events);
15522           //grid.api.registerMethodsFromObject(publicApi.methods);
15523
15524         },
15525
15526         defaultGridOptions: function (gridOptions) {
15527
15528           /**
15529            *  @ngdoc object
15530            *  @name ui.grid.edit.api:GridOptions
15531            *
15532            *  @description Options for configuring the edit feature, these are available to be
15533            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15534            */
15535
15536           /**
15537            *  @ngdoc object
15538            *  @name enableCellEdit
15539            *  @propertyOf  ui.grid.edit.api:GridOptions
15540            *  @description If defined, sets the default value for the editable flag on each individual colDefs
15541            *  if their individual enableCellEdit configuration is not defined. Defaults to undefined.
15542            */
15543
15544           /**
15545            *  @ngdoc object
15546            *  @name cellEditableCondition
15547            *  @propertyOf  ui.grid.edit.api:GridOptions
15548            *  @description If specified, either a value or function to be used by all columns before editing.
15549            *  If falsy, then editing of cell is not allowed.
15550            *  @example
15551            *  <pre>
15552            *  function($scope){
15553            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15554            *    return true;
15555            *  }
15556            *  </pre>
15557            */
15558           gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
15559
15560           /**
15561            *  @ngdoc object
15562            *  @name editableCellTemplate
15563            *  @propertyOf  ui.grid.edit.api:GridOptions
15564            *  @description If specified, cellTemplate to use as the editor for all columns.
15565            *  <br/> defaults to 'ui-grid/cellTextEditor'
15566            */
15567
15568           /**
15569            *  @ngdoc object
15570            *  @name enableCellEditOnFocus
15571            *  @propertyOf  ui.grid.edit.api:GridOptions
15572            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15573            *  <br/>_requires cellNav feature and the edit feature to be enabled_
15574            */
15575             //enableCellEditOnFocus can only be used if cellnav module is used
15576           gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
15577         },
15578
15579         /**
15580          * @ngdoc service
15581          * @name editColumnBuilder
15582          * @methodOf ui.grid.edit.service:uiGridEditService
15583          * @description columnBuilder function that adds edit properties to grid column
15584          * @returns {promise} promise that will load any needed templates when resolved
15585          */
15586         editColumnBuilder: function (colDef, col, gridOptions) {
15587
15588           var promises = [];
15589
15590           /**
15591            *  @ngdoc object
15592            *  @name ui.grid.edit.api:ColumnDef
15593            *
15594            *  @description Column Definition for edit feature, these are available to be
15595            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15596            */
15597
15598           /**
15599            *  @ngdoc object
15600            *  @name enableCellEdit
15601            *  @propertyOf  ui.grid.edit.api:ColumnDef
15602            *  @description enable editing on column
15603            */
15604           colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
15605             (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
15606
15607           /**
15608            *  @ngdoc object
15609            *  @name cellEditableCondition
15610            *  @propertyOf  ui.grid.edit.api:ColumnDef
15611            *  @description If specified, either a value or function evaluated before editing cell.  If falsy, then editing of cell is not allowed.
15612            *  @example
15613            *  <pre>
15614            *  function($scope){
15615            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15616            *    return true;
15617            *  }
15618            *  </pre>
15619            */
15620           colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition :  colDef.cellEditableCondition;
15621
15622           /**
15623            *  @ngdoc object
15624            *  @name editableCellTemplate
15625            *  @propertyOf  ui.grid.edit.api:ColumnDef
15626            *  @description cell template to be used when editing this column. Can be Url or text template
15627            *  <br/>Defaults to gridOptions.editableCellTemplate
15628            */
15629           if (colDef.enableCellEdit) {
15630             colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
15631
15632             promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
15633               .then(
15634               function (template) {
15635                 col.editableCellTemplate = template;
15636               },
15637               function (res) {
15638                 // Todo handle response error here?
15639                 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
15640               }));
15641           }
15642
15643           /**
15644            *  @ngdoc object
15645            *  @name enableCellEditOnFocus
15646            *  @propertyOf  ui.grid.edit.api:ColumnDef
15647            *  @requires ui.grid.cellNav
15648            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15649            *  <br>_requires both the cellNav feature and the edit feature to be enabled_
15650            */
15651             //enableCellEditOnFocus can only be used if cellnav module is used
15652           colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
15653
15654
15655           /**
15656            *  @ngdoc string
15657            *  @name editModelField
15658            *  @propertyOf  ui.grid.edit.api:ColumnDef
15659            *  @description a bindable string value that is used when binding to edit controls instead of colDef.field
15660            *  <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}.  The
15661            *  grid should display state.name in the cell and sort/filter based on the state.name property but the editor
15662            *  requires the full state object.
15663            *  <br/>colDef.field = 'state.name'
15664            *  <br/>colDef.editModelField = 'state'
15665            */
15666           //colDef.editModelField
15667
15668           return $q.all(promises);
15669         },
15670
15671         /**
15672          * @ngdoc service
15673          * @name isStartEditKey
15674          * @methodOf ui.grid.edit.service:uiGridEditService
15675          * @description  Determines if a keypress should start editing.  Decorate this service to override with your
15676          * own key events.  See service decorator in angular docs.
15677          * @param {Event} evt keydown event
15678          * @returns {boolean} true if an edit should start
15679          */
15680         isStartEditKey: function (evt) {
15681           if (evt.metaKey ||
15682               evt.keyCode === uiGridConstants.keymap.ESC ||
15683               evt.keyCode === uiGridConstants.keymap.SHIFT ||
15684               evt.keyCode === uiGridConstants.keymap.CTRL ||
15685               evt.keyCode === uiGridConstants.keymap.ALT ||
15686               evt.keyCode === uiGridConstants.keymap.WIN ||
15687               evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
15688
15689              evt.keyCode === uiGridConstants.keymap.LEFT ||
15690             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
15691
15692             evt.keyCode === uiGridConstants.keymap.RIGHT ||
15693             evt.keyCode === uiGridConstants.keymap.TAB ||
15694
15695             evt.keyCode === uiGridConstants.keymap.UP ||
15696             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
15697
15698             evt.keyCode === uiGridConstants.keymap.DOWN ||
15699             evt.keyCode === uiGridConstants.keymap.ENTER) {
15700             return false;
15701
15702           }
15703           return true;
15704         }
15705
15706
15707       };
15708
15709       return service;
15710
15711     }]);
15712
15713   /**
15714    *  @ngdoc directive
15715    *  @name ui.grid.edit.directive:uiGridEdit
15716    *  @element div
15717    *  @restrict A
15718    *
15719    *  @description Adds editing features to the ui-grid directive.
15720    *
15721    *  @example
15722    <example module="app">
15723    <file name="app.js">
15724    var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
15725
15726    app.controller('MainCtrl', ['$scope', function ($scope) {
15727       $scope.data = [
15728         { name: 'Bob', title: 'CEO' },
15729             { name: 'Frank', title: 'Lowly Developer' }
15730       ];
15731
15732       $scope.columnDefs = [
15733         {name: 'name', enableCellEdit: true},
15734         {name: 'title', enableCellEdit: true}
15735       ];
15736     }]);
15737    </file>
15738    <file name="index.html">
15739    <div ng-controller="MainCtrl">
15740    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
15741    </div>
15742    </file>
15743    </example>
15744    */
15745   module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
15746     return {
15747       replace: true,
15748       priority: 0,
15749       require: '^uiGrid',
15750       scope: false,
15751       compile: function () {
15752         return {
15753           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15754             uiGridEditService.initializeGrid(uiGridCtrl.grid);
15755           },
15756           post: function ($scope, $elm, $attrs, uiGridCtrl) {
15757           }
15758         };
15759       }
15760     };
15761   }]);
15762
15763   /**
15764    *  @ngdoc directive
15765    *  @name ui.grid.edit.directive:uiGridRenderContainer
15766    *  @element div
15767    *  @restrict A
15768    *
15769    *  @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
15770    *
15771    */
15772   module.directive('uiGridViewport', [ 'uiGridEditConstants',
15773     function ( uiGridEditConstants) {
15774       return {
15775         replace: true,
15776         priority: -99998, //run before cellNav
15777         require: ['^uiGrid', '^uiGridRenderContainer'],
15778         scope: false,
15779         compile: function () {
15780           return {
15781             post: function ($scope, $elm, $attrs, controllers) {
15782               var uiGridCtrl = controllers[0];
15783
15784               // Skip attaching if edit and cellNav is not enabled
15785               if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
15786
15787               var containerId =  controllers[1].containerId;
15788               //no need to process for other containers
15789               if (containerId !== 'body') {
15790                 return;
15791               }
15792
15793               //refocus on the grid
15794               $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
15795                 uiGridCtrl.focus();
15796               });
15797               $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
15798                 uiGridCtrl.focus();
15799               });
15800
15801             }
15802           };
15803         }
15804       };
15805     }]);
15806
15807   /**
15808    *  @ngdoc directive
15809    *  @name ui.grid.edit.directive:uiGridCell
15810    *  @element div
15811    *  @restrict A
15812    *
15813    *  @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
15814    *  Editing Actions.
15815    *
15816    *  Binds edit start events to the uiGridCell element.  When the events fire, the gridCell element is appended
15817    *  with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
15818    *
15819    *  The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
15820    *  and do the initial steps needed to edit the cell (setfocus on input element, etc).
15821    *
15822    *  When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
15823    *  it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
15824    *
15825    *  If editableCellTemplate recognizes that the editing has been cancelled (esc key)
15826    *  it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event.  The original value
15827    *  will be set back on the model by the uiGridCell directive.
15828    *
15829    *  Events that invoke editing:
15830    *    - dblclick
15831    *    - F2 keydown (when using cell selection)
15832    *
15833    *  Events that end editing:
15834    *    - Dependent on the specific editableCellTemplate
15835    *    - Standards should be blur and enter keydown
15836    *
15837    *  Events that cancel editing:
15838    *    - Dependent on the specific editableCellTemplate
15839    *    - Standards should be Esc keydown
15840    *
15841    *  Grid Events that end editing:
15842    *    - uiGridConstants.events.GRID_SCROLL
15843    *
15844    */
15845
15846   /**
15847    *  @ngdoc object
15848    *  @name ui.grid.edit.api:GridRow
15849    *
15850    *  @description GridRow options for edit feature, these are available to be
15851    *  set internally only, by other features
15852    */
15853
15854   /**
15855    *  @ngdoc object
15856    *  @name enableCellEdit
15857    *  @propertyOf  ui.grid.edit.api:GridRow
15858    *  @description enable editing on row, grouping for example might disable editing on group header rows
15859    */
15860
15861   module.directive('uiGridCell',
15862     ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
15863       function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
15864         var touchstartTimeout = 500;
15865         if ($injector.has('uiGridCellNavService')) {
15866           var uiGridCellNavService = $injector.get('uiGridCellNavService');
15867         }
15868
15869         return {
15870           priority: -100, // run after default uiGridCell directive
15871           restrict: 'A',
15872           scope: false,
15873           require: '?^uiGrid',
15874           link: function ($scope, $elm, $attrs, uiGridCtrl) {
15875             var html;
15876             var origCellValue;
15877             var inEdit = false;
15878             var cellModel;
15879             var cancelTouchstartTimeout;
15880
15881             var editCellScope;
15882
15883             if (!$scope.col.colDef.enableCellEdit) {
15884               return;
15885             }
15886
15887             var cellNavNavigateDereg = function() {};
15888             var viewPortKeyDownDereg = function() {};
15889
15890
15891             var setEditable = function() {
15892               if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
15893                 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
15894                   registerBeginEditEvents();
15895                 }
15896               } else {
15897                 if ($scope.beginEditEventsWired) {
15898                   cancelBeginEditEvents();
15899                 }
15900               }
15901             };
15902
15903             setEditable();
15904
15905             var rowWatchDereg = $scope.$watch('row', function (n, o) {
15906               if (n !== o) {
15907                 setEditable();
15908               }
15909             });
15910
15911
15912             $scope.$on( '$destroy', rowWatchDereg );
15913
15914             function registerBeginEditEvents() {
15915               $elm.on('dblclick', beginEdit);
15916
15917               // Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
15918               $elm.on('touchstart', touchStart);
15919
15920               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15921
15922                 viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
15923                   if (rowCol === null) {
15924                     return;
15925                   }
15926
15927                   if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
15928                     //important to do this before scrollToIfNecessary
15929                     beginEditKeyDown(evt);
15930                   }
15931                 });
15932
15933                 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol) {
15934                   if ($scope.col.colDef.enableCellEditOnFocus) {
15935                     // Don't begin edit if the cell hasn't changed
15936                     if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
15937                       newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
15938                       $timeout(function () {
15939                         beginEdit();
15940                       });
15941                     }
15942                   }
15943                 });
15944               }
15945
15946               $scope.beginEditEventsWired = true;
15947
15948             }
15949
15950             function touchStart(event) {
15951               // jQuery masks events
15952               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
15953                 event = event.originalEvent;
15954               }
15955
15956               // Bind touchend handler
15957               $elm.on('touchend', touchEnd);
15958
15959               // Start a timeout
15960               cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
15961
15962               // Timeout's done! Start the edit
15963               cancelTouchstartTimeout.then(function () {
15964                 // Use setTimeout to start the edit because beginEdit expects to be outside of $digest
15965                 setTimeout(beginEdit, 0);
15966
15967                 // Undbind the touchend handler, we don't need it anymore
15968                 $elm.off('touchend', touchEnd);
15969               });
15970             }
15971
15972             // Cancel any touchstart timeout
15973             function touchEnd(event) {
15974               $timeout.cancel(cancelTouchstartTimeout);
15975               $elm.off('touchend', touchEnd);
15976             }
15977
15978             function cancelBeginEditEvents() {
15979               $elm.off('dblclick', beginEdit);
15980               $elm.off('keydown', beginEditKeyDown);
15981               $elm.off('touchstart', touchStart);
15982               cellNavNavigateDereg();
15983               viewPortKeyDownDereg();
15984               $scope.beginEditEventsWired = false;
15985             }
15986
15987             function beginEditKeyDown(evt) {
15988               if (uiGridEditService.isStartEditKey(evt)) {
15989                 beginEdit(evt);
15990               }
15991             }
15992
15993             function shouldEdit(col, row) {
15994               return !row.isSaving &&
15995                 ( angular.isFunction(col.colDef.cellEditableCondition) ?
15996                     col.colDef.cellEditableCondition($scope) :
15997                     col.colDef.cellEditableCondition );
15998             }
15999
16000
16001             function beginEdit(triggerEvent) {
16002               //we need to scroll the cell into focus before invoking the editor
16003               $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
16004                 .then(function () {
16005                   beginEditAfterScroll(triggerEvent);
16006                 });
16007             }
16008
16009             /**
16010              *  @ngdoc property
16011              *  @name editDropdownOptionsArray
16012              *  @propertyOf ui.grid.edit.api:ColumnDef
16013              *  @description an array of values in the format
16014              *  [ {id: xxx, value: xxx} ], which is populated
16015              *  into the edit dropdown
16016              *
16017              */
16018             /**
16019              *  @ngdoc property
16020              *  @name editDropdownIdLabel
16021              *  @propertyOf ui.grid.edit.api:ColumnDef
16022              *  @description the label for the "id" field
16023              *  in the editDropdownOptionsArray.  Defaults
16024              *  to 'id'
16025              *  @example
16026              *  <pre>
16027              *    $scope.gridOptions = {
16028              *      columnDefs: [
16029              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16030              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16031              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16032              *      ],
16033              *  </pre>
16034              *
16035              */
16036             /**
16037              *  @ngdoc property
16038              *  @name editDropdownRowEntityOptionsArrayPath
16039              *  @propertyOf ui.grid.edit.api:ColumnDef
16040              *  @description a path to a property on row.entity containing an
16041              *  array of values in the format
16042              *  [ {id: xxx, value: xxx} ], which will be used to populate
16043              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16044              *  the backing row entity.
16045              *  If this property is set then editDropdownOptionsArray will be ignored.
16046              *  @example
16047              *  <pre>
16048              *    $scope.gridOptions = {
16049              *      columnDefs: [
16050              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16051              *          editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
16052              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16053              *      ],
16054              *  </pre>
16055              *
16056              */
16057             /**
16058              *  @ngdoc service
16059              *  @name editDropdownOptionsFunction
16060              *  @methodOf ui.grid.edit.api:ColumnDef
16061              *  @description a function returning an array of values in the format
16062              *  [ {id: xxx, value: xxx} ], which will be used to populate
16063              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16064              *  the backing row entity with some kind of algorithm.
16065              *  If this property is set then both editDropdownOptionsArray and 
16066              *  editDropdownRowEntityOptionsArrayPath will be ignored.
16067              *  @param {object} rowEntity the options.data element that the returned array refers to
16068              *  @param {object} colDef the column that implements this dropdown
16069              *  @returns {object} an array of values in the format
16070              *  [ {id: xxx, value: xxx} ] used to populate the edit dropdown
16071              *  @example
16072              *  <pre>
16073              *    $scope.gridOptions = {
16074              *      columnDefs: [
16075              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16076              *          editDropdownOptionsFunction: function(rowEntity, colDef) {
16077              *            if (rowEntity.foo === 'bar') {
16078              *              return [{id: 'bar1', value: 'BAR 1'},
16079              *                      {id: 'bar2', value: 'BAR 2'},
16080              *                      {id: 'bar3', value: 'BAR 3'}];
16081              *            } else {
16082              *              return [{id: 'foo1', value: 'FOO 1'},
16083              *                      {id: 'foo2', value: 'FOO 2'}];
16084              *            }
16085              *          },
16086              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16087              *      ],
16088              *  </pre>
16089              *
16090              */
16091             /**
16092              *  @ngdoc property
16093              *  @name editDropdownValueLabel
16094              *  @propertyOf ui.grid.edit.api:ColumnDef
16095              *  @description the label for the "value" field
16096              *  in the editDropdownOptionsArray.  Defaults
16097              *  to 'value'
16098              *  @example
16099              *  <pre>
16100              *    $scope.gridOptions = {
16101              *      columnDefs: [
16102              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16103              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16104              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16105              *      ],
16106              *  </pre>
16107              *
16108              */
16109             /**
16110              *  @ngdoc property
16111              *  @name editDropdownFilter
16112              *  @propertyOf ui.grid.edit.api:ColumnDef
16113              *  @description A filter that you would like to apply to the values in the options list
16114              *  of the dropdown.  For example if you were using angular-translate you might set this
16115              *  to `'translate'`
16116              *  @example
16117              *  <pre>
16118              *    $scope.gridOptions = {
16119              *      columnDefs: [
16120              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16121              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16122              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
16123              *      ],
16124              *  </pre>
16125              *
16126              */
16127             function beginEditAfterScroll(triggerEvent) {
16128               // If we are already editing, then just skip this so we don't try editing twice...
16129               if (inEdit) {
16130                 return;
16131               }
16132
16133               if (!shouldEdit($scope.col, $scope.row)) {
16134                 return;
16135               }
16136
16137
16138               cellModel = $parse($scope.row.getQualifiedColField($scope.col));
16139               //get original value from the cell
16140               origCellValue = cellModel($scope);
16141
16142               html = $scope.col.editableCellTemplate;
16143
16144               if ($scope.col.colDef.editModelField) {
16145                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField));
16146               }
16147               else {
16148                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
16149               }
16150
16151               html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
16152
16153               var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
16154               html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
16155
16156               var inputType = 'text';
16157               switch ($scope.col.colDef.type){
16158                 case 'boolean':
16159                   inputType = 'checkbox';
16160                   break;
16161                 case 'number':
16162                   inputType = 'number';
16163                   break;
16164                 case 'date':
16165                   inputType = 'date';
16166                   break;
16167               }
16168               html = html.replace('INPUT_TYPE', inputType);
16169
16170               // In order to fill dropdown options we use:
16171               // - A function/promise or
16172               // - An array inside of row entity if no function exists or
16173               // - A single array for the whole column if none of the previous exists.
16174               var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
16175               if (editDropdownOptionsFunction) {
16176                 $q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
16177                         .then(function(result) {
16178                   $scope.editDropdownOptionsArray = result;
16179                 });
16180               } else {
16181                 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
16182                 if (editDropdownRowEntityOptionsArrayPath) {
16183                   $scope.editDropdownOptionsArray =  resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
16184                 }
16185                 else {
16186                   $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
16187                 }
16188               }
16189               $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
16190               $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
16191
16192               var cellElement;
16193               var createEditor = function(){
16194                 inEdit = true;
16195                 cancelBeginEditEvents();
16196                 var cellElement = angular.element(html);
16197                 $elm.append(cellElement);
16198                 editCellScope = $scope.$new();
16199                 $compile(cellElement)(editCellScope);
16200                 var gridCellContentsEl = angular.element($elm.children()[0]);
16201                 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
16202               };
16203               if (!$rootScope.$$phase) {
16204                 $scope.$apply(createEditor);
16205               } else {
16206                 createEditor();
16207               }
16208
16209               //stop editing when grid is scrolled
16210               var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
16211                 if ($scope.grid.disableScrolling) {
16212                   return;
16213                 }
16214                 endEdit();
16215                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16216                 deregOnGridScroll();
16217                 deregOnEndCellEdit();
16218                 deregOnCancelCellEdit();
16219               });
16220
16221               //end editing
16222               var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16223                 endEdit();
16224                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16225                 deregOnEndCellEdit();
16226                 deregOnGridScroll();
16227                 deregOnCancelCellEdit();
16228               });
16229
16230               //cancel editing
16231               var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16232                 cancelEdit();
16233                 deregOnCancelCellEdit();
16234                 deregOnGridScroll();
16235                 deregOnEndCellEdit();
16236               });
16237
16238               $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
16239               $timeout(function () {
16240                 //execute in a timeout to give any complex editor templates a cycle to completely render
16241                 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
16242               });
16243             }
16244
16245             function endEdit() {
16246               $scope.grid.disableScrolling = false;
16247               if (!inEdit) {
16248                 return;
16249               }
16250
16251               //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
16252               //back to grid here. The focus call needs to be before the $destroy and removal of the control,
16253               //otherwise ng-model-options of UpdateOn: 'blur' will not work.
16254               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16255                 uiGridCtrl.focus();
16256               }
16257
16258               var gridCellContentsEl = angular.element($elm.children()[0]);
16259               //remove edit element
16260               editCellScope.$destroy();
16261               angular.element($elm.children()[1]).remove();
16262               gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
16263               inEdit = false;
16264               registerBeginEditEvents();
16265               $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
16266             }
16267
16268             function cancelEdit() {
16269               $scope.grid.disableScrolling = false;
16270               if (!inEdit) {
16271                 return;
16272               }
16273               cellModel.assign($scope, origCellValue);
16274               $scope.$apply();
16275
16276               $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
16277               endEdit();
16278             }
16279
16280             // resolves a string path against the given object
16281             // shamelessly borrowed from
16282             // http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
16283             function resolveObjectFromPath(object, path) {
16284               path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
16285               path = path.replace(/^\./, '');           // strip a leading dot
16286               var a = path.split('.');
16287               while (a.length) {
16288                   var n = a.shift();
16289                   if (n in object) {
16290                       object = object[n];
16291                   } else {
16292                       return;
16293                   }
16294               }
16295               return object;
16296             }
16297
16298           }
16299         };
16300       }]);
16301
16302   /**
16303    *  @ngdoc directive
16304    *  @name ui.grid.edit.directive:uiGridEditor
16305    *  @element div
16306    *  @restrict A
16307    *
16308    *  @description input editor directive for editable fields.
16309    *  Provides EndEdit and CancelEdit events
16310    *
16311    *  Events that end editing:
16312    *     blur and enter keydown
16313    *
16314    *  Events that cancel editing:
16315    *    - Esc keydown
16316    *
16317    */
16318   module.directive('uiGridEditor',
16319     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
16320       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
16321         return {
16322           scope: true,
16323           require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
16324           compile: function () {
16325             return {
16326               pre: function ($scope, $elm, $attrs) {
16327
16328               },
16329               post: function ($scope, $elm, $attrs, controllers) {
16330                 var uiGridCtrl, renderContainerCtrl, ngModel;
16331                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16332                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16333                 if (controllers[2]) { ngModel = controllers[2]; }
16334
16335                 //set focus at start of edit
16336                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
16337                   $timeout(function () {
16338                     $elm[0].focus();
16339                     //only select text if it is not being replaced below in the cellNav viewPortKeyPress
16340                     if ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav)) {
16341                       $elm[0].select();
16342                     }
16343                     else {
16344                       //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
16345                       //fields should not allow setSelectionRange.  We ignore the error for those browsers
16346                       //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
16347                       try {
16348                         $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
16349                       }
16350                       catch (ex) {
16351                         //ignore
16352                       }
16353                     }
16354                   });
16355
16356                   //set the keystroke that started the edit event
16357                   //we must do this because the BeginEdit is done in a different event loop than the intitial
16358                   //keydown event
16359                   //fire this event for the keypress that is received
16360                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16361                     var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
16362                       if (uiGridEditService.isStartEditKey(evt)) {
16363                         ngModel.$setViewValue(String.fromCharCode( typeof evt.which === 'number' ? evt.which : evt.keyCode), evt);
16364                         ngModel.$render();
16365                       }
16366                       viewPortKeyDownUnregister();
16367                     });
16368                   }
16369
16370                   $elm.on('blur', function (evt) {
16371                     $scope.stopEdit(evt);
16372                   });
16373                 });
16374
16375
16376                 $scope.deepEdit = false;
16377
16378                 $scope.stopEdit = function (evt) {
16379                   if ($scope.inputForm && !$scope.inputForm.$valid) {
16380                     evt.stopPropagation();
16381                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16382                   }
16383                   else {
16384                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16385                   }
16386                   $scope.deepEdit = false;
16387                 };
16388
16389
16390                 $elm.on('click', function (evt) {
16391                   if ($elm[0].type !== 'checkbox') {
16392                     $scope.deepEdit = true;
16393                     $timeout(function () {
16394                       $scope.grid.disableScrolling = true;
16395                     });
16396                   }
16397                 });
16398
16399                 $elm.on('keydown', function (evt) {
16400                   switch (evt.keyCode) {
16401                     case uiGridConstants.keymap.ESC:
16402                       evt.stopPropagation();
16403                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16404                       break;
16405                   }
16406
16407                   if ($scope.deepEdit &&
16408                     (evt.keyCode === uiGridConstants.keymap.LEFT ||
16409                      evt.keyCode === uiGridConstants.keymap.RIGHT ||
16410                      evt.keyCode === uiGridConstants.keymap.UP ||
16411                      evt.keyCode === uiGridConstants.keymap.DOWN)) {
16412                     evt.stopPropagation();
16413                   }
16414                   // Pass the keydown event off to the cellNav service, if it exists
16415                   else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16416                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16417                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16418                       $scope.stopEdit(evt);
16419                     }
16420                   }
16421                   else {
16422                     //handle enter and tab for editing not using cellNav
16423                     switch (evt.keyCode) {
16424                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16425                       case uiGridConstants.keymap.TAB:
16426                         evt.stopPropagation();
16427                         evt.preventDefault();
16428                         $scope.stopEdit(evt);
16429                         break;
16430                     }
16431                   }
16432
16433                   return true;
16434                 });
16435               }
16436             };
16437           }
16438         };
16439       }]);
16440
16441   /**
16442    *  @ngdoc directive
16443    *  @name ui.grid.edit.directive:input
16444    *  @element input
16445    *  @restrict E
16446    *
16447    *  @description directive to provide binding between input[date] value and ng-model for angular 1.2
16448    *  It is similar to input[date] directive of angular 1.3
16449    *
16450    *  Supported date format for input is 'yyyy-MM-dd'
16451    *  The directive will set the $valid property of input element and the enclosing form to false if
16452    *  model is invalid date or value of input is entered wrong.
16453    *
16454    */
16455     module.directive('uiGridEditor', ['$filter', function ($filter) {
16456       function parseDateString(dateString) {
16457         if (typeof(dateString) === 'undefined' || dateString === '') {
16458           return null;
16459         }
16460         var parts = dateString.split('-');
16461         if (parts.length !== 3) {
16462           return null;
16463         }
16464         var year = parseInt(parts[0], 10);
16465         var month = parseInt(parts[1], 10);
16466         var day = parseInt(parts[2], 10);
16467
16468         if (month < 1 || year < 1 || day < 1) {
16469           return null;
16470         }
16471         return new Date(year, (month - 1), day);
16472       }
16473       return {
16474         priority: -100, // run after default uiGridEditor directive
16475         require: '?ngModel',
16476         link: function (scope, element, attrs, ngModel) {
16477
16478           if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
16479
16480             ngModel.$formatters.push(function (modelValue) {
16481               ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
16482               return $filter('date')(modelValue, 'yyyy-MM-dd');
16483             });
16484
16485             ngModel.$parsers.push(function (viewValue) {
16486               if (viewValue && viewValue.length > 0) {
16487                 var dateValue = parseDateString(viewValue);
16488                 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
16489                 return dateValue;
16490               }
16491               else {
16492                 ngModel.$setValidity(null, true);
16493                 return null;
16494               }
16495             });
16496           }
16497         }
16498       };
16499     }]);
16500
16501
16502   /**
16503    *  @ngdoc directive
16504    *  @name ui.grid.edit.directive:uiGridEditDropdown
16505    *  @element div
16506    *  @restrict A
16507    *
16508    *  @description dropdown editor for editable fields.
16509    *  Provides EndEdit and CancelEdit events
16510    *
16511    *  Events that end editing:
16512    *     blur and enter keydown, and any left/right nav
16513    *
16514    *  Events that cancel editing:
16515    *    - Esc keydown
16516    *
16517    */
16518   module.directive('uiGridEditDropdown',
16519     ['uiGridConstants', 'uiGridEditConstants',
16520       function (uiGridConstants, uiGridEditConstants) {
16521         return {
16522           require: ['?^uiGrid', '?^uiGridRenderContainer'],
16523           scope: true,
16524           compile: function () {
16525             return {
16526               pre: function ($scope, $elm, $attrs) {
16527
16528               },
16529               post: function ($scope, $elm, $attrs, controllers) {
16530                 var uiGridCtrl = controllers[0];
16531                 var renderContainerCtrl = controllers[1];
16532
16533                 //set focus at start of edit
16534                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16535                   $elm[0].focus();
16536                   $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
16537                   $elm.on('blur', function (evt) {
16538                     $scope.stopEdit(evt);
16539                   });
16540                 });
16541
16542
16543                 $scope.stopEdit = function (evt) {
16544                   // no need to validate a dropdown - invalid values shouldn't be
16545                   // available in the list
16546                   $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16547                 };
16548
16549                 $elm.on('keydown', function (evt) {
16550                   switch (evt.keyCode) {
16551                     case uiGridConstants.keymap.ESC:
16552                       evt.stopPropagation();
16553                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16554                       break;
16555                   }
16556                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16557                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16558                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16559                       $scope.stopEdit(evt);
16560                     }
16561                   }
16562                   else {
16563                     //handle enter and tab for editing not using cellNav
16564                     switch (evt.keyCode) {
16565                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16566                       case uiGridConstants.keymap.TAB:
16567                         evt.stopPropagation();
16568                         evt.preventDefault();
16569                         $scope.stopEdit(evt);
16570                         break;
16571                     }
16572                   }
16573                   return true;
16574                 });
16575               }
16576             };
16577           }
16578         };
16579       }]);
16580
16581   /**
16582    *  @ngdoc directive
16583    *  @name ui.grid.edit.directive:uiGridEditFileChooser
16584    *  @element div
16585    *  @restrict A
16586    *
16587    *  @description input editor directive for editable fields.
16588    *  Provides EndEdit and CancelEdit events
16589    *
16590    *  Events that end editing:
16591    *     blur and enter keydown
16592    *
16593    *  Events that cancel editing:
16594    *    - Esc keydown
16595    *
16596    */
16597   module.directive('uiGridEditFileChooser',
16598     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
16599       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
16600         return {
16601           scope: true,
16602           require: ['?^uiGrid', '?^uiGridRenderContainer'],
16603           compile: function () {
16604             return {
16605               pre: function ($scope, $elm, $attrs) {
16606
16607               },
16608               post: function ($scope, $elm, $attrs, controllers) {
16609                 var uiGridCtrl, renderContainerCtrl;
16610                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16611                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16612                 var grid = uiGridCtrl.grid;
16613
16614                 var handleFileSelect = function( event ){
16615                   var target = event.srcElement || event.target;
16616
16617                   if (target && target.files && target.files.length > 0) {
16618                     /**
16619                      *  @ngdoc property
16620                      *  @name editFileChooserCallback
16621                      *  @propertyOf  ui.grid.edit.api:ColumnDef
16622                      *  @description A function that should be called when any files have been chosen
16623                      *  by the user.  You should use this to process the files appropriately for your
16624                      *  application.
16625                      *
16626                      *  It passes the gridCol, the gridRow (from which you can get gridRow.entity),
16627                      *  and the files.  The files are in the format as returned from the file chooser,
16628                      *  an array of files, with each having useful information such as:
16629                      *  - `files[0].lastModifiedDate`
16630                      *  - `files[0].name`
16631                      *  - `files[0].size`  (appears to be in bytes)
16632                      *  - `files[0].type`  (MIME type by the looks)
16633                      *
16634                      *  Typically you would do something with these files - most commonly you would
16635                      *  use the filename or read the file itself in.  The example function does both.
16636                      *
16637                      *  @example
16638                      *  <pre>
16639                      *  editFileChooserCallBack: function(gridRow, gridCol, files ){
16640                      *    // ignore all but the first file, it can only choose one anyway
16641                      *    // set the filename into this column
16642                      *    gridRow.entity.filename = file[0].name;
16643                      *
16644                      *    // read the file and set it into a hidden column, which we may do stuff with later
16645                      *    var setFile = function(fileContent){
16646                      *      gridRow.entity.file = fileContent.currentTarget.result;
16647                      *    };
16648                      *    var reader = new FileReader();
16649                      *    reader.onload = setFile;
16650                      *    reader.readAsText( files[0] );
16651                      *  }
16652                      *  </pre>
16653                      */
16654                     if ( typeof($scope.col.colDef.editFileChooserCallback) === 'function' ) {
16655                       $scope.col.colDef.editFileChooserCallback($scope.row, $scope.col, target.files);
16656                     } else {
16657                       gridUtil.logError('You need to set colDef.editFileChooserCallback to use the file chooser');
16658                     }
16659
16660                     target.form.reset();
16661                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16662                   } else {
16663                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16664                   }
16665                 };
16666
16667                 $elm[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
16668
16669                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16670                   $elm[0].focus();
16671                   $elm[0].select();
16672
16673                   $elm.on('blur', function (evt) {
16674                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16675                   });
16676                 });
16677               }
16678             };
16679           }
16680         };
16681       }]);
16682
16683
16684 })();
16685
16686 (function () {
16687   'use strict';
16688
16689   /**
16690    * @ngdoc overview
16691    * @name ui.grid.expandable
16692    * @description
16693    *
16694    * # ui.grid.expandable
16695    *
16696    * <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>
16697    *
16698    * This module provides the ability to create subgrids with the ability to expand a row
16699    * to show the subgrid.
16700    *
16701    * <div doc-module-components="ui.grid.expandable"></div>
16702    */
16703   var module = angular.module('ui.grid.expandable', ['ui.grid']);
16704
16705   /**
16706    *  @ngdoc service
16707    *  @name ui.grid.expandable.service:uiGridExpandableService
16708    *
16709    *  @description Services for the expandable grid
16710    */
16711   module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
16712     var service = {
16713       initializeGrid: function (grid) {
16714
16715         grid.expandable = {};
16716         grid.expandable.expandedAll = false;
16717
16718         /**
16719          *  @ngdoc object
16720          *  @name enableExpandable
16721          *  @propertyOf  ui.grid.expandable.api:GridOptions
16722          *  @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
16723          *  within your application, or in specific modes on _this_ grid. Defaults to true.
16724          *  @example
16725          *  <pre>
16726          *    $scope.gridOptions = {
16727          *      enableExpandable: false
16728          *    }
16729          *  </pre>
16730          */
16731         grid.options.enableExpandable = grid.options.enableExpandable !== false;
16732
16733         /**
16734          *  @ngdoc object
16735          *  @name expandableRowHeight
16736          *  @propertyOf  ui.grid.expandable.api:GridOptions
16737          *  @description Height in pixels of the expanded subgrid.  Defaults to
16738          *  150
16739          *  @example
16740          *  <pre>
16741          *    $scope.gridOptions = {
16742          *      expandableRowHeight: 150
16743          *    }
16744          *  </pre>
16745          */
16746         grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
16747
16748         /**
16749          *  @ngdoc object
16750          *  @name
16751          *  @propertyOf  ui.grid.expandable.api:GridOptions
16752          *  @description Width in pixels of the expandable column. Defaults to 40
16753          *  @example
16754          *  <pre>
16755          *    $scope.gridOptions = {
16756          *      expandableRowHeaderWidth: 40
16757          *    }
16758          *  </pre>
16759          */
16760         grid.options.expandableRowHeaderWidth = grid.options.expandableRowHeaderWidth || 40;
16761
16762         /**
16763          *  @ngdoc object
16764          *  @name expandableRowTemplate
16765          *  @propertyOf  ui.grid.expandable.api:GridOptions
16766          *  @description Mandatory. The template for your expanded row
16767          *  @example
16768          *  <pre>
16769          *    $scope.gridOptions = {
16770          *      expandableRowTemplate: 'expandableRowTemplate.html'
16771          *    }
16772          *  </pre>
16773          */
16774         if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
16775           gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
16776           grid.options.enableExpandable = false;
16777         }
16778
16779         /**
16780          *  @ngdoc object
16781          *  @name ui.grid.expandable.api:PublicApi
16782          *
16783          *  @description Public Api for expandable feature
16784          */
16785         /**
16786          *  @ngdoc object
16787          *  @name ui.grid.expandable.api:GridRow
16788          * 
16789          *  @description Additional properties added to GridRow when using the expandable module
16790          */
16791         /**
16792          *  @ngdoc object
16793          *  @name ui.grid.expandable.api:GridOptions
16794          *
16795          *  @description Options for configuring the expandable feature, these are available to be
16796          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16797          */
16798         var publicApi = {
16799           events: {
16800             expandable: {
16801               /**
16802                * @ngdoc event
16803                * @name rowExpandedStateChanged
16804                * @eventOf  ui.grid.expandable.api:PublicApi
16805                * @description raised when cell editing is complete
16806                * <pre>
16807                *      gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
16808                * </pre>
16809                * @param {GridRow} row the row that was expanded
16810                */
16811               rowExpandedBeforeStateChanged: function(scope,row){
16812               },
16813               rowExpandedStateChanged: function (scope, row) {
16814               }
16815             }
16816           },
16817
16818           methods: {
16819             expandable: {
16820               /**
16821                * @ngdoc method
16822                * @name toggleRowExpansion
16823                * @methodOf  ui.grid.expandable.api:PublicApi
16824                * @description Toggle a specific row
16825                * <pre>
16826                *      gridApi.expandable.toggleRowExpansion(rowEntity);
16827                * </pre>
16828                * @param {object} rowEntity the data entity for the row you want to expand
16829                */
16830               toggleRowExpansion: function (rowEntity) {
16831                 var row = grid.getRow(rowEntity);
16832                 if (row !== null) {
16833                   service.toggleRowExpansion(grid, row);
16834                 }
16835               },
16836
16837               /**
16838                * @ngdoc method
16839                * @name expandAllRows
16840                * @methodOf  ui.grid.expandable.api:PublicApi
16841                * @description Expand all subgrids.
16842                * <pre>
16843                *      gridApi.expandable.expandAllRows();
16844                * </pre>
16845                */
16846               expandAllRows: function() {
16847                 service.expandAllRows(grid);
16848               },
16849
16850               /**
16851                * @ngdoc method
16852                * @name collapseAllRows
16853                * @methodOf  ui.grid.expandable.api:PublicApi
16854                * @description Collapse all subgrids.
16855                * <pre>
16856                *      gridApi.expandable.collapseAllRows();
16857                * </pre>
16858                */
16859               collapseAllRows: function() {
16860                 service.collapseAllRows(grid);
16861               },
16862
16863               /**
16864                * @ngdoc method
16865                * @name toggleAllRows
16866                * @methodOf  ui.grid.expandable.api:PublicApi
16867                * @description Toggle all subgrids.
16868                * <pre>
16869                *      gridApi.expandable.toggleAllRows();
16870                * </pre>
16871                */
16872               toggleAllRows: function() {
16873                 service.toggleAllRows(grid);
16874               }
16875             }
16876           }
16877         };
16878         grid.api.registerEventsFromObject(publicApi.events);
16879         grid.api.registerMethodsFromObject(publicApi.methods);
16880       },
16881
16882       toggleRowExpansion: function (grid, row) {
16883         // trigger the "before change" event. Can change row height dynamically this way.
16884         grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
16885         /**
16886          *  @ngdoc object
16887          *  @name isExpanded
16888          *  @propertyOf  ui.grid.expandable.api:GridRow
16889          *  @description Whether or not the row is currently expanded.
16890          *  @example
16891          *  <pre>
16892          *    $scope.api.expandable.on.rowExpandedStateChanged($scope, function (row) {
16893          *      if (row.isExpanded) {
16894          *        //...
16895          *      }
16896          *    });
16897          *  </pre>
16898          */
16899         row.isExpanded = !row.isExpanded;
16900         if (angular.isUndefined(row.expandedRowHeight)){
16901           row.expandedRowHeight = grid.options.expandableRowHeight;
16902         }
16903               
16904         if (row.isExpanded) {
16905           row.height = row.grid.options.rowHeight + row.expandedRowHeight;
16906         }
16907         else {
16908           row.height = row.grid.options.rowHeight;
16909           grid.expandable.expandedAll = false;
16910         }
16911         grid.api.expandable.raise.rowExpandedStateChanged(row);
16912       },
16913
16914       expandAllRows: function(grid, $scope) {
16915         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16916           if (!row.isExpanded) {
16917             service.toggleRowExpansion(grid, row);
16918           }
16919         });
16920         grid.expandable.expandedAll = true;
16921         grid.queueGridRefresh();
16922       },
16923
16924       collapseAllRows: function(grid) {
16925         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16926           if (row.isExpanded) {
16927             service.toggleRowExpansion(grid, row);
16928           }
16929         });
16930         grid.expandable.expandedAll = false;
16931         grid.queueGridRefresh();
16932       },
16933
16934       toggleAllRows: function(grid) {
16935         if (grid.expandable.expandedAll) {
16936           service.collapseAllRows(grid);
16937         }
16938         else {
16939           service.expandAllRows(grid);
16940         }
16941       }
16942     };
16943     return service;
16944   }]);
16945
16946   /**
16947    *  @ngdoc object
16948    *  @name enableExpandableRowHeader
16949    *  @propertyOf  ui.grid.expandable.api:GridOptions
16950    *  @description Show a rowHeader to provide the expandable buttons.  If set to false then implies
16951    *  you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
16952    *  @example
16953    *  <pre>
16954    *    $scope.gridOptions = {
16955    *      enableExpandableRowHeader: false
16956    *    }
16957    *  </pre>
16958    */
16959   module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
16960     function (uiGridExpandableService, $templateCache) {
16961       return {
16962         replace: true,
16963         priority: 0,
16964         require: '^uiGrid',
16965         scope: false,
16966         compile: function () {
16967           return {
16968             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16969               if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
16970                 var expandableRowHeaderColDef = {
16971                   name: 'expandableButtons',
16972                   displayName: '',
16973                   exporterSuppressExport: true,
16974                   enableColumnResizing: false,
16975                   enableColumnMenu: false,
16976                   width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
16977                 };
16978                 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
16979                 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
16980                 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
16981               }
16982               uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
16983             },
16984             post: function ($scope, $elm, $attrs, uiGridCtrl) {
16985             }
16986           };
16987         }
16988       };
16989     }]);
16990
16991   /**
16992    *  @ngdoc directive
16993    *  @name ui.grid.expandable.directive:uiGrid
16994    *  @description stacks on the uiGrid directive to register child grid with parent row when child is created
16995    */
16996   module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
16997     function (uiGridExpandableService, $templateCache) {
16998       return {
16999         replace: true,
17000         priority: 599,
17001         require: '^uiGrid',
17002         scope: false,
17003         compile: function () {
17004           return {
17005             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17006
17007               uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
17008                 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
17009                 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
17010
17011                   /**
17012                    *  @ngdoc directive
17013                    *  @name ui.grid.expandable.class:Grid
17014                    *  @description Additional Grid properties added by expandable module
17015                    */
17016
17017                   /**
17018                    *  @ngdoc object
17019                    *  @name parentRow
17020                    *  @propertyOf ui.grid.expandable.class:Grid
17021                    *  @description reference to the expanded parent row that owns this grid
17022                    */
17023                   uiGridCtrl.grid.parentRow = $scope.row;
17024
17025                   //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
17026                  // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
17027                  //   uiGridCtrl.grid.parentRow = newHeight;
17028                  // });
17029                 }
17030
17031               });
17032             },
17033             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17034
17035             }
17036           };
17037         }
17038       };
17039     }]);
17040
17041   /**
17042    *  @ngdoc directive
17043    *  @name ui.grid.expandable.directive:uiGridExpandableRow
17044    *  @description directive to render the expandable row template
17045    */
17046   module.directive('uiGridExpandableRow',
17047   ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
17048     function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
17049
17050       return {
17051         replace: false,
17052         priority: 0,
17053         scope: false,
17054
17055         compile: function () {
17056           return {
17057             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17058               gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
17059                 function (template) {
17060                   if ($scope.grid.options.expandableRowScope) {
17061                     var expandableRowScope = $scope.grid.options.expandableRowScope;
17062                     for (var property in expandableRowScope) {
17063                       if (expandableRowScope.hasOwnProperty(property)) {
17064                         $scope[property] = expandableRowScope[property];
17065                       }
17066                     }
17067                   }
17068                   var expandedRowElement = $compile(template)($scope);
17069                   $elm.append(expandedRowElement);
17070                   $scope.row.expandedRendered = true;
17071               });
17072             },
17073
17074             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17075               $scope.$on('$destroy', function() {
17076                 $scope.row.expandedRendered = false;
17077               });
17078             }
17079           };
17080         }
17081       };
17082     }]);
17083
17084   /**
17085    *  @ngdoc directive
17086    *  @name ui.grid.expandable.directive:uiGridRow
17087    *  @description stacks on the uiGridRow directive to add support for expandable rows
17088    */
17089   module.directive('uiGridRow',
17090     ['$compile', 'gridUtil', '$templateCache',
17091       function ($compile, gridUtil, $templateCache) {
17092         return {
17093           priority: -200,
17094           scope: false,
17095           compile: function ($elm, $attrs) {
17096             return {
17097               pre: function ($scope, $elm, $attrs, controllers) {
17098
17099                 $scope.expandableRow = {};
17100
17101                 $scope.expandableRow.shouldRenderExpand = function () {
17102                   var ret = $scope.colContainer.name === 'body' &&  $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
17103                   return ret;
17104                 };
17105
17106                 $scope.expandableRow.shouldRenderFiller = function () {
17107                   var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
17108                   return ret;
17109                 };
17110
17111  /*
17112   * Commented out @PaulL1.  This has no purpose that I can see, and causes #2964.  If this code needs to be reinstated for some
17113   * reason it needs to use drawnWidth, not width, and needs to check column visibility.  It should really use render container
17114   * visible column cache also instead of checking column.renderContainer.
17115                   function updateRowContainerWidth() {
17116                       var grid = $scope.grid;
17117                       var colWidth = 0;
17118                       grid.columns.forEach( function (column) {
17119                           if (column.renderContainer === 'left') {
17120                             colWidth += column.width;
17121                           }
17122                       });
17123                       colWidth = Math.floor(colWidth);
17124                       return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
17125                           ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
17126                           ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
17127                   }
17128
17129                   if ($scope.colContainer.name === 'left') {
17130                       $scope.grid.registerStyleComputation({
17131                           priority: 15,
17132                           func: updateRowContainerWidth
17133                       });
17134                   }*/
17135
17136               },
17137               post: function ($scope, $elm, $attrs, controllers) {
17138               }
17139             };
17140           }
17141         };
17142       }]);
17143
17144   /**
17145    *  @ngdoc directive
17146    *  @name ui.grid.expandable.directive:uiGridViewport
17147    *  @description stacks on the uiGridViewport directive to append the expandable row html elements to the
17148    *  default gridRow template
17149    */
17150   module.directive('uiGridViewport',
17151     ['$compile', 'gridUtil', '$templateCache',
17152       function ($compile, gridUtil, $templateCache) {
17153         return {
17154           priority: -200,
17155           scope: false,
17156           compile: function ($elm, $attrs) {
17157             var rowRepeatDiv = angular.element($elm.children().children()[0]);
17158             var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
17159             var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
17160             rowRepeatDiv.append(expandedRowElement);
17161             rowRepeatDiv.append(expandedRowFillerElement);
17162             return {
17163               pre: function ($scope, $elm, $attrs, controllers) {
17164               },
17165               post: function ($scope, $elm, $attrs, controllers) {
17166               }
17167             };
17168           }
17169         };
17170       }]);
17171
17172 })();
17173
17174 /* global console */
17175
17176 (function () {
17177   'use strict';
17178
17179   /**
17180    * @ngdoc overview
17181    * @name ui.grid.exporter
17182    * @description
17183    *
17184    * # ui.grid.exporter
17185    *
17186    * <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>
17187    *
17188    * This module provides the ability to export data from the grid.
17189    *
17190    * Data can be exported in a range of formats, and all data, visible
17191    * data, or selected rows can be exported, with all columns or visible
17192    * columns.
17193    *
17194    * No UI is provided, the caller should provide their own UI/buttons
17195    * as appropriate, or enable the gridMenu
17196    *
17197    * <br/>
17198    * <br/>
17199    *
17200    * <div doc-module-components="ui.grid.exporter"></div>
17201    */
17202
17203   var module = angular.module('ui.grid.exporter', ['ui.grid']);
17204
17205   /**
17206    *  @ngdoc object
17207    *  @name ui.grid.exporter.constant:uiGridExporterConstants
17208    *
17209    *  @description constants available in exporter module
17210    */
17211   /**
17212    * @ngdoc property
17213    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17214    * @name ALL
17215    * @description export all data, including data not visible.  Can
17216    * be set for either rowTypes or colTypes
17217    */
17218   /**
17219    * @ngdoc property
17220    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17221    * @name VISIBLE
17222    * @description export only visible data, including data not visible.  Can
17223    * be set for either rowTypes or colTypes
17224    */
17225   /**
17226    * @ngdoc property
17227    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17228    * @name SELECTED
17229    * @description export all data, including data not visible.  Can
17230    * be set only for rowTypes, selection of only some columns is
17231    * not supported
17232    */
17233   module.constant('uiGridExporterConstants', {
17234     featureName: 'exporter',
17235     ALL: 'all',
17236     VISIBLE: 'visible',
17237     SELECTED: 'selected',
17238     CSV_CONTENT: 'CSV_CONTENT',
17239     BUTTON_LABEL: 'BUTTON_LABEL',
17240     FILE_NAME: 'FILE_NAME'
17241   });
17242
17243   /**
17244    *  @ngdoc service
17245    *  @name ui.grid.exporter.service:uiGridExporterService
17246    *
17247    *  @description Services for exporter feature
17248    */
17249   module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
17250     function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
17251
17252       var service = {
17253
17254         delay: 100,
17255
17256         initializeGrid: function (grid) {
17257
17258           //add feature namespace and any properties to grid for needed state
17259           grid.exporter = {};
17260           this.defaultGridOptions(grid.options);
17261
17262           /**
17263            *  @ngdoc object
17264            *  @name ui.grid.exporter.api:PublicApi
17265            *
17266            *  @description Public Api for exporter feature
17267            */
17268           var publicApi = {
17269             events: {
17270               exporter: {
17271               }
17272             },
17273             methods: {
17274               exporter: {
17275                 /**
17276                  * @ngdoc function
17277                  * @name csvExport
17278                  * @methodOf  ui.grid.exporter.api:PublicApi
17279                  * @description Exports rows from the grid in csv format,
17280                  * the data exported is selected based on the provided options
17281                  * @param {string} rowTypes which rows to export, valid values are
17282                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17283                  * uiGridExporterConstants.SELECTED
17284                  * @param {string} colTypes which columns to export, valid values are
17285                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
17286                  */
17287                 csvExport: function (rowTypes, colTypes) {
17288                   service.csvExport(grid, rowTypes, colTypes);
17289                 },
17290                 /**
17291                  * @ngdoc function
17292                  * @name pdfExport
17293                  * @methodOf  ui.grid.exporter.api:PublicApi
17294                  * @description Exports rows from the grid in pdf format,
17295                  * the data exported is selected based on the provided options
17296                  * Note that this function has a dependency on pdfMake, all
17297                  * going well this has been installed for you.
17298                  * The resulting pdf opens in a new browser window.
17299                  * @param {string} rowTypes which rows to export, valid values are
17300                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17301                  * uiGridExporterConstants.SELECTED
17302                  * @param {string} colTypes which columns to export, valid values are
17303                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
17304                  */
17305                 pdfExport: function (rowTypes, colTypes) {
17306                   service.pdfExport(grid, rowTypes, colTypes);
17307                 }
17308               }
17309             }
17310           };
17311
17312           grid.api.registerEventsFromObject(publicApi.events);
17313
17314           grid.api.registerMethodsFromObject(publicApi.methods);
17315
17316           if (grid.api.core.addToGridMenu){
17317             service.addToMenu( grid );
17318           } else {
17319             // order of registration is not guaranteed, register in a little while
17320             $interval( function() {
17321               if (grid.api.core.addToGridMenu){
17322                 service.addToMenu( grid );
17323               }
17324             }, this.delay, 1);
17325           }
17326
17327         },
17328
17329         defaultGridOptions: function (gridOptions) {
17330           //default option to true unless it was explicitly set to false
17331           /**
17332            * @ngdoc object
17333            * @name ui.grid.exporter.api:GridOptions
17334            *
17335            * @description GridOptions for exporter feature, these are available to be
17336            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17337            */
17338           /**
17339            * @ngdoc object
17340            * @name ui.grid.exporter.api:ColumnDef
17341            * @description ColumnDef settings for exporter
17342            */
17343           /**
17344            * @ngdoc object
17345            * @name exporterSuppressMenu
17346            * @propertyOf  ui.grid.exporter.api:GridOptions
17347            * @description Don't show the export menu button, implying the user
17348            * will roll their own UI for calling the exporter
17349            * <br/>Defaults to false
17350            */
17351           gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
17352           /**
17353            * @ngdoc object
17354            * @name exporterMenuLabel
17355            * @propertyOf  ui.grid.exporter.api:GridOptions
17356            * @description The text to show on the exporter menu button
17357            * link
17358            * <br/>Defaults to 'Export'
17359            */
17360           gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
17361           /**
17362            * @ngdoc object
17363            * @name exporterSuppressColumns
17364            * @propertyOf  ui.grid.exporter.api:GridOptions
17365            * @description Columns that should not be exported.  The selectionRowHeader is already automatically
17366            * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
17367            * output then add it in this list.  You should provide an array of column names.
17368            * <br/>Defaults to: []
17369            * <pre>
17370            *   gridOptions.exporterSuppressColumns = [ 'buttons' ];
17371            * </pre>
17372            */
17373           gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
17374           /**
17375            * @ngdoc object
17376            * @name exporterCsvColumnSeparator
17377            * @propertyOf  ui.grid.exporter.api:GridOptions
17378            * @description The character to use as column separator
17379            * link
17380            * <br/>Defaults to ','
17381            */
17382           gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
17383           /**
17384            * @ngdoc object
17385            * @name exporterCsvFilename
17386            * @propertyOf  ui.grid.exporter.api:GridOptions
17387            * @description The default filename to use when saving the downloaded csv.
17388            * This will only work in some browsers.
17389            * <br/>Defaults to 'download.csv'
17390            */
17391           gridOptions.exporterCsvFilename = gridOptions.exporterCsvFilename ? gridOptions.exporterCsvFilename : 'download.csv';
17392           /**
17393            * @ngdoc object
17394            * @name exporterPdfFilename
17395            * @propertyOf  ui.grid.exporter.api:GridOptions
17396            * @description The default filename to use when saving the downloaded pdf, only used in IE (other browsers open pdfs in a new window)
17397            * <br/>Defaults to 'download.pdf'
17398            */
17399           gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
17400           /**
17401            * @ngdoc object
17402            * @name exporterOlderExcelCompatibility
17403            * @propertyOf  ui.grid.exporter.api:GridOptions
17404            * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
17405            * through as ï»¿ in the first column header.  Setting this option to false will suppress this, at the
17406            * expense of proper utf-16 handling in applications that do recognise the BOM
17407            * <br/>Defaults to false
17408            */
17409           gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
17410           /**
17411            * @ngdoc object
17412            * @name exporterPdfDefaultStyle
17413            * @propertyOf  ui.grid.exporter.api:GridOptions
17414            * @description The default style in pdfMake format
17415            * <br/>Defaults to:
17416            * <pre>
17417            *   {
17418            *     fontSize: 11
17419            *   }
17420            * </pre>
17421            */
17422           gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
17423           /**
17424            * @ngdoc object
17425            * @name exporterPdfTableStyle
17426            * @propertyOf  ui.grid.exporter.api:GridOptions
17427            * @description The table style in pdfMake format
17428            * <br/>Defaults to:
17429            * <pre>
17430            *   {
17431            *     margin: [0, 5, 0, 15]
17432            *   }
17433            * </pre>
17434            */
17435           gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
17436           /**
17437            * @ngdoc object
17438            * @name exporterPdfTableHeaderStyle
17439            * @propertyOf  ui.grid.exporter.api:GridOptions
17440            * @description The tableHeader style in pdfMake format
17441            * <br/>Defaults to:
17442            * <pre>
17443            *   {
17444            *     bold: true,
17445            *     fontSize: 12,
17446            *     color: 'black'
17447            *   }
17448            * </pre>
17449            */
17450           gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
17451           /**
17452            * @ngdoc object
17453            * @name exporterPdfHeader
17454            * @propertyOf  ui.grid.exporter.api:GridOptions
17455            * @description The header section for pdf exports.  Can be
17456            * simple text:
17457            * <pre>
17458            *   gridOptions.exporterPdfHeader = 'My Header';
17459            * </pre>
17460            * Can be a more complex object in pdfMake format:
17461            * <pre>
17462            *   gridOptions.exporterPdfHeader = {
17463            *     columns: [
17464            *       'Left part',
17465            *       { text: 'Right part', alignment: 'right' }
17466            *     ]
17467            *   };
17468            * </pre>
17469            * Or can be a function, allowing page numbers and the like
17470            * <pre>
17471            *   gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
17472            * </pre>
17473            */
17474           gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
17475           /**
17476            * @ngdoc object
17477            * @name exporterPdfFooter
17478            * @propertyOf  ui.grid.exporter.api:GridOptions
17479            * @description The header section for pdf exports.  Can be
17480            * simple text:
17481            * <pre>
17482            *   gridOptions.exporterPdfFooter = 'My Footer';
17483            * </pre>
17484            * Can be a more complex object in pdfMake format:
17485            * <pre>
17486            *   gridOptions.exporterPdfFooter = {
17487            *     columns: [
17488            *       'Left part',
17489            *       { text: 'Right part', alignment: 'right' }
17490            *     ]
17491            *   };
17492            * </pre>
17493            * Or can be a function, allowing page numbers and the like
17494            * <pre>
17495            *   gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
17496            * </pre>
17497            */
17498           gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
17499           /**
17500            * @ngdoc object
17501            * @name exporterPdfOrientation
17502            * @propertyOf  ui.grid.exporter.api:GridOptions
17503            * @description The orientation, should be a valid pdfMake value,
17504            * 'landscape' or 'portrait'
17505            * <br/>Defaults to landscape
17506            */
17507           gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
17508           /**
17509            * @ngdoc object
17510            * @name exporterPdfPageSize
17511            * @propertyOf  ui.grid.exporter.api:GridOptions
17512            * @description The orientation, should be a valid pdfMake
17513            * paper size, usually 'A4' or 'LETTER'
17514            * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
17515            * <br/>Defaults to A4
17516            */
17517           gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
17518           /**
17519            * @ngdoc object
17520            * @name exporterPdfMaxGridWidth
17521            * @propertyOf  ui.grid.exporter.api:GridOptions
17522            * @description The maxium grid width - the current grid width
17523            * will be scaled to match this, with any fixed width columns
17524            * being adjusted accordingly.
17525            * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
17526            */
17527           gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
17528           /**
17529            * @ngdoc object
17530            * @name exporterPdfTableLayout
17531            * @propertyOf  ui.grid.exporter.api:GridOptions
17532            * @description A tableLayout in pdfMake format,
17533            * controls gridlines and the like.  We use the default
17534            * layout usually.
17535            * <br/>Defaults to null, which means no layout
17536            */
17537
17538           /**
17539            * @ngdoc object
17540            * @name exporterMenuAllData
17541            * @porpertyOf  ui.grid.exporter.api:GridOptions
17542            * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17543            */
17544           gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
17545
17546           /**
17547            * @ngdoc object
17548            * @name exporterMenuVisibleData
17549            * @porpertyOf  ui.grid.exporter.api:GridOptions
17550            * @description Add export visible data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17551            */
17552           gridOptions.exporterMenuVisibleData = gridOptions.exporterMenuVisibleData !== undefined ? gridOptions.exporterMenuVisibleData : true;
17553
17554           /**
17555            * @ngdoc object
17556            * @name exporterMenuSelectedData
17557            * @porpertyOf  ui.grid.exporter.api:GridOptions
17558            * @description Add export selected data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17559            */
17560           gridOptions.exporterMenuSelectedData = gridOptions.exporterMenuSelectedData !== undefined ? gridOptions.exporterMenuSelectedData : true;
17561
17562           /**
17563            * @ngdoc object
17564            * @name exporterMenuCsv
17565            * @propertyOf  ui.grid.exporter.api:GridOptions
17566            * @description Add csv export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17567            */
17568           gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
17569
17570           /**
17571            * @ngdoc object
17572            * @name exporterMenuPdf
17573            * @propertyOf  ui.grid.exporter.api:GridOptions
17574            * @description Add pdf export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17575            */
17576           gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
17577
17578           /**
17579            * @ngdoc object
17580            * @name exporterPdfCustomFormatter
17581            * @propertyOf  ui.grid.exporter.api:GridOptions
17582            * @description A custom callback routine that changes the pdf document, adding any
17583            * custom styling or content that is supported by pdfMake.  Takes in the complete docDefinition, and
17584            * must return an updated docDefinition ready for pdfMake.
17585            * @example
17586            * In this example we add a style to the style array, so that we can use it in our
17587            * footer definition.
17588            * <pre>
17589            *   gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
17590            *     docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
17591            *     return docDefinition;
17592            *   }
17593            *
17594            *   gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
17595            * </pre>
17596            */
17597           gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
17598
17599           /**
17600            * @ngdoc object
17601            * @name exporterHeaderFilterUseName
17602            * @propertyOf  ui.grid.exporter.api:GridOptions
17603            * @description Defaults to false, which leads to `displayName` being passed into the headerFilter.
17604            * If set to true, then will pass `name` instead.
17605            *
17606            *
17607            * @example
17608            * <pre>
17609            *   gridOptions.exporterHeaderFilterUseName = true;
17610            * </pre>
17611            */
17612           gridOptions.exporterHeaderFilterUseName = gridOptions.exporterHeaderFilterUseName === true;
17613
17614           /**
17615            * @ngdoc object
17616            * @name exporterHeaderFilter
17617            * @propertyOf  ui.grid.exporter.api:GridOptions
17618            * @description A function to apply to the header displayNames before exporting.  Useful for internationalisation,
17619            * for example if you were using angular-translate you'd set this to `$translate.instant`.  Note that this
17620            * call must be synchronous, it cannot be a call that returns a promise.
17621            *
17622            * Behaviour can be changed to pass in `name` instead of `displayName` through use of `exporterHeaderFilterUseName: true`.
17623            *
17624            * @example
17625            * <pre>
17626            *   gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
17627            * </pre>
17628            * OR
17629            * <pre>
17630            *   gridOptions.exporterHeaderFilter = $translate.instant;
17631            * </pre>
17632            */
17633
17634           /**
17635            * @ngdoc function
17636            * @name exporterFieldCallback
17637            * @propertyOf  ui.grid.exporter.api:GridOptions
17638            * @description A function to call for each field before exporting it.  Allows
17639            * massaging of raw data into a display format, for example if you have applied
17640            * filters to convert codes into decodes, or you require
17641            * a specific date format in the exported content.
17642            *
17643            * The method is called once for each field exported, and provides the grid, the
17644            * gridCol and the GridRow for you to use as context in massaging the data.
17645            *
17646            * @param {Grid} grid provides the grid in case you have need of it
17647            * @param {GridRow} row the row from which the data comes
17648            * @param {GridCol} col the column from which the data comes
17649            * @param {object} value the value for your massaging
17650            * @returns {object} you must return the massaged value ready for exporting
17651            *
17652            * @example
17653            * <pre>
17654            *   gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
17655            *     if ( col.name === 'status' ){
17656            *       value = decodeStatus( value );
17657            *     }
17658            *     return value;
17659            *   }
17660            * </pre>
17661            */
17662           gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
17663
17664           /**
17665            * @ngdoc function
17666            * @name exporterAllDataFn
17667            * @propertyOf  ui.grid.exporter.api:GridOptions
17668            * @description This promise is needed when exporting all rows,
17669            * and the data need to be provided by server side. Default is null.
17670            * @returns {Promise} a promise to load all data from server
17671            *
17672            * @example
17673            * <pre>
17674            *   gridOptions.exporterAllDataFn = function () {
17675            *     return $http.get('/data/100.json')
17676            *   }
17677            * </pre>
17678            */
17679           gridOptions.exporterAllDataFn = gridOptions.exporterAllDataFn ? gridOptions.exporterAllDataFn : null;
17680
17681           /**
17682            * @ngdoc function
17683            * @name exporterAllDataPromise
17684            * @propertyOf  ui.grid.exporter.api:GridOptions
17685            * @description DEPRECATED - exporterAllDataFn used to be
17686            * called this, but it wasn't a promise, it was a function that returned
17687            * a promise.  Deprecated, but supported for backward compatibility, use
17688            * exporterAllDataFn instead.
17689            * @returns {Promise} a promise to load all data from server
17690            *
17691            * @example
17692            * <pre>
17693            *   gridOptions.exporterAllDataFn = function () {
17694            *     return $http.get('/data/100.json')
17695            *   }
17696            * </pre>
17697            */
17698           if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
17699             gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
17700           }
17701         },
17702
17703
17704         /**
17705          * @ngdoc function
17706          * @name addToMenu
17707          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17708          * @description Adds export items to the grid menu,
17709          * allowing the user to select export options
17710          * @param {Grid} grid the grid from which data should be exported
17711          */
17712         addToMenu: function ( grid ) {
17713           grid.api.core.addToGridMenu( grid, [
17714             {
17715               title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
17716               action: function ($event) {
17717                 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17718               },
17719               shown: function() {
17720                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
17721               },
17722               order: 200
17723             },
17724             {
17725               title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
17726               action: function ($event) {
17727                 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17728               },
17729               shown: function() {
17730                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuVisibleData;
17731               },
17732               order: 201
17733             },
17734             {
17735               title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
17736               action: function ($event) {
17737                 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17738               },
17739               shown: function() {
17740                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuSelectedData &&
17741                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17742               },
17743               order: 202
17744             },
17745             {
17746               title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
17747               action: function ($event) {
17748                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17749               },
17750               shown: function() {
17751                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
17752               },
17753               order: 203
17754             },
17755             {
17756               title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
17757               action: function ($event) {
17758                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17759               },
17760               shown: function() {
17761                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuVisibleData;
17762               },
17763               order: 204
17764             },
17765             {
17766               title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
17767               action: function ($event) {
17768                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17769               },
17770               shown: function() {
17771                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuSelectedData &&
17772                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17773               },
17774               order: 205
17775             }
17776           ]);
17777         },
17778
17779
17780         /**
17781          * @ngdoc function
17782          * @name csvExport
17783          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17784          * @description Exports rows from the grid in csv format,
17785          * the data exported is selected based on the provided options
17786          * @param {Grid} grid the grid from which data should be exported
17787          * @param {string} rowTypes which rows to export, valid values are
17788          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17789          * uiGridExporterConstants.SELECTED
17790          * @param {string} colTypes which columns to export, valid values are
17791          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17792          * uiGridExporterConstants.SELECTED
17793          */
17794         csvExport: function (grid, rowTypes, colTypes) {
17795           var self = this;
17796           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
17797             var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
17798             var exportData = self.getData(grid, rowTypes, colTypes);
17799             var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
17800
17801             self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterOlderExcelCompatibility);
17802           });
17803         },
17804
17805         /**
17806          * @ngdoc function
17807          * @name loadAllDataIfNeeded
17808          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17809          * @description When using server side pagination, use exporterAllDataFn to
17810          * load all data before continuing processing.
17811          * When using client side pagination, return a resolved promise so processing
17812          * continues immediately
17813          * @param {Grid} grid the grid from which data should be exported
17814          * @param {string} rowTypes which rows to export, valid values are
17815          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17816          * uiGridExporterConstants.SELECTED
17817          * @param {string} colTypes which columns to export, valid values are
17818          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17819          * uiGridExporterConstants.SELECTED
17820          */
17821         loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
17822           if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
17823             return grid.options.exporterAllDataFn()
17824               .then(function() {
17825                 grid.modifyRows(grid.options.data);
17826               });
17827           } else {
17828             var deferred = $q.defer();
17829             deferred.resolve();
17830             return deferred.promise;
17831           }
17832         },
17833
17834         /**
17835          * @ngdoc property
17836          * @propertyOf ui.grid.exporter.api:ColumnDef
17837          * @name exporterSuppressExport
17838          * @description Suppresses export for this column.  Used by selection and expandable.
17839          */
17840
17841         /**
17842          * @ngdoc function
17843          * @name getColumnHeaders
17844          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17845          * @description Gets the column headers from the grid to use
17846          * as a title row for the exported file, all headers have
17847          * headerCellFilters applied as appropriate.
17848          *
17849          * Column headers are an array of objects, each object has
17850          * name, displayName, width and align attributes.  Only name is
17851          * used for csv, all attributes are used for pdf.
17852          *
17853          * @param {Grid} grid the grid from which data should be exported
17854          * @param {string} colTypes which columns to export, valid values are
17855          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17856          * uiGridExporterConstants.SELECTED
17857          */
17858         getColumnHeaders: function (grid, colTypes) {
17859           var headers = [];
17860           var columns;
17861
17862           if ( colTypes === uiGridExporterConstants.ALL ){
17863             columns = grid.columns;
17864           } else {
17865             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17866             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17867             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17868
17869             columns = leftColumns.concat(bodyColumns,rightColumns);
17870           }
17871
17872           columns.forEach( function( gridCol, index ) {
17873             if ( gridCol.colDef.exporterSuppressExport !== true &&
17874                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17875               headers.push({
17876                 name: gridCol.field,
17877                 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
17878                 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
17879                 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
17880               });
17881             }
17882           });
17883
17884           return headers;
17885         },
17886
17887
17888         /**
17889          * @ngdoc property
17890          * @propertyOf ui.grid.exporter.api:ColumnDef
17891          * @name exporterPdfAlign
17892          * @description the alignment you'd like for this specific column when
17893          * exported into a pdf.  Can be 'left', 'right', 'center' or any other
17894          * valid pdfMake alignment option.
17895          */
17896
17897
17898         /**
17899          * @ngdoc object
17900          * @name ui.grid.exporter.api:GridRow
17901          * @description GridRow settings for exporter
17902          */
17903         /**
17904          * @ngdoc object
17905          * @name exporterEnableExporting
17906          * @propertyOf  ui.grid.exporter.api:GridRow
17907          * @description If set to false, then don't export this row, notwithstanding visible or
17908          * other settings
17909          * <br/>Defaults to true
17910          */
17911
17912         /**
17913          * @ngdoc function
17914          * @name getData
17915          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17916          * @description Gets data from the grid based on the provided options,
17917          * all cells have cellFilters applied as appropriate.  Any rows marked
17918          * `exporterEnableExporting: false` will not be exported
17919          * @param {Grid} grid the grid from which data should be exported
17920          * @param {string} rowTypes which rows to export, valid values are
17921          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17922          * uiGridExporterConstants.SELECTED
17923          * @param {string} colTypes which columns to export, valid values are
17924          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17925          * uiGridExporterConstants.SELECTED
17926          * @param {boolean} applyCellFilters whether or not to get the display value or the raw value of the data
17927          */
17928         getData: function (grid, rowTypes, colTypes, applyCellFilters) {
17929           var data = [];
17930           var rows;
17931           var columns;
17932
17933           switch ( rowTypes ) {
17934             case uiGridExporterConstants.ALL:
17935               rows = grid.rows;
17936               break;
17937             case uiGridExporterConstants.VISIBLE:
17938               rows = grid.getVisibleRows();
17939               break;
17940             case uiGridExporterConstants.SELECTED:
17941               if ( grid.api.selection ){
17942                 rows = grid.api.selection.getSelectedGridRows();
17943               } else {
17944                 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
17945               }
17946               break;
17947           }
17948
17949           if ( colTypes === uiGridExporterConstants.ALL ){
17950             columns = grid.columns;
17951           } else {
17952             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17953             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17954             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17955
17956             columns = leftColumns.concat(bodyColumns,rightColumns);
17957           }
17958
17959           rows.forEach( function( row, index ) {
17960
17961             if (row.exporterEnableExporting !== false) {
17962               var extractedRow = [];
17963
17964
17965               columns.forEach( function( gridCol, index ) {
17966               if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
17967                    gridCol.colDef.exporterSuppressExport !== true &&
17968                    grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17969                   var cellValue = applyCellFilters ? grid.getCellDisplayValue( row, gridCol ) : grid.getCellValue( row, gridCol );
17970                   var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, cellValue ) };
17971                   if ( gridCol.colDef.exporterPdfAlign ) {
17972                     extractedField.alignment = gridCol.colDef.exporterPdfAlign;
17973                   }
17974                   extractedRow.push(extractedField);
17975                 }
17976               });
17977
17978               data.push(extractedRow);
17979             }
17980           });
17981
17982           return data;
17983         },
17984
17985
17986         /**
17987          * @ngdoc function
17988          * @name formatAsCSV
17989          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17990          * @description Formats the column headers and data as a CSV,
17991          * and sends that data to the user
17992          * @param {array} exportColumnHeaders an array of column headers,
17993          * where each header is an object with name, width and maybe alignment
17994          * @param {array} exportData an array of rows, where each row is
17995          * an array of column data
17996          * @returns {string} csv the formatted csv as a string
17997          */
17998         formatAsCsv: function (exportColumnHeaders, exportData, separator) {
17999           var self = this;
18000
18001           var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
18002
18003           var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
18004
18005           csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
18006
18007           return csv;
18008         },
18009
18010         /**
18011          * @ngdoc function
18012          * @name formatRowAsCsv
18013          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18014          * @description Renders a single field as a csv field, including
18015          * quotes around the value
18016          * @param {exporterService} exporter pass in exporter
18017          * @param {array} row the row to be turned into a csv string
18018          * @returns {string} a csv-ified version of the row
18019          */
18020         formatRowAsCsv: function (exporter, separator) {
18021           return function (row) {
18022             return row.map(exporter.formatFieldAsCsv).join(separator);
18023           };
18024         },
18025
18026         /**
18027          * @ngdoc function
18028          * @name formatFieldAsCsv
18029          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18030          * @description Renders a single field as a csv field, including
18031          * quotes around the value
18032          * @param {field} field the field to be turned into a csv string,
18033          * may be of any type
18034          * @returns {string} a csv-ified version of the field
18035          */
18036         formatFieldAsCsv: function (field) {
18037           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18038             return '';
18039           }
18040           if (typeof(field.value) === 'number') {
18041             return field.value;
18042           }
18043           if (typeof(field.value) === 'boolean') {
18044             return (field.value ? 'TRUE' : 'FALSE') ;
18045           }
18046           if (typeof(field.value) === 'string') {
18047             return '"' + field.value.replace(/"/g,'""') + '"';
18048           }
18049
18050           return JSON.stringify(field.value);
18051         },
18052
18053
18054         /**
18055          * @ngdoc function
18056          * @name isIE
18057          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18058          * @description Checks whether current browser is IE and returns it's version if it is
18059         */
18060         isIE: function () {
18061           var match = navigator.userAgent.search(/(?:Edge|MSIE|Trident\/.*; rv:)/);
18062           var isIE = false;
18063
18064           if (match !== -1) {
18065             isIE = true;
18066           }
18067
18068           return isIE;
18069         },
18070
18071
18072         /**
18073          * @ngdoc function
18074          * @name downloadFile
18075          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18076          * @description Triggers download of a csv file.  Logic provided
18077          * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
18078          * @param {string} fileName the filename we'd like our file to be
18079          * given
18080          * @param {string} csvContent the csv content that we'd like to
18081          * download as a file
18082          * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
18083          */
18084         downloadFile: function (fileName, csvContent, exporterOlderExcelCompatibility) {
18085           var D = document;
18086           var a = D.createElement('a');
18087           var strMimeType = 'application/octet-stream;charset=utf-8';
18088           var rawFile;
18089           var ieVersion;
18090
18091           ieVersion = this.isIE();
18092           if (ieVersion && ieVersion < 10) {
18093             var frame = D.createElement('iframe');
18094             document.body.appendChild(frame);
18095
18096             frame.contentWindow.document.open("text/html", "replace");
18097             frame.contentWindow.document.write('sep=,\r\n' + csvContent);
18098             frame.contentWindow.document.close();
18099             frame.contentWindow.focus();
18100             frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18101
18102             document.body.removeChild(frame);
18103             return true;
18104           }
18105
18106           // IE10+
18107           if (navigator.msSaveBlob) {
18108             return navigator.msSaveOrOpenBlob(
18109               new Blob(
18110                 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18111                 { type: strMimeType } ),
18112               fileName
18113             );
18114           }
18115
18116           //html5 A[download]
18117           if ('download' in a) {
18118             var blob = new Blob(
18119               [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18120               { type: strMimeType }
18121             );
18122             rawFile = URL.createObjectURL(blob);
18123             a.setAttribute('download', fileName);
18124           } else {
18125             rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
18126             a.setAttribute('target', '_blank');
18127           }
18128
18129           a.href = rawFile;
18130           a.setAttribute('style', 'display:none;');
18131           D.body.appendChild(a);
18132           setTimeout(function() {
18133             if (a.click) {
18134               a.click();
18135               // Workaround for Safari 5
18136             } else if (document.createEvent) {
18137               var eventObj = document.createEvent('MouseEvents');
18138               eventObj.initEvent('click', true, true);
18139               a.dispatchEvent(eventObj);
18140             }
18141             D.body.removeChild(a);
18142
18143           }, this.delay);
18144         },
18145
18146         /**
18147          * @ngdoc function
18148          * @name pdfExport
18149          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18150          * @description Exports rows from the grid in pdf format,
18151          * the data exported is selected based on the provided options.
18152          * Note that this function has a dependency on pdfMake, which must
18153          * be installed.  The resulting pdf opens in a new
18154          * browser window.
18155          * @param {Grid} grid the grid from which data should be exported
18156          * @param {string} rowTypes which rows to export, valid values are
18157          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18158          * uiGridExporterConstants.SELECTED
18159          * @param {string} colTypes which columns to export, valid values are
18160          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18161          * uiGridExporterConstants.SELECTED
18162          */
18163         pdfExport: function (grid, rowTypes, colTypes) {
18164           var self = this;
18165           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
18166             var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
18167             var exportData = self.getData(grid, rowTypes, colTypes);
18168             var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
18169
18170             if (self.isIE() || navigator.appVersion.indexOf("Edge") !== -1) {
18171               self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
18172             } else {
18173               pdfMake.createPdf(docDefinition).open();
18174             }
18175           });
18176         },
18177
18178
18179         /**
18180          * @ngdoc function
18181          * @name downloadPdf
18182          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18183          * @description Generates and retrieves the pdf as a blob, then downloads
18184          * it as a file.  Only used in IE, in all other browsers we use the native
18185          * pdfMake.open function to just open the PDF
18186          * @param {string} fileName the filename to give to the pdf, can be set
18187          * through exporterPdfFilename
18188          * @param {object} docDefinition a pdf docDefinition that we can generate
18189          * and get a blob from
18190          */
18191         downloadPDF: function (fileName, docDefinition) {
18192           var D = document;
18193           var a = D.createElement('a');
18194           var strMimeType = 'application/octet-stream;charset=utf-8';
18195           var rawFile;
18196           var ieVersion;
18197
18198           ieVersion = this.isIE(); // This is now a boolean value
18199           var doc = pdfMake.createPdf(docDefinition);
18200           var blob;
18201
18202           doc.getBuffer( function (buffer) {
18203             blob = new Blob([buffer]);
18204
18205             // IE10+
18206             if (navigator.msSaveBlob) {
18207               return navigator.msSaveBlob(
18208                 blob, fileName
18209               );
18210             }
18211
18212             // Previously:  && ieVersion < 10
18213             // ieVersion now returns a boolean for the
18214             // sake of sanity. We just check `msSaveBlob` first.
18215             if (ieVersion) {
18216               var frame = D.createElement('iframe');
18217               document.body.appendChild(frame);
18218
18219               frame.contentWindow.document.open("text/html", "replace");
18220               frame.contentWindow.document.write(blob);
18221               frame.contentWindow.document.close();
18222               frame.contentWindow.focus();
18223               frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18224
18225               document.body.removeChild(frame);
18226               return true;
18227             }
18228           });
18229         },
18230
18231
18232         /**
18233          * @ngdoc function
18234          * @name renderAsPdf
18235          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18236          * @description Renders the data into a pdf, and opens that pdf.
18237          *
18238          * @param {Grid} grid the grid from which data should be exported
18239          * @param {array} exportColumnHeaders an array of column headers,
18240          * where each header is an object with name, width and maybe alignment
18241          * @param {array} exportData an array of rows, where each row is
18242          * an array of column data
18243          * @returns {object} a pdfMake format document definition, ready
18244          * for generation
18245          */
18246         prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
18247           var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
18248
18249           var headerColumns = exportColumnHeaders.map( function( header ) {
18250             return { text: header.displayName, style: 'tableHeader' };
18251           });
18252
18253           var stringData = exportData.map(this.formatRowAsPdf(this));
18254
18255           var allData = [headerColumns].concat(stringData);
18256
18257           var docDefinition = {
18258             pageOrientation: grid.options.exporterPdfOrientation,
18259             pageSize: grid.options.exporterPdfPageSize,
18260             content: [{
18261               style: 'tableStyle',
18262               table: {
18263                 headerRows: 1,
18264                 widths: headerWidths,
18265                 body: allData
18266               }
18267             }],
18268             styles: {
18269               tableStyle: grid.options.exporterPdfTableStyle,
18270               tableHeader: grid.options.exporterPdfTableHeaderStyle
18271             },
18272             defaultStyle: grid.options.exporterPdfDefaultStyle
18273           };
18274
18275           if ( grid.options.exporterPdfLayout ){
18276             docDefinition.layout = grid.options.exporterPdfLayout;
18277           }
18278
18279           if ( grid.options.exporterPdfHeader ){
18280             docDefinition.header = grid.options.exporterPdfHeader;
18281           }
18282
18283           if ( grid.options.exporterPdfFooter ){
18284             docDefinition.footer = grid.options.exporterPdfFooter;
18285           }
18286
18287           if ( grid.options.exporterPdfCustomFormatter ){
18288             docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
18289           }
18290           return docDefinition;
18291
18292         },
18293
18294
18295         /**
18296          * @ngdoc function
18297          * @name calculatePdfHeaderWidths
18298          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18299          * @description Determines the column widths base on the
18300          * widths we got from the grid.  If the column is drawn
18301          * then we have a drawnWidth.  If the column is not visible
18302          * then we have '*', 'x%' or a width.  When columns are
18303          * not visible they don't contribute to the overall gridWidth,
18304          * so we need to adjust to allow for extra columns
18305          *
18306          * Our basic heuristic is to take the current gridWidth, plus
18307          * numeric columns and call this the base gridwidth.
18308          *
18309          * To that we add 100 for any '*' column, and x% of the base gridWidth
18310          * for any column that is a %
18311          *
18312          * @param {Grid} grid the grid from which data should be exported
18313          * @param {array} exportHeaders array of header information
18314          * @returns {object} an array of header widths
18315          */
18316         calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
18317           var baseGridWidth = 0;
18318           exportHeaders.forEach( function(value){
18319             if (typeof(value.width) === 'number'){
18320               baseGridWidth += value.width;
18321             }
18322           });
18323
18324           var extraColumns = 0;
18325           exportHeaders.forEach( function(value){
18326             if (value.width === '*'){
18327               extraColumns += 100;
18328             }
18329             if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
18330               var percent = parseInt(value.width.match(/(\d)*%/)[0]);
18331
18332               value.width = baseGridWidth * percent / 100;
18333               extraColumns += value.width;
18334             }
18335           });
18336
18337           var gridWidth = baseGridWidth + extraColumns;
18338
18339           return exportHeaders.map(function( header ) {
18340             return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
18341           });
18342
18343         },
18344
18345         /**
18346          * @ngdoc function
18347          * @name formatRowAsPdf
18348          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18349          * @description Renders a row in a format consumable by PDF,
18350          * mainly meaning casting everything to a string
18351          * @param {exporterService} exporter pass in exporter
18352          * @param {array} row the row to be turned into a csv string
18353          * @returns {string} a csv-ified version of the row
18354          */
18355         formatRowAsPdf: function ( exporter ) {
18356           return function( row ) {
18357             return row.map(exporter.formatFieldAsPdfString);
18358           };
18359         },
18360
18361
18362         /**
18363          * @ngdoc function
18364          * @name formatFieldAsCsv
18365          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18366          * @description Renders a single field as a pdf-able field, which
18367          * is different from a csv field only in that strings don't have quotes
18368          * around them
18369          * @param {field} field the field to be turned into a pdf string,
18370          * may be of any type
18371          * @returns {string} a string-ified version of the field
18372          */
18373         formatFieldAsPdfString: function (field) {
18374           var returnVal;
18375           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18376             returnVal = '';
18377           } else if (typeof(field.value) === 'number') {
18378             returnVal = field.value.toString();
18379           } else if (typeof(field.value) === 'boolean') {
18380             returnVal = (field.value ? 'TRUE' : 'FALSE') ;
18381           } else if (typeof(field.value) === 'string') {
18382             returnVal = field.value.replace(/"/g,'""');
18383           } else {
18384             returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
18385           }
18386
18387           if (field.alignment && typeof(field.alignment) === 'string' ){
18388             returnVal = { text: returnVal, alignment: field.alignment };
18389           }
18390
18391           return returnVal;
18392         }
18393       };
18394
18395       return service;
18396
18397     }
18398   ]);
18399
18400   /**
18401    *  @ngdoc directive
18402    *  @name ui.grid.exporter.directive:uiGridExporter
18403    *  @element div
18404    *  @restrict A
18405    *
18406    *  @description Adds exporter features to grid
18407    *
18408    *  @example
18409    <example module="app">
18410    <file name="app.js">
18411    var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
18412
18413    app.controller('MainCtrl', ['$scope', function ($scope) {
18414       $scope.data = [
18415         { name: 'Bob', title: 'CEO' },
18416             { name: 'Frank', title: 'Lowly Developer' }
18417       ];
18418
18419       $scope.gridOptions = {
18420         enableGridMenu: true,
18421         exporterMenuCsv: false,
18422         columnDefs: [
18423           {name: 'name', enableCellEdit: true},
18424           {name: 'title', enableCellEdit: true}
18425         ],
18426         data: $scope.data
18427       };
18428     }]);
18429    </file>
18430    <file name="index.html">
18431    <div ng-controller="MainCtrl">
18432    <div ui-grid="gridOptions" ui-grid-exporter></div>
18433    </div>
18434    </file>
18435    </example>
18436    */
18437   module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
18438     function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
18439       return {
18440         replace: true,
18441         priority: 0,
18442         require: '^uiGrid',
18443         scope: false,
18444         link: function ($scope, $elm, $attrs, uiGridCtrl) {
18445           uiGridExporterService.initializeGrid(uiGridCtrl.grid);
18446           uiGridCtrl.grid.exporter.$scope = $scope;
18447         }
18448       };
18449     }
18450   ]);
18451 })();
18452
18453 (function () {
18454   'use strict';
18455
18456   /**
18457    * @ngdoc overview
18458    * @name ui.grid.grouping
18459    * @description
18460    *
18461    * # ui.grid.grouping
18462    *
18463    * <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>
18464    *
18465    * This module provides grouping of rows based on the data in them, similar
18466    * in concept to excel grouping.  You can group multiple columns, resulting in
18467    * nested grouping.
18468    *
18469    * In concept this feature is similar to sorting + grid footer/aggregation, it
18470    * sorts the data based on the grouped columns, then creates group rows that
18471    * reflect a break in the data.  Each of those group rows can have aggregations for
18472    * the data within that group.
18473    *
18474    * This feature leverages treeBase to provide the tree functionality itself,
18475    * the key thing this feature does therefore is to set treeLevels on the rows
18476    * and insert the group headers.
18477    *
18478    * Design information:
18479    * -------------------
18480    *
18481    * Each column will get new menu items - group by, and aggregate by.  Group by
18482    * will cause this column to be sorted (if not already), and will move this column
18483    * to the front of the sorted columns (i.e. grouped columns take precedence over
18484    * sorted columns).  It will respect the sort order already set if there is one,
18485    * and it will allow the sorting logic to change that sort order, it just forces
18486    * the column to the front of the sorting.  You can group by multiple columns, the
18487    * logic will add this column to the sorting after any already grouped columns.
18488    *
18489    * Once a grouping is defined, grouping logic is added to the rowsProcessors.  This
18490    * will process the rows, identifying a break in the data value, and inserting a grouping row.
18491    * Grouping rows have specific attributes on them:
18492    *
18493    *  - internalRow = true: tells us that this isn't a real row, so we can ignore it
18494    *    from any processing that it looking at core data rows.  This is used by the core
18495    *    logic (or will be one day), as it's not grouping specific
18496    *  - groupHeader = true: tells us this is a groupHeader.  This is used by the grouping logic
18497    *    to know if this is a groupHeader row or not
18498    *
18499    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
18500    * row order or filtering or anything like that is changed.  In order to avoid the row instantiation
18501    * time, and to preserve state across invocations, we hold a cache of the rows that we created
18502    * last time, and we use them again this time if we can.
18503    *
18504    * By default rows are collapsed, which means all data rows have their visible property
18505    * set to false, and only level 0 group rows are set to visible.
18506    *
18507    * <br/>
18508    * <br/>
18509    *
18510    * <div doc-module-components="ui.grid.grouping"></div>
18511    */
18512
18513   var module = angular.module('ui.grid.grouping', ['ui.grid', 'ui.grid.treeBase']);
18514
18515   /**
18516    *  @ngdoc object
18517    *  @name ui.grid.grouping.constant:uiGridGroupingConstants
18518    *
18519    *  @description constants available in grouping module, this includes
18520    *  all the constants declared in the treeBase module (these are manually copied
18521    *  as there isn't an easy way to include constants in another constants file, and
18522    *  we don't want to make users include treeBase)
18523    *
18524    */
18525   module.constant('uiGridGroupingConstants', {
18526     featureName: "grouping",
18527     rowHeaderColName: 'treeBaseRowHeaderCol',
18528     EXPANDED: 'expanded',
18529     COLLAPSED: 'collapsed',
18530     aggregation: {
18531       COUNT: 'count',
18532       SUM: 'sum',
18533       MAX: 'max',
18534       MIN: 'min',
18535       AVG: 'avg'
18536     }
18537   });
18538
18539   /**
18540    *  @ngdoc service
18541    *  @name ui.grid.grouping.service:uiGridGroupingService
18542    *
18543    *  @description Services for grouping features
18544    */
18545   module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
18546   function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
18547
18548     var service = {
18549
18550       initializeGrid: function (grid, $scope) {
18551         uiGridTreeBaseService.initializeGrid( grid, $scope );
18552
18553         //add feature namespace and any properties to grid for needed
18554         /**
18555          *  @ngdoc object
18556          *  @name ui.grid.grouping.grid:grouping
18557          *
18558          *  @description Grid properties and functions added for grouping
18559          */
18560         grid.grouping = {};
18561
18562         /**
18563          *  @ngdoc property
18564          *  @propertyOf ui.grid.grouping.grid:grouping
18565          *  @name groupHeaderCache
18566          *
18567          *  @description Cache that holds the group header rows we created last time, we'll
18568          *  reuse these next time, not least because they hold our expanded states.
18569          *
18570          *  We need to take care with these that they don't become a memory leak, we
18571          *  create a new cache each time using the values from the old cache.  This works
18572          *  so long as we're creating group rows for invisible rows as well.
18573          *
18574          *  The cache is a nested hash, indexed on the value we grouped by.  So if we
18575          *  grouped by gender then age, we'd maybe have something like:
18576          *  ```
18577          *    {
18578          *      male: {
18579          *        row: <pointer to the old row>,
18580          *        children: {
18581          *          22: { row: <pointer to the old row> },
18582          *          31: { row: <pointer to the old row> }
18583          *      },
18584          *      female: {
18585          *        row: <pointer to the old row>,
18586          *        children: {
18587          *          28: { row: <pointer to the old row> },
18588          *          55: { row: <pointer to the old row> }
18589          *      }
18590          *    }
18591          *  ```
18592          *
18593          *  We create new rows for any missing rows, this means that they come in as collapsed.
18594          *
18595          */
18596         grid.grouping.groupHeaderCache = {};
18597
18598         service.defaultGridOptions(grid.options);
18599
18600         grid.registerRowsProcessor(service.groupRows, 400);
18601
18602         grid.registerColumnBuilder( service.groupingColumnBuilder);
18603
18604         grid.registerColumnsProcessor(service.groupingColumnProcessor, 400);
18605
18606         /**
18607          *  @ngdoc object
18608          *  @name ui.grid.grouping.api:PublicApi
18609          *
18610          *  @description Public Api for grouping feature
18611          */
18612         var publicApi = {
18613           events: {
18614             grouping: {
18615               /**
18616                * @ngdoc event
18617                * @eventOf ui.grid.grouping.api:PublicApi
18618                * @name aggregationChanged
18619                * @description raised whenever aggregation is changed, added or removed from a column
18620                *
18621                * <pre>
18622                *      gridApi.grouping.on.aggregationChanged(scope,function(col){})
18623                * </pre>
18624                * @param {gridCol} col the column which on which aggregation changed. The aggregation
18625                * type is available as `col.treeAggregation.type`
18626                */
18627               aggregationChanged: {},
18628               /**
18629                * @ngdoc event
18630                * @eventOf ui.grid.grouping.api:PublicApi
18631                * @name groupingChanged
18632                * @description raised whenever the grouped columns changes
18633                *
18634                * <pre>
18635                *      gridApi.grouping.on.groupingChanged(scope,function(col){})
18636                * </pre>
18637                * @param {gridCol} col the column which on which grouping changed. The new grouping is
18638                * available as `col.grouping`
18639                */
18640               groupingChanged: {}
18641             }
18642           },
18643           methods: {
18644             grouping: {
18645               /**
18646                * @ngdoc function
18647                * @name getGrouping
18648                * @methodOf  ui.grid.grouping.api:PublicApi
18649                * @description Get the grouping configuration for this grid,
18650                * used by the saveState feature.  Adds expandedState to the information
18651                * provided by the internal getGrouping, and removes any aggregations that have a source
18652                * of grouping (i.e. will be automatically reapplied when we regroup the column)
18653                * Returned grouping is an object
18654                *   `{ grouping: groupArray, treeAggregations: aggregateArray, expandedState: hash }`
18655                * where grouping contains an array of objects:
18656                *   `{ field: column.field, colName: column.name, groupPriority: column.grouping.groupPriority }`
18657                * and aggregations contains an array of objects:
18658                *   `{ field: column.field, colName: column.name, aggregation: column.grouping.aggregation }`
18659                * and expandedState is a hash of the currently expanded nodes
18660                *
18661                * The groupArray will be sorted by groupPriority.
18662                *
18663                * @param {boolean} getExpanded whether or not to return the expanded state
18664                * @returns {object} grouping configuration
18665                */
18666               getGrouping: function ( getExpanded ) {
18667                 var grouping = service.getGrouping(grid);
18668
18669                 grouping.grouping.forEach( function( group ) {
18670                   group.colName = group.col.name;
18671                   delete group.col;
18672                 });
18673
18674                 grouping.aggregations.forEach( function( aggregation ) {
18675                   aggregation.colName = aggregation.col.name;
18676                   delete aggregation.col;
18677                 });
18678
18679                 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
18680                   return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
18681                 });
18682
18683                 if ( getExpanded ){
18684                   grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
18685                 }
18686
18687                 return grouping;
18688               },
18689
18690               /**
18691                * @ngdoc function
18692                * @name setGrouping
18693                * @methodOf  ui.grid.grouping.api:PublicApi
18694                * @description Set the grouping configuration for this grid,
18695                * used by the saveState feature, but can also be used by any
18696                * user to specify a combined grouping and aggregation configuration
18697                * @param {object} config the config you want to apply, in the format
18698                * provided out by getGrouping
18699                */
18700               setGrouping: function ( config ) {
18701                 service.setGrouping(grid, config);
18702               },
18703
18704               /**
18705                * @ngdoc function
18706                * @name groupColumn
18707                * @methodOf  ui.grid.grouping.api:PublicApi
18708                * @description Adds this column to the existing grouping, at the end of the priority order.
18709                * If the column doesn't have a sort, adds one, by default ASC
18710                *
18711                * This column will move to the left of any non-group columns, the
18712                * move is handled in a columnProcessor, so gets called as part of refresh
18713                *
18714                * @param {string} columnName the name of the column we want to group
18715                */
18716               groupColumn: function( columnName ) {
18717                 var column = grid.getColumn(columnName);
18718                 service.groupColumn(grid, column);
18719               },
18720
18721               /**
18722                * @ngdoc function
18723                * @name ungroupColumn
18724                * @methodOf  ui.grid.grouping.api:PublicApi
18725                * @description Removes the groupPriority from this column.  If the
18726                * column was previously aggregated the aggregation will come back.
18727                * The sort will remain.
18728                *
18729                * This column will move to the right of any other group columns, the
18730                * move is handled in a columnProcessor, so gets called as part of refresh
18731                *
18732                * @param {string} columnName the name of the column we want to ungroup
18733                */
18734               ungroupColumn: function( columnName ) {
18735                 var column = grid.getColumn(columnName);
18736                 service.ungroupColumn(grid, column);
18737               },
18738
18739               /**
18740                * @ngdoc function
18741                * @name clearGrouping
18742                * @methodOf  ui.grid.grouping.api:PublicApi
18743                * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
18744                * as we don't know whether that sorting was added by grouping or was there beforehand
18745                *
18746                */
18747               clearGrouping: function() {
18748                 service.clearGrouping(grid);
18749               },
18750
18751               /**
18752                * @ngdoc function
18753                * @name aggregateColumn
18754                * @methodOf  ui.grid.grouping.api:PublicApi
18755                * @description Sets the aggregation type on a column, if the
18756                * column is currently grouped then it removes the grouping first.
18757                * If the aggregationDef is null then will result in the aggregation
18758                * being removed
18759                *
18760                * @param {string} columnName the column we want to aggregate
18761                * @param {string} or {function} aggregationDef one of the recognised types
18762                * from uiGridGroupingConstants or a custom aggregation function.
18763                * @param {string} aggregationLabel (optional) The label to use for this aggregation.
18764                */
18765               aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
18766                 var column = grid.getColumn(columnName);
18767                 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
18768               }
18769
18770             }
18771           }
18772         };
18773
18774         grid.api.registerEventsFromObject(publicApi.events);
18775
18776         grid.api.registerMethodsFromObject(publicApi.methods);
18777
18778         grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
18779
18780       },
18781
18782       defaultGridOptions: function (gridOptions) {
18783         //default option to true unless it was explicitly set to false
18784         /**
18785          *  @ngdoc object
18786          *  @name ui.grid.grouping.api:GridOptions
18787          *
18788          *  @description GridOptions for grouping feature, these are available to be
18789          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18790          */
18791
18792         /**
18793          *  @ngdoc object
18794          *  @name enableGrouping
18795          *  @propertyOf  ui.grid.grouping.api:GridOptions
18796          *  @description Enable row grouping for entire grid.
18797          *  <br/>Defaults to true
18798          */
18799         gridOptions.enableGrouping = gridOptions.enableGrouping !== false;
18800
18801         /**
18802          *  @ngdoc object
18803          *  @name groupingShowCounts
18804          *  @propertyOf  ui.grid.grouping.api:GridOptions
18805          *  @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
18806          *  sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
18807          *  to break, since the group header rows will always be a string with groupingShowCounts enabled.
18808          *  <br/>Defaults to true except on columns of type 'date'
18809          */
18810         gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
18811
18812         /**
18813          *  @ngdoc object
18814          *  @name groupingNullLabel
18815          *  @propertyOf  ui.grid.grouping.api:GridOptions
18816          *  @description The string to use for the grouping header row label on rows which contain a null or undefined value in the grouped column.
18817          *  <br/>Defaults to "Null"
18818          */
18819         gridOptions.groupingNullLabel = typeof(gridOptions.groupingNullLabel) === 'undefined' ? 'Null' : gridOptions.groupingNullLabel;
18820
18821         /**
18822          *  @ngdoc object
18823          *  @name enableGroupHeaderSelection
18824          *  @propertyOf  ui.grid.grouping.api:GridOptions
18825          *  @description Allows group header rows to be selected.
18826          *  <br/>Defaults to false
18827          */
18828         gridOptions.enableGroupHeaderSelection = gridOptions.enableGroupHeaderSelection === true;
18829       },
18830
18831
18832       /**
18833        * @ngdoc function
18834        * @name groupingColumnBuilder
18835        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18836        * @description Sets the grouping defaults based on the columnDefs
18837        *
18838        * @param {object} colDef columnDef we're basing on
18839        * @param {GridCol} col the column we're to update
18840        * @param {object} gridOptions the options we should use
18841        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
18842        */
18843       groupingColumnBuilder: function (colDef, col, gridOptions) {
18844         /**
18845          *  @ngdoc object
18846          *  @name ui.grid.grouping.api:ColumnDef
18847          *
18848          *  @description ColumnDef for grouping feature, these are available to be
18849          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
18850          */
18851
18852         /**
18853          *  @ngdoc object
18854          *  @name enableGrouping
18855          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18856          *  @description Enable grouping on this column
18857          *  <br/>Defaults to true.
18858          */
18859         if (colDef.enableGrouping === false){
18860           return;
18861         }
18862
18863         /**
18864          *  @ngdoc object
18865          *  @name grouping
18866          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18867          *  @description Set the grouping for a column.  Format is:
18868          *  ```
18869          *    {
18870          *      groupPriority: <number, starts at 0, if less than 0 or undefined then we're aggregating in this column>
18871          *    }
18872          *  ```
18873          *
18874          *  **Note that aggregation used to be included in grouping, but is now separately set on the column via treeAggregation
18875          *  setting in treeBase**
18876          *
18877          *  We group in the priority order given, this will also put these columns to the high order of the sort irrespective
18878          *  of the sort priority given them.  If there is no sort defined then we sort ascending, if there is a sort defined then
18879          *  we use that sort.
18880          *
18881          *  If the groupPriority is undefined or less than 0, then we expect to be aggregating, and we look at the
18882          *  aggregation types to determine what sort of aggregation we can do.  Values are in the constants file, but
18883          *  include SUM, COUNT, MAX, MIN
18884          *
18885          *  groupPriorities should generally be sequential, if they're not then the next time getGrouping is called
18886          *  we'll renumber them to be sequential.
18887          *  <br/>Defaults to undefined.
18888          */
18889
18890         if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
18891           col.grouping = angular.copy(colDef.grouping);
18892           if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
18893             col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
18894             col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
18895           }
18896         } else if (typeof(col.grouping) === 'undefined'){
18897           col.grouping = {};
18898         }
18899
18900         if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
18901           col.suppressRemoveSort = true;
18902         }
18903
18904         var groupColumn = {
18905           name: 'ui.grid.grouping.group',
18906           title: i18nService.get().grouping.group,
18907           icon: 'ui-grid-icon-indent-right',
18908           shown: function () {
18909             return typeof(this.context.col.grouping) === 'undefined' ||
18910                    typeof(this.context.col.grouping.groupPriority) === 'undefined' ||
18911                    this.context.col.grouping.groupPriority < 0;
18912           },
18913           action: function () {
18914             service.groupColumn( this.context.col.grid, this.context.col );
18915           }
18916         };
18917
18918         var ungroupColumn = {
18919           name: 'ui.grid.grouping.ungroup',
18920           title: i18nService.get().grouping.ungroup,
18921           icon: 'ui-grid-icon-indent-left',
18922           shown: function () {
18923             return typeof(this.context.col.grouping) !== 'undefined' &&
18924                    typeof(this.context.col.grouping.groupPriority) !== 'undefined' &&
18925                    this.context.col.grouping.groupPriority >= 0;
18926           },
18927           action: function () {
18928             service.ungroupColumn( this.context.col.grid, this.context.col );
18929           }
18930         };
18931
18932         var aggregateRemove = {
18933           name: 'ui.grid.grouping.aggregateRemove',
18934           title: i18nService.get().grouping.aggregate_remove,
18935           shown: function () {
18936             return typeof(this.context.col.treeAggregationFn) !== 'undefined';
18937           },
18938           action: function () {
18939             service.aggregateColumn( this.context.col.grid, this.context.col, null);
18940           }
18941         };
18942
18943         // generic adder for the aggregation menus, which follow a pattern
18944         var addAggregationMenu = function(type, title){
18945           title = title || i18nService.get().grouping['aggregate_' + type] || type;
18946           var menuItem = {
18947             name: 'ui.grid.grouping.aggregate' + type,
18948             title: title,
18949             shown: function () {
18950               return typeof(this.context.col.treeAggregation) === 'undefined' ||
18951                      typeof(this.context.col.treeAggregation.type) === 'undefined' ||
18952                      this.context.col.treeAggregation.type !== type;
18953             },
18954             action: function () {
18955               service.aggregateColumn( this.context.col.grid, this.context.col, type);
18956             }
18957           };
18958
18959           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregate' + type)) {
18960             col.menuItems.push(menuItem);
18961           }
18962         };
18963
18964         /**
18965          *  @ngdoc object
18966          *  @name groupingShowGroupingMenu
18967          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18968          *  @description Show the grouping (group and ungroup items) menu on this column
18969          *  <br/>Defaults to true.
18970          */
18971         if ( col.colDef.groupingShowGroupingMenu !== false ){
18972           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
18973             col.menuItems.push(groupColumn);
18974           }
18975
18976           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.ungroup')) {
18977             col.menuItems.push(ungroupColumn);
18978           }
18979         }
18980
18981
18982         /**
18983          *  @ngdoc object
18984          *  @name groupingShowAggregationMenu
18985          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18986          *  @description Show the aggregation menu on this column
18987          *  <br/>Defaults to true.
18988          */
18989         if ( col.colDef.groupingShowAggregationMenu !== false ){
18990           angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
18991             addAggregationMenu(name);
18992           });
18993           angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
18994             addAggregationMenu(name, aggregationDef.menuTitle);
18995           });
18996
18997           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregateRemove')) {
18998             col.menuItems.push(aggregateRemove);
18999           }
19000         }
19001       },
19002
19003
19004
19005
19006       /**
19007        * @ngdoc function
19008        * @name groupingColumnProcessor
19009        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19010        * @description Moves the columns around based on which are grouped
19011        *
19012        * @param {array} columns the columns to consider rendering
19013        * @param {array} rows the grid rows, which we don't use but are passed to us
19014        * @returns {array} updated columns array
19015        */
19016       groupingColumnProcessor: function( columns, rows ) {
19017         var grid = this;
19018
19019         columns = service.moveGroupColumns(this, columns, rows);
19020         return columns;
19021       },
19022
19023       /**
19024        * @ngdoc function
19025        * @name groupedFinalizerFn
19026        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19027        * @description Used on group columns to display the rendered value and optionally
19028        * display the count of rows.
19029        *
19030        * @param {aggregation} the aggregation entity for a grouped column
19031        */
19032       groupedFinalizerFn: function( aggregation ){
19033         var col = this;
19034
19035         if ( typeof(aggregation.groupVal) !== 'undefined') {
19036           aggregation.rendered = aggregation.groupVal;
19037           if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' ){
19038             aggregation.rendered += (' (' + aggregation.value + ')');
19039           }
19040         } else {
19041           aggregation.rendered = null;
19042         }
19043       },
19044
19045       /**
19046        * @ngdoc function
19047        * @name moveGroupColumns
19048        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19049        * @description Moves the column order so that the grouped columns are lined up
19050        * to the left (well, unless you're RTL, then it's the right).  By doing this in
19051        * the columnsProcessor, we make it transient - when the column is ungrouped it'll
19052        * go back to where it was.
19053        *
19054        * Does nothing if the option `moveGroupColumns` is set to false.
19055        *
19056        * @param {Grid} grid grid object
19057        * @param {array} columns the columns that we should process/move
19058        * @param {array} rows the grid rows
19059        * @returns {array} updated columns
19060        */
19061       moveGroupColumns: function( grid, columns, rows ){
19062         if ( grid.options.moveGroupColumns === false){
19063           return columns;
19064         }
19065
19066         columns.forEach( function(column, index){
19067           // position used to make stable sort in moveGroupColumns
19068           column.groupingPosition = index;
19069         });
19070
19071         columns.sort(function(a, b){
19072           var a_group, b_group;
19073           if (a.isRowHeader){
19074             a_group = -1000;
19075           }
19076           else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
19077             a_group = null;
19078           } else {
19079             a_group = a.grouping.groupPriority;
19080           }
19081
19082           if (b.isRowHeader){
19083             b_group = -1000;
19084           }
19085           else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
19086             b_group = null;
19087           } else {
19088             b_group = b.grouping.groupPriority;
19089           }
19090
19091           // groups get sorted to the top
19092           if ( a_group !== null && b_group === null) { return -1; }
19093           if ( b_group !== null && a_group === null) { return 1; }
19094           if ( a_group !== null && b_group !== null) {return a_group - b_group; }
19095
19096           return a.groupingPosition - b.groupingPosition;
19097         });
19098
19099         columns.forEach( function(column, index) {
19100           delete column.groupingPosition;
19101         });
19102
19103         return columns;
19104       },
19105
19106
19107       /**
19108        * @ngdoc function
19109        * @name groupColumn
19110        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19111        * @description Adds this column to the existing grouping, at the end of the priority order.
19112        * If the column doesn't have a sort, adds one, by default ASC
19113        *
19114        * This column will move to the left of any non-group columns, the
19115        * move is handled in a columnProcessor, so gets called as part of refresh
19116        *
19117        * @param {Grid} grid grid object
19118        * @param {GridCol} column the column we want to group
19119        */
19120       groupColumn: function( grid, column){
19121         if ( typeof(column.grouping) === 'undefined' ){
19122           column.grouping = {};
19123         }
19124
19125         // set the group priority to the next number in the hierarchy
19126         var existingGrouping = service.getGrouping( grid );
19127         column.grouping.groupPriority = existingGrouping.grouping.length;
19128
19129         // add sort if not present
19130         if ( !column.sort ){
19131           column.sort = { direction: uiGridConstants.ASC };
19132         } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
19133           column.sort.direction = uiGridConstants.ASC;
19134         }
19135
19136         column.treeAggregation = { type: uiGridGroupingConstants.aggregation.COUNT, source: 'grouping' };
19137         column.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19138         column.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19139
19140         grid.api.grouping.raise.groupingChanged(column);
19141         // This indirectly calls service.tidyPriorities( grid );
19142         grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
19143
19144         grid.queueGridRefresh();
19145       },
19146
19147
19148        /**
19149        * @ngdoc function
19150        * @name ungroupColumn
19151        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19152        * @description Removes the groupPriority from this column.  If the
19153        * column was previously aggregated the aggregation will come back.
19154        * The sort will remain.
19155        *
19156        * This column will move to the right of any other group columns, the
19157        * move is handled in a columnProcessor, so gets called as part of refresh
19158        *
19159        * @param {Grid} grid grid object
19160        * @param {GridCol} column the column we want to ungroup
19161        */
19162       ungroupColumn: function( grid, column){
19163         if ( typeof(column.grouping) === 'undefined' ){
19164           return;
19165         }
19166
19167         delete column.grouping.groupPriority;
19168         delete column.treeAggregation;
19169         delete column.customTreeAggregationFinalizer;
19170
19171         service.tidyPriorities( grid );
19172
19173         grid.api.grouping.raise.groupingChanged(column);
19174
19175         grid.queueGridRefresh();
19176       },
19177
19178       /**
19179        * @ngdoc function
19180        * @name aggregateColumn
19181        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19182        * @description Sets the aggregation type on a column, if the
19183        * column is currently grouped then it removes the grouping first.
19184        *
19185        * @param {Grid} grid grid object
19186        * @param {GridCol} column the column we want to aggregate
19187        * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
19188        */
19189       aggregateColumn: function( grid, column, aggregationType){
19190
19191         if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19192           service.ungroupColumn( grid, column );
19193         }
19194
19195         var aggregationDef = {};
19196         if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
19197           aggregationDef = grid.options.treeCustomAggregations[aggregationType];
19198         } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
19199           aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
19200         }
19201
19202         column.treeAggregation = { type: aggregationType, label:  i18nService.get().aggregation[aggregationDef.label] || aggregationDef.label };
19203         column.treeAggregationFn = aggregationDef.aggregationFn;
19204         column.treeAggregationFinalizerFn = aggregationDef.finalizerFn;
19205
19206         grid.api.grouping.raise.aggregationChanged(column);
19207
19208         grid.queueGridRefresh();
19209       },
19210
19211
19212       /**
19213        * @ngdoc function
19214        * @name setGrouping
19215        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19216        * @description Set the grouping based on a config object, used by the save state feature
19217        * (more specifically, by the restore function in that feature )
19218        *
19219        * @param {Grid} grid grid object
19220        * @param {object} config the config we want to set, same format as that returned by getGrouping
19221        */
19222       setGrouping: function ( grid, config ){
19223         if ( typeof(config) === 'undefined' ){
19224           return;
19225         }
19226
19227         // first remove any existing grouping
19228         service.clearGrouping(grid);
19229
19230         if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
19231           config.grouping.forEach( function( group ) {
19232             var col = grid.getColumn(group.colName);
19233
19234             if ( col ) {
19235               service.groupColumn( grid, col );
19236             }
19237           });
19238         }
19239
19240         if ( config.aggregations && config.aggregations.length ){
19241           config.aggregations.forEach( function( aggregation ) {
19242             var col = grid.getColumn(aggregation.colName);
19243
19244             if ( col ) {
19245               service.aggregateColumn( grid, col, aggregation.aggregation.type );
19246             }
19247           });
19248         }
19249
19250         if ( config.rowExpandedStates ){
19251           service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
19252         }
19253       },
19254
19255
19256       /**
19257        * @ngdoc function
19258        * @name clearGrouping
19259        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19260        * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
19261        * as we don't know whether that sorting was added by grouping or was there beforehand
19262        *
19263        * @param {Grid} grid grid object
19264        */
19265       clearGrouping: function( grid ) {
19266         var currentGrouping = service.getGrouping(grid);
19267
19268         if ( currentGrouping.grouping.length > 0 ){
19269           currentGrouping.grouping.forEach( function( group ) {
19270             if (!group.col){
19271               // should have a group.colName if there's no col
19272               group.col = grid.getColumn(group.colName);
19273             }
19274             service.ungroupColumn(grid, group.col);
19275           });
19276         }
19277
19278         if ( currentGrouping.aggregations.length > 0 ){
19279           currentGrouping.aggregations.forEach( function( aggregation ){
19280             if (!aggregation.col){
19281               // should have a group.colName if there's no col
19282               aggregation.col = grid.getColumn(aggregation.colName);
19283             }
19284             service.aggregateColumn(grid, aggregation.col, null);
19285           });
19286         }
19287       },
19288
19289
19290       /**
19291        * @ngdoc function
19292        * @name tidyPriorities
19293        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19294        * @description Renumbers groupPriority and sortPriority such that
19295        * groupPriority is contiguous, and sortPriority either matches
19296        * groupPriority (for group columns), and otherwise is contiguous and
19297        * higher than groupPriority.
19298        *
19299        * @param {Grid} grid grid object
19300        */
19301       tidyPriorities: function( grid ){
19302         // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
19303         if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
19304           grid = this.grid;
19305         }
19306
19307         var groupArray = [];
19308         var sortArray = [];
19309
19310         grid.columns.forEach( function(column, index){
19311           if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19312             groupArray.push(column);
19313           } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
19314             sortArray.push(column);
19315           }
19316         });
19317
19318         groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
19319         groupArray.forEach( function(column, index){
19320           column.grouping.groupPriority = index;
19321           column.suppressRemoveSort = true;
19322           if ( typeof(column.sort) === 'undefined'){
19323             column.sort = {};
19324           }
19325           column.sort.priority = index;
19326         });
19327
19328         var i = groupArray.length;
19329         sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
19330         sortArray.forEach( function(column, index){
19331           column.sort.priority = i;
19332           column.suppressRemoveSort = column.colDef.suppressRemoveSort;
19333           i++;
19334         });
19335       },
19336
19337
19338       /**
19339        * @ngdoc function
19340        * @name groupRows
19341        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19342        * @description The rowProcessor that creates the groupHeaders (i.e. does
19343        * the actual grouping).
19344        *
19345        * Assumes it is always called after the sorting processor, guaranteed by the priority setting
19346        *
19347        * Processes all the rows in order, inserting a groupHeader row whenever there is a change
19348        * in value of a grouped row, based on the sortAlgorithm used for the column.  The group header row
19349        * is looked up in the groupHeaderCache, and used from there if there is one. The entity is reset
19350        * to {} if one is found.
19351        *
19352        * As it processes it maintains a `processingState` array. This records, for each level of grouping we're
19353        * working with, the following information:
19354        * ```
19355        *   {
19356        *     fieldName: name,
19357        *     col: col,
19358        *     initialised: boolean,
19359        *     currentValue: value,
19360        *     currentRow: gridRow,
19361        *   }
19362        * ```
19363        * We look for changes in the currentValue at any of the levels.  Where we find a change we:
19364        *
19365        * - create a new groupHeader row in the array
19366        *
19367        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
19368        * @returns {array} the updated rows, including our new group rows
19369        */
19370       groupRows: function( renderableRows ) {
19371         if (renderableRows.length === 0){
19372           return renderableRows;
19373         }
19374
19375         var grid = this;
19376         grid.grouping.oldGroupingHeaderCache = grid.grouping.groupingHeaderCache || {};
19377         grid.grouping.groupingHeaderCache = {};
19378
19379         var processingState = service.initialiseProcessingState( grid );
19380
19381         // processes each of the fields we are grouping by, checks if the value has changed and inserts a groupHeader
19382         // Broken out as shouldn't create functions in a loop.
19383         var updateProcessingState = function( groupFieldState, stateIndex ) {
19384           var fieldValue = grid.getCellValue(row, groupFieldState.col);
19385
19386           // look for change of value - and insert a header
19387           if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
19388             service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
19389             i++;
19390           }
19391         };
19392
19393         // use a for loop because it's tolerant of the array length changing whilst we go - we can
19394         // manipulate the iterator when we insert groupHeader rows
19395         for (var i = 0; i < renderableRows.length; i++ ){
19396           var row = renderableRows[i];
19397
19398           if ( row.visible ){
19399             processingState.forEach( updateProcessingState );
19400           }
19401         }
19402
19403         delete grid.grouping.oldGroupingHeaderCache;
19404         return renderableRows;
19405       },
19406
19407
19408       /**
19409        * @ngdoc function
19410        * @name initialiseProcessingState
19411        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19412        * @description Creates the processing state array that is used
19413        * for groupRows.
19414        *
19415        * @param {Grid} grid grid object
19416        * @returns {array} an array in the format described in the groupRows method,
19417        * initialised with blank values
19418        */
19419       initialiseProcessingState: function( grid ){
19420         var processingState = [];
19421         var columnSettings = service.getGrouping( grid );
19422
19423         columnSettings.grouping.forEach( function( groupItem, index){
19424           processingState.push({
19425             fieldName: groupItem.field,
19426             col: groupItem.col,
19427             initialised: false,
19428             currentValue: null,
19429             currentRow: null
19430           });
19431         });
19432
19433         return processingState;
19434       },
19435
19436
19437       /**
19438        * @ngdoc function
19439        * @name getGrouping
19440        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19441        * @description Get the grouping settings from the columns.  As a side effect
19442        * this always renumbers the grouping starting at 0
19443        * @param {Grid} grid grid object
19444        * @returns {array} an array of the group fields, in order of priority
19445        */
19446       getGrouping: function( grid ){
19447         var groupArray = [];
19448         var aggregateArray = [];
19449
19450         // get all the grouping
19451         grid.columns.forEach( function(column, columnIndex){
19452           if ( column.grouping ){
19453             if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19454               groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
19455             }
19456           }
19457           if ( column.treeAggregation && column.treeAggregation.type ){
19458             aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
19459           }
19460         });
19461
19462         // sort grouping into priority order
19463         groupArray.sort( function(a, b){
19464           return a.groupPriority - b.groupPriority;
19465         });
19466
19467         // renumber the priority in case it was somewhat messed up, then remove the grouping reference
19468         groupArray.forEach( function( group, index) {
19469           group.grouping.groupPriority = index;
19470           group.groupPriority = index;
19471           delete group.grouping;
19472         });
19473
19474         return { grouping: groupArray, aggregations: aggregateArray };
19475       },
19476
19477
19478       /**
19479        * @ngdoc function
19480        * @name insertGroupHeader
19481        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19482        * @description Create a group header row, and link it to the various configuration
19483        * items that we use.
19484        *
19485        * Look for the row in the oldGroupingHeaderCache, write the row into the new groupingHeaderCache.
19486        *
19487        * @param {Grid} grid grid object
19488        * @param {array} renderableRows the rows that we are processing
19489        * @param {number} rowIndex the row we were up to processing
19490        * @param {array} processingState the current processing state
19491        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header -
19492        * i.e. the column that we want to create a header for
19493        */
19494       insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
19495         // set the value that caused the end of a group into the header row and the processing state
19496         var fieldName = processingState[stateIndex].fieldName;
19497         var col = processingState[stateIndex].col;
19498
19499         var newValue = grid.getCellValue(renderableRows[rowIndex], col);
19500         var newDisplayValue = newValue;
19501         if ( typeof(newValue) === 'undefined' || newValue === null ) {
19502           newDisplayValue = grid.options.groupingNullLabel;
19503         }
19504
19505         var getKeyAsValueForCacheMap = function(key) {
19506           if (angular.isObject(key)) {
19507               return JSON.stringify(key);
19508           } else {
19509               return key;
19510           }
19511         };
19512
19513         var cacheItem = grid.grouping.oldGroupingHeaderCache;
19514         for ( var i = 0; i < stateIndex; i++ ){
19515           if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)] ){
19516             cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
19517           }
19518         }
19519
19520         var headerRow;
19521         if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(newValue)]){
19522           headerRow = cacheItem[getKeyAsValueForCacheMap(newValue)].row;
19523           headerRow.entity = {};
19524         } else {
19525           headerRow = new GridRow( {}, null, grid );
19526           gridClassFactory.rowTemplateAssigner.call(grid, headerRow);
19527         }
19528
19529         headerRow.entity['$$' + processingState[stateIndex].col.uid] = { groupVal: newDisplayValue };
19530         headerRow.treeLevel = stateIndex;
19531         headerRow.groupHeader = true;
19532         headerRow.internalRow = true;
19533         headerRow.enableCellEdit = false;
19534         headerRow.enableSelection = grid.options.enableGroupHeaderSelection;
19535         processingState[stateIndex].initialised = true;
19536         processingState[stateIndex].currentValue = newValue;
19537         processingState[stateIndex].currentRow = headerRow;
19538
19539         // set all processing states below this one to not be initialised - change of this state
19540         // means all those need to start again
19541         service.finaliseProcessingState( processingState, stateIndex + 1);
19542
19543         // insert our new header row
19544         renderableRows.splice(rowIndex, 0, headerRow);
19545
19546         // add our new header row to the cache
19547         cacheItem = grid.grouping.groupingHeaderCache;
19548         for ( i = 0; i < stateIndex; i++ ){
19549           cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
19550         }
19551         cacheItem[getKeyAsValueForCacheMap(newValue)] = { row: headerRow, children: {} };
19552       },
19553
19554
19555       /**
19556        * @ngdoc function
19557        * @name finaliseProcessingState
19558        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19559        * @description Set all processing states lower than the one that had a break in value to
19560        * no longer be initialised.  Render the counts into the entity ready for display.
19561        *
19562        * @param {Grid} grid grid object
19563        * @param {array} processingState the current processing state
19564        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
19565        * processing states after this need to be finalised
19566        */
19567       finaliseProcessingState: function( processingState, stateIndex ){
19568         for ( var i = stateIndex; i < processingState.length; i++){
19569           processingState[i].initialised = false;
19570           processingState[i].currentRow = null;
19571           processingState[i].currentValue = null;
19572         }
19573       },
19574
19575
19576       /**
19577        * @ngdoc function
19578        * @name getRowExpandedStates
19579        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19580        * @description Extract the groupHeaderCache hash, pulling out only the states.
19581        *
19582        * The example below shows a grid that is grouped by gender then age
19583        *
19584        * <pre>
19585        *   {
19586        *     male: {
19587        *       state: 'expanded',
19588        *       children: {
19589        *         22: { state: 'expanded' },
19590        *         30: { state: 'collapsed' }
19591        *       }
19592        *     },
19593        *     female: {
19594        *       state: 'expanded',
19595        *       children: {
19596        *         28: { state: 'expanded' },
19597        *         55: { state: 'collapsed' }
19598        *       }
19599        *     }
19600        *   }
19601        * </pre>
19602        *
19603        * @param {Grid} grid grid object
19604        * @returns {hash} the expanded states as a hash
19605        */
19606       getRowExpandedStates: function(treeChildren){
19607         if ( typeof(treeChildren) === 'undefined' ){
19608           return {};
19609         }
19610
19611         var newChildren = {};
19612
19613         angular.forEach( treeChildren, function( value, key ){
19614           newChildren[key] = { state: value.row.treeNode.state };
19615           if ( value.children ){
19616             newChildren[key].children = service.getRowExpandedStates( value.children );
19617           } else {
19618             newChildren[key].children = {};
19619           }
19620         });
19621
19622         return newChildren;
19623       },
19624
19625
19626       /**
19627        * @ngdoc function
19628        * @name applyRowExpandedStates
19629        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19630        * @description Take a hash in the format as created by getRowExpandedStates,
19631        * and apply it to the grid.grouping.groupHeaderCache.
19632        *
19633        * Takes a treeSubset, and applies to a treeSubset - so can be called
19634        * recursively.
19635        *
19636        * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
19637        * the children of that hash
19638        * @returns {hash} expandedStates can be the full expanded states, or children
19639        * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
19640        */
19641       applyRowExpandedStates: function( currentNode, expandedStates ){
19642         if ( typeof(expandedStates) === 'undefined' ){
19643           return;
19644         }
19645
19646         angular.forEach(expandedStates, function( value, key ) {
19647           if ( currentNode[key] ){
19648             currentNode[key].row.treeNode.state = value.state;
19649
19650             if (value.children && currentNode[key].children){
19651               service.applyRowExpandedStates( currentNode[key].children, value.children );
19652             }
19653           }
19654         });
19655       }
19656
19657
19658     };
19659
19660     return service;
19661
19662   }]);
19663
19664
19665   /**
19666    *  @ngdoc directive
19667    *  @name ui.grid.grouping.directive:uiGridGrouping
19668    *  @element div
19669    *  @restrict A
19670    *
19671    *  @description Adds grouping features to grid
19672    *
19673    *  @example
19674    <example module="app">
19675    <file name="app.js">
19676    var app = angular.module('app', ['ui.grid', 'ui.grid.grouping']);
19677
19678    app.controller('MainCtrl', ['$scope', function ($scope) {
19679       $scope.data = [
19680         { name: 'Bob', title: 'CEO' },
19681             { name: 'Frank', title: 'Lowly Developer' }
19682       ];
19683
19684       $scope.columnDefs = [
19685         {name: 'name', enableCellEdit: true},
19686         {name: 'title', enableCellEdit: true}
19687       ];
19688
19689       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
19690     }]);
19691    </file>
19692    <file name="index.html">
19693    <div ng-controller="MainCtrl">
19694    <div ui-grid="gridOptions" ui-grid-grouping></div>
19695    </div>
19696    </file>
19697    </example>
19698    */
19699   module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
19700   function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
19701     return {
19702       replace: true,
19703       priority: 0,
19704       require: '^uiGrid',
19705       scope: false,
19706       compile: function () {
19707         return {
19708           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
19709             if (uiGridCtrl.grid.options.enableGrouping !== false){
19710               uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
19711             }
19712           },
19713           post: function ($scope, $elm, $attrs, uiGridCtrl) {
19714           }
19715         };
19716       }
19717     };
19718   }]);
19719
19720 })();
19721
19722 (function () {
19723   'use strict';
19724
19725   /**
19726    * @ngdoc overview
19727    * @name ui.grid.importer
19728    * @description
19729    *
19730    * # ui.grid.importer
19731    *
19732    * <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>
19733    *
19734    * This module provides the ability to import data into the grid. It
19735    * uses the column defs to work out which data belongs in which column,
19736    * and creates entities from a configured class (typically a $resource).
19737    *
19738    * If the rowEdit feature is enabled, it also calls save on those newly
19739    * created objects, and then displays any errors in the imported data.
19740    *
19741    * Currently the importer imports only CSV and json files, although provision has been
19742    * made to process other file formats, and these can be added over time.
19743    *
19744    * For json files, the properties within each object in the json must match the column names
19745    * (to put it another way, the importer doesn't process the json, it just copies the objects
19746    * within the json into a new instance of the specified object type)
19747    *
19748    * For CSV import, the default column identification relies on each column in the
19749    * header row matching a column.name or column.displayName. Optionally, a column identification
19750    * callback can be used.  This allows matching using other attributes, which is particularly
19751    * useful if your application has internationalised column headings (i.e. the headings that
19752    * the user sees don't match the column names).
19753    *
19754    * The importer makes use of the grid menu as the UI for requesting an
19755    * import.
19756    *
19757    * <div ui-grid-importer></div>
19758    */
19759
19760   var module = angular.module('ui.grid.importer', ['ui.grid']);
19761
19762   /**
19763    *  @ngdoc object
19764    *  @name ui.grid.importer.constant:uiGridImporterConstants
19765    *
19766    *  @description constants available in importer module
19767    */
19768
19769   module.constant('uiGridImporterConstants', {
19770     featureName: 'importer'
19771   });
19772
19773   /**
19774    *  @ngdoc service
19775    *  @name ui.grid.importer.service:uiGridImporterService
19776    *
19777    *  @description Services for importer feature
19778    */
19779   module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
19780     function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
19781
19782       var service = {
19783
19784         initializeGrid: function ($scope, grid) {
19785
19786           //add feature namespace and any properties to grid for needed state
19787           grid.importer = {
19788             $scope: $scope
19789           };
19790
19791           this.defaultGridOptions(grid.options);
19792
19793           /**
19794            *  @ngdoc object
19795            *  @name ui.grid.importer.api:PublicApi
19796            *
19797            *  @description Public Api for importer feature
19798            */
19799           var publicApi = {
19800             events: {
19801               importer: {
19802               }
19803             },
19804             methods: {
19805               importer: {
19806                 /**
19807                  * @ngdoc function
19808                  * @name importFile
19809                  * @methodOf  ui.grid.importer.api:PublicApi
19810                  * @description Imports a file into the grid using the file object
19811                  * provided.  Bypasses the grid menu
19812                  * @param {File} fileObject the file we want to import, as a javascript
19813                  * File object
19814                  */
19815                 importFile: function ( fileObject ) {
19816                   service.importThisFile( grid, fileObject );
19817                 }
19818               }
19819             }
19820           };
19821
19822           grid.api.registerEventsFromObject(publicApi.events);
19823
19824           grid.api.registerMethodsFromObject(publicApi.methods);
19825
19826           if ( grid.options.enableImporter && grid.options.importerShowMenu ){
19827             if ( grid.api.core.addToGridMenu ){
19828               service.addToMenu( grid );
19829             } else {
19830               // order of registration is not guaranteed, register in a little while
19831               $interval( function() {
19832                 if (grid.api.core.addToGridMenu){
19833                   service.addToMenu( grid );
19834                 }
19835               }, 100, 1);
19836             }
19837           }
19838         },
19839
19840
19841         defaultGridOptions: function (gridOptions) {
19842           //default option to true unless it was explicitly set to false
19843           /**
19844            * @ngdoc object
19845            * @name ui.grid.importer.api:GridOptions
19846            *
19847            * @description GridOptions for importer feature, these are available to be
19848            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
19849            */
19850
19851           /**
19852            * @ngdoc property
19853            * @propertyOf ui.grid.importer.api:GridOptions
19854            * @name enableImporter
19855            * @description Whether or not importer is enabled.  Automatically set
19856            * to false if the user's browser does not support the required fileApi.
19857            * Otherwise defaults to true.
19858            *
19859            */
19860           if (gridOptions.enableImporter  || gridOptions.enableImporter === undefined) {
19861             if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
19862               gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
19863               gridOptions.enableImporter = false;
19864             } else {
19865               gridOptions.enableImporter = true;
19866             }
19867           } else {
19868             gridOptions.enableImporter = false;
19869           }
19870
19871           /**
19872            * @ngdoc method
19873            * @name importerProcessHeaders
19874            * @methodOf ui.grid.importer.api:GridOptions
19875            * @description A callback function that will process headers using custom
19876            * logic.  Set this callback function if the headers that your user will provide in their
19877            * import file don't necessarily match the grid header or field names.  This might commonly
19878            * occur where your application is internationalised, and therefore the field names
19879            * that the user recognises are in a different language than the field names that
19880            * ui-grid knows about.
19881            *
19882            * Defaults to the internal `processHeaders` method, which seeks to match using both
19883            * displayName and column.name.  Any non-matching columns are discarded.
19884            *
19885            * Your callback routine should respond by processing the header array, and returning an array
19886            * of matching column names.  A null value in any given position means "don't import this column"
19887            *
19888            * <pre>
19889            *      gridOptions.importerProcessHeaders: function( headerArray ) {
19890            *        var myHeaderColumns = [];
19891            *        var thisCol;
19892            *        headerArray.forEach( function( value, index ) {
19893            *          thisCol = mySpecialLookupFunction( value );
19894            *          myHeaderColumns.push( thisCol.name );
19895            *        });
19896            *
19897            *        return myHeaderCols;
19898            *      })
19899            * </pre>
19900            * @param {Grid} grid the grid we're importing into
19901            * @param {array} headerArray an array of the text from the first row of the csv file,
19902            * which you need to match to column.names
19903            * @returns {array} array of matching column names, in the same order as the headerArray
19904            *
19905            */
19906           gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
19907
19908           /**
19909            * @ngdoc method
19910            * @name importerHeaderFilter
19911            * @methodOf ui.grid.importer.api:GridOptions
19912            * @description A callback function that will filter (usually translate) a single
19913            * header.  Used when you want to match the passed in column names to the column
19914            * displayName after the header filter.
19915            *
19916            * Your callback routine needs to return the filtered header value.
19917            * <pre>
19918            *      gridOptions.importerHeaderFilter: function( displayName ) {
19919            *        return $translate.instant( displayName );
19920            *      })
19921            * </pre>
19922            *
19923            * or:
19924            * <pre>
19925            *      gridOptions.importerHeaderFilter: $translate.instant
19926            * </pre>
19927            * @param {string} displayName the displayName that we'd like to translate
19928            * @returns {string} the translated name
19929            *
19930            */
19931           gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
19932
19933           /**
19934            * @ngdoc method
19935            * @name importerErrorCallback
19936            * @methodOf ui.grid.importer.api:GridOptions
19937            * @description A callback function that provides custom error handling, rather
19938            * than the standard grid behaviour of an alert box and a console message.  You
19939            * might use this to internationalise the console log messages, or to write to a
19940            * custom logging routine that returned errors to the server.
19941            *
19942            * <pre>
19943            *      gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
19944            *        myUserDisplayRoutine( errorKey );
19945            *        myLoggingRoutine( consoleMessage, context );
19946            *      })
19947            * </pre>
19948            * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
19949            * in some way
19950            * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
19951            * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
19952            * @param {string} consoleMessage the English console message that importer would have written
19953            * @param {object} context the context data that importer would have appended to that console message,
19954            * often the file content itself or the element that is in error
19955            *
19956            */
19957           if ( !gridOptions.importerErrorCallback ||  typeof(gridOptions.importerErrorCallback) !== 'function' ){
19958             delete gridOptions.importerErrorCallback;
19959           }
19960
19961           /**
19962            * @ngdoc method
19963            * @name importerDataAddCallback
19964            * @methodOf ui.grid.importer.api:GridOptions
19965            * @description A mandatory callback function that adds data to the source data array.  The grid
19966            * generally doesn't add rows to the source data array, it is tidier to handle this through a user
19967            * callback.
19968            *
19969            * <pre>
19970            *      gridOptions.importerDataAddCallback: function( grid, newObjects ) {
19971            *        $scope.myData = $scope.myData.concat( newObjects );
19972            *      })
19973            * </pre>
19974            * @param {Grid} grid the grid we're importing into, may be useful in some way
19975            * @param {array} newObjects an array of new objects that you should add to your data
19976            *
19977            */
19978           if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
19979             gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
19980             gridOptions.enableImporter = false;
19981           }
19982
19983           /**
19984            * @ngdoc object
19985            * @name importerNewObject
19986            * @propertyOf  ui.grid.importer.api:GridOptions
19987            * @description An object on which we call `new` to create each new row before inserting it into
19988            * the data array.  Typically this would be a $resource entity, which means that if you're using
19989            * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
19990            *
19991            * Defaults to a vanilla javascript object
19992            *
19993            * @example
19994            * <pre>
19995            *   gridOptions.importerNewObject = MyRes;
19996            * </pre>
19997            *
19998            */
19999
20000           /**
20001            * @ngdoc property
20002            * @propertyOf ui.grid.importer.api:GridOptions
20003            * @name importerShowMenu
20004            * @description Whether or not to show an item in the grid menu.  Defaults to true.
20005            *
20006            */
20007           gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
20008
20009           /**
20010            * @ngdoc method
20011            * @methodOf ui.grid.importer.api:GridOptions
20012            * @name importerObjectCallback
20013            * @description A callback that massages the data for each object.  For example,
20014            * you might have data stored as a code value, but display the decode.  This callback
20015            * can be used to change the decoded value back into a code.  Defaults to doing nothing.
20016            * @param {Grid} grid in case you need it
20017            * @param {object} newObject the new object as importer has created it, modify it
20018            * then return the modified version
20019            * @returns {object} the modified object
20020            * @example
20021            * <pre>
20022            *   gridOptions.importerObjectCallback = function ( grid, newObject ) {
20023            *     switch newObject.status {
20024            *       case 'Active':
20025            *         newObject.status = 1;
20026            *         break;
20027            *       case 'Inactive':
20028            *         newObject.status = 2;
20029            *         break;
20030            *     }
20031            *     return newObject;
20032            *   };
20033            * </pre>
20034            */
20035           gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
20036         },
20037
20038
20039         /**
20040          * @ngdoc function
20041          * @name addToMenu
20042          * @methodOf  ui.grid.importer.service:uiGridImporterService
20043          * @description Adds import menu item to the grid menu,
20044          * allowing the user to request import of a file
20045          * @param {Grid} grid the grid into which data should be imported
20046          */
20047         addToMenu: function ( grid ) {
20048           grid.api.core.addToGridMenu( grid, [
20049             {
20050               title: i18nService.getSafeText('gridMenu.importerTitle'),
20051               order: 150
20052             },
20053             {
20054               templateUrl: 'ui-grid/importerMenuItemContainer',
20055               action: function ($event) {
20056                 this.grid.api.importer.importAFile( grid );
20057               },
20058               order: 151
20059             }
20060           ]);
20061         },
20062
20063
20064         /**
20065          * @ngdoc function
20066          * @name importThisFile
20067          * @methodOf ui.grid.importer.service:uiGridImporterService
20068          * @description Imports the provided file into the grid using the file object
20069          * provided.  Bypasses the grid menu
20070          * @param {Grid} grid the grid we're importing into
20071          * @param {File} fileObject the file we want to import, as returned from the File
20072          * javascript object
20073          */
20074         importThisFile: function ( grid, fileObject ) {
20075           if (!fileObject){
20076             gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
20077             return;
20078           }
20079
20080           var reader = new FileReader();
20081
20082           switch ( fileObject.type ){
20083             case 'application/json':
20084               reader.onload = service.importJsonClosure( grid );
20085               break;
20086             default:
20087               reader.onload = service.importCsvClosure( grid );
20088               break;
20089           }
20090
20091           reader.readAsText( fileObject );
20092         },
20093
20094
20095         /**
20096          * @ngdoc function
20097          * @name importJson
20098          * @methodOf ui.grid.importer.service:uiGridImporterService
20099          * @description Creates a function that imports a json file into the grid.
20100          * The json data is imported into new objects of type `gridOptions.importerNewObject`,
20101          * and if the rowEdit feature is enabled the rows are marked as dirty
20102          * @param {Grid} grid the grid we want to import into
20103          * @param {FileObject} importFile the file that we want to import, as
20104          * a FileObject
20105          */
20106         importJsonClosure: function( grid ) {
20107           return function( importFile ){
20108             var newObjects = [];
20109             var newObject;
20110
20111             var importArray = service.parseJson( grid, importFile );
20112             if (importArray === null){
20113               return;
20114             }
20115             importArray.forEach(  function( value, index ) {
20116               newObject = service.newObject( grid );
20117               angular.extend( newObject, value );
20118               newObject = grid.options.importerObjectCallback( grid, newObject );
20119               newObjects.push( newObject );
20120             });
20121
20122             service.addObjects( grid, newObjects );
20123
20124           };
20125         },
20126
20127
20128         /**
20129          * @ngdoc function
20130          * @name parseJson
20131          * @methodOf ui.grid.importer.service:uiGridImporterService
20132          * @description Parses a json file, returns the parsed data.
20133          * Displays an error if file doesn't parse
20134          * @param {Grid} grid the grid that we want to import into
20135          * @param {FileObject} importFile the file that we want to import, as
20136          * a FileObject
20137          * @returns {array} array of objects from the imported json
20138          */
20139         parseJson: function( grid, importFile ){
20140           var loadedObjects;
20141           try {
20142             loadedObjects = JSON.parse( importFile.target.result );
20143           } catch (e) {
20144             service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
20145             return;
20146           }
20147
20148           if ( !Array.isArray( loadedObjects ) ){
20149             service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
20150             return [];
20151           } else {
20152             return loadedObjects;
20153           }
20154         },
20155
20156
20157
20158         /**
20159          * @ngdoc function
20160          * @name importCsvClosure
20161          * @methodOf ui.grid.importer.service:uiGridImporterService
20162          * @description Creates a function that imports a csv file into the grid
20163          * (allowing it to be used in the reader.onload event)
20164          * @param {Grid} grid the grid that we want to import into
20165          * @param {FileObject} importFile the file that we want to import, as
20166          * a file object
20167          */
20168         importCsvClosure: function( grid ) {
20169           return function( importFile ){
20170             var importArray = service.parseCsv( importFile );
20171             if ( !importArray || importArray.length < 1 ){
20172               service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
20173               return;
20174             }
20175
20176             var newObjects = service.createCsvObjects( grid, importArray );
20177             if ( !newObjects || newObjects.length === 0 ){
20178               service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
20179               return;
20180             }
20181
20182             service.addObjects( grid, newObjects );
20183           };
20184         },
20185
20186
20187         /**
20188          * @ngdoc function
20189          * @name parseCsv
20190          * @methodOf ui.grid.importer.service:uiGridImporterService
20191          * @description Parses a csv file into an array of arrays, with the first
20192          * array being the headers, and the remaining arrays being the data.
20193          * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
20194          * which is noted as being under the MIT license.  The code is modified to pass the jscs yoda condition
20195          * checker
20196          * @param {FileObject} importFile the file that we want to import, as a
20197          * file object
20198          */
20199         parseCsv: function( importFile ) {
20200           var csv = importFile.target.result;
20201
20202           // use the CSV-JS library to parse
20203           return CSV.parse(csv);
20204         },
20205
20206
20207         /**
20208          * @ngdoc function
20209          * @name createCsvObjects
20210          * @methodOf ui.grid.importer.service:uiGridImporterService
20211          * @description Converts an array of arrays (representing the csv file)
20212          * into a set of objects.  Uses the provided `gridOptions.importerNewObject`
20213          * to create the objects, and maps the header row into the individual columns
20214          * using either `gridOptions.importerProcessHeaders`, or by using a native method
20215          * of matching to either the displayName, column name or column field of
20216          * the columns in the column defs.  The resulting objects will have attributes
20217          * that are named based on the column.field or column.name, in that order.
20218          * @param {Grid} grid the grid that we want to import into
20219          * @param {Array} importArray the data that we want to import, as an array
20220          */
20221         createCsvObjects: function( grid, importArray ){
20222           // pull off header row and turn into headers
20223           var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
20224           if ( !headerMapping || headerMapping.length === 0 ){
20225             service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
20226             return [];
20227           }
20228
20229           var newObjects = [];
20230           var newObject;
20231           importArray.forEach( function( row, index ) {
20232             newObject = service.newObject( grid );
20233             if ( row !== null ){
20234               row.forEach( function( field, index ){
20235                 if ( headerMapping[index] !== null ){
20236                   newObject[ headerMapping[index] ] = field;
20237                 }
20238               });
20239             }
20240             newObject = grid.options.importerObjectCallback( grid, newObject );
20241             newObjects.push( newObject );
20242           });
20243
20244           return newObjects;
20245         },
20246
20247
20248         /**
20249          * @ngdoc function
20250          * @name processHeaders
20251          * @methodOf ui.grid.importer.service:uiGridImporterService
20252          * @description Determines the columns that the header row from
20253          * a csv (or other) file represents.
20254          * @param {Grid} grid the grid we're importing into
20255          * @param {array} headerRow the header row that we wish to match against
20256          * the column definitions
20257          * @returns {array} an array of the attribute names that should be used
20258          * for that column, based on matching the headers or creating the headers
20259          *
20260          */
20261         processHeaders: function( grid, headerRow ) {
20262           var headers = [];
20263           if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
20264             // we are going to create new columnDefs for all these columns, so just remove
20265             // spaces from the names to create fields
20266             headerRow.forEach( function( value, index ) {
20267               headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
20268             });
20269             return headers;
20270           } else {
20271             var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
20272             headerRow.forEach(  function( value, index ) {
20273               if ( lookupHash[value] ) {
20274                 headers.push( lookupHash[value] );
20275               } else if ( lookupHash[ value.toLowerCase() ] ) {
20276                 headers.push( lookupHash[ value.toLowerCase() ] );
20277               } else {
20278                 headers.push( null );
20279               }
20280             });
20281             return headers;
20282           }
20283         },
20284
20285
20286         /**
20287          * @name flattenColumnDefs
20288          * @methodOf ui.grid.importer.service:uiGridImporterService
20289          * @description Runs through the column defs and creates a hash of
20290          * the displayName, name and field, and of each of those values forced to lower case,
20291          * with each pointing to the field or name
20292          * (whichever is present).  Used to lookup column headers and decide what
20293          * attribute name to give to the resulting field.
20294          * @param {Grid} grid the grid we're importing into
20295          * @param {array} columnDefs the columnDefs that we should flatten
20296          * @returns {hash} the flattened version of the column def information, allowing
20297          * us to look up a value by `flattenedHash[ headerValue ]`
20298          */
20299         flattenColumnDefs: function( grid, columnDefs ){
20300           var flattenedHash = {};
20301           columnDefs.forEach(  function( columnDef, index) {
20302             if ( columnDef.name ){
20303               flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
20304               flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
20305             }
20306
20307             if ( columnDef.field ){
20308               flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
20309               flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
20310             }
20311
20312             if ( columnDef.displayName ){
20313               flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
20314               flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
20315             }
20316
20317             if ( columnDef.displayName && grid.options.importerHeaderFilter ){
20318               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
20319               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
20320             }
20321           });
20322
20323           return flattenedHash;
20324         },
20325
20326
20327         /**
20328          * @ngdoc function
20329          * @name addObjects
20330          * @methodOf ui.grid.importer.service:uiGridImporterService
20331          * @description Inserts our new objects into the grid data, and
20332          * sets the rows to dirty if the rowEdit feature is being used
20333          *
20334          * Does this by registering a watch on dataChanges, which essentially
20335          * is waiting on the result of the grid data watch, and downstream processing.
20336          *
20337          * When the callback is called, it deregisters itself - we don't want to run
20338          * again next time data is added.
20339          *
20340          * If we never get called, we deregister on destroy.
20341          *
20342          * @param {Grid} grid the grid we're importing into
20343          * @param {array} newObjects the objects we want to insert into the grid data
20344          * @returns {object} the new object
20345          */
20346         addObjects: function( grid, newObjects, $scope ){
20347           if ( grid.api.rowEdit ){
20348             var dataChangeDereg = grid.registerDataChangeCallback( function() {
20349               grid.api.rowEdit.setRowsDirty( newObjects );
20350               dataChangeDereg();
20351             }, [uiGridConstants.dataChange.ROW] );
20352
20353             grid.importer.$scope.$on( '$destroy', dataChangeDereg );
20354           }
20355
20356           grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
20357
20358         },
20359
20360
20361         /**
20362          * @ngdoc function
20363          * @name newObject
20364          * @methodOf ui.grid.importer.service:uiGridImporterService
20365          * @description Makes a new object based on `gridOptions.importerNewObject`,
20366          * or based on an empty object if not present
20367          * @param {Grid} grid the grid we're importing into
20368          * @returns {object} the new object
20369          */
20370         newObject: function( grid ){
20371           if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
20372             return new grid.options.importerNewObject();
20373           } else {
20374             return {};
20375           }
20376         },
20377
20378
20379         /**
20380          * @ngdoc function
20381          * @name alertError
20382          * @methodOf ui.grid.importer.service:uiGridImporterService
20383          * @description Provides an internationalised user alert for the failure,
20384          * and logs a console message including diagnostic content.
20385          * Optionally, if the the `gridOptions.importerErrorCallback` routine
20386          * is defined, then calls that instead, allowing user specified error routines
20387          * @param {Grid} grid the grid we're importing into
20388          * @param {array} headerRow the header row that we wish to match against
20389          * the column definitions
20390          */
20391         alertError: function( grid, alertI18nToken, consoleMessage, context ){
20392           if ( grid.options.importerErrorCallback ){
20393             grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
20394           } else {
20395             $window.alert(i18nService.getSafeText( alertI18nToken ));
20396             gridUtil.logError(consoleMessage + context );
20397           }
20398         }
20399       };
20400
20401       return service;
20402
20403     }
20404   ]);
20405
20406   /**
20407    *  @ngdoc directive
20408    *  @name ui.grid.importer.directive:uiGridImporter
20409    *  @element div
20410    *  @restrict A
20411    *
20412    *  @description Adds importer features to grid
20413    *
20414    */
20415   module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
20416     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
20417       return {
20418         replace: true,
20419         priority: 0,
20420         require: '^uiGrid',
20421         scope: false,
20422         link: function ($scope, $elm, $attrs, uiGridCtrl) {
20423           uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
20424         }
20425       };
20426     }
20427   ]);
20428
20429   /**
20430    *  @ngdoc directive
20431    *  @name ui.grid.importer.directive:uiGridImporterMenuItem
20432    *  @element div
20433    *  @restrict A
20434    *
20435    *  @description Handles the processing from the importer menu item - once a file is
20436    *  selected
20437    *
20438    */
20439   module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
20440     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
20441       return {
20442         replace: true,
20443         priority: 0,
20444         require: '^uiGrid',
20445         scope: false,
20446         templateUrl: 'ui-grid/importerMenuItem',
20447         link: function ($scope, $elm, $attrs, uiGridCtrl) {
20448           var handleFileSelect = function( event ){
20449             var target = event.srcElement || event.target;
20450
20451             if (target && target.files && target.files.length === 1) {
20452               var fileObject = target.files[0];
20453               uiGridImporterService.importThisFile( grid, fileObject );
20454               target.form.reset();
20455             }
20456           };
20457
20458           var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
20459           var grid = uiGridCtrl.grid;
20460
20461           if ( fileChooser.length !== 1 ){
20462             gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
20463           } else {
20464             fileChooser[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
20465           }
20466         }
20467       };
20468     }
20469   ]);
20470 })();
20471
20472 (function() {
20473   'use strict';
20474   /**
20475    *  @ngdoc overview
20476    *  @name ui.grid.infiniteScroll
20477    *
20478    *  @description
20479    *
20480    * #ui.grid.infiniteScroll
20481    *
20482    * <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>
20483    *
20484    * This module provides infinite scroll functionality to ui-grid
20485    *
20486    */
20487   var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
20488   /**
20489    *  @ngdoc service
20490    *  @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20491    *
20492    *  @description Service for infinite scroll features
20493    */
20494   module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
20495
20496     var service = {
20497
20498       /**
20499        * @ngdoc function
20500        * @name initializeGrid
20501        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20502        * @description This method register events and methods into grid public API
20503        */
20504
20505       initializeGrid: function(grid, $scope) {
20506         service.defaultGridOptions(grid.options);
20507
20508         if (!grid.options.enableInfiniteScroll){
20509           return;
20510         }
20511
20512         grid.infiniteScroll = { dataLoading: false };
20513         service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
20514           grid.api.core.on.scrollEnd($scope, service.handleScroll);
20515
20516         /**
20517          *  @ngdoc object
20518          *  @name ui.grid.infiniteScroll.api:PublicAPI
20519          *
20520          *  @description Public API for infinite scroll feature
20521          */
20522         var publicApi = {
20523           events: {
20524             infiniteScroll: {
20525
20526               /**
20527                * @ngdoc event
20528                * @name needLoadMoreData
20529                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
20530                * @description This event fires when scroll reaches bottom percentage of grid
20531                * and needs to load data
20532                */
20533
20534               needLoadMoreData: function ($scope, fn) {
20535               },
20536
20537               /**
20538                * @ngdoc event
20539                * @name needLoadMoreDataTop
20540                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
20541                * @description This event fires when scroll reaches top percentage of grid
20542                * and needs to load data
20543                */
20544
20545               needLoadMoreDataTop: function ($scope, fn) {
20546               }
20547             }
20548           },
20549           methods: {
20550             infiniteScroll: {
20551
20552               /**
20553                * @ngdoc function
20554                * @name dataLoaded
20555                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20556                * @description Call this function when you have loaded the additional data
20557                * requested.  You should set scrollUp and scrollDown to indicate
20558                * whether there are still more pages in each direction.
20559                *
20560                * If you call dataLoaded without first calling `saveScrollPercentage` then we will
20561                * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
20562                * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
20563                * on variable speed internet connections.  Using `saveScrollPercentage` as demonstrated in the tutorial
20564                * should give a smoother scrolling experience for users.
20565                *
20566                * See infinite_scroll tutorial for example of usage
20567                * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
20568                * any more infinite scroll events upward
20569                * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
20570                * fire any more infinite scroll events downward
20571                * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted.  If you're
20572                * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
20573                */
20574               dataLoaded: function( scrollUp, scrollDown ) {
20575                 service.setScrollDirections(grid, scrollUp, scrollDown);
20576
20577                 var promise = service.adjustScroll(grid).then(function() {
20578                   grid.infiniteScroll.dataLoading = false;
20579                 });
20580
20581                 return promise;
20582               },
20583
20584               /**
20585                * @ngdoc function
20586                * @name resetScroll
20587                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20588                * @description Call this function when you have taken some action that makes the current
20589                * scroll position invalid.  For example, if you're using external sorting and you've resorted
20590                * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
20591                * you've reused an existing grid for a new data set
20592                *
20593                * You must tell us whether there is data upwards or downwards after the reset
20594                *
20595                * @param {boolean} scrollUp flag that there are pages upwards, fire
20596                * infinite scroll events upward
20597                * @param {boolean} scrollDown flag that there are pages downwards, so
20598                * fire infinite scroll events downward
20599                * @returns {promise} promise that is resolved when the scroll reset is complete
20600                */
20601               resetScroll: function( scrollUp, scrollDown ) {
20602                 service.setScrollDirections( grid, scrollUp, scrollDown);
20603
20604                 return service.adjustInfiniteScrollPosition(grid, 0);
20605               },
20606
20607
20608               /**
20609                * @ngdoc function
20610                * @name saveScrollPercentage
20611                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20612                * @description Saves the scroll percentage and number of visible rows before you adjust the data,
20613                * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
20614                */
20615               saveScrollPercentage: function() {
20616                 grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
20617                 grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
20618               },
20619
20620
20621               /**
20622                * @ngdoc function
20623                * @name dataRemovedTop
20624                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20625                * @description Adjusts the scroll position after you've removed data at the top
20626                * @param {boolean} scrollUp flag that there are pages upwards, fire
20627                * infinite scroll events upward
20628                * @param {boolean} scrollDown flag that there are pages downwards, so
20629                * fire infinite scroll events downward
20630                */
20631               dataRemovedTop: function( scrollUp, scrollDown ) {
20632                 service.dataRemovedTop( grid, scrollUp, scrollDown );
20633               },
20634
20635               /**
20636                * @ngdoc function
20637                * @name dataRemovedBottom
20638                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20639                * @description Adjusts the scroll position after you've removed data at the bottom
20640                * @param {boolean} scrollUp flag that there are pages upwards, fire
20641                * infinite scroll events upward
20642                * @param {boolean} scrollDown flag that there are pages downwards, so
20643                * fire infinite scroll events downward
20644                */
20645               dataRemovedBottom: function( scrollUp, scrollDown ) {
20646                 service.dataRemovedBottom( grid, scrollUp, scrollDown );
20647               },
20648
20649               /**
20650                * @ngdoc function
20651                * @name setScrollDirections
20652                * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20653                * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20654                * and also sets the grid.suppressParentScroll
20655                * @param {boolean} scrollUp whether there are pages available up - defaults to false
20656                * @param {boolean} scrollDown whether there are pages available down - defaults to true
20657                */
20658               setScrollDirections:  function ( scrollUp, scrollDown ) {
20659                 service.setScrollDirections( grid, scrollUp, scrollDown );
20660               }
20661
20662             }
20663           }
20664         };
20665         grid.api.registerEventsFromObject(publicApi.events);
20666         grid.api.registerMethodsFromObject(publicApi.methods);
20667       },
20668
20669
20670       defaultGridOptions: function (gridOptions) {
20671         //default option to true unless it was explicitly set to false
20672         /**
20673          *  @ngdoc object
20674          *  @name ui.grid.infiniteScroll.api:GridOptions
20675          *
20676          *  @description GridOptions for infinite scroll feature, these are available to be
20677          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20678          */
20679
20680         /**
20681          *  @ngdoc object
20682          *  @name enableInfiniteScroll
20683          *  @propertyOf  ui.grid.infiniteScroll.api:GridOptions
20684          *  @description Enable infinite scrolling for this grid
20685          *  <br/>Defaults to true
20686          */
20687         gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
20688
20689         /**
20690          * @ngdoc property
20691          * @name infiniteScrollRowsFromEnd
20692          * @propertyOf ui.grid.class:GridOptions
20693          * @description This setting controls how close to the end of the dataset a user gets before
20694          * more data is requested by the infinite scroll, whether scrolling up or down.  This allows you to
20695          * 'prefetch' rows before the user actually runs out of scrolling.
20696          *
20697          * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
20698          * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
20699          * preserve that scroll position
20700          *
20701          * <br> Defaults to 20
20702          */
20703         gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
20704
20705         /**
20706          * @ngdoc property
20707          * @name infiniteScrollUp
20708          * @propertyOf ui.grid.class:GridOptions
20709          * @description Whether you allow infinite scroll up, implying that the first page of data
20710          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20711          * needMoreDataTop event when the user hits the top of the scrollbar.
20712          * <br> Defaults to false
20713          */
20714         gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
20715
20716         /**
20717          * @ngdoc property
20718          * @name infiniteScrollDown
20719          * @propertyOf ui.grid.class:GridOptions
20720          * @description Whether you allow infinite scroll down, implying that the first page of data
20721          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20722          * needMoreData event when the user hits the bottom of the scrollbar.
20723          * <br> Defaults to true
20724          */
20725         gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
20726       },
20727
20728
20729       /**
20730        * @ngdoc function
20731        * @name setScrollDirections
20732        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20733        * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20734        * and also sets the grid.suppressParentScroll
20735        * @param {grid} grid the grid we're operating on
20736        * @param {boolean} scrollUp whether there are pages available up - defaults to false
20737        * @param {boolean} scrollDown whether there are pages available down - defaults to true
20738        */
20739       setScrollDirections:  function ( grid, scrollUp, scrollDown ) {
20740         grid.infiniteScroll.scrollUp = ( scrollUp === true );
20741         grid.suppressParentScrollUp = ( scrollUp === true );
20742
20743         grid.infiniteScroll.scrollDown = ( scrollDown !== false);
20744         grid.suppressParentScrollDown = ( scrollDown !== false);
20745       },
20746
20747
20748       /**
20749        * @ngdoc function
20750        * @name handleScroll
20751        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20752        * @description Called whenever the grid scrolls, determines whether the scroll should
20753        * trigger an infinite scroll request for more data
20754        * @param {object} args the args from the event
20755        */
20756       handleScroll:  function (args) {
20757         // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
20758         if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
20759           return;
20760         }
20761
20762         if (args.y) {
20763           var percentage;
20764           var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
20765           if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
20766             percentage = args.y.percentage;
20767             if (percentage <= targetPercentage){
20768               service.loadData(args.grid);
20769             }
20770           } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
20771             percentage = 1 - args.y.percentage;
20772             if (percentage <= targetPercentage){
20773               service.loadData(args.grid);
20774             }
20775           }
20776         }
20777       },
20778
20779
20780       /**
20781        * @ngdoc function
20782        * @name loadData
20783        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20784        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20785        * and whether there are more pages upwards or downwards.  It also stores the number of rows that we had previously,
20786        * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
20787        * @param {Grid} grid the grid we're working on
20788        */
20789       loadData: function (grid) {
20790         // save number of currently visible rows to calculate new scroll position later - we know that we want
20791         // to be at approximately the row we're currently at
20792         grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20793         grid.infiniteScroll.direction = grid.scrollDirection;
20794         delete grid.infiniteScroll.prevScrollTop;
20795
20796         if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
20797           grid.infiniteScroll.dataLoading = true;
20798           grid.api.infiniteScroll.raise.needLoadMoreDataTop();
20799         } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
20800           grid.infiniteScroll.dataLoading = true;
20801           grid.api.infiniteScroll.raise.needLoadMoreData();
20802         }
20803       },
20804
20805
20806       /**
20807        * @ngdoc function
20808        * @name adjustScroll
20809        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20810        * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
20811        * addition and to make things look clean.
20812        *
20813        * If we're scrolling up we scroll to the first row of the old data set -
20814        * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
20815        * 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
20816        * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
20817        * the data comes back.
20818        *
20819        * Neither of these are good assumptions, but making this a smoother experience really requires
20820        * 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
20821        * 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
20822        * somewhere else in the mean-time, in which case they'll get a jump back to the new data.  Anyway, this will do for
20823        * now, until someone wants to do better.
20824        * @param {Grid} grid the grid we're working on
20825        * @returns {promise} a promise that is resolved when scrolling has finished
20826        */
20827       adjustScroll: function(grid){
20828         var promise = $q.defer();
20829         $timeout(function () {
20830           var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
20831
20832           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
20833           rowHeight = grid.options.rowHeight;
20834
20835           if ( grid.infiniteScroll.direction === undefined ){
20836             // called from initialize, tweak our scroll up a little
20837             service.adjustInfiniteScrollPosition(grid, 0);
20838           }
20839
20840           newVisibleRows = grid.getVisibleRowCount();
20841
20842           // in case not enough data is loaded to enable scroller - load more data
20843           var canvasHeight = rowHeight * newVisibleRows;
20844           if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
20845             grid.api.infiniteScroll.raise.needLoadMoreData();
20846           }
20847
20848           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
20849             oldTop = grid.infiniteScroll.prevScrollTop || 0;
20850             newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
20851             service.adjustInfiniteScrollPosition(grid, newTop);
20852             $timeout( function() {
20853               promise.resolve();
20854             });
20855           }
20856
20857           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
20858             newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
20859             service.adjustInfiniteScrollPosition(grid, newTop);
20860             $timeout( function() {
20861               promise.resolve();
20862             });
20863           }
20864         }, 0);
20865
20866         return promise.promise;
20867       },
20868
20869
20870       /**
20871        * @ngdoc function
20872        * @name adjustInfiniteScrollPosition
20873        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20874        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20875        * @param {Grid} grid the grid we're working on
20876        * @param {number} scrollTop the position through the grid that we want to scroll to
20877        * @returns {promise} a promise that is resolved when the scrolling finishes
20878        */
20879       adjustInfiniteScrollPosition: function (grid, scrollTop) {
20880         var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
20881           visibleRows = grid.getVisibleRowCount(),
20882           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
20883           rowHeight = grid.options.rowHeight,
20884           scrollHeight = visibleRows*rowHeight-viewportHeight;
20885
20886         //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
20887         if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
20888           // using pixels results in a relative scroll, hence we have to use percentage
20889           scrollEvent.y = {percentage: 1/scrollHeight};
20890         }
20891         else {
20892           scrollEvent.y = {percentage: scrollTop/scrollHeight};
20893         }
20894         grid.scrollContainers('', scrollEvent);
20895       },
20896
20897
20898       /**
20899        * @ngdoc function
20900        * @name dataRemovedTop
20901        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20902        * @description Adjusts the scroll position after you've removed data at the top. You should
20903        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
20904        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
20905        * before you start removing data
20906        * @param {Grid} grid the grid we're working on
20907        * @param {boolean} scrollUp flag that there are pages upwards, fire
20908        * infinite scroll events upward
20909        * @param {boolean} scrollDown flag that there are pages downwards, so
20910        * fire infinite scroll events downward
20911        * @returns {promise} a promise that is resolved when the scrolling finishes
20912        */
20913       dataRemovedTop: function( grid, scrollUp, scrollDown ) {
20914         var newVisibleRows, oldTop, newTop, rowHeight;
20915         service.setScrollDirections( grid, scrollUp, scrollDown );
20916
20917         newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20918         oldTop = grid.infiniteScroll.prevScrollTop;
20919         rowHeight = grid.options.rowHeight;
20920
20921         // since we removed from the top, our new scroll row will be the old scroll row less the number
20922         // of rows removed
20923         newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
20924
20925         return service.adjustInfiniteScrollPosition( grid, newTop );
20926       },
20927
20928       /**
20929        * @ngdoc function
20930        * @name dataRemovedBottom
20931        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20932        * @description Adjusts the scroll position after you've removed data at the bottom.  You should
20933        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
20934        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
20935        * before you start removing data
20936        * @param {Grid} grid the grid we're working on
20937        * @param {boolean} scrollUp flag that there are pages upwards, fire
20938        * infinite scroll events upward
20939        * @param {boolean} scrollDown flag that there are pages downwards, so
20940        * fire infinite scroll events downward
20941        */
20942       dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
20943         var newTop;
20944         service.setScrollDirections( grid, scrollUp, scrollDown );
20945
20946         newTop = grid.infiniteScroll.prevScrollTop;
20947
20948         return service.adjustInfiniteScrollPosition( grid, newTop );
20949       }
20950     };
20951     return service;
20952   }]);
20953   /**
20954    *  @ngdoc directive
20955    *  @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
20956    *  @element div
20957    *  @restrict A
20958    *
20959    *  @description Adds infinite scroll features to grid
20960    *
20961    *  @example
20962    <example module="app">
20963    <file name="app.js">
20964    var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
20965
20966    app.controller('MainCtrl', ['$scope', function ($scope) {
20967       $scope.data = [
20968         { name: 'Alex', car: 'Toyota' },
20969             { name: 'Sam', car: 'Lexus' }
20970       ];
20971
20972       $scope.columnDefs = [
20973         {name: 'name'},
20974         {name: 'car'}
20975       ];
20976     }]);
20977    </file>
20978    <file name="index.html">
20979    <div ng-controller="MainCtrl">
20980    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
20981    </div>
20982    </file>
20983    </example>
20984    */
20985
20986   module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
20987     function (uiGridInfiniteScrollService) {
20988       return {
20989         priority: -200,
20990         scope: false,
20991         require: '^uiGrid',
20992         compile: function($scope, $elm, $attr){
20993           return {
20994             pre: function($scope, $elm, $attr, uiGridCtrl) {
20995               uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
20996             },
20997             post: function($scope, $elm, $attr) {
20998             }
20999           };
21000         }
21001       };
21002     }]);
21003
21004 })();
21005
21006 (function () {
21007   'use strict';
21008
21009   /**
21010    * @ngdoc overview
21011    * @name ui.grid.moveColumns
21012    * @description
21013    *
21014    * # ui.grid.moveColumns
21015    *
21016    * <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>
21017    *
21018    * This module provides column moving capability to ui.grid. It enables to change the position of columns.
21019    * <div doc-module-components="ui.grid.moveColumns"></div>
21020    */
21021   var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
21022
21023   /**
21024    *  @ngdoc service
21025    *  @name ui.grid.moveColumns.service:uiGridMoveColumnService
21026    *  @description Service for column moving feature.
21027    */
21028   module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
21029
21030     var service = {
21031       initializeGrid: function (grid) {
21032         var self = this;
21033         this.registerPublicApi(grid);
21034         this.defaultGridOptions(grid.options);
21035         grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
21036         grid.registerColumnBuilder(self.movableColumnBuilder);
21037         grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
21038       },
21039       registerPublicApi: function (grid) {
21040         var self = this;
21041         /**
21042          *  @ngdoc object
21043          *  @name ui.grid.moveColumns.api:PublicApi
21044          *  @description Public Api for column moving feature.
21045          */
21046         var publicApi = {
21047           events: {
21048             /**
21049              * @ngdoc event
21050              * @name columnPositionChanged
21051              * @eventOf  ui.grid.moveColumns.api:PublicApi
21052              * @description raised when column is moved
21053              * <pre>
21054              *      gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
21055              * </pre>
21056              * @param {object} colDef the column that was moved
21057              * @param {integer} originalPosition of the column
21058              * @param {integer} finalPosition of the column
21059              */
21060             colMovable: {
21061               columnPositionChanged: function (colDef, originalPosition, newPosition) {
21062               }
21063             }
21064           },
21065           methods: {
21066             /**
21067              * @ngdoc method
21068              * @name moveColumn
21069              * @methodOf  ui.grid.moveColumns.api:PublicApi
21070              * @description Method can be used to change column position.
21071              * <pre>
21072              *      gridApi.colMovable.moveColumn(oldPosition, newPosition)
21073              * </pre>
21074              * @param {integer} originalPosition of the column
21075              * @param {integer} finalPosition of the column
21076              */
21077             colMovable: {
21078               moveColumn: function (originalPosition, finalPosition) {
21079                 var columns = grid.columns;
21080                 if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
21081                   gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
21082                   return;
21083                 }
21084                 var nonMovableColumns = 0;
21085                 for (var i = 0; i < columns.length; i++) {
21086                   if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
21087                     nonMovableColumns++;
21088                   }
21089                 }
21090                 if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
21091                   gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
21092                   return;
21093                 }
21094                 var findPositionForRenderIndex = function (index) {
21095                   var position = index;
21096                   for (var i = 0; i <= position; i++) {
21097                     if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
21098                       position++;
21099                     }
21100                   }
21101                   return position;
21102                 };
21103                 self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
21104               }
21105             }
21106           }
21107         };
21108         grid.api.registerEventsFromObject(publicApi.events);
21109         grid.api.registerMethodsFromObject(publicApi.methods);
21110       },
21111       defaultGridOptions: function (gridOptions) {
21112         /**
21113          *  @ngdoc object
21114          *  @name ui.grid.moveColumns.api:GridOptions
21115          *
21116          *  @description Options for configuring the move column feature, these are available to be
21117          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21118          */
21119         /**
21120          *  @ngdoc object
21121          *  @name enableColumnMoving
21122          *  @propertyOf  ui.grid.moveColumns.api:GridOptions
21123          *  @description If defined, sets the default value for the colMovable flag on each individual colDefs
21124          *  if their individual enableColumnMoving configuration is not defined. Defaults to true.
21125          */
21126         gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
21127       },
21128       movableColumnBuilder: function (colDef, col, gridOptions) {
21129         var promises = [];
21130         /**
21131          *  @ngdoc object
21132          *  @name ui.grid.moveColumns.api:ColumnDef
21133          *
21134          *  @description Column Definition for move column feature, these are available to be
21135          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
21136          */
21137         /**
21138          *  @ngdoc object
21139          *  @name enableColumnMoving
21140          *  @propertyOf  ui.grid.moveColumns.api:ColumnDef
21141          *  @description Enable column moving for the column.
21142          */
21143         colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
21144           : colDef.enableColumnMoving;
21145         return $q.all(promises);
21146       },
21147       /**
21148        * @ngdoc method
21149        * @name updateColumnCache
21150        * @methodOf  ui.grid.moveColumns
21151        * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
21152        */
21153       updateColumnCache: function(grid){
21154         grid.moveColumns.orderCache = grid.getOnlyDataColumns();
21155       },
21156       /**
21157        * @ngdoc method
21158        * @name verifyColumnOrder
21159        * @methodOf  ui.grid.moveColumns
21160        * @description dataChangeCallback which uses the cached column order to restore the column order
21161        * when it is reset by altering the columnDefs array.
21162        */
21163       verifyColumnOrder: function(grid){
21164         var headerRowOffset = grid.rowHeaderColumns.length;
21165         var newIndex;
21166
21167         angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
21168           newIndex = grid.columns.indexOf(cacheCol);
21169           if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
21170             var column = grid.columns.splice(newIndex, 1)[0];
21171             grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
21172           }
21173         });
21174       },
21175       redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
21176         if (originalPosition === newPosition) {
21177           return;
21178         }
21179
21180         var columns = grid.columns;
21181
21182         var originalColumn = columns[originalPosition];
21183         if (originalColumn.colDef.enableColumnMoving) {
21184           if (originalPosition > newPosition) {
21185             for (var i1 = originalPosition; i1 > newPosition; i1--) {
21186               columns[i1] = columns[i1 - 1];
21187             }
21188           }
21189           else if (newPosition > originalPosition) {
21190             for (var i2 = originalPosition; i2 < newPosition; i2++) {
21191               columns[i2] = columns[i2 + 1];
21192             }
21193           }
21194           columns[newPosition] = originalColumn;
21195           service.updateColumnCache(grid);
21196           grid.queueGridRefresh();
21197           $timeout(function () {
21198             grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
21199             grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
21200           });
21201         }
21202       }
21203     };
21204     return service;
21205   }]);
21206
21207   /**
21208    *  @ngdoc directive
21209    *  @name ui.grid.moveColumns.directive:uiGridMoveColumns
21210    *  @element div
21211    *  @restrict A
21212    *  @description Adds column moving features to the ui-grid directive.
21213    *  @example
21214    <example module="app">
21215    <file name="app.js">
21216    var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
21217    app.controller('MainCtrl', ['$scope', function ($scope) {
21218         $scope.data = [
21219           { name: 'Bob', title: 'CEO', age: 45 },
21220           { name: 'Frank', title: 'Lowly Developer', age: 25 },
21221           { name: 'Jenny', title: 'Highly Developer', age: 35 }
21222         ];
21223         $scope.columnDefs = [
21224           {name: 'name'},
21225           {name: 'title'},
21226           {name: 'age'}
21227         ];
21228       }]);
21229    </file>
21230    <file name="main.css">
21231    .grid {
21232       width: 100%;
21233       height: 150px;
21234     }
21235    </file>
21236    <file name="index.html">
21237    <div ng-controller="MainCtrl">
21238    <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
21239    </div>
21240    </file>
21241    </example>
21242    */
21243   module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
21244     return {
21245       replace: true,
21246       priority: 0,
21247       require: '^uiGrid',
21248       scope: false,
21249       compile: function () {
21250         return {
21251           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
21252             uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
21253           },
21254           post: function ($scope, $elm, $attrs, uiGridCtrl) {
21255           }
21256         };
21257       }
21258     };
21259   }]);
21260
21261   /**
21262    *  @ngdoc directive
21263    *  @name ui.grid.moveColumns.directive:uiGridHeaderCell
21264    *  @element div
21265    *  @restrict A
21266    *
21267    *  @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
21268    *
21269    *  On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
21270    *  In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
21271    *  On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
21272    *
21273    *  Events that invoke cloning of header cell:
21274    *    - mousedown
21275    *
21276    *  Events that invoke movement of cloned header cell:
21277    *    - mousemove
21278    *
21279    *  Events that invoke repositioning of column:
21280    *    - mouseup
21281    */
21282   module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
21283     function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
21284       return {
21285         priority: -10,
21286         require: '^uiGrid',
21287         compile: function () {
21288           return {
21289             post: function ($scope, $elm, $attrs, uiGridCtrl) {
21290
21291               if ($scope.col.colDef.enableColumnMoving) {
21292
21293                 /*
21294                  * Our general approach to column move is that we listen to a touchstart or mousedown
21295                  * event over the column header.  When we hear one, then we wait for a move of the same type
21296                  * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
21297                  * a mousemove (i.e. a drag) before we decide that there's a move underway.  If there's never a move,
21298                  * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
21299                  *
21300                  */
21301                 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
21302
21303                 var gridLeft;
21304                 var previousMouseX;
21305                 var totalMouseMovement;
21306                 var rightMoveLimit;
21307                 var elmCloned = false;
21308                 var movingElm;
21309                 var reducedWidth;
21310                 var moveOccurred = false;
21311
21312                 var downFn = function( event ){
21313                   //Setting some variables required for calculations.
21314                   gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
21315                   if ( $scope.grid.hasLeftContainer() ){
21316                     gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
21317                   }
21318
21319                   previousMouseX = event.pageX;
21320                   totalMouseMovement = 0;
21321                   rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
21322
21323                   if ( event.type === 'mousedown' ){
21324                     $document.on('mousemove', moveFn);
21325                     $document.on('mouseup', upFn);
21326                   } else if ( event.type === 'touchstart' ){
21327                     $document.on('touchmove', moveFn);
21328                     $document.on('touchend', upFn);
21329                   }
21330                 };
21331
21332                 var moveFn = function( event ) {
21333                   var changeValue = event.pageX - previousMouseX;
21334                   if ( changeValue === 0 ){ return; }
21335                   //Disable text selection in Chrome during column move
21336                   document.onselectstart = function() { return false; };
21337
21338                   moveOccurred = true;
21339
21340                   if (!elmCloned) {
21341                     cloneElement();
21342                   }
21343                   else if (elmCloned) {
21344                     moveElement(changeValue);
21345                     previousMouseX = event.pageX;
21346                   }
21347                 };
21348
21349                 var upFn = function( event ){
21350                   //Re-enable text selection after column move
21351                   document.onselectstart = null;
21352
21353                   //Remove the cloned element on mouse up.
21354                   if (movingElm) {
21355                     movingElm.remove();
21356                     elmCloned = false;
21357                   }
21358
21359                   offAllEvents();
21360                   onDownEvents();
21361
21362                   if (!moveOccurred){
21363                     return;
21364                   }
21365
21366                   var columns = $scope.grid.columns;
21367                   var columnIndex = 0;
21368                   for (var i = 0; i < columns.length; i++) {
21369                     if (columns[i].colDef.name !== $scope.col.colDef.name) {
21370                       columnIndex++;
21371                     }
21372                     else {
21373                       break;
21374                     }
21375                   }
21376
21377                   //Case where column should be moved to a position on its left
21378                   if (totalMouseMovement < 0) {
21379                     var totalColumnsLeftWidth = 0;
21380                     for (var il = columnIndex - 1; il >= 0; il--) {
21381                       if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
21382                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
21383                         if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
21384                           uiGridMoveColumnService.redrawColumnAtPosition
21385                           ($scope.grid, columnIndex, il + 1);
21386                           break;
21387                         }
21388                       }
21389                     }
21390                     //Case where column should be moved to beginning of the grid.
21391                     if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
21392                       uiGridMoveColumnService.redrawColumnAtPosition
21393                       ($scope.grid, columnIndex, 0);
21394                     }
21395                   }
21396
21397                   //Case where column should be moved to a position on its right
21398                   else if (totalMouseMovement > 0) {
21399                     var totalColumnsRightWidth = 0;
21400                     for (var ir = columnIndex + 1; ir < columns.length; ir++) {
21401                       if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
21402                         totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
21403                         if (totalColumnsRightWidth > totalMouseMovement) {
21404                           uiGridMoveColumnService.redrawColumnAtPosition
21405                           ($scope.grid, columnIndex, ir - 1);
21406                           break;
21407                         }
21408                       }
21409                     }
21410                     //Case where column should be moved to end of the grid.
21411                     if (totalColumnsRightWidth < totalMouseMovement) {
21412                       uiGridMoveColumnService.redrawColumnAtPosition
21413                       ($scope.grid, columnIndex, columns.length - 1);
21414                     }
21415                   }
21416                 };
21417
21418                 var onDownEvents = function(){
21419                   $contentsElm.on('touchstart', downFn);
21420                   $contentsElm.on('mousedown', downFn);
21421                 };
21422
21423                 var offAllEvents = function() {
21424                   $contentsElm.off('touchstart', downFn);
21425                   $contentsElm.off('mousedown', downFn);
21426
21427                   $document.off('mousemove', moveFn);
21428                   $document.off('touchmove', moveFn);
21429
21430                   $document.off('mouseup', upFn);
21431                   $document.off('touchend', upFn);
21432                 };
21433
21434                 onDownEvents();
21435
21436
21437                 var cloneElement = function () {
21438                   elmCloned = true;
21439
21440                   //Cloning header cell and appending to current header cell.
21441                   movingElm = $elm.clone();
21442                   $elm.parent().append(movingElm);
21443
21444                   //Left of cloned element should be aligned to original header cell.
21445                   movingElm.addClass('movingColumn');
21446                   var movingElementStyles = {};
21447                   movingElementStyles.left = $elm[0].offsetLeft + 'px';
21448                   var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
21449                   var elmRight = $elm[0].getBoundingClientRect().right;
21450                   if (elmRight > gridRight) {
21451                     reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
21452                     movingElementStyles.width = reducedWidth + 'px';
21453                   }
21454                   movingElm.css(movingElementStyles);
21455                 };
21456
21457                 var moveElement = function (changeValue) {
21458                   //Calculate total column width
21459                   var columns = $scope.grid.columns;
21460                   var totalColumnWidth = 0;
21461                   for (var i = 0; i < columns.length; i++) {
21462                     if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
21463                       totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
21464                     }
21465                   }
21466
21467                   //Calculate new position of left of column
21468                   var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
21469                   var currentElmRight = movingElm[0].getBoundingClientRect().right;
21470                   var newElementLeft;
21471
21472                   newElementLeft = currentElmLeft - gridLeft + changeValue;
21473                   newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
21474
21475                   //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
21476                   if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
21477                     movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft + 
21478                       (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
21479                   }
21480                   else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
21481                     changeValue *= 8;
21482                     var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
21483                     scrollEvent.x = {pixels: changeValue};
21484                     scrollEvent.grid.scrollContainers('',scrollEvent);
21485                   }
21486
21487                   //Calculate total width of columns on the left of the moving column and the mouse movement
21488                   var totalColumnsLeftWidth = 0;
21489                   for (var il = 0; il < columns.length; il++) {
21490                     if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
21491                       if (columns[il].colDef.name !== $scope.col.colDef.name) {
21492                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
21493                       }
21494                       else {
21495                         break;
21496                       }
21497                     }
21498                   }
21499                   if ($scope.newScrollLeft === undefined) {
21500                     totalMouseMovement += changeValue;
21501                   }
21502                   else {
21503                     totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
21504                   }
21505
21506                   //Increase width of moving column, in case the rightmost column was moved and its width was
21507                   //decreased because of overflow
21508                   if (reducedWidth < $scope.col.drawnWidth) {
21509                     reducedWidth += Math.abs(changeValue);
21510                     movingElm.css({'width': reducedWidth + 'px'});
21511                   }
21512                 };
21513               }
21514             }
21515           };
21516         }
21517       };
21518     }]);
21519 })();
21520
21521 (function() {
21522   'use strict';
21523
21524   /**
21525    * @ngdoc overview
21526    * @name ui.grid.pagination
21527    *
21528    * @description
21529    *
21530    * # ui.grid.pagination
21531    *
21532    * <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>
21533    *
21534    * This module provides pagination support to ui-grid
21535    */
21536   var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);
21537
21538   /**
21539    * @ngdoc service
21540    * @name ui.grid.pagination.service:uiGridPaginationService
21541    *
21542    * @description Service for the pagination feature
21543    */
21544   module.service('uiGridPaginationService', ['gridUtil',
21545     function (gridUtil) {
21546       var service = {
21547         /**
21548          * @ngdoc method
21549          * @name initializeGrid
21550          * @methodOf ui.grid.pagination.service:uiGridPaginationService
21551          * @description Attaches the service to a certain grid
21552          * @param {Grid} grid The grid we want to work with
21553          */
21554         initializeGrid: function (grid) {
21555           service.defaultGridOptions(grid.options);
21556
21557           /**
21558           * @ngdoc object
21559           * @name ui.grid.pagination.api:PublicAPI
21560           *
21561           * @description Public API for the pagination feature
21562           */
21563           var publicApi = {
21564             events: {
21565               pagination: {
21566               /**
21567                * @ngdoc event
21568                * @name paginationChanged
21569                * @eventOf ui.grid.pagination.api:PublicAPI
21570                * @description This event fires when the pageSize or currentPage changes
21571                * @param {int} currentPage requested page number
21572                * @param {int} pageSize requested page size
21573                */
21574                 paginationChanged: function (currentPage, pageSize) { }
21575               }
21576             },
21577             methods: {
21578               pagination: {
21579                 /**
21580                  * @ngdoc method
21581                  * @name getPage
21582                  * @methodOf ui.grid.pagination.api:PublicAPI
21583                  * @description Returns the number of the current page
21584                  */
21585                 getPage: function () {
21586                   return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
21587                 },
21588                 /**
21589                  * @ngdoc method
21590                  * @name getTotalPages
21591                  * @methodOf ui.grid.pagination.api:PublicAPI
21592                  * @description Returns the total number of pages
21593                  */
21594                 getTotalPages: function () {
21595                   if (!grid.options.enablePagination) {
21596                     return null;
21597                   }
21598
21599                   return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
21600                 },
21601                 /**
21602                  * @ngdoc method
21603                  * @name nextPage
21604                  * @methodOf ui.grid.pagination.api:PublicAPI
21605                  * @description Moves to the next page, if possible
21606                  */
21607                 nextPage: function () {
21608                   if (!grid.options.enablePagination) {
21609                     return;
21610                   }
21611
21612                   if (grid.options.totalItems > 0) {
21613                     grid.options.paginationCurrentPage = Math.min(
21614                       grid.options.paginationCurrentPage + 1,
21615                       publicApi.methods.pagination.getTotalPages()
21616                     );
21617                   } else {
21618                     grid.options.paginationCurrentPage++;
21619                   }
21620                 },
21621                 /**
21622                  * @ngdoc method
21623                  * @name previousPage
21624                  * @methodOf ui.grid.pagination.api:PublicAPI
21625                  * @description Moves to the previous page, if we're not on the first page
21626                  */
21627                 previousPage: function () {
21628                   if (!grid.options.enablePagination) {
21629                     return;
21630                   }
21631
21632                   grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
21633                 },
21634                 /**
21635                  * @ngdoc method
21636                  * @name seek
21637                  * @methodOf ui.grid.pagination.api:PublicAPI
21638                  * @description Moves to the requested page
21639                  * @param {int} page The number of the page that should be displayed
21640                  */
21641                 seek: function (page) {
21642                   if (!grid.options.enablePagination) {
21643                     return;
21644                   }
21645                   if (!angular.isNumber(page) || page < 1) {
21646                     throw 'Invalid page number: ' + page;
21647                   }
21648
21649                   grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
21650                 }
21651               }
21652             }
21653           };
21654
21655           grid.api.registerEventsFromObject(publicApi.events);
21656           grid.api.registerMethodsFromObject(publicApi.methods);
21657
21658           var processPagination = function( renderableRows ){
21659             if (grid.options.useExternalPagination || !grid.options.enablePagination) {
21660               return renderableRows;
21661             }
21662             //client side pagination
21663             var pageSize = parseInt(grid.options.paginationPageSize, 10);
21664             var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
21665
21666             var visibleRows = renderableRows.filter(function (row) { return row.visible; });
21667             grid.options.totalItems = visibleRows.length;
21668
21669             var firstRow = (currentPage - 1) * pageSize;
21670             if (firstRow > visibleRows.length) {
21671               currentPage = grid.options.paginationCurrentPage = 1;
21672               firstRow = (currentPage - 1) * pageSize;
21673             }
21674             return visibleRows.slice(firstRow, firstRow + pageSize);
21675           };
21676
21677           grid.registerRowsProcessor(processPagination, 900 );
21678
21679         },
21680         defaultGridOptions: function (gridOptions) {
21681           /**
21682            * @ngdoc object
21683            * @name ui.grid.pagination.api:GridOptions
21684            *
21685            * @description GridOptions for the pagination feature, these are available to be
21686            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21687            */
21688
21689           /**
21690            * @ngdoc property
21691            * @name enablePagination
21692            * @propertyOf ui.grid.pagination.api:GridOptions
21693            * @description Enables pagination.  Defaults to true.
21694            */
21695           gridOptions.enablePagination = gridOptions.enablePagination !== false;
21696           /**
21697            * @ngdoc property
21698            * @name enablePaginationControls
21699            * @propertyOf ui.grid.pagination.api:GridOptions
21700            * @description Enables the paginator at the bottom of the grid. Turn this off if you want to implement your
21701            *              own controls outside the grid.
21702            */
21703           gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
21704           /**
21705            * @ngdoc property
21706            * @name useExternalPagination
21707            * @propertyOf ui.grid.pagination.api:GridOptions
21708            * @description Disables client side pagination. When true, handle the paginationChanged event and set data
21709            *              and totalItems.  Defaults to `false`
21710            */
21711           gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
21712           /**
21713            * @ngdoc property
21714            * @name totalItems
21715            * @propertyOf ui.grid.pagination.api:GridOptions
21716            * @description Total number of items, set automatically when using client side pagination, but needs set by user
21717            *              for server side pagination
21718            */
21719           if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
21720             gridOptions.totalItems = 0;
21721           }
21722           /**
21723            * @ngdoc property
21724            * @name paginationPageSizes
21725            * @propertyOf ui.grid.pagination.api:GridOptions
21726            * @description Array of page sizes, defaults to `[250, 500, 1000]`
21727            */
21728           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
21729             gridOptions.paginationPageSizes = [250, 500, 1000];
21730           }
21731           /**
21732            * @ngdoc property
21733            * @name paginationPageSize
21734            * @propertyOf ui.grid.pagination.api:GridOptions
21735            * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
21736            */
21737           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
21738             if (gridOptions.paginationPageSizes.length > 0) {
21739               gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
21740             } else {
21741               gridOptions.paginationPageSize = 0;
21742             }
21743           }
21744           /**
21745            * @ngdoc property
21746            * @name paginationCurrentPage
21747            * @propertyOf ui.grid.pagination.api:GridOptions
21748            * @description Current page number, defaults to 1
21749            */
21750           if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
21751             gridOptions.paginationCurrentPage = 1;
21752           }
21753
21754           /**
21755            * @ngdoc property
21756            * @name paginationTemplate
21757            * @propertyOf ui.grid.pagination.api:GridOptions
21758            * @description A custom template for the pager, defaults to `ui-grid/pagination`
21759            */
21760           if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
21761             gridOptions.paginationTemplate = 'ui-grid/pagination';
21762           }
21763         },
21764         /**
21765          * @ngdoc method
21766          * @methodOf ui.grid.pagination.service:uiGridPaginationService
21767          * @name uiGridPaginationService
21768          * @description  Raises paginationChanged and calls refresh for client side pagination
21769          * @param {Grid} grid the grid for which the pagination changed
21770          * @param {int} currentPage requested page number
21771          * @param {int} pageSize requested page size
21772          */
21773         onPaginationChanged: function (grid, currentPage, pageSize) {
21774             grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
21775             if (!grid.options.useExternalPagination) {
21776               grid.queueGridRefresh(); //client side pagination
21777             }
21778         }
21779       };
21780
21781       return service;
21782     }
21783   ]);
21784   /**
21785    *  @ngdoc directive
21786    *  @name ui.grid.pagination.directive:uiGridPagination
21787    *  @element div
21788    *  @restrict A
21789    *
21790    *  @description Adds pagination features to grid
21791    *  @example
21792    <example module="app">
21793    <file name="app.js">
21794    var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);
21795
21796    app.controller('MainCtrl', ['$scope', function ($scope) {
21797       $scope.data = [
21798         { name: 'Alex', car: 'Toyota' },
21799         { name: 'Sam', car: 'Lexus' },
21800         { name: 'Joe', car: 'Dodge' },
21801         { name: 'Bob', car: 'Buick' },
21802         { name: 'Cindy', car: 'Ford' },
21803         { name: 'Brian', car: 'Audi' },
21804         { name: 'Malcom', car: 'Mercedes Benz' },
21805         { name: 'Dave', car: 'Ford' },
21806         { name: 'Stacey', car: 'Audi' },
21807         { name: 'Amy', car: 'Acura' },
21808         { name: 'Scott', car: 'Toyota' },
21809         { name: 'Ryan', car: 'BMW' },
21810       ];
21811
21812       $scope.gridOptions = {
21813         data: 'data',
21814         paginationPageSizes: [5, 10, 25],
21815         paginationPageSize: 5,
21816         columnDefs: [
21817           {name: 'name'},
21818           {name: 'car'}
21819         ]
21820        }
21821     }]);
21822    </file>
21823    <file name="index.html">
21824    <div ng-controller="MainCtrl">
21825    <div ui-grid="gridOptions" ui-grid-pagination></div>
21826    </div>
21827    </file>
21828    </example>
21829    */
21830   module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
21831     function (gridUtil, uiGridPaginationService) {
21832       return {
21833         priority: -200,
21834         scope: false,
21835         require: 'uiGrid',
21836         link: {
21837           pre: function ($scope, $elm, $attr, uiGridCtrl) {
21838             uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
21839
21840             gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
21841               .then(function (contents) {
21842                 var template = angular.element(contents);
21843                 $elm.append(template);
21844                 uiGridCtrl.innerCompile(template);
21845               });
21846           }
21847         }
21848       };
21849     }
21850   ]);
21851
21852   /**
21853    *  @ngdoc directive
21854    *  @name ui.grid.pagination.directive:uiGridPager
21855    *  @element div
21856    *
21857    *  @description Panel for handling pagination
21858    */
21859   module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
21860     function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
21861       return {
21862         priority: -200,
21863         scope: true,
21864         require: '^uiGrid',
21865         link: function ($scope, $elm, $attr, uiGridCtrl) {
21866           var defaultFocusElementSelector = '.ui-grid-pager-control-input';
21867           $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
21868
21869           $scope.paginationApi = uiGridCtrl.grid.api.pagination;
21870           $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
21871           $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
21872           $scope.paginationOf = i18nService.getSafeText('pagination.of');
21873           $scope.paginationThrough = i18nService.getSafeText('pagination.through');
21874
21875           var options = uiGridCtrl.grid.options;
21876
21877           uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
21878             adjustment.height = adjustment.height - gridUtil.elementHeight($elm, "padding");
21879             return adjustment;
21880           });
21881
21882           var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
21883             if (!grid.options.useExternalPagination) {
21884               grid.options.totalItems = grid.rows.length;
21885             }
21886           }, [uiGridConstants.dataChange.ROW]);
21887
21888           $scope.$on('$destroy', dataChangeDereg);
21889
21890           var setShowing = function () {
21891             $scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
21892             $scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
21893           };
21894
21895           var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);
21896
21897           var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
21898               if (newValues === oldValues || oldValues === undefined) {
21899                 return;
21900               }
21901
21902               if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
21903                 options.paginationCurrentPage = 1;
21904                 return;
21905               }
21906
21907               if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
21908                 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
21909                 return;
21910               }
21911
21912               setShowing();
21913               uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
21914             }
21915           );
21916
21917           $scope.$on('$destroy', function() {
21918             deregT();
21919             deregP();
21920           });
21921
21922           $scope.cantPageForward = function () {
21923             if (options.totalItems > 0) {
21924               return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
21925             } else {
21926               return options.data.length < 1;
21927             }
21928           };
21929
21930           $scope.cantPageToLast = function () {
21931             if (options.totalItems > 0) {
21932               return $scope.cantPageForward();
21933             } else {
21934               return true;
21935             }
21936           };
21937
21938           $scope.cantPageBackward = function () {
21939             return options.paginationCurrentPage <= 1;
21940           };
21941
21942           var focusToInputIf = function(condition){
21943             if (condition){
21944               gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
21945             }
21946           };
21947
21948           //Takes care of setting focus to the middle element when focus is lost
21949           $scope.pageFirstPageClick = function () {
21950             $scope.paginationApi.seek(1);
21951             focusToInputIf($scope.cantPageBackward());
21952           };
21953
21954           $scope.pagePreviousPageClick = function () {
21955             $scope.paginationApi.previousPage();
21956             focusToInputIf($scope.cantPageBackward());
21957           };
21958
21959           $scope.pageNextPageClick = function () {
21960             $scope.paginationApi.nextPage();
21961             focusToInputIf($scope.cantPageForward());
21962           };
21963
21964           $scope.pageLastPageClick = function () {
21965             $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
21966             focusToInputIf($scope.cantPageToLast());
21967           };
21968
21969         }
21970       };
21971     }
21972   ]);
21973 })();
21974
21975 (function () {
21976   'use strict';
21977
21978   /**
21979    * @ngdoc overview
21980    * @name ui.grid.pinning
21981    * @description
21982    *
21983    * # ui.grid.pinning
21984    *
21985    * <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>
21986    *
21987    * This module provides column pinning to the end user via menu options in the column header
21988    *
21989    * <div doc-module-components="ui.grid.pinning"></div>
21990    */
21991
21992   var module = angular.module('ui.grid.pinning', ['ui.grid']);
21993
21994   module.constant('uiGridPinningConstants', {
21995     container: {
21996       LEFT: 'left',
21997       RIGHT: 'right',
21998       NONE: ''
21999     }
22000   });
22001
22002   module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) {
22003     var service = {
22004
22005       initializeGrid: function (grid) {
22006         service.defaultGridOptions(grid.options);
22007
22008         // Register a column builder to add new menu items for pinning left and right
22009         grid.registerColumnBuilder(service.pinningColumnBuilder);
22010
22011         /**
22012          *  @ngdoc object
22013          *  @name ui.grid.pinning.api:PublicApi
22014          *
22015          *  @description Public Api for pinning feature
22016          */
22017         var publicApi = {
22018           events: {
22019             pinning: {
22020               /**
22021                * @ngdoc event
22022                * @name columnPin
22023                * @eventOf ui.grid.pinning.api:PublicApi
22024                * @description raised when column pin state has changed
22025                * <pre>
22026                *   gridApi.pinning.on.columnPinned(scope, function(colDef){})
22027                * </pre>
22028                * @param {object} colDef the column that was changed
22029                * @param {string} container the render container the column is in ('left', 'right', '')
22030                */
22031               columnPinned: function(colDef, container) {
22032               }
22033             }
22034           },
22035           methods: {
22036             pinning: {
22037               /**
22038                * @ngdoc function
22039                * @name pinColumn
22040                * @methodOf ui.grid.pinning.api:PublicApi
22041                * @description pin column left, right, or none
22042                * <pre>
22043                *   gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
22044                * </pre>
22045                * @param {gridColumn} col the column being pinned
22046                * @param {string} container one of the recognised types
22047                * from uiGridPinningConstants
22048                */
22049               pinColumn: function(col, container) {
22050                 service.pinColumn(grid, col, container);
22051               }
22052             }
22053           }
22054         };
22055
22056         grid.api.registerEventsFromObject(publicApi.events);
22057         grid.api.registerMethodsFromObject(publicApi.methods);
22058       },
22059
22060       defaultGridOptions: function (gridOptions) {
22061         //default option to true unless it was explicitly set to false
22062         /**
22063          *  @ngdoc object
22064          *  @name ui.grid.pinning.api:GridOptions
22065          *
22066          *  @description GridOptions for pinning feature, these are available to be
22067            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22068          */
22069
22070         /**
22071          *  @ngdoc object
22072          *  @name enablePinning
22073          *  @propertyOf  ui.grid.pinning.api:GridOptions
22074          *  @description Enable pinning for the entire grid.
22075          *  <br/>Defaults to true
22076          */
22077         gridOptions.enablePinning = gridOptions.enablePinning !== false;
22078
22079       },
22080
22081       pinningColumnBuilder: function (colDef, col, gridOptions) {
22082         //default to true unless gridOptions or colDef is explicitly false
22083
22084         /**
22085          *  @ngdoc object
22086          *  @name ui.grid.pinning.api:ColumnDef
22087          *
22088          *  @description ColumnDef for pinning feature, these are available to be
22089          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
22090          */
22091
22092         /**
22093          *  @ngdoc object
22094          *  @name enablePinning
22095          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22096          *  @description Enable pinning for the individual column.
22097          *  <br/>Defaults to true
22098          */
22099         colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
22100
22101
22102         /**
22103          *  @ngdoc object
22104          *  @name pinnedLeft
22105          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22106          *  @description Column is pinned left when grid is rendered
22107          *  <br/>Defaults to false
22108          */
22109
22110         /**
22111          *  @ngdoc object
22112          *  @name pinnedRight
22113          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22114          *  @description Column is pinned right when grid is rendered
22115          *  <br/>Defaults to false
22116          */
22117         if (colDef.pinnedLeft) {
22118           col.renderContainer = 'left';
22119           col.grid.createLeftContainer();
22120         }
22121         else if (colDef.pinnedRight) {
22122           col.renderContainer = 'right';
22123           col.grid.createRightContainer();
22124         }
22125
22126         if (!colDef.enablePinning) {
22127           return;
22128         }
22129
22130         var pinColumnLeftAction = {
22131           name: 'ui.grid.pinning.pinLeft',
22132           title: i18nService.get().pinning.pinLeft,
22133           icon: 'ui-grid-icon-left-open',
22134           shown: function () {
22135             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
22136           },
22137           action: function () {
22138             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT);
22139           }
22140         };
22141
22142         var pinColumnRightAction = {
22143           name: 'ui.grid.pinning.pinRight',
22144           title: i18nService.get().pinning.pinRight,
22145           icon: 'ui-grid-icon-right-open',
22146           shown: function () {
22147             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
22148           },
22149           action: function () {
22150             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT);
22151           }
22152         };
22153
22154         var removePinAction = {
22155           name: 'ui.grid.pinning.unpin',
22156           title: i18nService.get().pinning.unpin,
22157           icon: 'ui-grid-icon-cancel',
22158           shown: function () {
22159             return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
22160           },
22161           action: function () {
22162             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.UNPIN);
22163           }
22164         };
22165
22166         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
22167           col.menuItems.push(pinColumnLeftAction);
22168         }
22169         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
22170           col.menuItems.push(pinColumnRightAction);
22171         }
22172         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
22173           col.menuItems.push(removePinAction);
22174         }
22175       },
22176
22177       pinColumn: function(grid, col, container) {
22178         if (container === uiGridPinningConstants.container.NONE) {
22179           col.renderContainer = null;
22180         }
22181         else {
22182           col.renderContainer = container;
22183           if (container === uiGridPinningConstants.container.LEFT) {
22184             grid.createLeftContainer();
22185           }
22186           else if (container === uiGridPinningConstants.container.RIGHT) {
22187             grid.createRightContainer();
22188           }
22189         }
22190
22191         grid.refresh()
22192         .then(function() {
22193           grid.api.pinning.raise.columnPinned( col.colDef, container );
22194         });
22195       }
22196     };
22197
22198     return service;
22199   }]);
22200
22201   module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
22202     function (gridUtil, uiGridPinningService) {
22203       return {
22204         require: 'uiGrid',
22205         scope: false,
22206         compile: function () {
22207           return {
22208             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22209               uiGridPinningService.initializeGrid(uiGridCtrl.grid);
22210             },
22211             post: function ($scope, $elm, $attrs, uiGridCtrl) {
22212             }
22213           };
22214         }
22215       };
22216     }]);
22217
22218
22219 })();
22220
22221 (function(){
22222   'use strict';
22223
22224   /**
22225    * @ngdoc overview
22226    * @name ui.grid.resizeColumns
22227    * @description
22228    *
22229    * # ui.grid.resizeColumns
22230    *
22231    * <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>
22232    *
22233    * This module allows columns to be resized.
22234    */
22235   var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
22236
22237   module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
22238     function (gridUtil, $q, $timeout) {
22239
22240       var service = {
22241         defaultGridOptions: function(gridOptions){
22242           //default option to true unless it was explicitly set to false
22243           /**
22244            *  @ngdoc object
22245            *  @name ui.grid.resizeColumns.api:GridOptions
22246            *
22247            *  @description GridOptions for resizeColumns feature, these are available to be
22248            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22249            */
22250
22251           /**
22252            *  @ngdoc object
22253            *  @name enableColumnResizing
22254            *  @propertyOf  ui.grid.resizeColumns.api:GridOptions
22255            *  @description Enable column resizing on the entire grid
22256            *  <br/>Defaults to true
22257            */
22258           gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
22259
22260           //legacy support
22261           //use old name if it is explicitly false
22262           if (gridOptions.enableColumnResize === false){
22263             gridOptions.enableColumnResizing = false;
22264           }
22265         },
22266
22267         colResizerColumnBuilder: function (colDef, col, gridOptions) {
22268
22269           var promises = [];
22270           /**
22271            *  @ngdoc object
22272            *  @name ui.grid.resizeColumns.api:ColumnDef
22273            *
22274            *  @description ColumnDef for resizeColumns feature, these are available to be
22275            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
22276            */
22277
22278           /**
22279            *  @ngdoc object
22280            *  @name enableColumnResizing
22281            *  @propertyOf  ui.grid.resizeColumns.api:ColumnDef
22282            *  @description Enable column resizing on an individual column
22283            *  <br/>Defaults to GridOptions.enableColumnResizing
22284            */
22285           //default to true unless gridOptions or colDef is explicitly false
22286           colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
22287
22288
22289           //legacy support of old option name
22290           if (colDef.enableColumnResize === false){
22291             colDef.enableColumnResizing = false;
22292           }
22293
22294           return $q.all(promises);
22295         },
22296
22297         registerPublicApi: function (grid) {
22298             /**
22299              *  @ngdoc object
22300              *  @name ui.grid.resizeColumns.api:PublicApi
22301              *  @description Public Api for column resize feature.
22302              */
22303             var publicApi = {
22304               events: {
22305                 /**
22306                  * @ngdoc event
22307                  * @name columnSizeChanged
22308                  * @eventOf  ui.grid.resizeColumns.api:PublicApi
22309                  * @description raised when column is resized
22310                  * <pre>
22311                  *      gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
22312                  * </pre>
22313                  * @param {object} colDef the column that was resized
22314                  * @param {integer} delta of the column size change
22315                  */
22316                 colResizable: {
22317                   columnSizeChanged: function (colDef, deltaChange) {
22318                   }
22319                 }
22320               }
22321             };
22322             grid.api.registerEventsFromObject(publicApi.events);
22323         },
22324
22325         fireColumnSizeChanged: function (grid, colDef, deltaChange) {
22326           $timeout(function () {
22327             if ( grid.api.colResizable ){
22328               grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
22329             } else {
22330               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.");
22331             }
22332           });
22333         },
22334
22335         // get either this column, or the column next to this column, to resize,
22336         // returns the column we're going to resize
22337         findTargetCol: function(col, position, rtlMultiplier){
22338           var renderContainer = col.getRenderContainer();
22339
22340           if (position === 'left') {
22341             // Get the column to the left of this one
22342             var colIndex = renderContainer.visibleColumnCache.indexOf(col);
22343             return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
22344           } else {
22345             return col;
22346           }
22347         }
22348
22349       };
22350
22351       return service;
22352
22353     }]);
22354
22355
22356   /**
22357    * @ngdoc directive
22358    * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
22359    * @element div
22360    * @restrict A
22361    * @description
22362    * 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
22363    * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
22364    *
22365    * @example
22366    <doc:example module="app">
22367    <doc:source>
22368    <script>
22369    var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
22370
22371    app.controller('MainCtrl', ['$scope', function ($scope) {
22372           $scope.gridOpts = {
22373             data: [
22374               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
22375               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
22376               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
22377               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
22378             ]
22379           };
22380         }]);
22381    </script>
22382
22383    <div ng-controller="MainCtrl">
22384    <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
22385    </div>
22386    </doc:source>
22387    <doc:scenario>
22388
22389    </doc:scenario>
22390    </doc:example>
22391    */
22392   module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
22393     return {
22394       replace: true,
22395       priority: 0,
22396       require: '^uiGrid',
22397       scope: false,
22398       compile: function () {
22399         return {
22400           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22401             uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
22402             uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
22403             uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
22404           },
22405           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22406           }
22407         };
22408       }
22409     };
22410   }]);
22411
22412   // Extend the uiGridHeaderCell directive
22413   module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
22414     return {
22415       // Run after the original uiGridHeaderCell
22416       priority: -10,
22417       require: '^uiGrid',
22418       // scope: false,
22419       compile: function() {
22420         return {
22421           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22422             var grid = uiGridCtrl.grid;
22423
22424             if (grid.options.enableColumnResizing) {
22425               var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
22426
22427               var rtlMultiplier = 1;
22428               //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
22429               if (grid.isRTL()) {
22430                 $scope.position = 'left';
22431                 rtlMultiplier = -1;
22432               }
22433
22434               var displayResizers = function(){
22435
22436                 // remove any existing resizers.
22437                 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
22438                 for ( var i = 0; i < resizers.length; i++ ){
22439                   angular.element(resizers[i]).remove();
22440                 }
22441
22442                 // get the target column for the left resizer
22443                 var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
22444                 var renderContainer = $scope.col.getRenderContainer();
22445
22446                 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
22447                 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
22448                   var resizerLeft = angular.element(columnResizerElm).clone();
22449                   resizerLeft.attr('position', 'left');
22450
22451                   $elm.prepend(resizerLeft);
22452                   $compile(resizerLeft)($scope);
22453                 }
22454
22455                 // Don't append the right resizer if this column has resizing disabled
22456                 if ($scope.col.colDef.enableColumnResizing !== false) {
22457                   var resizerRight = angular.element(columnResizerElm).clone();
22458                   resizerRight.attr('position', 'right');
22459
22460                   $elm.append(resizerRight);
22461                   $compile(resizerRight)($scope);
22462                 }
22463               };
22464
22465               displayResizers();
22466
22467               var waitDisplay = function(){
22468                 $timeout(displayResizers);
22469               };
22470
22471               var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
22472
22473               $scope.$on( '$destroy', dataChangeDereg );
22474             }
22475           }
22476         };
22477       }
22478     };
22479   }]);
22480
22481
22482
22483   /**
22484    * @ngdoc directive
22485    * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
22486    * @element div
22487    * @restrict A
22488    *
22489    * @description
22490    * Draggable handle that controls column resizing.
22491    *
22492    * @example
22493    <doc:example module="app">
22494      <doc:source>
22495        <script>
22496         var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
22497
22498         app.controller('MainCtrl', ['$scope', function ($scope) {
22499           $scope.gridOpts = {
22500             enableColumnResizing: true,
22501             data: [
22502               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
22503               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
22504               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
22505               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
22506             ]
22507           };
22508         }]);
22509        </script>
22510
22511        <div ng-controller="MainCtrl">
22512         <div class="testGrid" ui-grid="gridOpts"></div>
22513        </div>
22514      </doc:source>
22515      <doc:scenario>
22516       // TODO: e2e specs?
22517
22518       // TODO: post-resize a horizontal scroll event should be fired
22519      </doc:scenario>
22520    </doc:example>
22521    */
22522   module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
22523     var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
22524
22525     var resizer = {
22526       priority: 0,
22527       scope: {
22528         col: '=',
22529         position: '@',
22530         renderIndex: '='
22531       },
22532       require: '?^uiGrid',
22533       link: function ($scope, $elm, $attrs, uiGridCtrl) {
22534         var startX = 0,
22535             x = 0,
22536             gridLeft = 0,
22537             rtlMultiplier = 1;
22538
22539         //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
22540         if (uiGridCtrl.grid.isRTL()) {
22541           $scope.position = 'left';
22542           rtlMultiplier = -1;
22543         }
22544
22545         if ($scope.position === 'left') {
22546           $elm.addClass('left');
22547         }
22548         else if ($scope.position === 'right') {
22549           $elm.addClass('right');
22550         }
22551
22552         // Refresh the grid canvas
22553         //   takes an argument representing the diff along the X-axis that the resize had
22554         function refreshCanvas(xDiff) {
22555           // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
22556           uiGridCtrl.grid.refreshCanvas(true).then( function() {
22557             uiGridCtrl.grid.queueGridRefresh();
22558           });
22559         }
22560
22561         // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
22562         // Returns the new recommended with, after constraints applied
22563         function constrainWidth(col, width){
22564           var newWidth = width;
22565
22566           // If the new width would be less than the column's allowably minimum width, don't allow it
22567           if (col.minWidth && newWidth < col.minWidth) {
22568             newWidth = col.minWidth;
22569           }
22570           else if (col.maxWidth && newWidth > col.maxWidth) {
22571             newWidth = col.maxWidth;
22572           }
22573
22574           return newWidth;
22575         }
22576
22577
22578         /*
22579          * Our approach to event handling aims to deal with both touch devices and mouse devices
22580          * We register down handlers on both touch and mouse.  When a touchstart or mousedown event
22581          * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
22582          *
22583          * This way we can listen for both without worrying about the fact many touch devices also emulate
22584          * mouse events - basically whichever one we hear first is what we'll go with.
22585          */
22586         function moveFunction(event, args) {
22587           if (event.originalEvent) { event = event.originalEvent; }
22588           event.preventDefault();
22589
22590           x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22591
22592           if (x < 0) { x = 0; }
22593           else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
22594
22595           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22596
22597           // Don't resize if it's disabled on this column
22598           if (col.colDef.enableColumnResizing === false) {
22599             return;
22600           }
22601
22602           if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
22603             uiGridCtrl.grid.element.addClass('column-resizing');
22604           }
22605
22606           // Get the diff along the X axis
22607           var xDiff = x - startX;
22608
22609           // Get the width that this mouse would give the column
22610           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22611
22612           // check we're not outside the allowable bounds for this column
22613           x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
22614
22615           resizeOverlay.css({ left: x + 'px' });
22616
22617           uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
22618         }
22619
22620
22621         function upFunction(event, args) {
22622           if (event.originalEvent) { event = event.originalEvent; }
22623           event.preventDefault();
22624
22625           uiGridCtrl.grid.element.removeClass('column-resizing');
22626
22627           resizeOverlay.remove();
22628
22629           // Resize the column
22630           x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
22631           var xDiff = x - startX;
22632
22633           if (xDiff === 0) {
22634             // no movement, so just reset event handlers, including turning back on both
22635             // down events - we turned one off when this event started
22636             offAllEvents();
22637             onDownEvents();
22638             return;
22639           }
22640
22641           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22642
22643           // Don't resize if it's disabled on this column
22644           if (col.colDef.enableColumnResizing === false) {
22645             return;
22646           }
22647
22648           // Get the new width
22649           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22650
22651           // check we're not outside the allowable bounds for this column
22652           col.width = constrainWidth(col, newWidth);
22653           col.hasCustomWidth = true;
22654
22655           refreshCanvas(xDiff);
22656
22657           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
22658
22659           // stop listening of up and move events - wait for next down
22660           // reset the down events - we will have turned one off when this event started
22661           offAllEvents();
22662           onDownEvents();
22663         }
22664
22665
22666         var downFunction = function(event, args) {
22667           if (event.originalEvent) { event = event.originalEvent; }
22668           event.stopPropagation();
22669
22670           // Get the left offset of the grid
22671           // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
22672           gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
22673
22674           // Get the starting X position, which is the X coordinate of the click minus the grid's offset
22675           startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22676
22677           // Append the resizer overlay
22678           uiGridCtrl.grid.element.append(resizeOverlay);
22679
22680           // Place the resizer overlay at the start position
22681           resizeOverlay.css({ left: startX });
22682
22683           // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
22684           // we were touchdown then we listen for touchmove and touchup.  Also remove the handler for the equivalent
22685           // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
22686           // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
22687           if ( event.type === 'touchstart' ){
22688             $document.on('touchend', upFunction);
22689             $document.on('touchmove', moveFunction);
22690             $elm.off('mousedown', downFunction);
22691           } else {
22692             $document.on('mouseup', upFunction);
22693             $document.on('mousemove', moveFunction);
22694             $elm.off('touchstart', downFunction);
22695           }
22696         };
22697
22698         var onDownEvents = function() {
22699           $elm.on('mousedown', downFunction);
22700           $elm.on('touchstart', downFunction);
22701         };
22702
22703         var offAllEvents = function() {
22704           $document.off('mouseup', upFunction);
22705           $document.off('touchend', upFunction);
22706           $document.off('mousemove', moveFunction);
22707           $document.off('touchmove', moveFunction);
22708           $elm.off('mousedown', downFunction);
22709           $elm.off('touchstart', downFunction);
22710         };
22711
22712         onDownEvents();
22713
22714
22715         // On doubleclick, resize to fit all rendered cells
22716         var dblClickFn = function(event, args){
22717           event.stopPropagation();
22718
22719           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22720
22721           // Don't resize if it's disabled on this column
22722           if (col.colDef.enableColumnResizing === false) {
22723             return;
22724           }
22725
22726           // Go through the rendered rows and find out the max size for the data in this column
22727           var maxWidth = 0;
22728           var xDiff = 0;
22729
22730           // Get the parent render container element
22731           var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
22732
22733           // 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
22734           var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
22735           Array.prototype.forEach.call(cells, function (cell) {
22736               // Get the cell width
22737               // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
22738
22739               // Account for the menu button if it exists
22740               var menuButton;
22741               if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
22742                 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
22743               }
22744
22745               gridUtil.fakeElement(cell, {}, function(newElm) {
22746                 // Make the element float since it's a div and can expand to fill its container
22747                 var e = angular.element(newElm);
22748                 e.attr('style', 'float: left');
22749
22750                 var width = gridUtil.elementWidth(e);
22751
22752                 if (menuButton) {
22753                   var menuButtonWidth = gridUtil.elementWidth(menuButton);
22754                   width = width + menuButtonWidth;
22755                 }
22756
22757                 if (width > maxWidth) {
22758                   maxWidth = width;
22759                   xDiff = maxWidth - width;
22760                 }
22761               });
22762             });
22763
22764           // check we're not outside the allowable bounds for this column
22765           col.width = constrainWidth(col, maxWidth);
22766           col.hasCustomWidth = true;
22767
22768           refreshCanvas(xDiff);
22769
22770           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);        };
22771         $elm.on('dblclick', dblClickFn);
22772
22773         $elm.on('$destroy', function() {
22774           $elm.off('dblclick', dblClickFn);
22775           offAllEvents();
22776         });
22777       }
22778     };
22779
22780     return resizer;
22781   }]);
22782
22783 })();
22784
22785 (function () {
22786   'use strict';
22787
22788   /**
22789    * @ngdoc overview
22790    * @name ui.grid.rowEdit
22791    * @description
22792    *
22793    * # ui.grid.rowEdit
22794    *
22795    * <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>
22796    *
22797    * This module extends the edit feature to provide tracking and saving of rows
22798    * of data.  The tutorial provides more information on how this feature is best
22799    * used {@link tutorial/205_row_editable here}.
22800    * <br/>
22801    * This feature depends on usage of the ui-grid-edit feature, and also benefits
22802    * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
22803    * experience
22804    *
22805    */
22806
22807   var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
22808
22809   /**
22810    *  @ngdoc object
22811    *  @name ui.grid.rowEdit.constant:uiGridRowEditConstants
22812    *
22813    *  @description constants available in row edit module
22814    */
22815   module.constant('uiGridRowEditConstants', {
22816   });
22817
22818   /**
22819    *  @ngdoc service
22820    *  @name ui.grid.rowEdit.service:uiGridRowEditService
22821    *
22822    *  @description Services for row editing features
22823    */
22824   module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
22825     function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
22826
22827       var service = {
22828
22829         initializeGrid: function (scope, grid) {
22830           /**
22831            *  @ngdoc object
22832            *  @name ui.grid.rowEdit.api:PublicApi
22833            *
22834            *  @description Public Api for rowEdit feature
22835            */
22836
22837           grid.rowEdit = {};
22838
22839           var publicApi = {
22840             events: {
22841               rowEdit: {
22842                 /**
22843                  * @ngdoc event
22844                  * @eventOf ui.grid.rowEdit.api:PublicApi
22845                  * @name saveRow
22846                  * @description raised when a row is ready for saving.  Once your
22847                  * row has saved you may need to use angular.extend to update the
22848                  * data entity with any changed data from your save (for example,
22849                  * lock version information if you're using optimistic locking,
22850                  * or last update time/user information).
22851                  *
22852                  * Your method should call setSavePromise somewhere in the body before
22853                  * returning control.  The feature will then wait, with the gridRow greyed out
22854                  * whilst this promise is being resolved.
22855                  *
22856                  * <pre>
22857                  *      gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
22858                  * </pre>
22859                  * and somewhere within the event handler:
22860                  * <pre>
22861                  *      gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
22862                  * </pre>
22863                  * @param {object} rowEntity the options.data element that was edited
22864                  * @returns {promise} Your saveRow method should return a promise, the
22865                  * promise should either be resolved (implying successful save), or
22866                  * rejected (implying an error).
22867                  */
22868                 saveRow: function (rowEntity) {
22869                 }
22870               }
22871             },
22872             methods: {
22873               rowEdit: {
22874                 /**
22875                  * @ngdoc method
22876                  * @methodOf ui.grid.rowEdit.api:PublicApi
22877                  * @name setSavePromise
22878                  * @description Sets the promise associated with the row save, mandatory that
22879                  * the saveRow event handler calls this method somewhere before returning.
22880                  * <pre>
22881                  *      gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
22882                  * </pre>
22883                  * @param {object} rowEntity a data row from the grid for which a save has
22884                  * been initiated
22885                  * @param {promise} savePromise the promise that will be resolved when the
22886                  * save is successful, or rejected if the save fails
22887                  *
22888                  */
22889                 setSavePromise: function ( rowEntity, savePromise) {
22890                   service.setSavePromise(grid, rowEntity, savePromise);
22891                 },
22892                 /**
22893                  * @ngdoc method
22894                  * @methodOf ui.grid.rowEdit.api:PublicApi
22895                  * @name getDirtyRows
22896                  * @description Returns all currently dirty rows
22897                  * <pre>
22898                  *      gridApi.rowEdit.getDirtyRows(grid)
22899                  * </pre>
22900                  * @returns {array} An array of gridRows that are currently dirty
22901                  *
22902                  */
22903                 getDirtyRows: function () {
22904                   return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
22905                 },
22906                 /**
22907                  * @ngdoc method
22908                  * @methodOf ui.grid.rowEdit.api:PublicApi
22909                  * @name getErrorRows
22910                  * @description Returns all currently errored rows
22911                  * <pre>
22912                  *      gridApi.rowEdit.getErrorRows(grid)
22913                  * </pre>
22914                  * @returns {array} An array of gridRows that are currently in error
22915                  *
22916                  */
22917                 getErrorRows: function () {
22918                   return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
22919                 },
22920                 /**
22921                  * @ngdoc method
22922                  * @methodOf ui.grid.rowEdit.api:PublicApi
22923                  * @name flushDirtyRows
22924                  * @description Triggers a save event for all currently dirty rows, could
22925                  * be used where user presses a save button or navigates away from the page
22926                  * <pre>
22927                  *      gridApi.rowEdit.flushDirtyRows(grid)
22928                  * </pre>
22929                  * @returns {promise} a promise that represents the aggregate of all
22930                  * of the individual save promises - i.e. it will be resolved when all
22931                  * the individual save promises have been resolved.
22932                  *
22933                  */
22934                 flushDirtyRows: function () {
22935                   return service.flushDirtyRows(grid);
22936                 },
22937
22938                 /**
22939                  * @ngdoc method
22940                  * @methodOf ui.grid.rowEdit.api:PublicApi
22941                  * @name setRowsDirty
22942                  * @description Sets each of the rows passed in dataRows
22943                  * to be dirty.  note that if you have only just inserted the
22944                  * rows into your data you will need to wait for a $digest cycle
22945                  * before the gridRows are present - so often you would wrap this
22946                  * call in a $interval or $timeout
22947                  * <pre>
22948                  *      $interval( function() {
22949                  *        gridApi.rowEdit.setRowsDirty(myDataRows);
22950                  *      }, 0, 1);
22951                  * </pre>
22952                  * @param {array} dataRows the data entities for which the gridRows
22953                  * should be set dirty.
22954                  *
22955                  */
22956                 setRowsDirty: function ( dataRows) {
22957                   service.setRowsDirty(grid, dataRows);
22958                 },
22959
22960                 /**
22961                  * @ngdoc method
22962                  * @methodOf ui.grid.rowEdit.api:PublicApi
22963                  * @name setRowsClean
22964                  * @description Sets each of the rows passed in dataRows
22965                  * to be clean, removing them from the dirty cache and the error cache,
22966                  * and clearing the error flag and the dirty flag
22967                  * <pre>
22968                  *      var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
22969                  *      var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
22970                  *      $scope.gridApi.rowEdit.setRowsClean( dataRows );
22971                  * </pre>
22972                  * @param {array} dataRows the data entities for which the gridRows
22973                  * should be set clean.
22974                  *
22975                  */
22976                 setRowsClean: function ( dataRows) {
22977                   service.setRowsClean(grid, dataRows);
22978                 }
22979               }
22980             }
22981           };
22982
22983           grid.api.registerEventsFromObject(publicApi.events);
22984           grid.api.registerMethodsFromObject(publicApi.methods);
22985
22986           grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
22987             grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
22988             grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
22989             grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
22990
22991             if ( grid.api.cellNav ) {
22992               grid.api.cellNav.on.navigate( scope, service.navigate );
22993             }
22994           });
22995
22996         },
22997
22998         defaultGridOptions: function (gridOptions) {
22999
23000           /**
23001            *  @ngdoc object
23002            *  @name ui.grid.rowEdit.api:GridOptions
23003            *
23004            *  @description Options for configuring the rowEdit feature, these are available to be
23005            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23006            */
23007
23008         },
23009
23010
23011         /**
23012          * @ngdoc method
23013          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23014          * @name saveRow
23015          * @description  Returns a function that saves the specified row from the grid,
23016          * and returns a promise
23017          * @param {object} grid the grid for which dirty rows should be flushed
23018          * @param {GridRow} gridRow the row that should be saved
23019          * @returns {function} the saveRow function returns a function.  That function
23020          * in turn, when called, returns a promise relating to the save callback
23021          */
23022         saveRow: function ( grid, gridRow ) {
23023           var self = this;
23024
23025           return function() {
23026             gridRow.isSaving = true;
23027
23028             if ( gridRow.rowEditSavePromise ){
23029               // don't save the row again if it's already saving - that causes stale object exceptions
23030               return gridRow.rowEditSavePromise;
23031             }
23032
23033             var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
23034
23035             if ( gridRow.rowEditSavePromise ){
23036               gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
23037             } else {
23038               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' );
23039             }
23040             return promise;
23041           };
23042         },
23043
23044
23045         /**
23046          * @ngdoc method
23047          * @methodOf  ui.grid.rowEdit.service:uiGridRowEditService
23048          * @name setSavePromise
23049          * @description Sets the promise associated with the row save, mandatory that
23050          * the saveRow event handler calls this method somewhere before returning.
23051          * <pre>
23052          *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
23053          * </pre>
23054          * @param {object} grid the grid for which dirty rows should be returned
23055          * @param {object} rowEntity a data row from the grid for which a save has
23056          * been initiated
23057          * @param {promise} savePromise the promise that will be resolved when the
23058          * save is successful, or rejected if the save fails
23059          *
23060          */
23061         setSavePromise: function (grid, rowEntity, savePromise) {
23062           var gridRow = grid.getRow( rowEntity );
23063           gridRow.rowEditSavePromise = savePromise;
23064         },
23065
23066
23067         /**
23068          * @ngdoc method
23069          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23070          * @name processSuccessPromise
23071          * @description  Returns a function that processes the successful
23072          * resolution of a save promise
23073          * @param {object} grid the grid for which the promise should be processed
23074          * @param {GridRow} gridRow the row that has been saved
23075          * @returns {function} the success handling function
23076          */
23077         processSuccessPromise: function ( grid, gridRow ) {
23078           var self = this;
23079
23080           return function() {
23081             delete gridRow.isSaving;
23082             delete gridRow.isDirty;
23083             delete gridRow.isError;
23084             delete gridRow.rowEditSaveTimer;
23085             delete gridRow.rowEditSavePromise;
23086             self.removeRow( grid.rowEdit.errorRows, gridRow );
23087             self.removeRow( grid.rowEdit.dirtyRows, gridRow );
23088           };
23089         },
23090
23091
23092         /**
23093          * @ngdoc method
23094          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23095          * @name processErrorPromise
23096          * @description  Returns a function that processes the failed
23097          * resolution of a save promise
23098          * @param {object} grid the grid for which the promise should be processed
23099          * @param {GridRow} gridRow the row that is now in error
23100          * @returns {function} the error handling function
23101          */
23102         processErrorPromise: function ( grid, gridRow ) {
23103           return function() {
23104             delete gridRow.isSaving;
23105             delete gridRow.rowEditSaveTimer;
23106             delete gridRow.rowEditSavePromise;
23107
23108             gridRow.isError = true;
23109
23110             if (!grid.rowEdit.errorRows){
23111               grid.rowEdit.errorRows = [];
23112             }
23113             if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
23114               grid.rowEdit.errorRows.push( gridRow );
23115             }
23116           };
23117         },
23118
23119
23120         /**
23121          * @ngdoc method
23122          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23123          * @name removeRow
23124          * @description  Removes a row from a cache of rows - either
23125          * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows.  If the row
23126          * is not present silently does nothing.
23127          * @param {array} rowArray the array from which to remove the row
23128          * @param {GridRow} gridRow the row that should be removed
23129          */
23130         removeRow: function( rowArray, removeGridRow ){
23131           if (typeof(rowArray) === 'undefined' || rowArray === null){
23132             return;
23133           }
23134
23135           rowArray.forEach( function( gridRow, index ){
23136             if ( gridRow.uid === removeGridRow.uid ){
23137               rowArray.splice( index, 1);
23138             }
23139           });
23140         },
23141
23142
23143         /**
23144          * @ngdoc method
23145          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23146          * @name isRowPresent
23147          * @description  Checks whether a row is already present
23148          * in the given array
23149          * @param {array} rowArray the array in which to look for the row
23150          * @param {GridRow} gridRow the row that should be looked for
23151          */
23152         isRowPresent: function( rowArray, removeGridRow ){
23153           var present = false;
23154           rowArray.forEach( function( gridRow, index ){
23155             if ( gridRow.uid === removeGridRow.uid ){
23156               present = true;
23157             }
23158           });
23159           return present;
23160         },
23161
23162
23163         /**
23164          * @ngdoc method
23165          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23166          * @name flushDirtyRows
23167          * @description Triggers a save event for all currently dirty rows, could
23168          * be used where user presses a save button or navigates away from the page
23169          * <pre>
23170          *      gridApi.rowEdit.flushDirtyRows(grid)
23171          * </pre>
23172          * @param {object} grid the grid for which dirty rows should be flushed
23173          * @returns {promise} a promise that represents the aggregate of all
23174          * of the individual save promises - i.e. it will be resolved when all
23175          * the individual save promises have been resolved.
23176          *
23177          */
23178         flushDirtyRows: function(grid){
23179           var promises = [];
23180           grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
23181             service.saveRow( grid, gridRow )();
23182             promises.push( gridRow.rowEditSavePromise );
23183           });
23184
23185           return $q.all( promises );
23186         },
23187
23188
23189         /**
23190          * @ngdoc method
23191          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23192          * @name endEditCell
23193          * @description Receives an afterCellEdit event from the edit function,
23194          * and sets flags as appropriate.  Only the rowEntity parameter
23195          * is processed, although other params are available.  Grid
23196          * is automatically provided by the gridApi.
23197          * @param {object} rowEntity the data entity for which the cell
23198          * was edited
23199          */
23200         endEditCell: function( rowEntity, colDef, newValue, previousValue ){
23201           var grid = this.grid;
23202           var gridRow = grid.getRow( rowEntity );
23203           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
23204
23205           if ( newValue !== previousValue || gridRow.isDirty ){
23206             if ( !grid.rowEdit.dirtyRows ){
23207               grid.rowEdit.dirtyRows = [];
23208             }
23209
23210             if ( !gridRow.isDirty ){
23211               gridRow.isDirty = true;
23212               grid.rowEdit.dirtyRows.push( gridRow );
23213             }
23214
23215             delete gridRow.isError;
23216
23217             service.considerSetTimer( grid, gridRow );
23218           }
23219         },
23220
23221
23222         /**
23223          * @ngdoc method
23224          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23225          * @name beginEditCell
23226          * @description Receives a beginCellEdit event from the edit function,
23227          * and cancels any rowEditSaveTimers if present, as the user is still editing
23228          * this row.  Only the rowEntity parameter
23229          * is processed, although other params are available.  Grid
23230          * is automatically provided by the gridApi.
23231          * @param {object} rowEntity the data entity for which the cell
23232          * editing has commenced
23233          */
23234         beginEditCell: function( rowEntity, colDef ){
23235           var grid = this.grid;
23236           var gridRow = grid.getRow( rowEntity );
23237           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
23238
23239           service.cancelTimer( grid, gridRow );
23240         },
23241
23242
23243         /**
23244          * @ngdoc method
23245          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23246          * @name cancelEditCell
23247          * @description Receives a cancelCellEdit event from the edit function,
23248          * and if the row was already dirty, restarts the save timer.  If the row
23249          * was not already dirty, then it's not dirty now either and does nothing.
23250          *
23251          * Only the rowEntity parameter
23252          * is processed, although other params are available.  Grid
23253          * is automatically provided by the gridApi.
23254          *
23255          * @param {object} rowEntity the data entity for which the cell
23256          * editing was cancelled
23257          */
23258         cancelEditCell: function( rowEntity, colDef ){
23259           var grid = this.grid;
23260           var gridRow = grid.getRow( rowEntity );
23261           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
23262
23263           service.considerSetTimer( grid, gridRow );
23264         },
23265
23266
23267         /**
23268          * @ngdoc method
23269          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23270          * @name navigate
23271          * @description cellNav tells us that the selected cell has changed.  If
23272          * the new row had a timer running, then stop it similar to in a beginCellEdit
23273          * call.  If the old row is dirty and not the same as the new row, then
23274          * start a timer on it.
23275          * @param {object} newRowCol the row and column that were selected
23276          * @param {object} oldRowCol the row and column that was left
23277          *
23278          */
23279         navigate: function( newRowCol, oldRowCol ){
23280           var grid = this.grid;
23281           if ( newRowCol.row.rowEditSaveTimer ){
23282             service.cancelTimer( grid, newRowCol.row );
23283           }
23284
23285           if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
23286             service.considerSetTimer( grid, oldRowCol.row );
23287           }
23288         },
23289
23290
23291         /**
23292          * @ngdoc property
23293          * @propertyOf ui.grid.rowEdit.api:GridOptions
23294          * @name rowEditWaitInterval
23295          * @description How long the grid should wait for another change on this row
23296          * before triggering a save (in milliseconds).  If set to -1, then saves are
23297          * never triggered by timer (implying that the user will call flushDirtyRows()
23298          * manually)
23299          *
23300          * @example
23301          * Setting the wait interval to 4 seconds
23302          * <pre>
23303          *   $scope.gridOptions = { rowEditWaitInterval: 4000 }
23304          * </pre>
23305          *
23306          */
23307         /**
23308          * @ngdoc method
23309          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23310          * @name considerSetTimer
23311          * @description Consider setting a timer on this row (if it is dirty).  if there is a timer running
23312          * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
23313          * dirty and not currently saving then set a new timer
23314          * @param {object} grid the grid for which we are processing
23315          * @param {GridRow} gridRow the row for which the timer should be adjusted
23316          *
23317          */
23318         considerSetTimer: function( grid, gridRow ){
23319           service.cancelTimer( grid, gridRow );
23320
23321           if ( gridRow.isDirty && !gridRow.isSaving ){
23322             if ( grid.options.rowEditWaitInterval !== -1 ){
23323               var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
23324               gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
23325             }
23326           }
23327         },
23328
23329
23330         /**
23331          * @ngdoc method
23332          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23333          * @name cancelTimer
23334          * @description cancel the $interval for any timer running on this row
23335          * then delete the timer itself
23336          * @param {object} grid the grid for which we are processing
23337          * @param {GridRow} gridRow the row for which the timer should be adjusted
23338          *
23339          */
23340         cancelTimer: function( grid, gridRow ){
23341           if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
23342             $interval.cancel(gridRow.rowEditSaveTimer);
23343             delete gridRow.rowEditSaveTimer;
23344           }
23345         },
23346
23347
23348         /**
23349          * @ngdoc method
23350          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23351          * @name setRowsDirty
23352          * @description Sets each of the rows passed in dataRows
23353          * to be dirty.  note that if you have only just inserted the
23354          * rows into your data you will need to wait for a $digest cycle
23355          * before the gridRows are present - so often you would wrap this
23356          * call in a $interval or $timeout
23357          * <pre>
23358          *      $interval( function() {
23359          *        gridApi.rowEdit.setRowsDirty( myDataRows);
23360          *      }, 0, 1);
23361          * </pre>
23362          * @param {object} grid the grid for which rows should be set dirty
23363          * @param {array} dataRows the data entities for which the gridRows
23364          * should be set dirty.
23365          *
23366          */
23367         setRowsDirty: function( grid, myDataRows ) {
23368           var gridRow;
23369           myDataRows.forEach( function( value, index ){
23370             gridRow = grid.getRow( value );
23371             if ( gridRow ){
23372               if ( !grid.rowEdit.dirtyRows ){
23373                 grid.rowEdit.dirtyRows = [];
23374               }
23375
23376               if ( !gridRow.isDirty ){
23377                 gridRow.isDirty = true;
23378                 grid.rowEdit.dirtyRows.push( gridRow );
23379               }
23380
23381               delete gridRow.isError;
23382
23383               service.considerSetTimer( grid, gridRow );
23384             } else {
23385               gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
23386             }
23387           });
23388         },
23389
23390
23391         /**
23392          * @ngdoc method
23393          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23394          * @name setRowsClean
23395          * @description Sets each of the rows passed in dataRows
23396          * to be clean, clearing the dirty flag and the error flag, and removing
23397          * the rows from the dirty and error caches.
23398          * @param {object} grid the grid for which rows should be set clean
23399          * @param {array} dataRows the data entities for which the gridRows
23400          * should be set clean.
23401          *
23402          */
23403         setRowsClean: function( grid, myDataRows ) {
23404           var gridRow;
23405
23406           myDataRows.forEach( function( value, index ){
23407             gridRow = grid.getRow( value );
23408             if ( gridRow ){
23409               delete gridRow.isDirty;
23410               service.removeRow( grid.rowEdit.dirtyRows, gridRow );
23411               service.cancelTimer( grid, gridRow );
23412
23413               delete gridRow.isError;
23414               service.removeRow( grid.rowEdit.errorRows, gridRow );
23415             } else {
23416               gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
23417             }
23418           });
23419         }
23420
23421       };
23422
23423       return service;
23424
23425     }]);
23426
23427   /**
23428    *  @ngdoc directive
23429    *  @name ui.grid.rowEdit.directive:uiGridEdit
23430    *  @element div
23431    *  @restrict A
23432    *
23433    *  @description Adds row editing features to the ui-grid-edit directive.
23434    *
23435    */
23436   module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
23437   function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
23438     return {
23439       replace: true,
23440       priority: 0,
23441       require: '^uiGrid',
23442       scope: false,
23443       compile: function () {
23444         return {
23445           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23446             uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
23447           },
23448           post: function ($scope, $elm, $attrs, uiGridCtrl) {
23449           }
23450         };
23451       }
23452     };
23453   }]);
23454
23455
23456   /**
23457    *  @ngdoc directive
23458    *  @name ui.grid.rowEdit.directive:uiGridViewport
23459    *  @element div
23460    *
23461    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
23462    *  for the grid row to allow coloring of saving and error rows
23463    */
23464   module.directive('uiGridViewport',
23465     ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
23466       function ($compile, uiGridConstants, gridUtil, $parse) {
23467         return {
23468           priority: -200, // run after default  directive
23469           scope: false,
23470           compile: function ($elm, $attrs) {
23471             var rowRepeatDiv = angular.element($elm.children().children()[0]);
23472
23473             var existingNgClass = rowRepeatDiv.attr("ng-class");
23474             var newNgClass = '';
23475             if ( existingNgClass ) {
23476               newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
23477             } else {
23478               newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
23479             }
23480             rowRepeatDiv.attr("ng-class", newNgClass);
23481
23482             return {
23483               pre: function ($scope, $elm, $attrs, controllers) {
23484
23485               },
23486               post: function ($scope, $elm, $attrs, controllers) {
23487               }
23488             };
23489           }
23490         };
23491       }]);
23492
23493 })();
23494
23495 (function () {
23496   'use strict';
23497
23498   /**
23499    * @ngdoc overview
23500    * @name ui.grid.saveState
23501    * @description
23502    *
23503    * # ui.grid.saveState
23504    *
23505    * <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>
23506    *
23507    * This module provides the ability to save the grid state, and restore
23508    * it when the user returns to the page.
23509    *
23510    * No UI is provided, the caller should provide their own UI/buttons
23511    * as appropriate. Usually the navigate events would be used to save
23512    * the grid state and restore it.
23513    *
23514    * <br/>
23515    * <br/>
23516    *
23517    * <div doc-module-components="ui.grid.save-state"></div>
23518    */
23519
23520   var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
23521
23522   /**
23523    *  @ngdoc object
23524    *  @name ui.grid.saveState.constant:uiGridSaveStateConstants
23525    *
23526    *  @description constants available in save state module
23527    */
23528
23529   module.constant('uiGridSaveStateConstants', {
23530     featureName: 'saveState'
23531   });
23532
23533   /**
23534    *  @ngdoc service
23535    *  @name ui.grid.saveState.service:uiGridSaveStateService
23536    *
23537    *  @description Services for saveState feature
23538    */
23539   module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
23540     function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
23541
23542       var service = {
23543
23544         initializeGrid: function (grid) {
23545
23546           //add feature namespace and any properties to grid for needed state
23547           grid.saveState = {};
23548           this.defaultGridOptions(grid.options);
23549
23550           /**
23551            *  @ngdoc object
23552            *  @name ui.grid.saveState.api:PublicApi
23553            *
23554            *  @description Public Api for saveState feature
23555            */
23556           var publicApi = {
23557             events: {
23558               saveState: {
23559               }
23560             },
23561             methods: {
23562               saveState: {
23563                 /**
23564                  * @ngdoc function
23565                  * @name save
23566                  * @methodOf  ui.grid.saveState.api:PublicApi
23567                  * @description Packages the current state of the grid into
23568                  * an object, and provides it to the user for saving
23569                  * @returns {object} the state as a javascript object that can be saved
23570                  */
23571                 save: function () {
23572                   return service.save(grid);
23573                 },
23574                 /**
23575                  * @ngdoc function
23576                  * @name restore
23577                  * @methodOf  ui.grid.saveState.api:PublicApi
23578                  * @description Restores the provided state into the grid
23579                  * @param {scope} $scope a scope that we can broadcast on
23580                  * @param {object} state the state that should be restored into the grid
23581                  */
23582                 restore: function ( $scope, state) {
23583                   service.restore(grid, $scope, state);
23584                 }
23585               }
23586             }
23587           };
23588
23589           grid.api.registerEventsFromObject(publicApi.events);
23590
23591           grid.api.registerMethodsFromObject(publicApi.methods);
23592
23593         },
23594
23595         defaultGridOptions: function (gridOptions) {
23596           //default option to true unless it was explicitly set to false
23597           /**
23598            * @ngdoc object
23599            * @name ui.grid.saveState.api:GridOptions
23600            *
23601            * @description GridOptions for saveState feature, these are available to be
23602            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23603            */
23604           /**
23605            * @ngdoc object
23606            * @name saveWidths
23607            * @propertyOf  ui.grid.saveState.api:GridOptions
23608            * @description Save the current column widths.  Note that unless
23609            * you've provided the user with some way to resize their columns (say
23610            * the resize columns feature), then this makes little sense.
23611            * <br/>Defaults to true
23612            */
23613           gridOptions.saveWidths = gridOptions.saveWidths !== false;
23614           /**
23615            * @ngdoc object
23616            * @name saveOrder
23617            * @propertyOf  ui.grid.saveState.api:GridOptions
23618            * @description Restore the current column order.  Note that unless
23619            * you've provided the user with some way to reorder their columns (for
23620            * example the move columns feature), this makes little sense.
23621            * <br/>Defaults to true
23622            */
23623           gridOptions.saveOrder = gridOptions.saveOrder !== false;
23624           /**
23625            * @ngdoc object
23626            * @name saveScroll
23627            * @propertyOf  ui.grid.saveState.api:GridOptions
23628            * @description Save the current scroll position.  Note that this
23629            * is saved as the percentage of the grid scrolled - so if your
23630            * user returns to a grid with a significantly different number of
23631            * rows (perhaps some data has been deleted) then the scroll won't
23632            * actually show the same rows as before.  If you want to scroll to
23633            * a specific row then you should instead use the saveFocus option, which
23634            * is the default.
23635            *
23636            * Note that this element will only be saved if the cellNav feature is
23637            * enabled
23638            * <br/>Defaults to false
23639            */
23640           gridOptions.saveScroll = gridOptions.saveScroll === true;
23641           /**
23642            * @ngdoc object
23643            * @name saveFocus
23644            * @propertyOf  ui.grid.saveState.api:GridOptions
23645            * @description Save the current focused cell.  On returning
23646            * to this focused cell we'll also scroll.  This option is
23647            * preferred to the saveScroll option, so is set to true by
23648            * default.  If saveScroll is set to true then this option will
23649            * be disabled.
23650            *
23651            * By default this option saves the current row number and column
23652            * number, and returns to that row and column.  However, if you define
23653            * a saveRowIdentity function, then it will return you to the currently
23654            * selected column within that row (in a business sense - so if some
23655            * rows have been deleted, it will still find the same data, presuming it
23656            * still exists in the list.  If it isn't in the list then it will instead
23657            * return to the same row number - i.e. scroll percentage)
23658            *
23659            * Note that this option will do nothing if the cellNav
23660            * feature is not enabled.
23661            *
23662            * <br/>Defaults to true (unless saveScroll is true)
23663            */
23664           gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
23665           /**
23666            * @ngdoc object
23667            * @name saveRowIdentity
23668            * @propertyOf  ui.grid.saveState.api:GridOptions
23669            * @description A function that can be called, passing in a rowEntity,
23670            * and that will return a unique id for that row.  This might simply
23671            * return the `id` field from that row (if you have one), or it might
23672            * concatenate some fields within the row to make a unique value.
23673            *
23674            * This value will be used to find the same row again and set the focus
23675            * to it, if it exists when we return.
23676            *
23677            * <br/>Defaults to undefined
23678            */
23679           /**
23680            * @ngdoc object
23681            * @name saveVisible
23682            * @propertyOf  ui.grid.saveState.api:GridOptions
23683            * @description Save whether or not columns are visible.
23684            *
23685            * <br/>Defaults to true
23686            */
23687           gridOptions.saveVisible = gridOptions.saveVisible !== false;
23688           /**
23689            * @ngdoc object
23690            * @name saveSort
23691            * @propertyOf  ui.grid.saveState.api:GridOptions
23692            * @description Save the current sort state for each column
23693            *
23694            * <br/>Defaults to true
23695            */
23696           gridOptions.saveSort = gridOptions.saveSort !== false;
23697           /**
23698            * @ngdoc object
23699            * @name saveFilter
23700            * @propertyOf  ui.grid.saveState.api:GridOptions
23701            * @description Save the current filter state for each column
23702            *
23703            * <br/>Defaults to true
23704            */
23705           gridOptions.saveFilter = gridOptions.saveFilter !== false;
23706           /**
23707            * @ngdoc object
23708            * @name saveSelection
23709            * @propertyOf  ui.grid.saveState.api:GridOptions
23710            * @description Save the currently selected rows.  If the `saveRowIdentity` callback
23711            * is defined, then it will save the id of the row and select that.  If not, then
23712            * it will attempt to select the rows by row number, which will give the wrong results
23713            * if the data set has changed in the mean-time.
23714            *
23715            * Note that this option only does anything
23716            * if the selection feature is enabled.
23717            *
23718            * <br/>Defaults to true
23719            */
23720           gridOptions.saveSelection = gridOptions.saveSelection !== false;
23721           /**
23722            * @ngdoc object
23723            * @name saveGrouping
23724            * @propertyOf  ui.grid.saveState.api:GridOptions
23725            * @description Save the grouping configuration.  If set to true and the
23726            * grouping feature is not enabled then does nothing.
23727            *
23728            * <br/>Defaults to true
23729            */
23730           gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
23731           /**
23732            * @ngdoc object
23733            * @name saveGroupingExpandedStates
23734            * @propertyOf  ui.grid.saveState.api:GridOptions
23735            * @description Save the grouping row expanded states.  If set to true and the
23736            * grouping feature is not enabled then does nothing.
23737            *
23738            * This can be quite a bit of data, in many cases you wouldn't want to save this
23739            * information.
23740            *
23741            * <br/>Defaults to false
23742            */
23743           gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true;
23744           /**
23745            * @ngdoc object
23746            * @name savePinning
23747            * @propertyOf ui.grid.saveState.api:GridOptions
23748            * @description Save pinning state for columns.
23749            *
23750            * <br/>Defaults to true
23751            */
23752           gridOptions.savePinning = gridOptions.savePinning !== false;
23753           /**
23754            * @ngdoc object
23755            * @name saveTreeView
23756            * @propertyOf  ui.grid.saveState.api:GridOptions
23757            * @description Save the treeView configuration.  If set to true and the
23758            * treeView feature is not enabled then does nothing.
23759            *
23760            * <br/>Defaults to true
23761            */
23762           gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
23763         },
23764
23765
23766
23767         /**
23768          * @ngdoc function
23769          * @name save
23770          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23771          * @description Saves the current grid state into an object, and
23772          * passes that object back to the caller
23773          * @param {Grid} grid the grid whose state we'd like to save
23774          * @returns {object} the state ready to be saved
23775          */
23776         save: function (grid) {
23777           var savedState = {};
23778
23779           savedState.columns = service.saveColumns( grid );
23780           savedState.scrollFocus = service.saveScrollFocus( grid );
23781           savedState.selection = service.saveSelection( grid );
23782           savedState.grouping = service.saveGrouping( grid );
23783           savedState.treeView = service.saveTreeView( grid );
23784
23785           return savedState;
23786         },
23787
23788
23789         /**
23790          * @ngdoc function
23791          * @name restore
23792          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23793          * @description Applies the provided state to the grid
23794          *
23795          * @param {Grid} grid the grid whose state we'd like to restore
23796          * @param {scope} $scope a scope that we can broadcast on
23797          * @param {object} state the state we'd like to restore
23798          */
23799         restore: function( grid, $scope, state ){
23800           if ( state.columns ) {
23801             service.restoreColumns( grid, state.columns );
23802           }
23803
23804           if ( state.scrollFocus ){
23805             service.restoreScrollFocus( grid, $scope, state.scrollFocus );
23806           }
23807
23808           if ( state.selection ){
23809             service.restoreSelection( grid, state.selection );
23810           }
23811
23812           if ( state.grouping ){
23813             service.restoreGrouping( grid, state.grouping );
23814           }
23815
23816           if ( state.treeView ){
23817             service.restoreTreeView( grid, state.treeView );
23818           }
23819
23820           grid.refresh();
23821         },
23822
23823
23824         /**
23825          * @ngdoc function
23826          * @name saveColumns
23827          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23828          * @description Saves the column setup, including sort, filters, ordering,
23829          * pinning and column widths.
23830          *
23831          * Works through the current columns, storing them in order.  Stores the
23832          * column name, then the visible flag, width, sort and filters for each column.
23833          *
23834          * @param {Grid} grid the grid whose state we'd like to save
23835          * @returns {array} the columns state ready to be saved
23836          */
23837         saveColumns: function( grid ) {
23838           var columns = [];
23839           grid.getOnlyDataColumns().forEach( function( column ) {
23840             var savedColumn = {};
23841             savedColumn.name = column.name;
23842
23843             if ( grid.options.saveVisible ){
23844               savedColumn.visible = column.visible;
23845             }
23846
23847             if ( grid.options.saveWidths ){
23848               savedColumn.width = column.width;
23849             }
23850
23851             // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
23852             if ( grid.options.saveSort ){
23853               savedColumn.sort = angular.copy( column.sort );
23854             }
23855
23856             if ( grid.options.saveFilter ){
23857               savedColumn.filters = [];
23858               column.filters.forEach( function( filter ){
23859                 var copiedFilter = {};
23860                 angular.forEach( filter, function( value, key) {
23861                   if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
23862                     copiedFilter[key] = value;
23863                   }
23864                 });
23865                 savedColumn.filters.push(copiedFilter);
23866               });
23867             }
23868
23869             if ( !!grid.api.pinning && grid.options.savePinning ){
23870               savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
23871             }
23872
23873             columns.push( savedColumn );
23874           });
23875
23876           return columns;
23877         },
23878
23879
23880         /**
23881          * @ngdoc function
23882          * @name saveScrollFocus
23883          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23884          * @description Saves the currently scroll or focus.
23885          *
23886          * If cellNav isn't present then does nothing - we can't return
23887          * to the scroll position without cellNav anyway.
23888          *
23889          * If the cellNav module is present, and saveFocus is true, then
23890          * it saves the currently focused cell.  If rowIdentity is present
23891          * then saves using rowIdentity, otherwise saves visibleRowNum.
23892          *
23893          * If the cellNav module is not present, and saveScroll is true, then
23894          * it approximates the current scroll row and column, and saves that.
23895          *
23896          * @param {Grid} grid the grid whose state we'd like to save
23897          * @returns {object} the selection state ready to be saved
23898          */
23899         saveScrollFocus: function( grid ){
23900           if ( !grid.api.cellNav ){
23901             return {};
23902           }
23903
23904           var scrollFocus = {};
23905           if ( grid.options.saveFocus ){
23906             scrollFocus.focus = true;
23907             var rowCol = grid.api.cellNav.getFocusedCell();
23908             if ( rowCol !== null ) {
23909               if ( rowCol.col !== null ){
23910                 scrollFocus.colName = rowCol.col.colDef.name;
23911               }
23912               if ( rowCol.row !== null ){
23913                 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
23914               }
23915             }
23916           }
23917
23918           if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
23919             scrollFocus.focus = false;
23920             if ( grid.renderContainers.body.prevRowScrollIndex ){
23921               scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
23922             }
23923
23924             if ( grid.renderContainers.body.prevColScrollIndex ){
23925               scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
23926             }
23927           }
23928
23929           return scrollFocus;
23930         },
23931
23932
23933         /**
23934          * @ngdoc function
23935          * @name saveSelection
23936          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23937          * @description Saves the currently selected rows, if the selection feature is enabled
23938          * @param {Grid} grid the grid whose state we'd like to save
23939          * @returns {array} the selection state ready to be saved
23940          */
23941         saveSelection: function( grid ){
23942           if ( !grid.api.selection || !grid.options.saveSelection ){
23943             return [];
23944           }
23945
23946           var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
23947             return service.getRowVal( grid, gridRow );
23948           });
23949
23950           return selection;
23951         },
23952
23953
23954         /**
23955          * @ngdoc function
23956          * @name saveGrouping
23957          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23958          * @description Saves the grouping state, if the grouping feature is enabled
23959          * @param {Grid} grid the grid whose state we'd like to save
23960          * @returns {object} the grouping state ready to be saved
23961          */
23962         saveGrouping: function( grid ){
23963           if ( !grid.api.grouping || !grid.options.saveGrouping ){
23964             return {};
23965           }
23966
23967           return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
23968         },
23969
23970
23971         /**
23972          * @ngdoc function
23973          * @name saveTreeView
23974          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23975          * @description Saves the tree view state, if the tree feature is enabled
23976          * @param {Grid} grid the grid whose state we'd like to save
23977          * @returns {object} the tree view state ready to be saved
23978          */
23979         saveTreeView: function( grid ){
23980           if ( !grid.api.treeView || !grid.options.saveTreeView ){
23981             return {};
23982           }
23983
23984           return grid.api.treeView.getTreeView();
23985         },
23986
23987
23988         /**
23989          * @ngdoc function
23990          * @name getRowVal
23991          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23992          * @description Helper function that gets either the rowNum or
23993          * the saveRowIdentity, given a gridRow
23994          * @param {Grid} grid the grid the row is in
23995          * @param {GridRow} gridRow the row we want the rowNum for
23996          * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
23997          *
23998          */
23999         getRowVal: function( grid, gridRow ){
24000           if ( !gridRow ) {
24001             return null;
24002           }
24003
24004           var rowVal = {};
24005           if ( grid.options.saveRowIdentity ){
24006             rowVal.identity = true;
24007             rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
24008           } else {
24009             rowVal.identity = false;
24010             rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
24011           }
24012           return rowVal;
24013         },
24014
24015
24016         /**
24017          * @ngdoc function
24018          * @name restoreColumns
24019          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24020          * @description Restores the columns, including order, visible, width,
24021          * pinning, sort and filters.
24022          *
24023          * @param {Grid} grid the grid whose state we'd like to restore
24024          * @param {object} columnsState the list of columns we had before, with their state
24025          */
24026         restoreColumns: function( grid, columnsState ){
24027           var isSortChanged = false;
24028
24029           columnsState.forEach( function( columnState, index ) {
24030             var currentCol = grid.getColumn( columnState.name );
24031
24032             if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
24033               if ( grid.options.saveVisible &&
24034                    ( currentCol.visible !== columnState.visible ||
24035                      currentCol.colDef.visible !== columnState.visible ) ){
24036                 currentCol.visible = columnState.visible;
24037                 currentCol.colDef.visible = columnState.visible;
24038                 grid.api.core.raise.columnVisibilityChanged(currentCol);
24039               }
24040
24041               if ( grid.options.saveWidths ){
24042                 currentCol.width = columnState.width;
24043               }
24044
24045               if ( grid.options.saveSort &&
24046                    !angular.equals(currentCol.sort, columnState.sort) &&
24047                    !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
24048                 currentCol.sort = angular.copy( columnState.sort );
24049                 isSortChanged = true;
24050               }
24051
24052               if ( grid.options.saveFilter &&
24053                    !angular.equals(currentCol.filters, columnState.filters ) ){
24054                 columnState.filters.forEach( function( filter, index ){
24055                   angular.extend( currentCol.filters[index], filter );
24056                   if ( typeof(filter.term) === 'undefined' || filter.term === null ){
24057                     delete currentCol.filters[index].term;
24058                   }
24059                 });
24060                 grid.api.core.raise.filterChanged();
24061               }
24062
24063               if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
24064                 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
24065               }
24066
24067               var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
24068               if (currentIndex !== -1) {
24069                 if (grid.options.saveOrder && currentIndex !== index) {
24070                   var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
24071                   grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
24072                 }
24073               }
24074             }
24075           });
24076
24077           if ( isSortChanged ) {
24078             grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
24079           }
24080         },
24081
24082
24083         /**
24084          * @ngdoc function
24085          * @name restoreScrollFocus
24086          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24087          * @description Scrolls to the position that was saved.  If focus is true, then
24088          * sets focus to the specified row/col.  If focus is false, then scrolls to the
24089          * specified row/col.
24090          *
24091          * @param {Grid} grid the grid whose state we'd like to restore
24092          * @param {scope} $scope a scope that we can broadcast on
24093          * @param {object} scrollFocusState the scroll/focus state ready to be restored
24094          */
24095         restoreScrollFocus: function( grid, $scope, scrollFocusState ){
24096           if ( !grid.api.cellNav ){
24097             return;
24098           }
24099
24100           var colDef, row;
24101           if ( scrollFocusState.colName ){
24102             var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
24103             if ( colDefs.length > 0 ){
24104               colDef = colDefs[0];
24105             }
24106           }
24107
24108           if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
24109             if ( scrollFocusState.rowVal.identity ){
24110               row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
24111             } else {
24112               row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
24113             }
24114           }
24115
24116           var entity = row && row.entity ? row.entity : null ;
24117
24118           if ( colDef || entity ) {
24119             if (scrollFocusState.focus ){
24120               grid.api.cellNav.scrollToFocus( entity, colDef );
24121             } else {
24122               grid.scrollTo( entity, colDef );
24123             }
24124           }
24125         },
24126
24127
24128         /**
24129          * @ngdoc function
24130          * @name restoreSelection
24131          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24132          * @description Selects the rows that are provided in the selection
24133          * state.  If you are using `saveRowIdentity` and more than one row matches the identity
24134          * function then only the first is selected.
24135          * @param {Grid} grid the grid whose state we'd like to restore
24136          * @param {object} selectionState the selection state ready to be restored
24137          */
24138         restoreSelection: function( grid, selectionState ){
24139           if ( !grid.api.selection ){
24140             return;
24141           }
24142
24143           grid.api.selection.clearSelectedRows();
24144
24145           selectionState.forEach(  function( rowVal ) {
24146             if ( rowVal.identity ){
24147               var foundRow = service.findRowByIdentity( grid, rowVal );
24148
24149               if ( foundRow ){
24150                 grid.api.selection.selectRow( foundRow.entity );
24151               }
24152
24153             } else {
24154               grid.api.selection.selectRowByVisibleIndex( rowVal.row );
24155             }
24156           });
24157         },
24158
24159
24160         /**
24161          * @ngdoc function
24162          * @name restoreGrouping
24163          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24164          * @description Restores the grouping configuration, if the grouping feature
24165          * is enabled.
24166          * @param {Grid} grid the grid whose state we'd like to restore
24167          * @param {object} groupingState the grouping state ready to be restored
24168          */
24169         restoreGrouping: function( grid, groupingState ){
24170           if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
24171             return;
24172           }
24173
24174           grid.api.grouping.setGrouping( groupingState );
24175         },
24176
24177         /**
24178          * @ngdoc function
24179          * @name restoreTreeView
24180          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24181          * @description Restores the tree view configuration, if the tree view feature
24182          * is enabled.
24183          * @param {Grid} grid the grid whose state we'd like to restore
24184          * @param {object} treeViewState the tree view state ready to be restored
24185          */
24186         restoreTreeView: function( grid, treeViewState ){
24187           if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
24188             return;
24189           }
24190
24191           grid.api.treeView.setTreeView( treeViewState );
24192         },
24193
24194         /**
24195          * @ngdoc function
24196          * @name findRowByIdentity
24197          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24198          * @description Finds a row given it's identity value, returns the first found row
24199          * if any are found, otherwise returns null if no rows are found.
24200          * @param {Grid} grid the grid whose state we'd like to restore
24201          * @param {object} rowVal the row we'd like to find
24202          * @returns {gridRow} the found row, or null if none found
24203          */
24204         findRowByIdentity: function( grid, rowVal ){
24205           if ( !grid.options.saveRowIdentity ){
24206             return null;
24207           }
24208
24209           var filteredRows = grid.rows.filter( function( gridRow ) {
24210             if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
24211               return true;
24212             } else {
24213               return false;
24214             }
24215           });
24216
24217           if ( filteredRows.length > 0 ){
24218             return filteredRows[0];
24219           } else {
24220             return null;
24221           }
24222         }
24223       };
24224
24225       return service;
24226
24227     }
24228   ]);
24229
24230   /**
24231    *  @ngdoc directive
24232    *  @name ui.grid.saveState.directive:uiGridSaveState
24233    *  @element div
24234    *  @restrict A
24235    *
24236    *  @description Adds saveState features to grid
24237    *
24238    *  @example
24239    <example module="app">
24240    <file name="app.js">
24241    var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
24242
24243    app.controller('MainCtrl', ['$scope', function ($scope) {
24244       $scope.data = [
24245         { name: 'Bob', title: 'CEO' },
24246         { name: 'Frank', title: 'Lowly Developer' }
24247       ];
24248
24249       $scope.gridOptions = {
24250         columnDefs: [
24251           {name: 'name'},
24252           {name: 'title', enableCellEdit: true}
24253         ],
24254         data: $scope.data
24255       };
24256     }]);
24257    </file>
24258    <file name="index.html">
24259    <div ng-controller="MainCtrl">
24260    <div ui-grid="gridOptions" ui-grid-save-state></div>
24261    </div>
24262    </file>
24263    </example>
24264    */
24265   module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
24266     function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
24267       return {
24268         replace: true,
24269         priority: 0,
24270         require: '^uiGrid',
24271         scope: false,
24272         link: function ($scope, $elm, $attrs, uiGridCtrl) {
24273           uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
24274         }
24275       };
24276     }
24277   ]);
24278 })();
24279
24280 (function () {
24281   'use strict';
24282
24283   /**
24284    * @ngdoc overview
24285    * @name ui.grid.selection
24286    * @description
24287    *
24288    * # ui.grid.selection
24289    * This module provides row selection
24290    *
24291    * <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>
24292    *
24293    * <div doc-module-components="ui.grid.selection"></div>
24294    */
24295
24296   var module = angular.module('ui.grid.selection', ['ui.grid']);
24297
24298   /**
24299    *  @ngdoc object
24300    *  @name ui.grid.selection.constant:uiGridSelectionConstants
24301    *
24302    *  @description constants available in selection module
24303    */
24304   module.constant('uiGridSelectionConstants', {
24305     featureName: "selection",
24306     selectionRowHeaderColName: 'selectionRowHeaderCol'
24307   });
24308
24309   //add methods to GridRow
24310   angular.module('ui.grid').config(['$provide', function($provide) {
24311     $provide.decorator('GridRow', ['$delegate', function($delegate) {
24312
24313       /**
24314        *  @ngdoc object
24315        *  @name ui.grid.selection.api:GridRow
24316        *
24317        *  @description GridRow prototype functions added for selection
24318        */
24319
24320       /**
24321        *  @ngdoc object
24322        *  @name enableSelection
24323        *  @propertyOf  ui.grid.selection.api:GridRow
24324        *  @description Enable row selection for this row, only settable by internal code.
24325        *
24326        *  The grouping feature, for example, might set group header rows to not be selectable.
24327        *  <br/>Defaults to true
24328        */
24329
24330       /**
24331        *  @ngdoc object
24332        *  @name isSelected
24333        *  @propertyOf  ui.grid.selection.api:GridRow
24334        *  @description Selected state of row.  Should be readonly. Make any changes to selected state using setSelected().
24335        *  <br/>Defaults to false
24336        */
24337
24338
24339         /**
24340          * @ngdoc function
24341          * @name setSelected
24342          * @methodOf ui.grid.selection.api:GridRow
24343          * @description Sets the isSelected property and updates the selectedCount
24344          * Changes to isSelected state should only be made via this function
24345          * @param {bool} selected value to set
24346          */
24347         $delegate.prototype.setSelected = function(selected) {
24348           this.isSelected = selected;
24349           if (selected) {
24350             this.grid.selection.selectedCount++;
24351           }
24352           else {
24353             this.grid.selection.selectedCount--;
24354           }
24355         };
24356
24357       return $delegate;
24358     }]);
24359   }]);
24360
24361   /**
24362    *  @ngdoc service
24363    *  @name ui.grid.selection.service:uiGridSelectionService
24364    *
24365    *  @description Services for selection features
24366    */
24367   module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
24368     function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
24369
24370       var service = {
24371
24372         initializeGrid: function (grid) {
24373
24374           //add feature namespace and any properties to grid for needed
24375           /**
24376            *  @ngdoc object
24377            *  @name ui.grid.selection.grid:selection
24378            *
24379            *  @description Grid properties and functions added for selection
24380            */
24381           grid.selection = {};
24382           grid.selection.lastSelectedRow = null;
24383           grid.selection.selectAll = false;
24384
24385
24386           /**
24387            *  @ngdoc object
24388            *  @name selectedCount
24389            *  @propertyOf  ui.grid.selection.grid:selection
24390            *  @description Current count of selected rows
24391            *  @example
24392            *  var count = grid.selection.selectedCount
24393            */
24394           grid.selection.selectedCount = 0;
24395
24396           service.defaultGridOptions(grid.options);
24397
24398           /**
24399            *  @ngdoc object
24400            *  @name ui.grid.selection.api:PublicApi
24401            *
24402            *  @description Public Api for selection feature
24403            */
24404           var publicApi = {
24405             events: {
24406               selection: {
24407                 /**
24408                  * @ngdoc event
24409                  * @name rowSelectionChanged
24410                  * @eventOf  ui.grid.selection.api:PublicApi
24411                  * @description  is raised after the row.isSelected state is changed
24412                  * @param {GridRow} row the row that was selected/deselected
24413                  * @param {Event} event object if raised from an event
24414                  */
24415                 rowSelectionChanged: function (scope, row, evt) {
24416                 },
24417                 /**
24418                  * @ngdoc event
24419                  * @name rowSelectionChangedBatch
24420                  * @eventOf  ui.grid.selection.api:PublicApi
24421                  * @description  is raised after the row.isSelected state is changed
24422                  * in bulk, if the `enableSelectionBatchEvent` option is set to true
24423                  * (which it is by default).  This allows more efficient processing
24424                  * of bulk events.
24425                  * @param {array} rows the rows that were selected/deselected
24426                  * @param {Event} event object if raised from an event
24427                  */
24428                 rowSelectionChangedBatch: function (scope, rows, evt) {
24429                 }
24430               }
24431             },
24432             methods: {
24433               selection: {
24434                 /**
24435                  * @ngdoc function
24436                  * @name toggleRowSelection
24437                  * @methodOf  ui.grid.selection.api:PublicApi
24438                  * @description Toggles data row as selected or unselected
24439                  * @param {object} rowEntity gridOptions.data[] array instance
24440                  * @param {Event} event object if raised from an event
24441                  */
24442                 toggleRowSelection: function (rowEntity, evt) {
24443                   var row = grid.getRow(rowEntity);
24444                   if (row !== null) {
24445                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24446                   }
24447                 },
24448                 /**
24449                  * @ngdoc function
24450                  * @name selectRow
24451                  * @methodOf  ui.grid.selection.api:PublicApi
24452                  * @description Select the data row
24453                  * @param {object} rowEntity gridOptions.data[] array instance
24454                  * @param {Event} event object if raised from an event
24455                  */
24456                 selectRow: function (rowEntity, evt) {
24457                   var row = grid.getRow(rowEntity);
24458                   if (row !== null && !row.isSelected) {
24459                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24460                   }
24461                 },
24462                 /**
24463                  * @ngdoc function
24464                  * @name selectRowByVisibleIndex
24465                  * @methodOf  ui.grid.selection.api:PublicApi
24466                  * @description Select the specified row by visible index (i.e. if you
24467                  * specify row 0 you'll get the first visible row selected).  In this context
24468                  * visible means of those rows that are theoretically visible (i.e. not filtered),
24469                  * rather than rows currently rendered on the screen.
24470                  * @param {number} index index within the rowsVisible array
24471                  * @param {Event} event object if raised from an event
24472                  */
24473                 selectRowByVisibleIndex: function ( rowNum, evt ) {
24474                   var row = grid.renderContainers.body.visibleRowCache[rowNum];
24475                   if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
24476                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24477                   }
24478                 },
24479                 /**
24480                  * @ngdoc function
24481                  * @name unSelectRow
24482                  * @methodOf  ui.grid.selection.api:PublicApi
24483                  * @description UnSelect the data row
24484                  * @param {object} rowEntity gridOptions.data[] array instance
24485                  * @param {Event} event object if raised from an event
24486                  */
24487                 unSelectRow: function (rowEntity, evt) {
24488                   var row = grid.getRow(rowEntity);
24489                   if (row !== null && row.isSelected) {
24490                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24491                   }
24492                 },
24493                 /**
24494                  * @ngdoc function
24495                  * @name selectAllRows
24496                  * @methodOf  ui.grid.selection.api:PublicApi
24497                  * @description Selects all rows.  Does nothing if multiSelect = false
24498                  * @param {Event} event object if raised from an event
24499                  */
24500                 selectAllRows: function (evt) {
24501                   if (grid.options.multiSelect === false) {
24502                     return;
24503                   }
24504
24505                   var changedRows = [];
24506                   grid.rows.forEach(function (row) {
24507                     if ( !row.isSelected && row.enableSelection !== false ){
24508                       row.setSelected(true);
24509                       service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24510                     }
24511                   });
24512                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24513                   grid.selection.selectAll = true;
24514                 },
24515                 /**
24516                  * @ngdoc function
24517                  * @name selectAllVisibleRows
24518                  * @methodOf  ui.grid.selection.api:PublicApi
24519                  * @description Selects all visible rows.  Does nothing if multiSelect = false
24520                  * @param {Event} event object if raised from an event
24521                  */
24522                 selectAllVisibleRows: function (evt) {
24523                   if (grid.options.multiSelect === false) {
24524                     return;
24525                   }
24526
24527                   var changedRows = [];
24528                   grid.rows.forEach(function (row) {
24529                     if (row.visible) {
24530                       if (!row.isSelected && row.enableSelection !== false){
24531                         row.setSelected(true);
24532                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24533                       }
24534                     } else {
24535                       if (row.isSelected){
24536                         row.setSelected(false);
24537                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24538                       }
24539                     }
24540                   });
24541                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24542                   grid.selection.selectAll = true;
24543                 },
24544                 /**
24545                  * @ngdoc function
24546                  * @name clearSelectedRows
24547                  * @methodOf  ui.grid.selection.api:PublicApi
24548                  * @description Unselects all rows
24549                  * @param {Event} event object if raised from an event
24550                  */
24551                 clearSelectedRows: function (evt) {
24552                   service.clearSelectedRows(grid, evt);
24553                 },
24554                 /**
24555                  * @ngdoc function
24556                  * @name getSelectedRows
24557                  * @methodOf  ui.grid.selection.api:PublicApi
24558                  * @description returns all selectedRow's entity references
24559                  */
24560                 getSelectedRows: function () {
24561                   return service.getSelectedRows(grid).map(function (gridRow) {
24562                     return gridRow.entity;
24563                   });
24564                 },
24565                 /**
24566                  * @ngdoc function
24567                  * @name getSelectedGridRows
24568                  * @methodOf  ui.grid.selection.api:PublicApi
24569                  * @description returns all selectedRow's as gridRows
24570                  */
24571                 getSelectedGridRows: function () {
24572                   return service.getSelectedRows(grid);
24573                 },
24574                 /**
24575                  * @ngdoc function
24576                  * @name getSelectedCount
24577                  * @methodOf  ui.grid.selection.api:PublicApi
24578                  * @description returns the number of rows selected
24579                  */
24580                 getSelectedCount: function () {
24581                   return grid.selection.selectedCount;
24582                 },
24583                 /**
24584                  * @ngdoc function
24585                  * @name setMultiSelect
24586                  * @methodOf  ui.grid.selection.api:PublicApi
24587                  * @description Sets the current gridOption.multiSelect to true or false
24588                  * @param {bool} multiSelect true to allow multiple rows
24589                  */
24590                 setMultiSelect: function (multiSelect) {
24591                   grid.options.multiSelect = multiSelect;
24592                 },
24593                 /**
24594                  * @ngdoc function
24595                  * @name setModifierKeysToMultiSelect
24596                  * @methodOf  ui.grid.selection.api:PublicApi
24597                  * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
24598                  * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
24599                  */
24600                 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
24601                   grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
24602                 },
24603                 /**
24604                  * @ngdoc function
24605                  * @name getSelectAllState
24606                  * @methodOf  ui.grid.selection.api:PublicApi
24607                  * @description Returns whether or not the selectAll checkbox is currently ticked.  The
24608                  * grid doesn't automatically select rows when you add extra data - so when you add data
24609                  * you need to explicitly check whether the selectAll is set, and then call setVisible rows
24610                  * if it is
24611                  */
24612                 getSelectAllState: function () {
24613                   return grid.selection.selectAll;
24614                 }
24615
24616               }
24617             }
24618           };
24619
24620           grid.api.registerEventsFromObject(publicApi.events);
24621
24622           grid.api.registerMethodsFromObject(publicApi.methods);
24623
24624         },
24625
24626         defaultGridOptions: function (gridOptions) {
24627           //default option to true unless it was explicitly set to false
24628           /**
24629            *  @ngdoc object
24630            *  @name ui.grid.selection.api:GridOptions
24631            *
24632            *  @description GridOptions for selection feature, these are available to be
24633            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
24634            */
24635
24636           /**
24637            *  @ngdoc object
24638            *  @name enableRowSelection
24639            *  @propertyOf  ui.grid.selection.api:GridOptions
24640            *  @description Enable row selection for entire grid.
24641            *  <br/>Defaults to true
24642            */
24643           gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
24644           /**
24645            *  @ngdoc object
24646            *  @name multiSelect
24647            *  @propertyOf  ui.grid.selection.api:GridOptions
24648            *  @description Enable multiple row selection for entire grid
24649            *  <br/>Defaults to true
24650            */
24651           gridOptions.multiSelect = gridOptions.multiSelect !== false;
24652           /**
24653            *  @ngdoc object
24654            *  @name noUnselect
24655            *  @propertyOf  ui.grid.selection.api:GridOptions
24656            *  @description Prevent a row from being unselected.  Works in conjunction
24657            *  with `multiselect = false` and `gridApi.selection.selectRow()` to allow
24658            *  you to create a single selection only grid - a row is always selected, you
24659            *  can only select different rows, you can't unselect the row.
24660            *  <br/>Defaults to false
24661            */
24662           gridOptions.noUnselect = gridOptions.noUnselect === true;
24663           /**
24664            *  @ngdoc object
24665            *  @name modifierKeysToMultiSelect
24666            *  @propertyOf  ui.grid.selection.api:GridOptions
24667            *  @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
24668            *  <br/>Defaults to false
24669            */
24670           gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
24671           /**
24672            *  @ngdoc object
24673            *  @name enableRowHeaderSelection
24674            *  @propertyOf  ui.grid.selection.api:GridOptions
24675            *  @description Enable a row header to be used for selection
24676            *  <br/>Defaults to true
24677            */
24678           gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
24679           /**
24680            *  @ngdoc object
24681            *  @name enableFullRowSelection
24682            *  @propertyOf  ui.grid.selection.api:GridOptions
24683            *  @description Enable selection by clicking anywhere on the row.  Defaults to
24684            *  false if `enableRowHeaderSelection` is true, otherwise defaults to false.
24685            */
24686           if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
24687             gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
24688           }
24689           /**
24690            *  @ngdoc object
24691            *  @name enableSelectAll
24692            *  @propertyOf  ui.grid.selection.api:GridOptions
24693            *  @description Enable the select all checkbox at the top of the selectionRowHeader
24694            *  <br/>Defaults to true
24695            */
24696           gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
24697           /**
24698            *  @ngdoc object
24699            *  @name enableSelectionBatchEvent
24700            *  @propertyOf  ui.grid.selection.api:GridOptions
24701            *  @description If selected rows are changed in bulk, either via the API or
24702            *  via the selectAll checkbox, then a separate event is fired.  Setting this
24703            *  option to false will cause the rowSelectionChanged event to be called multiple times
24704            *  instead
24705            *  <br/>Defaults to true
24706            */
24707           gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
24708           /**
24709            *  @ngdoc object
24710            *  @name selectionRowHeaderWidth
24711            *  @propertyOf  ui.grid.selection.api:GridOptions
24712            *  @description can be used to set a custom width for the row header selection column
24713            *  <br/>Defaults to 30px
24714            */
24715           gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
24716
24717           /**
24718            *  @ngdoc object
24719            *  @name enableFooterTotalSelected
24720            *  @propertyOf  ui.grid.selection.api:GridOptions
24721            *  @description Shows the total number of selected items in footer if true.
24722            *  <br/>Defaults to true.
24723            *  <br/>GridOptions.showGridFooter must also be set to true.
24724            */
24725           gridOptions.enableFooterTotalSelected = gridOptions.enableFooterTotalSelected !== false;
24726
24727           /**
24728            *  @ngdoc object
24729            *  @name isRowSelectable
24730            *  @propertyOf  ui.grid.selection.api:GridOptions
24731            *  @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
24732            */
24733
24734           gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
24735         },
24736
24737         /**
24738          * @ngdoc function
24739          * @name toggleRowSelection
24740          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24741          * @description Toggles row as selected or unselected
24742          * @param {Grid} grid grid object
24743          * @param {GridRow} row row to select or deselect
24744          * @param {Event} event object if resulting from event
24745          * @param {bool} multiSelect if false, only one row at time can be selected
24746          * @param {bool} noUnselect if true then rows cannot be unselected
24747          */
24748         toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
24749           var selected = row.isSelected;
24750
24751           if ( row.enableSelection === false && !selected ){
24752             return;
24753           }
24754
24755           var selectedRows;
24756           if (!multiSelect && !selected) {
24757             service.clearSelectedRows(grid, evt);
24758           } else if (!multiSelect && selected) {
24759             selectedRows = service.getSelectedRows(grid);
24760             if (selectedRows.length > 1) {
24761               selected = false; // Enable reselect of the row
24762               service.clearSelectedRows(grid, evt);
24763             }
24764           }
24765
24766           if (selected && noUnselect){
24767             // don't deselect the row
24768           } else {
24769             row.setSelected(!selected);
24770             if (row.isSelected === true) {
24771               grid.selection.lastSelectedRow = row;
24772             }
24773
24774             selectedRows = service.getSelectedRows(grid);
24775             grid.selection.selectAll = grid.rows.length === selectedRows.length;
24776
24777             grid.api.selection.raise.rowSelectionChanged(row, evt);
24778           }
24779         },
24780         /**
24781          * @ngdoc function
24782          * @name shiftSelect
24783          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24784          * @description selects a group of rows from the last selected row using the shift key
24785          * @param {Grid} grid grid object
24786          * @param {GridRow} clicked row
24787          * @param {Event} event object if raised from an event
24788          * @param {bool} multiSelect if false, does nothing this is for multiSelect only
24789          */
24790         shiftSelect: function (grid, row, evt, multiSelect) {
24791           if (!multiSelect) {
24792             return;
24793           }
24794           var selectedRows = service.getSelectedRows(grid);
24795           var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
24796           var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
24797           //reverse select direction
24798           if (fromRow > toRow) {
24799             var tmp = fromRow;
24800             fromRow = toRow;
24801             toRow = tmp;
24802           }
24803
24804           var changedRows = [];
24805           for (var i = fromRow; i <= toRow; i++) {
24806             var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
24807             if (rowToSelect) {
24808               if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
24809                 rowToSelect.setSelected(true);
24810                 grid.selection.lastSelectedRow = rowToSelect;
24811                 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
24812               }
24813             }
24814           }
24815           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24816         },
24817         /**
24818          * @ngdoc function
24819          * @name getSelectedRows
24820          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24821          * @description Returns all the selected rows
24822          * @param {Grid} grid grid object
24823          */
24824         getSelectedRows: function (grid) {
24825           return grid.rows.filter(function (row) {
24826             return row.isSelected;
24827           });
24828         },
24829
24830         /**
24831          * @ngdoc function
24832          * @name clearSelectedRows
24833          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24834          * @description Clears all selected rows
24835          * @param {Grid} grid grid object
24836          * @param {Event} event object if raised from an event
24837          */
24838         clearSelectedRows: function (grid, evt) {
24839           var changedRows = [];
24840           service.getSelectedRows(grid).forEach(function (row) {
24841             if ( row.isSelected ){
24842               row.setSelected(false);
24843               service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24844             }
24845           });
24846           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24847           grid.selection.selectAll = false;
24848           grid.selection.selectedCount = 0;
24849         },
24850
24851         /**
24852          * @ngdoc function
24853          * @name decideRaiseSelectionEvent
24854          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24855          * @description Decides whether to raise a single event or a batch event
24856          * @param {Grid} grid grid object
24857          * @param {GridRow} row row that has changed
24858          * @param {array} changedRows an array to which we can append the changed
24859          * @param {Event} event object if raised from an event
24860          * row if we're doing batch events
24861          */
24862         decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
24863           if ( !grid.options.enableSelectionBatchEvent ){
24864             grid.api.selection.raise.rowSelectionChanged(row, evt);
24865           } else {
24866             changedRows.push(row);
24867           }
24868         },
24869
24870         /**
24871          * @ngdoc function
24872          * @name raiseSelectionEvent
24873          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24874          * @description Decides whether we need to raise a batch event, and
24875          * raises it if we do.
24876          * @param {Grid} grid grid object
24877          * @param {array} changedRows an array of changed rows, only populated
24878          * @param {Event} event object if raised from an event
24879          * if we're doing batch events
24880          */
24881         decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
24882           if ( changedRows.length > 0 ){
24883             grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
24884           }
24885         }
24886       };
24887
24888       return service;
24889
24890     }]);
24891
24892   /**
24893    *  @ngdoc directive
24894    *  @name ui.grid.selection.directive:uiGridSelection
24895    *  @element div
24896    *  @restrict A
24897    *
24898    *  @description Adds selection features to grid
24899    *
24900    *  @example
24901    <example module="app">
24902    <file name="app.js">
24903    var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
24904
24905    app.controller('MainCtrl', ['$scope', function ($scope) {
24906       $scope.data = [
24907         { name: 'Bob', title: 'CEO' },
24908             { name: 'Frank', title: 'Lowly Developer' }
24909       ];
24910
24911       $scope.columnDefs = [
24912         {name: 'name', enableCellEdit: true},
24913         {name: 'title', enableCellEdit: true}
24914       ];
24915     }]);
24916    </file>
24917    <file name="index.html">
24918    <div ng-controller="MainCtrl">
24919    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
24920    </div>
24921    </file>
24922    </example>
24923    */
24924   module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
24925     function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
24926       return {
24927         replace: true,
24928         priority: 0,
24929         require: '^uiGrid',
24930         scope: false,
24931         compile: function () {
24932           return {
24933             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24934               uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
24935               if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
24936                 var selectionRowHeaderDef = {
24937                   name: uiGridSelectionConstants.selectionRowHeaderColName,
24938                   displayName: '',
24939                   width:  uiGridCtrl.grid.options.selectionRowHeaderWidth,
24940                   minWidth: 10,
24941                   cellTemplate: 'ui-grid/selectionRowHeader',
24942                   headerCellTemplate: 'ui-grid/selectionHeaderCell',
24943                   enableColumnResizing: false,
24944                   enableColumnMenu: false,
24945                   exporterSuppressExport: true,
24946                   allowCellFocus: true
24947                 };
24948
24949                 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
24950               }
24951
24952               var processorSet = false;
24953
24954               var processSelectableRows = function( rows ){
24955                 rows.forEach(function(row){
24956                   row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
24957                 });
24958                 return rows;
24959               };
24960
24961               var updateOptions = function(){
24962                 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
24963                   uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
24964                   processorSet = true;
24965                 }
24966               };
24967
24968               updateOptions();
24969
24970               var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
24971
24972               $scope.$on( '$destroy', dataChangeDereg);
24973             },
24974             post: function ($scope, $elm, $attrs, uiGridCtrl) {
24975
24976             }
24977           };
24978         }
24979       };
24980     }]);
24981
24982   module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService', 'gridUtil',
24983     function ($templateCache, uiGridSelectionService, gridUtil) {
24984       return {
24985         replace: true,
24986         restrict: 'E',
24987         template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
24988         scope: true,
24989         require: '^uiGrid',
24990         link: function($scope, $elm, $attrs, uiGridCtrl) {
24991           var self = uiGridCtrl.grid;
24992           $scope.selectButtonClick = selectButtonClick;
24993
24994           // On IE, prevent mousedowns on the select button from starting a selection.
24995           //   If this is not done and you shift+click on another row, the browser will select a big chunk of text
24996           if (gridUtil.detectBrowser() === 'ie') {
24997             $elm.on('mousedown', selectButtonMouseDown);
24998           }
24999
25000
25001           function selectButtonClick(row, evt) {
25002             evt.stopPropagation();
25003
25004             if (evt.shiftKey) {
25005               uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
25006             }
25007             else if (evt.ctrlKey || evt.metaKey) {
25008               uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
25009             }
25010             else {
25011               uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
25012             }
25013           }
25014
25015           function selectButtonMouseDown(evt) {
25016             if (evt.ctrlKey || evt.shiftKey) {
25017               evt.target.onselectstart = function () { return false; };
25018               window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
25019             }
25020           }
25021         }
25022       };
25023     }]);
25024
25025   module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
25026     function ($templateCache, uiGridSelectionService) {
25027       return {
25028         replace: true,
25029         restrict: 'E',
25030         template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
25031         scope: false,
25032         link: function($scope, $elm, $attrs, uiGridCtrl) {
25033           var self = $scope.col.grid;
25034
25035           $scope.headerButtonClick = function(row, evt) {
25036             if ( self.selection.selectAll ){
25037               uiGridSelectionService.clearSelectedRows(self, evt);
25038               if ( self.options.noUnselect ){
25039                 self.api.selection.selectRowByVisibleIndex(0, evt);
25040               }
25041               self.selection.selectAll = false;
25042             } else {
25043               if ( self.options.multiSelect ){
25044                 self.api.selection.selectAllVisibleRows(evt);
25045                 self.selection.selectAll = true;
25046               }
25047             }
25048           };
25049         }
25050       };
25051     }]);
25052
25053   /**
25054    *  @ngdoc directive
25055    *  @name ui.grid.selection.directive:uiGridViewport
25056    *  @element div
25057    *
25058    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
25059    *  for the grid row
25060    */
25061   module.directive('uiGridViewport',
25062     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
25063       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
25064         return {
25065           priority: -200, // run after default  directive
25066           scope: false,
25067           compile: function ($elm, $attrs) {
25068             var rowRepeatDiv = angular.element($elm.children().children()[0]);
25069
25070             var existingNgClass = rowRepeatDiv.attr("ng-class");
25071             var newNgClass = '';
25072             if ( existingNgClass ) {
25073               newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
25074             } else {
25075               newNgClass = "{'ui-grid-row-selected': row.isSelected}";
25076             }
25077             rowRepeatDiv.attr("ng-class", newNgClass);
25078
25079             return {
25080               pre: function ($scope, $elm, $attrs, controllers) {
25081
25082               },
25083               post: function ($scope, $elm, $attrs, controllers) {
25084               }
25085             };
25086           }
25087         };
25088       }]);
25089
25090   /**
25091    *  @ngdoc directive
25092    *  @name ui.grid.selection.directive:uiGridCell
25093    *  @element div
25094    *  @restrict A
25095    *
25096    *  @description Stacks on top of ui.grid.uiGridCell to provide selection feature
25097    */
25098   module.directive('uiGridCell',
25099     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
25100       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
25101         return {
25102           priority: -200, // run after default uiGridCell directive
25103           restrict: 'A',
25104           require: '?^uiGrid',
25105           scope: false,
25106           link: function ($scope, $elm, $attrs, uiGridCtrl) {
25107
25108             var touchStartTime = 0;
25109             var touchTimeout = 300;
25110
25111             // Bind to keydown events in the render container
25112             if (uiGridCtrl.grid.api.cellNav) {
25113
25114               uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
25115                 if (rowCol === null ||
25116                   rowCol.row !== $scope.row ||
25117                   rowCol.col !== $scope.col) {
25118                   return;
25119                 }
25120
25121                 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
25122                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25123                   $scope.$apply();
25124                 }
25125
25126               //  uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
25127               });
25128             }
25129
25130             //$elm.bind('keydown', function (evt) {
25131             //  if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
25132             //    uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25133             //    $scope.$apply();
25134             //  }
25135             //});
25136
25137             var selectCells = function(evt){
25138               // if we get a click, then stop listening for touchend
25139               $elm.off('touchend', touchEnd);
25140
25141               if (evt.shiftKey) {
25142                 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
25143               }
25144               else if (evt.ctrlKey || evt.metaKey) {
25145                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
25146               }
25147               else {
25148                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25149               }
25150               $scope.$apply();
25151
25152               // don't re-enable the touchend handler for a little while - some devices generate both, and it will
25153               // take a little while to move your hand from the mouse to the screen if you have both modes of input
25154               $timeout(function() {
25155                 $elm.on('touchend', touchEnd);
25156               }, touchTimeout);
25157             };
25158
25159             var touchStart = function(evt){
25160               touchStartTime = (new Date()).getTime();
25161
25162               // if we get a touch event, then stop listening for click
25163               $elm.off('click', selectCells);
25164             };
25165
25166             var touchEnd = function(evt) {
25167               var touchEndTime = (new Date()).getTime();
25168               var touchTime = touchEndTime - touchStartTime;
25169
25170               if (touchTime < touchTimeout ) {
25171                 // short touch
25172                 selectCells(evt);
25173               }
25174
25175               // don't re-enable the click handler for a little while - some devices generate both, and it will
25176               // take a little while to move your hand from the screen to the mouse if you have both modes of input
25177               $timeout(function() {
25178                 $elm.on('click', selectCells);
25179               }, touchTimeout);
25180             };
25181
25182             function registerRowSelectionEvents() {
25183               if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
25184                 $elm.addClass('ui-grid-disable-selection');
25185                 $elm.on('touchstart', touchStart);
25186                 $elm.on('touchend', touchEnd);
25187                 $elm.on('click', selectCells);
25188
25189                 $scope.registered = true;
25190               }
25191             }
25192
25193             function deregisterRowSelectionEvents() {
25194               if ($scope.registered){
25195                 $elm.removeClass('ui-grid-disable-selection');
25196
25197                 $elm.off('touchstart', touchStart);
25198                 $elm.off('touchend', touchEnd);
25199                 $elm.off('click', selectCells);
25200
25201                 $scope.registered = false;
25202               }
25203             }
25204
25205             registerRowSelectionEvents();
25206             // register a dataChange callback so that we can change the selection configuration dynamically
25207             // if the user changes the options
25208             var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
25209               if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
25210                 !$scope.registered ){
25211                 registerRowSelectionEvents();
25212               } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
25213                 $scope.registered ){
25214                 deregisterRowSelectionEvents();
25215               }
25216             }, [uiGridConstants.dataChange.OPTIONS] );
25217
25218             $elm.on( '$destroy', dataChangeDereg);
25219           }
25220         };
25221       }]);
25222
25223   module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
25224     return {
25225       restrict: 'EA',
25226       replace: true,
25227       priority: -1000,
25228       require: '^uiGrid',
25229       scope: true,
25230       compile: function ($elm, $attrs) {
25231         return {
25232           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
25233
25234             if (!uiGridCtrl.grid.options.showGridFooter) {
25235               return;
25236             }
25237
25238
25239             gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
25240               .then(function (contents) {
25241                 var template = angular.element(contents);
25242
25243                 var newElm = $compile(template)($scope);
25244
25245                 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
25246               });
25247           },
25248
25249           post: function ($scope, $elm, $attrs, controllers) {
25250
25251           }
25252         };
25253       }
25254     };
25255   }]);
25256
25257 })();
25258
25259 (function () {
25260   'use strict';
25261
25262   /**
25263    * @ngdoc overview
25264    * @name ui.grid.treeBase
25265    * @description
25266    *
25267    * # ui.grid.treeBase
25268    *
25269    * <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>
25270    *
25271    * This module provides base tree handling functions that are shared by other features, notably grouping
25272    * and treeView.  It provides a tree view of the data, with nodes in that
25273    * tree and leaves.
25274    *
25275    * Design information:
25276    * -------------------
25277    *
25278    * The raw data that is provided must come with a $$treeLevel on any non-leaf node.  Grouping will create
25279    * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
25280    * TreeBase will run a rowsProcessor that:
25281    *  - builds `treeBase.tree` out of the provided rows
25282    *  - permits a recursive sort of the tree
25283    *  - maintains the expand/collapse state of each node
25284    *  - provides the expand/collapse all button and the expand/collapse buttons
25285    *  - maintains the count of children for each node
25286    *
25287    * Each row is updated with a link to the tree node that represents it.  Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
25288    * for information.
25289    *
25290    *  TreeBase adds information to the rows
25291    *  - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
25292    *  - treeNode: pointer to the node in the grid.treeBase.tree that refers
25293    *    to this row, allowing us to manipulate the state
25294    *
25295    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
25296    * row order or filtering or anything like that is changed.  We recall the expanded state
25297    * across invocations of the rowsProcessors by the reference to the treeNode on the individual
25298    * rows.  We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
25299    * get the state, but we overwrite the other data in that treeNode.
25300    *
25301    * By default rows are collapsed, which means all data rows have their visible property
25302    * set to false, and only level 0 group rows are set to visible.
25303    *
25304    * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
25305    * grid.treeBase.tree, then call refresh.  This is because we can't easily change the visible
25306    * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
25307    * well use it all the time.
25308    *
25309    * Tree base provides sorting (on non-grouped columns).
25310    *
25311    * Sorting works in two passes.  The standard sorting is performed for any columns that are important to building
25312    * the tree (for example, any grouped columns).  Then after the tree is built, a recursive tree sort is performed
25313    * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
25314    * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
25315    *
25316    * To achieve this we make use of the `ignoreSort` property on the sort configuration.  The parent feature (treeView or grouping)
25317    * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
25318    * the `ignoreSort`on any sort that it wants to run on the tree.  TreeBase will clear the ignoreSort on all sorts - so it
25319    * will turn on any sorts that haven't run.  It will then call a recursive sort on the tree.
25320    *
25321    * Tree base provides treeAggregation.  It checks the treeAggregation configuration on each column, and aggregates based on
25322    * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
25323    * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
25324    * treeAggregations in the column footer.  Aggregation information will be collected in the format:
25325    *
25326    * ```
25327    *   {
25328    *     type: 'count',
25329    *     value: 4,
25330    *     label: 'count: ',
25331    *     rendered: 'count: 4'
25332    *   }
25333    * ```
25334    *
25335    * A callback is provided to format the value once it is finalised (aka a valueFilter).
25336    *
25337    * <br/>
25338    * <br/>
25339    *
25340    * <div doc-module-components="ui.grid.treeBase"></div>
25341    */
25342
25343   var module = angular.module('ui.grid.treeBase', ['ui.grid']);
25344
25345   /**
25346    *  @ngdoc object
25347    *  @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
25348    *
25349    *  @description constants available in treeBase module.
25350    *
25351    *  These constants are manually copied into grouping and treeView,
25352    *  as I haven't found a way to simply include them, and it's not worth
25353    *  investing time in for something that changes very infrequently.
25354    *
25355    */
25356   module.constant('uiGridTreeBaseConstants', {
25357     featureName: "treeBase",
25358     rowHeaderColName: 'treeBaseRowHeaderCol',
25359     EXPANDED: 'expanded',
25360     COLLAPSED: 'collapsed',
25361     aggregation: {
25362       COUNT: 'count',
25363       SUM: 'sum',
25364       MAX: 'max',
25365       MIN: 'min',
25366       AVG: 'avg'
25367     }
25368   });
25369
25370   /**
25371    *  @ngdoc service
25372    *  @name ui.grid.treeBase.service:uiGridTreeBaseService
25373    *
25374    *  @description Services for treeBase feature
25375    */
25376   /**
25377    *  @ngdoc object
25378    *  @name ui.grid.treeBase.api:ColumnDef
25379    *
25380    *  @description ColumnDef for tree feature, these are available to be
25381    *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
25382    */
25383
25384   module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
25385   function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
25386
25387     var service = {
25388
25389       initializeGrid: function (grid, $scope) {
25390
25391         //add feature namespace and any properties to grid for needed
25392         /**
25393          *  @ngdoc object
25394          *  @name ui.grid.treeBase.grid:treeBase
25395          *
25396          *  @description Grid properties and functions added for treeBase
25397          */
25398         grid.treeBase = {};
25399
25400         /**
25401          *  @ngdoc property
25402          *  @propertyOf ui.grid.treeBase.grid:treeBase
25403          *  @name numberLevels
25404          *
25405          *  @description Total number of tree levels currently used, calculated by the rowsProcessor by
25406          *  retaining the highest tree level it sees
25407          */
25408         grid.treeBase.numberLevels = 0;
25409
25410         /**
25411          *  @ngdoc property
25412          *  @propertyOf ui.grid.treeBase.grid:treeBase
25413          *  @name expandAll
25414          *
25415          *  @description Whether or not the expandAll box is selected
25416          */
25417         grid.treeBase.expandAll = false;
25418
25419         /**
25420          *  @ngdoc property
25421          *  @propertyOf ui.grid.treeBase.grid:treeBase
25422          *  @name tree
25423          *
25424          *  @description Tree represented as a nested array that holds the state of each node, along with a
25425          *  pointer to the row.  The array order is material - we will display the children in the order
25426          *  they are stored in the array
25427          *
25428          *  Each node stores:
25429          *
25430          *    - the state of this node
25431          *    - an array of children of this node
25432          *    - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
25433          *    - the number of children of this node
25434          *    - aggregation information calculated from the nodes
25435          *
25436          *  ```
25437          *    [{
25438          *      state: 'expanded',
25439          *      row: <reference to row>,
25440          *      parentRow: null,
25441          *      aggregations: [{
25442          *        type: 'count',
25443          *        col: <gridCol>,
25444          *        value: 2,
25445          *        label: 'count: ',
25446          *        rendered: 'count: 2'
25447          *      }],
25448          *      children: [
25449          *        {
25450          *          state: 'expanded',
25451          *          row: <reference to row>,
25452          *          parentRow: <reference to row>,
25453          *          aggregations: [{
25454          *            type: 'count',
25455          *            col: '<gridCol>,
25456          *            value: 4,
25457          *            label: 'count: ',
25458          *            rendered: 'count: 4'
25459          *          }],
25460          *          children: [
25461          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25462          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
25463          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25464          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
25465          *          ]
25466          *        },
25467          *        {
25468          *          state: 'collapsed',
25469          *          row: <reference to row>,
25470          *          parentRow: <reference to row>,
25471          *          aggregations: [{
25472          *            type: 'count',
25473          *            col: <gridCol>,
25474          *            value: 3,
25475          *            label: 'count: ',
25476          *            rendered: 'count: 3'
25477          *          }],
25478          *          children: [
25479          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25480          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
25481          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
25482          *          ]
25483          *        }
25484          *      ]
25485          *    }, {<another level 0 node maybe>} ]
25486          *  ```
25487          *  Missing state values are false - meaning they aren't expanded.
25488          *
25489          *  This is used because the rowProcessors run every time the grid is refreshed, so
25490          *  we'd lose the expanded state every time the grid was refreshed.  This instead gives
25491          *  us a reliable lookup that persists across rowProcessors.
25492          *
25493          *  This tree is rebuilt every time we run the rowsProcessors.  Since each row holds a pointer
25494          *  to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
25495          *  all transient information on the tree (children, childCount) and recalculate it
25496          *
25497          */
25498         grid.treeBase.tree = [];
25499
25500         service.defaultGridOptions(grid.options);
25501
25502         grid.registerRowsProcessor(service.treeRows, 410);
25503
25504         grid.registerColumnBuilder( service.treeBaseColumnBuilder );
25505
25506         service.createRowHeader( grid );
25507
25508         /**
25509          *  @ngdoc object
25510          *  @name ui.grid.treeBase.api:PublicApi
25511          *
25512          *  @description Public Api for treeBase feature
25513          */
25514         var publicApi = {
25515           events: {
25516             treeBase: {
25517               /**
25518                * @ngdoc event
25519                * @eventOf ui.grid.treeBase.api:PublicApi
25520                * @name rowExpanded
25521                * @description raised whenever a row is expanded.  If you are dynamically
25522                * rendering your tree you can listen to this event, and then retrieve
25523                * the children of this row and load them into the grid data.
25524                *
25525                * When the data is loaded the grid will automatically refresh to show these new rows
25526                *
25527                * <pre>
25528                *      gridApi.treeBase.on.rowExpanded(scope,function(row){})
25529                * </pre>
25530                * @param {gridRow} row the row that was expanded.  You can also
25531                * retrieve the grid from this row with row.grid
25532                */
25533               rowExpanded: {},
25534
25535               /**
25536                * @ngdoc event
25537                * @eventOf ui.grid.treeBase.api:PublicApi
25538                * @name rowCollapsed
25539                * @description raised whenever a row is collapsed.  Doesn't really have
25540                * a purpose at the moment, included for symmetry
25541                *
25542                * <pre>
25543                *      gridApi.treeBase.on.rowCollapsed(scope,function(row){})
25544                * </pre>
25545                * @param {gridRow} row the row that was collapsed.  You can also
25546                * retrieve the grid from this row with row.grid
25547                */
25548               rowCollapsed: {}
25549             }
25550           },
25551
25552           methods: {
25553             treeBase: {
25554               /**
25555                * @ngdoc function
25556                * @name expandAllRows
25557                * @methodOf  ui.grid.treeBase.api:PublicApi
25558                * @description Expands all tree rows
25559                */
25560               expandAllRows: function () {
25561                 service.expandAllRows(grid);
25562               },
25563
25564               /**
25565                * @ngdoc function
25566                * @name collapseAllRows
25567                * @methodOf  ui.grid.treeBase.api:PublicApi
25568                * @description collapse all tree rows
25569                */
25570               collapseAllRows: function () {
25571                 service.collapseAllRows(grid);
25572               },
25573
25574               /**
25575                * @ngdoc function
25576                * @name toggleRowTreeState
25577                * @methodOf  ui.grid.treeBase.api:PublicApi
25578                * @description  call expand if the row is collapsed, collapse if it is expanded
25579                * @param {gridRow} row the row you wish to toggle
25580                */
25581               toggleRowTreeState: function (row) {
25582                 service.toggleRowTreeState(grid, row);
25583               },
25584
25585               /**
25586                * @ngdoc function
25587                * @name expandRow
25588                * @methodOf  ui.grid.treeBase.api:PublicApi
25589                * @description expand the immediate children of the specified row
25590                * @param {gridRow} row the row you wish to expand
25591                */
25592               expandRow: function (row) {
25593                 service.expandRow(grid, row);
25594               },
25595
25596               /**
25597                * @ngdoc function
25598                * @name expandRowChildren
25599                * @methodOf  ui.grid.treeBase.api:PublicApi
25600                * @description expand all children of the specified row
25601                * @param {gridRow} row the row you wish to expand
25602                */
25603               expandRowChildren: function (row) {
25604                 service.expandRowChildren(grid, row);
25605               },
25606
25607               /**
25608                * @ngdoc function
25609                * @name collapseRow
25610                * @methodOf  ui.grid.treeBase.api:PublicApi
25611                * @description collapse  the specified row.  When
25612                * you expand the row again, all grandchildren will retain their state
25613                * @param {gridRow} row the row you wish to collapse
25614                */
25615               collapseRow: function ( row ) {
25616                 service.collapseRow(grid, row);
25617               },
25618
25619               /**
25620                * @ngdoc function
25621                * @name collapseRowChildren
25622                * @methodOf  ui.grid.treeBase.api:PublicApi
25623                * @description collapse all children of the specified row.  When
25624                * you expand the row again, all grandchildren will be collapsed
25625                * @param {gridRow} row the row you wish to collapse children for
25626                */
25627               collapseRowChildren: function ( row ) {
25628                 service.collapseRowChildren(grid, row);
25629               },
25630
25631               /**
25632                * @ngdoc function
25633                * @name getTreeState
25634                * @methodOf  ui.grid.treeBase.api:PublicApi
25635                * @description Get the tree state for this grid,
25636                * used by the saveState feature
25637                * Returned treeState as an object
25638                *   `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
25639                * where expandedState is a hash of row uid and the current expanded state
25640                *
25641                * @returns {object} tree state
25642                *
25643                * TODO - this needs work - we need an identifier that persists across instantiations,
25644                * not uid.  This really means we need a row identity defined, but that won't work for
25645                * grouping.  Perhaps this needs to be moved up to treeView and grouping, rather than
25646                * being in base.
25647                */
25648               getTreeExpandedState: function () {
25649                 return { expandedState: service.getTreeState(grid) };
25650               },
25651
25652               /**
25653                * @ngdoc function
25654                * @name setTreeState
25655                * @methodOf  ui.grid.treeBase.api:PublicApi
25656                * @description Set the expanded states of the tree
25657                * @param {object} config the config you want to apply, in the format
25658                * provided by getTreeState
25659                */
25660               setTreeState: function ( config ) {
25661                 service.setTreeState( grid, config );
25662               },
25663
25664               /**
25665                * @ngdoc function
25666                * @name getRowChildren
25667                * @methodOf  ui.grid.treeBase.api:PublicApi
25668                * @description Get the children of the specified row
25669                * @param {GridRow} row the row you want the children of
25670                * @returns {Array} array of children of this row, the children
25671                * are all gridRows
25672                */
25673               getRowChildren: function ( row ){
25674                 return row.treeNode.children.map( function( childNode ){
25675                   return childNode.row;
25676                 });
25677               }
25678             }
25679           }
25680         };
25681
25682         grid.api.registerEventsFromObject(publicApi.events);
25683
25684         grid.api.registerMethodsFromObject(publicApi.methods);
25685       },
25686
25687
25688       defaultGridOptions: function (gridOptions) {
25689         //default option to true unless it was explicitly set to false
25690         /**
25691          *  @ngdoc object
25692          *  @name ui.grid.treeBase.api:GridOptions
25693          *
25694          *  @description GridOptions for treeBase feature, these are available to be
25695          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
25696          */
25697
25698         /**
25699          *  @ngdoc object
25700          *  @name treeRowHeaderBaseWidth
25701          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25702          *  @description Base width of the tree header, provides for a single level of tree.  This
25703          *  is incremented by `treeIndent` for each extra level
25704          *  <br/>Defaults to 30
25705          */
25706         gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
25707
25708         /**
25709          *  @ngdoc object
25710          *  @name treeIndent
25711          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25712          *  @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
25713          *  but will make the tree row header wider
25714          *  <br/>Defaults to 10
25715          */
25716         gridOptions.treeIndent = gridOptions.treeIndent || 10;
25717
25718         /**
25719          *  @ngdoc object
25720          *  @name showTreeRowHeader
25721          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25722          *  @description If set to false, don't create the row header.  Youll need to programatically control the expand
25723          *  states
25724          *  <br/>Defaults to true
25725          */
25726         gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
25727
25728         /**
25729          *  @ngdoc object
25730          *  @name showTreeExpandNoChildren
25731          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25732          *  @description If set to true, show the expand/collapse button even if there are no
25733          *  children of a node.  You'd use this if you're planning to dynamically load the children
25734          *
25735          *  <br/>Defaults to true, grouping overrides to false
25736          */
25737         gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
25738
25739         /**
25740          *  @ngdoc object
25741          *  @name treeRowHeaderAlwaysVisible
25742          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25743          *  @description If set to true, row header even if there are no tree nodes
25744          *
25745          *  <br/>Defaults to true
25746          */
25747         gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
25748
25749         /**
25750          *  @ngdoc object
25751          *  @name treeCustomAggregations
25752          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25753          *  @description Define custom aggregation functions. The properties of this object will be
25754          *  aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
25755          *  If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
25756          *  The object format is:
25757          *
25758          *  <pre>
25759          *    {
25760          *      aggregationName: {
25761          *        label: (optional) string,
25762          *        aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
25763          *        finalizerFn: (optional) function( aggregation ){...}
25764        *        },
25765          *      mean: {
25766          *        label: 'mean',
25767          *        aggregationFn: function( aggregation, fieldValue, numValue ){
25768        *            aggregation.count = (aggregation.count || 1) + 1;
25769          *          aggregation.sum = (aggregation.sum || 0) + numValue;
25770          *        },
25771          *        finalizerFn: function( aggregation ){
25772          *          aggregation.value = aggregation.sum / aggregation.count
25773          *        }
25774          *      }
25775          *    }
25776          *  </pre>
25777          *
25778          *  <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
25779          *  apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
25780          *  rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
25781          *  the label and the value.
25782          *
25783          *  <br/>Defaults to {}
25784          */
25785         gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
25786       },
25787
25788
25789       /**
25790        * @ngdoc function
25791        * @name treeBaseColumnBuilder
25792        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25793        * @description Sets the tree defaults based on the columnDefs
25794        *
25795        * @param {object} colDef columnDef we're basing on
25796        * @param {GridCol} col the column we're to update
25797        * @param {object} gridOptions the options we should use
25798        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
25799        */
25800       treeBaseColumnBuilder: function (colDef, col, gridOptions) {
25801
25802
25803         /**
25804          *  @ngdoc object
25805          *  @name customTreeAggregationFn
25806          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25807          *  @description A custom function that aggregates rows into some form of
25808          *  total.  Aggregations run row-by-row, the function needs to be capable of
25809          *  creating a running total.
25810          *
25811          *  The function will be provided the aggregation item (in which you can store running
25812          *  totals), the row value that is to be aggregated, and that same row value converted to
25813          *  a number (most aggregations work on numbers)
25814          *  @example
25815          *  <pre>
25816          *    customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
25817          *      // calculates the average of the squares of the values
25818          *      if ( typeof(aggregation.count) === 'undefined' ){
25819          *        aggregation.count = 0;
25820          *      }
25821          *      aggregation.count++;
25822          *
25823          *      if ( !isNaN(numValue) ){
25824          *        if ( typeof(aggregation.total) === 'undefined' ){
25825          *          aggregation.total = 0;
25826          *        }
25827          *        aggregation.total = aggregation.total + numValue * numValue;
25828          *      }
25829          *
25830          *      aggregation.value = aggregation.total / aggregation.count;
25831          *    }
25832          *  </pre>
25833          *  <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
25834          */
25835         if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
25836           col.treeAggregationFn = colDef.customTreeAggregationFn;
25837         }
25838
25839         /**
25840          *  @ngdoc object
25841          *  @name treeAggregationType
25842          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25843          *  @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
25844          *  Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
25845          *  name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
25846          *
25847          *  <pre>
25848          *      treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
25849          *    }
25850          *  </pre>
25851          *
25852          *  If you are using aggregations you should either:
25853          *
25854          *   - also use grouping, in which case the aggregations are displayed in the group header, OR
25855          *   - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
25856          *     treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
25857          *     in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
25858          *
25859          *  <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
25860          *  <br/>Defaults to undefined.
25861          */
25862         if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
25863           col.treeAggregation = { type: colDef.treeAggregationType };
25864           if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
25865             col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
25866             col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
25867             col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
25868           } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
25869             col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
25870             col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
25871           }
25872         }
25873
25874          /**
25875          *  @ngdoc object
25876          *  @name treeAggregationLabel
25877          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25878          *  @description A custom label to use for this aggregation. If provided we don't use native i18n.
25879          */
25880         if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
25881           if (typeof(col.treeAggregation) === 'undefined' ){
25882             col.treeAggregation = {};
25883           }
25884           col.treeAggregation.label = colDef.treeAggregationLabel;
25885         }
25886
25887         /**
25888          *  @ngdoc object
25889          *  @name treeAggregationUpdateEntity
25890          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25891          *  @description Store calculated aggregations into the entity, allowing them
25892          *  to be displayed in the grid using a standard cellTemplate.  This defaults to true,
25893          *  if you are using grouping then you shouldn't set it to false, as then the aggregations won't
25894          *  display.
25895          *
25896          *  If you are using treeView in most cases you'll want to set this to true.  This will result in
25897          *  getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
25898          *  the entity.  If you want to render the underlying entity value (and do something else with the aggregation)
25899          *  then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
25900          *
25901          *  <br/>Defaults to true
25902          *
25903          *  @example
25904          *  <pre>
25905          *    gridOptions.columns = [{
25906          *      name: 'myCol',
25907          *      treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
25908          *      treeAggregationUpdateEntity: true
25909          *      cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
25910          *    }];
25911          * </pre>
25912          */
25913         col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
25914
25915         /**
25916          *  @ngdoc object
25917          *  @name customTreeAggregationFinalizerFn
25918          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25919          *  @description A custom function that populates aggregation.rendered, this is called when
25920          *  a particular aggregation has been fully calculated, and we want to render the value.
25921          *
25922          *  With the native aggregation options we just concatenate `aggregation.label` and
25923          *  `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
25924          *  or the value, you can do so with this function. This function will be called after the
25925          *  the default `finalizerFn`.
25926          *
25927          *  @example
25928          *  <pre>
25929          *    customTreeAggregationFinalizerFn = function ( aggregation ){
25930          *      aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
25931          *    }
25932          *  </pre>
25933          *  <br/>Defaults to undefined.
25934          */
25935         if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
25936           col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
25937         }
25938
25939       },
25940
25941
25942       /**
25943        * @ngdoc function
25944        * @name createRowHeader
25945        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25946        * @description Create the rowHeader.  If treeRowHeaderAlwaysVisible then
25947        * set it to visible, otherwise set it to invisible
25948        *
25949        * @param {Grid} grid grid object
25950        */
25951       createRowHeader: function( grid ){
25952         var rowHeaderColumnDef = {
25953           name: uiGridTreeBaseConstants.rowHeaderColName,
25954           displayName: '',
25955           width:  grid.options.treeRowHeaderBaseWidth,
25956           minWidth: 10,
25957           cellTemplate: 'ui-grid/treeBaseRowHeader',
25958           headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
25959           enableColumnResizing: false,
25960           enableColumnMenu: false,
25961           exporterSuppressExport: true,
25962           allowCellFocus: true
25963         };
25964
25965         rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
25966         grid.addRowHeaderColumn( rowHeaderColumnDef );
25967       },
25968
25969
25970       /**
25971        * @ngdoc function
25972        * @name expandAllRows
25973        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25974        * @description Expands all nodes in the tree
25975        *
25976        * @param {Grid} grid grid object
25977        */
25978       expandAllRows: function (grid) {
25979         grid.treeBase.tree.forEach( function( node ) {
25980           service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
25981         });
25982         grid.treeBase.expandAll = true;
25983         grid.queueGridRefresh();
25984       },
25985
25986
25987       /**
25988        * @ngdoc function
25989        * @name collapseAllRows
25990        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25991        * @description Collapses all nodes in the tree
25992        *
25993        * @param {Grid} grid grid object
25994        */
25995       collapseAllRows: function (grid) {
25996         grid.treeBase.tree.forEach( function( node ) {
25997           service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
25998         });
25999         grid.treeBase.expandAll = false;
26000         grid.queueGridRefresh();
26001       },
26002
26003
26004       /**
26005        * @ngdoc function
26006        * @name setAllNodes
26007        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26008        * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
26009        * all child nodes (and their descendents) of the provided node to the given state.
26010        *
26011        * Calls itself recursively on all nodes so as to achieve this.
26012        *
26013        * @param {Grid} grid the grid we're operating on (so we can raise events)
26014        * @param {object} treeNode a node in the tree that we want to update
26015        * @param {string} targetState the state we want to set it to
26016        */
26017       setAllNodes: function (grid, treeNode, targetState) {
26018         if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
26019           treeNode.state = targetState;
26020
26021           if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
26022             grid.api.treeBase.raise.rowExpanded(treeNode.row);
26023           } else {
26024             grid.api.treeBase.raise.rowCollapsed(treeNode.row);
26025           }
26026         }
26027
26028         // set all child nodes
26029         if ( treeNode.children ){
26030           treeNode.children.forEach(function( childNode ){
26031             service.setAllNodes(grid, childNode, targetState);
26032           });
26033         }
26034       },
26035
26036
26037       /**
26038        * @ngdoc function
26039        * @name toggleRowTreeState
26040        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26041        * @description Toggles the expand or collapse state of this grouped row, if
26042        * it's a parent row
26043        *
26044        * @param {Grid} grid grid object
26045        * @param {GridRow} row the row we want to toggle
26046        */
26047       toggleRowTreeState: function ( grid, row ){
26048         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26049           return;
26050         }
26051
26052         if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
26053           service.collapseRow(grid, row);
26054         } else {
26055           service.expandRow(grid, row);
26056         }
26057
26058         grid.queueGridRefresh();
26059       },
26060
26061
26062       /**
26063        * @ngdoc function
26064        * @name expandRow
26065        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26066        * @description Expands this specific row, showing only immediate children.
26067        *
26068        * @param {Grid} grid grid object
26069        * @param {GridRow} row the row we want to expand
26070        */
26071       expandRow: function ( grid, row ){
26072         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26073           return;
26074         }
26075
26076         if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
26077           row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
26078           grid.api.treeBase.raise.rowExpanded(row);
26079           grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
26080           grid.queueGridRefresh();
26081         }
26082       },
26083
26084
26085       /**
26086        * @ngdoc function
26087        * @name expandRowChildren
26088        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26089        * @description Expands this specific row, showing all children.
26090        *
26091        * @param {Grid} grid grid object
26092        * @param {GridRow} row the row we want to expand
26093        */
26094       expandRowChildren: function ( grid, row ){
26095         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26096           return;
26097         }
26098
26099         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
26100         grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
26101         grid.queueGridRefresh();
26102       },
26103
26104
26105       /**
26106        * @ngdoc function
26107        * @name collapseRow
26108        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26109        * @description Collapses this specific row
26110        *
26111        * @param {Grid} grid grid object
26112        * @param {GridRow} row the row we want to collapse
26113        */
26114       collapseRow: function( grid, row ){
26115         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26116           return;
26117         }
26118
26119         if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
26120           row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
26121           grid.treeBase.expandAll = false;
26122           grid.api.treeBase.raise.rowCollapsed(row);
26123           grid.queueGridRefresh();
26124         }
26125       },
26126
26127
26128       /**
26129        * @ngdoc function
26130        * @name collapseRowChildren
26131        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26132        * @description Collapses this specific row and all children
26133        *
26134        * @param {Grid} grid grid object
26135        * @param {GridRow} row the row we want to collapse
26136        */
26137       collapseRowChildren: function( grid, row ){
26138         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26139           return;
26140         }
26141
26142         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
26143         grid.treeBase.expandAll = false;
26144         grid.queueGridRefresh();
26145       },
26146
26147
26148       /**
26149        * @ngdoc function
26150        * @name allExpanded
26151        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26152        * @description Returns true if all rows are expanded, false
26153        * if they're not.  Walks the tree to determine this.  Used
26154        * to set the expandAll state.
26155        *
26156        * If the node has no children, then return true (it's immaterial
26157        * whether it is expanded).  If the node has children, then return
26158        * false if this node is collapsed, or if any child node is not all expanded
26159        *
26160        * @param {object} tree the grid to check
26161        * @returns {boolean} whether or not the tree is all expanded
26162        */
26163       allExpanded: function( tree ){
26164         var allExpanded = true;
26165         tree.forEach( function( node ){
26166           if ( !service.allExpandedInternal( node ) ){
26167             allExpanded = false;
26168           }
26169         });
26170         return allExpanded;
26171       },
26172
26173       allExpandedInternal: function( treeNode ){
26174         if ( treeNode.children && treeNode.children.length > 0 ){
26175           if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
26176             return false;
26177           }
26178           var allExpanded = true;
26179           treeNode.children.forEach( function( node ){
26180             if ( !service.allExpandedInternal( node ) ){
26181               allExpanded = false;
26182             }
26183           });
26184           return allExpanded;
26185         } else {
26186           return true;
26187         }
26188       },
26189
26190
26191       /**
26192        * @ngdoc function
26193        * @name treeRows
26194        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26195        * @description The rowProcessor that adds the nodes to the tree, and sets the visible
26196        * state of each row based on it's parent state
26197        *
26198        * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
26199        * Performs any tree sorts itself after having built the tree
26200        *
26201        * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
26202        * entity, and setting the visible state based on the parent's state.
26203        *
26204        * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
26205        * sized.
26206        *
26207        * Aggregates if necessary along the way.
26208        *
26209        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
26210        * @returns {array} the updated rows
26211        */
26212       treeRows: function( renderableRows ) {
26213         if (renderableRows.length === 0){
26214           return renderableRows;
26215         }
26216
26217         var grid = this;
26218         var currentLevel = 0;
26219         var currentState = uiGridTreeBaseConstants.EXPANDED;
26220         var parents = [];
26221
26222         grid.treeBase.tree = service.createTree( grid, renderableRows );
26223         service.updateRowHeaderWidth( grid );
26224
26225         service.sortTree( grid );
26226         service.fixFilter( grid );
26227
26228         return service.renderTree( grid.treeBase.tree );
26229       },
26230
26231
26232       /**
26233        * @ngdoc function
26234        * @name createOrUpdateRowHeaderWidth
26235        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26236        * @description Calculates the rowHeader width.
26237        *
26238        * If rowHeader is always present, updates the width.
26239        *
26240        * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
26241        * should be one, then creates or removes it as appropriate, with the created rowHeader having the
26242        * right width.
26243        *
26244        * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
26245        *
26246        * @param {Grid} grid the grid we want to set the row header on
26247        */
26248       updateRowHeaderWidth: function( grid ){
26249         var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
26250
26251         var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
26252         if ( rowHeader && newWidth !== rowHeader.width ){
26253           rowHeader.width = newWidth;
26254           grid.queueRefresh();
26255         }
26256
26257         var newVisibility = true;
26258         if ( grid.options.showTreeRowHeader === false ){
26259           newVisibility = false;
26260         }
26261         if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
26262           newVisibility = false;
26263         }
26264         if ( rowHeader.visible !== newVisibility ) {
26265           rowHeader.visible = newVisibility;
26266           rowHeader.colDef.visible = newVisibility;
26267           grid.queueGridRefresh();
26268         }
26269       },
26270
26271
26272       /**
26273        * @ngdoc function
26274        * @name renderTree
26275        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26276        * @description Creates an array of rows based on the tree, exporting only
26277        * the visible nodes and leaves
26278        *
26279        * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
26280        * we're calling recursively
26281        * @returns {array} renderable rows
26282        */
26283       renderTree: function( nodeList ){
26284         var renderableRows = [];
26285
26286         nodeList.forEach( function ( node ){
26287           if ( node.row.visible ){
26288             renderableRows.push( node.row );
26289           }
26290           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
26291             renderableRows = renderableRows.concat( service.renderTree( node.children ) );
26292           }
26293         });
26294         return renderableRows;
26295       },
26296
26297
26298       /**
26299        * @ngdoc function
26300        * @name createTree
26301        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26302        * @description Creates a tree from the renderableRows
26303        *
26304        * @param {Grid} grid the grid
26305        * @param {array} renderableRows the rows we want to create a tree from
26306        * @returns {object} the tree we've build
26307        */
26308       createTree: function( grid, renderableRows ) {
26309         var currentLevel = -1;
26310         var parents = [];
26311         var currentState;
26312         grid.treeBase.tree = [];
26313         grid.treeBase.numberLevels = 0;
26314         var aggregations = service.getAggregations( grid );
26315
26316         var createNode = function( row ){
26317           if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
26318             row.treeLevel = row.entity.$$treeLevel;
26319           }
26320
26321           if ( row.treeLevel <= currentLevel ){
26322             // pop any levels that aren't parents of this level, formatting the aggregation at the same time
26323             while ( row.treeLevel <= currentLevel ){
26324               var lastParent = parents.pop();
26325               service.finaliseAggregations( lastParent );
26326               currentLevel--;
26327             }
26328
26329             // reset our current state based on the new parent, set to expanded if this is a level 0 node
26330             if ( parents.length > 0 ){
26331               currentState = service.setCurrentState(parents);
26332             } else {
26333               currentState = uiGridTreeBaseConstants.EXPANDED;
26334             }
26335           }
26336
26337           // aggregate if this is a leaf node
26338           if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible  ){
26339             service.aggregate( grid, row, parents );
26340           }
26341
26342           // add this node to the tree
26343           service.addOrUseNode(grid, row, parents, aggregations);
26344
26345           if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
26346             parents.push(row);
26347             currentLevel++;
26348             currentState = service.setCurrentState(parents);
26349           }
26350
26351           // update the tree number of levels, so we can set header width if we need to
26352           if ( grid.treeBase.numberLevels < row.treeLevel + 1){
26353             grid.treeBase.numberLevels = row.treeLevel + 1;
26354           }
26355         };
26356
26357         renderableRows.forEach( createNode );
26358
26359         // finalise remaining aggregations
26360         while ( parents.length > 0 ){
26361           var lastParent = parents.pop();
26362           service.finaliseAggregations( lastParent );
26363         }
26364
26365         return grid.treeBase.tree;
26366       },
26367
26368
26369       /**
26370        * @ngdoc function
26371        * @name addOrUseNode
26372        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26373        * @description Creates a tree node for this row.  If this row already has a treeNode
26374        * recorded against it, preserves the state, but otherwise overwrites the data.
26375        *
26376        * @param {grid} grid the grid we're operating on
26377        * @param {gridRow} row the row we want to set
26378        * @param {array} parents an array of the parents this row should have
26379        * @param {array} aggregationBase empty aggregation information
26380        * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
26381        * grid.treeBase.tree
26382        */
26383       addOrUseNode: function( grid, row, parents, aggregationBase ){
26384         var newAggregations = [];
26385         aggregationBase.forEach( function(aggregation){
26386           newAggregations.push(service.buildAggregationObject(aggregation.col));
26387         });
26388
26389         var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
26390         if ( row.treeNode ){
26391           newNode.state = row.treeNode.state;
26392         }
26393         if ( parents.length > 0 ){
26394           newNode.parentRow = parents[parents.length - 1];
26395         }
26396         row.treeNode = newNode;
26397
26398         if ( parents.length === 0 ){
26399           grid.treeBase.tree.push( newNode );
26400         } else {
26401           parents[parents.length - 1].treeNode.children.push( newNode );
26402         }
26403       },
26404
26405
26406       /**
26407        * @ngdoc function
26408        * @name setCurrentState
26409        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26410        * @description Looks at the parents array to determine our current state.
26411        * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
26412        * expanded.
26413        *
26414        * @param {array} parents an array of the parents this row should have
26415        * @returns {string} the state we should be setting to any nodes we see
26416        */
26417       setCurrentState: function( parents ){
26418         var currentState = uiGridTreeBaseConstants.EXPANDED;
26419         parents.forEach( function(parent){
26420           if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
26421             currentState = uiGridTreeBaseConstants.COLLAPSED;
26422           }
26423         });
26424         return currentState;
26425       },
26426
26427
26428       /**
26429        * @ngdoc function
26430        * @name sortTree
26431        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26432        * @description Performs a recursive sort on the tree nodes, sorting the
26433        * children of each node and putting them back into the children array.
26434        *
26435        * Before doing this it turns back on all the sortIgnore - things that were previously
26436        * ignored we process now.  Since we're sorting within the nodes, presumably anything
26437        * that was already sorted is how we derived the nodes, we can keep those sorts too.
26438        *
26439        * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
26440        * nodes
26441        *
26442        * @param {Grid} grid the grid to get the aggregation information from
26443        * @returns {array} the aggregation information
26444        */
26445       sortTree: function( grid ){
26446         grid.columns.forEach( function( column ) {
26447           if ( column.sort && column.sort.ignoreSort ){
26448             delete column.sort.ignoreSort;
26449           }
26450         });
26451
26452         grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
26453       },
26454
26455       sortInternal: function( grid, treeList ){
26456         var rows = treeList.map( function( node ){
26457           return node.row;
26458         });
26459
26460         rows = rowSorter.sort( grid, rows, grid.columns );
26461
26462         var treeNodes = rows.map( function( row ){
26463           return row.treeNode;
26464         });
26465
26466         treeNodes.forEach( function( node ){
26467           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
26468             node.children = service.sortInternal( grid, node.children );
26469           }
26470         });
26471
26472         return treeNodes;
26473       },
26474
26475       /**
26476        * @ngdoc function
26477        * @name fixFilter
26478        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26479        * @description After filtering has run, we need to go back through the tree
26480        * and make sure the parent rows are always visible if any of the child rows
26481        * are visible (filtering may make a child visible, but the parent may not
26482        * match the filter criteria)
26483        *
26484        * This has a risk of being computationally expensive, we do it by walking
26485        * the tree and remembering whether there are any invisible nodes on the
26486        * way down.
26487        *
26488        * @param {Grid} grid the grid to fix filters on
26489        */
26490       fixFilter: function( grid ){
26491         var parentsVisible;
26492
26493         grid.treeBase.tree.forEach( function( node ){
26494           if ( node.children && node.children.length > 0 ){
26495             parentsVisible = node.row.visible;
26496             service.fixFilterInternal( node.children, parentsVisible );
26497           }
26498         });
26499       },
26500
26501       fixFilterInternal: function( nodes, parentsVisible) {
26502         nodes.forEach( function( node ){
26503           if ( node.row.visible && !parentsVisible ){
26504             service.setParentsVisible( node );
26505             parentsVisible = true;
26506           }
26507
26508           if ( node.children && node.children.length > 0 ){
26509             if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
26510               parentsVisible = true;
26511             }
26512           }
26513         });
26514
26515         return parentsVisible;
26516       },
26517
26518       setParentsVisible: function( node ){
26519         while ( node.parentRow ){
26520           node.parentRow.visible = true;
26521           node = node.parentRow.treeNode;
26522         }
26523       },
26524
26525       /**
26526        * @ngdoc function
26527        * @name buildAggregationObject
26528        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26529        * @description Build the object which is stored on the column for holding meta-data about the aggregation.
26530        * This method should only be called with columns which have an aggregation.
26531        *
26532        * @param {Column} the column which this object relates to
26533        * @returns {object} {col: Column object, label: string, type: string (optional)}
26534        */
26535       buildAggregationObject: function( column ){
26536         var newAggregation = { col: column };
26537
26538         if ( column.treeAggregation && column.treeAggregation.type ){
26539           newAggregation.type = column.treeAggregation.type;
26540         }
26541
26542         if ( column.treeAggregation && column.treeAggregation.label ){
26543           newAggregation.label = column.treeAggregation.label;
26544         }
26545
26546         return newAggregation;
26547       },
26548
26549       /**
26550        * @ngdoc function
26551        * @name getAggregations
26552        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26553        * @description Looks through the grid columns to find those with aggregations,
26554        * and collates the aggregation information into an array, returns that array
26555        *
26556        * @param {Grid} grid the grid to get the aggregation information from
26557        * @returns {array} the aggregation information
26558        */
26559       getAggregations: function( grid ){
26560         var aggregateArray = [];
26561
26562         grid.columns.forEach( function(column){
26563           if ( typeof(column.treeAggregationFn) !== 'undefined' ){
26564             aggregateArray.push( service.buildAggregationObject(column) );
26565
26566             if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
26567               // Add aggregation object for footer
26568               column.treeFooterAggregation = service.buildAggregationObject(column);
26569               column.aggregationType = service.treeFooterAggregationType;
26570             }
26571           }
26572         });
26573         return aggregateArray;
26574       },
26575
26576
26577       /**
26578        * @ngdoc function
26579        * @name aggregate
26580        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26581        * @description Accumulate the data from this row onto the aggregations for each parent
26582        *
26583        * Iterate over the parents, then iterate over the aggregations for each of those parents,
26584        * and perform the aggregation for each individual aggregation
26585        *
26586        * @param {Grid} grid grid object
26587        * @param {GridRow} row the row we want to set grouping visibility on
26588        * @param {array} parents the parents that we would want to aggregate onto
26589        */
26590       aggregate: function( grid, row, parents ){
26591         if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
26592           row.treeNode.aggregations.forEach(function(aggregation){
26593             // Calculate aggregations for footer even if there are no grouped rows
26594             if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
26595               var fieldValue = grid.getCellValue(row, aggregation.col);
26596               var numValue = Number(fieldValue);
26597               aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26598             }
26599           });
26600         }
26601
26602         parents.forEach( function( parent, index ){
26603           if ( parent.treeNode.aggregations ){
26604             parent.treeNode.aggregations.forEach( function( aggregation ){
26605               var fieldValue = grid.getCellValue(row, aggregation.col);
26606               var numValue = Number(fieldValue);
26607               aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
26608
26609               if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
26610                 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26611               }
26612             });
26613           }
26614         });
26615       },
26616
26617
26618       // Aggregation routines - no doco needed as self evident
26619       nativeAggregations: function() {
26620         var nativeAggregations = {
26621           count: {
26622             label: i18nService.get().aggregation.count,
26623             menuTitle: i18nService.get().grouping.aggregate_count,
26624             aggregationFn: function (aggregation, fieldValue, numValue) {
26625               if (typeof(aggregation.value) === 'undefined') {
26626                 aggregation.value = 1;
26627               } else {
26628                 aggregation.value++;
26629               }
26630             }
26631           },
26632
26633           sum: {
26634             label: i18nService.get().aggregation.sum,
26635             menuTitle: i18nService.get().grouping.aggregate_sum,
26636             aggregationFn: function( aggregation, fieldValue, numValue ) {
26637               if (!isNaN(numValue)) {
26638                 if (typeof(aggregation.value) === 'undefined') {
26639                   aggregation.value = numValue;
26640                 } else {
26641                   aggregation.value += numValue;
26642                 }
26643               }
26644             }
26645           },
26646
26647           min: {
26648             label: i18nService.get().aggregation.min,
26649             menuTitle: i18nService.get().grouping.aggregate_min,
26650             aggregationFn: function( aggregation, fieldValue, numValue ) {
26651               if (typeof(aggregation.value) === 'undefined') {
26652                 aggregation.value = fieldValue;
26653               } else {
26654                 if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
26655                   aggregation.value = fieldValue;
26656                 }
26657               }
26658             }
26659           },
26660
26661           max: {
26662             label: i18nService.get().aggregation.max,
26663             menuTitle: i18nService.get().grouping.aggregate_max,
26664             aggregationFn: function( aggregation, fieldValue, numValue ){
26665               if ( typeof(aggregation.value) === 'undefined' ){
26666                 aggregation.value = fieldValue;
26667               } else {
26668                 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
26669                   aggregation.value = fieldValue;
26670                 }
26671               }
26672             }
26673           },
26674
26675           avg: {
26676             label: i18nService.get().aggregation.avg,
26677             menuTitle: i18nService.get().grouping.aggregate_avg,
26678             aggregationFn: function( aggregation, fieldValue, numValue ){
26679               if ( typeof(aggregation.count) === 'undefined' ){
26680                 aggregation.count = 1;
26681               } else {
26682                 aggregation.count++;
26683               }
26684
26685               if ( isNaN(numValue) ){
26686                 return;
26687               }
26688
26689               if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
26690                 aggregation.value = numValue;
26691                 aggregation.sum = numValue;
26692               } else {
26693                 aggregation.sum += numValue;
26694                 aggregation.value = aggregation.sum / aggregation.count;
26695               }
26696             }
26697           }
26698         };
26699         return nativeAggregations;
26700       },
26701
26702       /**
26703        * @ngdoc function
26704        * @name finaliseAggregation
26705        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26706        * @description Helper function used to finalize aggregation nodes and footer cells
26707        *
26708        * @param {gridRow} row the parent we're finalising
26709        * @param {aggregation} the aggregation object manipulated by the aggregationFn
26710        */
26711       finaliseAggregation: function(row, aggregation){
26712         if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
26713           angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
26714         }
26715
26716         if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
26717           aggregation.col.treeAggregationFinalizerFn( aggregation );
26718         }
26719         if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
26720           aggregation.col.customTreeAggregationFinalizerFn( aggregation );
26721         }
26722         if ( typeof(aggregation.rendered) === 'undefined' ){
26723           aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
26724         }
26725       },
26726
26727       /**
26728        * @ngdoc function
26729        * @name finaliseAggregations
26730        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26731        * @description Format the data from the aggregation into the rendered text
26732        * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
26733        *
26734        * As part of this we call any formatting callback routines we've been provided.
26735        *
26736        * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
26737        * set on the column - we don't overwrite any information that's already there, we append
26738        * to it so that grouping can have set the groupVal beforehand without us overwriting it.
26739        *
26740        * We need to copy the data from the row.entity first before we finalise the aggregation,
26741        * we need that information for the finaliserFn
26742        *
26743        * @param {gridRow} row the parent we're finalising
26744        */
26745       finaliseAggregations: function( row ){
26746         if ( typeof(row.treeNode.aggregations) === 'undefined' ){
26747           return;
26748         }
26749
26750         row.treeNode.aggregations.forEach( function( aggregation ) {
26751           service.finaliseAggregation(row, aggregation);
26752
26753           if ( aggregation.col.treeAggregationUpdateEntity ){
26754             var aggregationCopy = {};
26755             angular.forEach( aggregation, function( value, key ){
26756               if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
26757                 aggregationCopy[key] = value;
26758               }
26759             });
26760
26761             row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
26762           }
26763         });
26764       },
26765
26766       /**
26767        * @ngdoc function
26768        * @name treeFooterAggregationType
26769        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26770        * @description Uses the tree aggregation functions and finalizers to set the
26771        * column footer aggregations.
26772        *
26773        * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
26774        * @param {gridColumn} the column we are finalizing
26775        */
26776       treeFooterAggregationType: function( rows, column ) {
26777         service.finaliseAggregation(undefined, column.treeFooterAggregation);
26778         if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
26779           // The was apparently no aggregation performed (perhaps this is a grouped column
26780           return '';
26781         }
26782         return column.treeFooterAggregation.rendered;
26783       }
26784     };
26785
26786     return service;
26787
26788   }]);
26789
26790
26791   /**
26792    *  @ngdoc directive
26793    *  @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
26794    *  @element div
26795    *
26796    *  @description Provides the expand/collapse button on rows
26797    */
26798   module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
26799   function ($templateCache, uiGridTreeBaseService) {
26800     return {
26801       replace: true,
26802       restrict: 'E',
26803       template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
26804       scope: true,
26805       require: '^uiGrid',
26806       link: function($scope, $elm, $attrs, uiGridCtrl) {
26807         var self = uiGridCtrl.grid;
26808         $scope.treeButtonClick = function(row, evt) {
26809           uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
26810         };
26811       }
26812     };
26813   }]);
26814
26815
26816   /**
26817    *  @ngdoc directive
26818    *  @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
26819    *  @element div
26820    *
26821    *  @description Provides the expand/collapse all button
26822    */
26823   module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
26824   function ($templateCache, uiGridTreeBaseService) {
26825     return {
26826       replace: true,
26827       restrict: 'E',
26828       template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
26829       scope: false,
26830       link: function($scope, $elm, $attrs, uiGridCtrl) {
26831         var self = $scope.col.grid;
26832
26833         $scope.headerButtonClick = function(row, evt) {
26834           if ( self.treeBase.expandAll ){
26835             uiGridTreeBaseService.collapseAllRows(self, evt);
26836           } else {
26837             uiGridTreeBaseService.expandAllRows(self, evt);
26838           }
26839         };
26840       }
26841     };
26842   }]);
26843
26844
26845   /**
26846    *  @ngdoc directive
26847    *  @name ui.grid.treeBase.directive:uiGridViewport
26848    *  @element div
26849    *
26850    *  @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
26851    */
26852   module.directive('uiGridViewport',
26853   ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
26854     function ($compile, uiGridConstants, gridUtil, $parse) {
26855       return {
26856         priority: -200, // run after default  directive
26857         scope: false,
26858         compile: function ($elm, $attrs) {
26859           var rowRepeatDiv = angular.element($elm.children().children()[0]);
26860
26861           var existingNgClass = rowRepeatDiv.attr("ng-class");
26862           var newNgClass = '';
26863           if ( existingNgClass ) {
26864             newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
26865           } else {
26866             newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
26867           }
26868           rowRepeatDiv.attr("ng-class", newNgClass);
26869
26870           return {
26871             pre: function ($scope, $elm, $attrs, controllers) {
26872
26873             },
26874             post: function ($scope, $elm, $attrs, controllers) {
26875             }
26876           };
26877         }
26878       };
26879     }]);
26880 })();
26881
26882 (function () {
26883   'use strict';
26884
26885   /**
26886    * @ngdoc overview
26887    * @name ui.grid.treeView
26888    * @description
26889    *
26890    * # ui.grid.treeView
26891    *
26892    * <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>
26893    *
26894    * This module provides a tree view of the data that it is provided, with nodes in that
26895    * tree and leaves.  Unlike grouping, the tree is an inherent property of the data and must
26896    * be provided with your data array.
26897    *
26898    * Design information:
26899    * -------------------
26900    *
26901    * TreeView uses treeBase for the underlying functionality, and is a very thin wrapper around
26902    * that logic.  Most of the design information has now moved to treebase.
26903    * <br/>
26904    * <br/>
26905    *
26906    * <div doc-module-components="ui.grid.treeView"></div>
26907    */
26908
26909   var module = angular.module('ui.grid.treeView', ['ui.grid', 'ui.grid.treeBase']);
26910
26911   /**
26912    *  @ngdoc object
26913    *  @name ui.grid.treeView.constant:uiGridTreeViewConstants
26914    *
26915    *  @description constants available in treeView module, this includes
26916    *  all the constants declared in the treeBase module (these are manually copied
26917    *  as there isn't an easy way to include constants in another constants file, and
26918    *  we don't want to make users include treeBase)
26919    *
26920    */
26921   module.constant('uiGridTreeViewConstants', {
26922     featureName: "treeView",
26923     rowHeaderColName: 'treeBaseRowHeaderCol',
26924     EXPANDED: 'expanded',
26925     COLLAPSED: 'collapsed',
26926     aggregation: {
26927       COUNT: 'count',
26928       SUM: 'sum',
26929       MAX: 'max',
26930       MIN: 'min',
26931       AVG: 'avg'
26932     }
26933   });
26934
26935   /**
26936    *  @ngdoc service
26937    *  @name ui.grid.treeView.service:uiGridTreeViewService
26938    *
26939    *  @description Services for treeView features
26940    */
26941   module.service('uiGridTreeViewService', ['$q', 'uiGridTreeViewConstants', 'uiGridTreeBaseConstants', 'uiGridTreeBaseService', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants',
26942   function ($q, uiGridTreeViewConstants, uiGridTreeBaseConstants, uiGridTreeBaseService, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants) {
26943
26944     var service = {
26945
26946       initializeGrid: function (grid, $scope) {
26947         uiGridTreeBaseService.initializeGrid( grid, $scope );
26948
26949         /**
26950          *  @ngdoc object
26951          *  @name ui.grid.treeView.grid:treeView
26952          *
26953          *  @description Grid properties and functions added for treeView
26954          */
26955         grid.treeView = {};
26956
26957         grid.registerRowsProcessor(service.adjustSorting, 60);
26958
26959         /**
26960          *  @ngdoc object
26961          *  @name ui.grid.treeView.api:PublicApi
26962          *
26963          *  @description Public Api for treeView feature
26964          */
26965         var publicApi = {
26966           events: {
26967             treeView: {
26968             }
26969           },
26970           methods: {
26971             treeView: {
26972             }
26973           }
26974         };
26975
26976         grid.api.registerEventsFromObject(publicApi.events);
26977
26978         grid.api.registerMethodsFromObject(publicApi.methods);
26979
26980       },
26981
26982       defaultGridOptions: function (gridOptions) {
26983         //default option to true unless it was explicitly set to false
26984         /**
26985          *  @ngdoc object
26986          *  @name ui.grid.treeView.api:GridOptions
26987          *
26988          *  @description GridOptions for treeView feature, these are available to be
26989          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
26990          *
26991          *  Many tree options are set on treeBase, make sure to look at that feature in
26992          *  conjunction with these options.
26993          */
26994
26995         /**
26996          *  @ngdoc object
26997          *  @name enableTreeView
26998          *  @propertyOf  ui.grid.treeView.api:GridOptions
26999          *  @description Enable row tree view for entire grid.
27000          *  <br/>Defaults to true
27001          */
27002         gridOptions.enableTreeView = gridOptions.enableTreeView !== false;
27003
27004       },
27005
27006
27007       /**
27008        * @ngdoc function
27009        * @name adjustSorting
27010        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27011        * @description Trees cannot be sorted the same as flat lists of rows -
27012        * trees are sorted recursively within each level - so the children of each
27013        * node are sorted, but not the full set of rows.
27014        *
27015        * To achieve this, we suppress the normal sorting by setting ignoreSort on
27016        * each of the sort columns.  When the treeBase rowsProcessor runs it will then
27017        * unignore these, and will perform a recursive sort against the tree that it builds.
27018        *
27019        * @param {array} renderableRows the rows that we need to pass on through
27020        * @returns {array} renderableRows that we passed on through
27021        */
27022       adjustSorting: function( renderableRows ) {
27023         var grid = this;
27024
27025         grid.columns.forEach( function( column ){
27026           if ( column.sort ){
27027             column.sort.ignoreSort = true;
27028           }
27029         });
27030
27031         return renderableRows;
27032       }
27033
27034     };
27035
27036     return service;
27037
27038   }]);
27039
27040   /**
27041    *  @ngdoc directive
27042    *  @name ui.grid.treeView.directive:uiGridTreeView
27043    *  @element div
27044    *  @restrict A
27045    *
27046    *  @description Adds treeView features to grid
27047    *
27048    *  @example
27049    <example module="app">
27050    <file name="app.js">
27051    var app = angular.module('app', ['ui.grid', 'ui.grid.treeView']);
27052
27053    app.controller('MainCtrl', ['$scope', function ($scope) {
27054       $scope.data = [
27055         { name: 'Bob', title: 'CEO' },
27056             { name: 'Frank', title: 'Lowly Developer' }
27057       ];
27058
27059       $scope.columnDefs = [
27060         {name: 'name', enableCellEdit: true},
27061         {name: 'title', enableCellEdit: true}
27062       ];
27063
27064       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
27065     }]);
27066    </file>
27067    <file name="index.html">
27068    <div ng-controller="MainCtrl">
27069    <div ui-grid="gridOptions" ui-grid-tree-view></div>
27070    </div>
27071    </file>
27072    </example>
27073    */
27074   module.directive('uiGridTreeView', ['uiGridTreeViewConstants', 'uiGridTreeViewService', '$templateCache',
27075   function (uiGridTreeViewConstants, uiGridTreeViewService, $templateCache) {
27076     return {
27077       replace: true,
27078       priority: 0,
27079       require: '^uiGrid',
27080       scope: false,
27081       compile: function () {
27082         return {
27083           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
27084             if (uiGridCtrl.grid.options.enableTreeView !== false){
27085               uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
27086             }
27087           },
27088           post: function ($scope, $elm, $attrs, uiGridCtrl) {
27089
27090           }
27091         };
27092       }
27093     };
27094   }]);
27095 })();
27096
27097 (function () {
27098   'use strict';
27099   
27100   /**
27101    * @ngdoc overview
27102    * @name ui.grid.validate
27103    * @description
27104    *
27105    * # ui.grid.validate
27106    *
27107    * <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>
27108    *
27109    * This module provides the ability to validate cells upon change.
27110    *
27111    * Design information:
27112    * -------------------
27113    *
27114    * Validation is not based on angularjs validation, since it would work only when editing the field.
27115    * 
27116    * Instead it adds custom properties to any field considered as invalid.
27117    *
27118    * <br/>
27119    * <br/>
27120    *
27121    * <div doc-module-components="ui.grid.expandable"></div>
27122    */
27123
27124   var module = angular.module('ui.grid.validate', ['ui.grid']);
27125   
27126   
27127   /**
27128    *  @ngdoc service
27129    *  @name ui.grid.validate.service:uiGridValidateService
27130    *
27131    *  @description Services for validation features
27132    */
27133   module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) {
27134
27135     var service = {
27136       
27137       /**
27138        *  @ngdoc object
27139        *  @name validatorFactories
27140        *  @propertyOf ui.grid.validate.service:uiGridValidateService
27141        *  @description object containing all the factories used to validate data.<br/>
27142        *  These factories will be in the form <br/>
27143        *  ```
27144        *  {
27145        *    validatorFactory: function(argument) {
27146        *                        return function(newValue, oldValue, rowEntity, colDef) {
27147        *                          return true || false || promise
27148        *                        }
27149        *                      },
27150        *    messageFunction: function(argument) {
27151        *                       return string
27152        *                     }
27153        *  }
27154        *  ```
27155        *
27156        * Promises should return true or false as result according to the result of validation.
27157        */
27158       validatorFactories: {},
27159
27160       
27161       /**
27162        * @ngdoc service
27163        * @name setExternalFactoryFunction
27164        * @methodOf ui.grid.validate.service:uiGridValidateService
27165        * @description Adds a way to retrieve validators from an external service
27166        * <p>Validators from this external service have a higher priority than default
27167        * ones
27168        * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a
27169        * validator factory and that returns an object with the same properties as 
27170        * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories}
27171        */
27172       setExternalFactoryFunction: function(externalFactoryFunction) {
27173         service.externalFactoryFunction = externalFactoryFunction;
27174       },
27175       
27176       /**
27177        * @ngdoc service
27178        * @name clearExternalFactory
27179        * @methodOf ui.grid.validate.service:uiGridValidateService
27180        * @description Removes any link to external factory from this service
27181        */
27182       clearExternalFactory: function() {
27183         delete service.externalFactoryFunction;
27184       },
27185
27186       /**
27187        * @ngdoc service
27188        * @name getValidatorFromExternalFactory
27189        * @methodOf ui.grid.validate.service:uiGridValidateService
27190        * @description Retrieves a validator by executing a validatorFactory
27191        * stored in an external service.
27192        * @param {string} name the name of the validator to retrieve
27193        * @param {object} argument an argument to pass to the validator factory
27194        */
27195       getValidatorFromExternalFactory: function(name, argument) {
27196         return service.externalFactoryFunction(name, argument).validatorFactory(argument);
27197       },
27198       
27199       /**
27200        * @ngdoc service
27201        * @name getMessageFromExternalFactory
27202        * @methodOf ui.grid.validate.service:uiGridValidateService
27203        * @description Retrieves a message stored in an external service.
27204        * @param {string} name the name of the validator
27205        * @param {object} argument an argument to pass to the message function
27206        */
27207       getMessageFromExternalFactory: function(name, argument) {
27208         return service.externalFactoryFunction(name, argument).messageFunction(argument);
27209       },
27210       
27211       /**
27212        * @ngdoc service
27213        * @name setValidator
27214        * @methodOf ui.grid.validate.service:uiGridValidateService
27215        * @description  Adds a new validator to the service
27216        * @param {string} name the name of the validator, must be unique
27217        * @param {function} validatorFactory a factory that return a validatorFunction
27218        * @param {function} messageFunction a function that return the error message
27219        */
27220       setValidator: function(name, validatorFactory, messageFunction) {
27221         service.validatorFactories[name] = {
27222           validatorFactory: validatorFactory,
27223           messageFunction: messageFunction
27224         };
27225       },
27226
27227       /**
27228        * @ngdoc service
27229        * @name getValidator
27230        * @methodOf ui.grid.validate.service:uiGridValidateService
27231        * @description Returns a validator registered to the service
27232        * or retrieved from the external factory
27233        * @param {string} name the name of the validator to retrieve
27234        * @param {object} argument an argument to pass to the validator factory
27235        * @returns {object} the validator function
27236        */
27237       getValidator: function(name, argument) {
27238         if (service.externalFactoryFunction) {
27239           var validator = service.getValidatorFromExternalFactory(name, argument);
27240           if (validator) {
27241             return validator;
27242           }
27243         }
27244         if (!service.validatorFactories[name]) {
27245           throw ("Invalid validator name: " + name);
27246         }
27247         return service.validatorFactories[name].validatorFactory(argument);
27248       },
27249
27250       /**
27251        * @ngdoc service
27252        * @name getMessage
27253        * @methodOf ui.grid.validate.service:uiGridValidateService
27254        * @description Returns the error message related to the validator 
27255        * @param {string} name the name of the validator
27256        * @param {object} argument an argument to pass to the message function
27257        * @returns {string} the error message related to the validator
27258        */
27259       getMessage: function(name, argument) {
27260         if (service.externalFactoryFunction) {
27261           var message = service.getMessageFromExternalFactory(name, argument);
27262           if (message) {
27263             return message;
27264           }
27265         }
27266         return service.validatorFactories[name].messageFunction(argument);
27267       },
27268
27269       /**
27270        * @ngdoc service
27271        * @name isInvalid
27272        * @methodOf ui.grid.validate.service:uiGridValidateService
27273        * @description Returns true if the cell (identified by rowEntity, colDef) is invalid 
27274        * @param {object} rowEntity the row entity of the cell
27275        * @param {object} colDef the colDef of the cell
27276        * @returns {boolean} true if the cell is invalid
27277        */
27278       isInvalid: function (rowEntity, colDef) {
27279         return rowEntity['$$invalid'+colDef.name];
27280       },
27281
27282       /**
27283        * @ngdoc service
27284        * @name setInvalid
27285        * @methodOf ui.grid.validate.service:uiGridValidateService
27286        * @description Makes the cell invalid by adding the proper field to the entity
27287        * @param {object} rowEntity the row entity of the cell
27288        * @param {object} colDef the colDef of the cell
27289        */
27290       setInvalid: function (rowEntity, colDef) {
27291         rowEntity['$$invalid'+colDef.name] = true;
27292       },
27293     
27294       /**
27295        * @ngdoc service
27296        * @name setValid
27297        * @methodOf ui.grid.validate.service:uiGridValidateService
27298        * @description Makes the cell valid by removing the proper error field from the entity
27299        * @param {object} rowEntity the row entity of the cell
27300        * @param {object} colDef the colDef of the cell
27301        */
27302       setValid: function (rowEntity, colDef) {
27303         delete rowEntity['$$invalid'+colDef.name];
27304       },
27305
27306       /**
27307        * @ngdoc service
27308        * @name setError
27309        * @methodOf ui.grid.validate.service:uiGridValidateService
27310        * @description Adds the proper error to the entity errors field
27311        * @param {object} rowEntity the row entity of the cell
27312        * @param {object} colDef the colDef of the cell
27313        * @param {string} validatorName the name of the validator that is failing
27314        */
27315       setError: function(rowEntity, colDef, validatorName) {
27316         if (!rowEntity['$$errors'+colDef.name]) {
27317           rowEntity['$$errors'+colDef.name] = {};
27318         }
27319         rowEntity['$$errors'+colDef.name][validatorName] = true;
27320       },
27321
27322       /**
27323        * @ngdoc service
27324        * @name clearError
27325        * @methodOf ui.grid.validate.service:uiGridValidateService
27326        * @description Removes the proper error from the entity errors field
27327        * @param {object} rowEntity the row entity of the cell
27328        * @param {object} colDef the colDef of the cell
27329        * @param {string} validatorName the name of the validator that is failing
27330        */
27331       clearError: function(rowEntity, colDef, validatorName) {
27332         if (!rowEntity['$$errors'+colDef.name]) {
27333           return;
27334         }
27335         if (validatorName in rowEntity['$$errors'+colDef.name]) {
27336             delete rowEntity['$$errors'+colDef.name][validatorName];
27337         }
27338       },
27339       
27340       /**
27341        * @ngdoc function
27342        * @name getErrorMessages
27343        * @methodOf ui.grid.validate.service:uiGridValidateService
27344        * @description returns an array of i18n-ed error messages.
27345        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27346        * @param {object} colDef the column whose errors we are looking for
27347        * @returns {array} An array of strings containing all the error messages for the cell
27348        */
27349       getErrorMessages: function(rowEntity, colDef) {
27350         var errors = [];
27351
27352         if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) {
27353           return errors;
27354         }
27355
27356         Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) {
27357           errors.push(service.getMessage(validatorName, colDef.validators[validatorName]));
27358         });
27359         
27360         return errors;
27361       },
27362       
27363       /**
27364        * @ngdoc function
27365        * @name getFormattedErrors
27366        * @methodOf  ui.grid.validate.service:uiGridValidateService
27367        * @description returns the error i18n-ed and formatted in html to be shown inside the page.
27368        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27369        * @param {object} colDef the column whose errors we are looking for
27370        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27371        * message inside the page (i.e. inside a div)
27372        */
27373       getFormattedErrors: function(rowEntity, colDef) {
27374
27375         var msgString = "";
27376
27377         var errors = service.getErrorMessages(rowEntity, colDef);
27378         
27379         if (!errors.length) {
27380           return;
27381         }
27382         
27383         errors.forEach(function(errorMsg) {
27384           msgString += errorMsg + "<br/>";
27385         });
27386
27387         return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString );
27388       },
27389
27390       /**
27391        * @ngdoc function
27392        * @name getTitleFormattedErrors
27393        * @methodOf ui.grid.validate.service:uiGridValidateService
27394        * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
27395        * title attribute.
27396        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27397        * @param {object} colDef the column whose errors we are looking for
27398        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27399        * message inside an html title attribute
27400        */
27401       getTitleFormattedErrors: function(rowEntity, colDef) {
27402
27403         var newLine = "\n";
27404
27405         var msgString = "";
27406         
27407         var errors = service.getErrorMessages(rowEntity, colDef);
27408         
27409         if (!errors.length) {
27410           return;
27411         }
27412         
27413         errors.forEach(function(errorMsg) {
27414           msgString += errorMsg + newLine;
27415         });
27416
27417         return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString);
27418       },
27419
27420       /**
27421        * @ngdoc function
27422        * @name getTitleFormattedErrors
27423        * @methodOf ui.grid.validate.service:uiGridValidateService
27424        * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors
27425        * @param {object} rowEntity the row entity of the cell we want to run the validators on
27426        * @param {object} colDef the column definition of the cell we want to run the validators on
27427        * @param {object} newValue the value the user just entered
27428        * @param {object} oldValue the value the field had before
27429        */
27430       runValidators: function(rowEntity, colDef, newValue, oldValue, grid) {
27431         
27432         if (newValue === oldValue) {
27433           // If the value has not changed we perform no validation
27434           return;
27435         }
27436         
27437         if (typeof(colDef.name) === 'undefined' || !colDef.name) {
27438           throw new Error('colDef.name is required to perform validation');
27439         }
27440         
27441         service.setValid(rowEntity, colDef);
27442         
27443         var validateClosureFactory = function(rowEntity, colDef, validatorName) {
27444           return function(value) {
27445             if (!value) {
27446               service.setInvalid(rowEntity, colDef);
27447               service.setError(rowEntity, colDef, validatorName);
27448               if (grid) {
27449                 grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue);
27450               }
27451             }
27452           };
27453         };
27454
27455         for (var validatorName in colDef.validators) {
27456           service.clearError(rowEntity, colDef, validatorName);
27457           var msg;
27458           var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]);
27459           // We pass the arguments as oldValue, newValue so they are in the same order 
27460           // as ng-model validators (modelValue, viewValue)
27461           $q.when(validatorFunction(oldValue, newValue, rowEntity, colDef))
27462             .then(validateClosureFactory(rowEntity, colDef, validatorName)
27463           );
27464         }
27465       },
27466
27467       /**
27468        * @ngdoc function
27469        * @name createDefaultValidators
27470        * @methodOf ui.grid.validate.service:uiGridValidateService
27471        * @description adds the basic validators to the list of service validators
27472        */
27473       createDefaultValidators: function() {
27474         service.setValidator('minLength',
27475                              function (argument) {
27476                                return function (oldValue, newValue, rowEntity, colDef) {
27477                                  if (newValue === undefined || newValue === null || newValue === '') {
27478                                    return true;
27479                                  }
27480                                  return newValue.length >= argument;
27481                                };
27482                              },
27483                                function(argument) {
27484                                  return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument);
27485                                });
27486         
27487         service.setValidator('maxLength',
27488                              function (argument) {
27489                                return function (oldValue, newValue, rowEntity, colDef) {
27490                                  if (newValue === undefined || newValue === null || newValue === '') {
27491                                    return true;
27492                                  }
27493                                  return newValue.length <= argument;
27494                                };
27495                              },
27496                              function(threshold) {
27497                                return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold);
27498                              });
27499         
27500         service.setValidator('required',
27501                              function (argument) {
27502                                return function (oldValue, newValue, rowEntity, colDef) {
27503                                  if (argument) {
27504                                    return !(newValue === undefined || newValue === null || newValue === '');
27505                                  }
27506                                  return true;
27507                                };
27508                              },
27509                              function(argument) {
27510                                return i18nService.getSafeText('validate.required');
27511                              });
27512       },
27513
27514       initializeGrid: function (scope, grid) {
27515         grid.validate = {
27516         
27517           isInvalid: service.isInvalid,
27518
27519           getFormattedErrors: service.getFormattedErrors,
27520          
27521           getTitleFormattedErrors: service.getTitleFormattedErrors,
27522
27523           runValidators: service.runValidators
27524         };
27525         
27526         /**
27527          *  @ngdoc object
27528          *  @name ui.grid.validate.api:PublicApi
27529          *
27530          *  @description Public Api for validation feature
27531          */
27532         var publicApi = {
27533           events: {
27534             validate: {
27535               /**
27536                * @ngdoc event
27537                * @name validationFailed
27538                * @eventOf  ui.grid.validate.api:PublicApi
27539                * @description raised when one or more failure happened during validation 
27540                * <pre>
27541                *      gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...})
27542                * </pre>
27543                * @param {object} rowEntity the options.data element whose validation failed
27544                * @param {object} colDef the column whose validation failed
27545                * @param {object} newValue new value
27546                * @param {object} oldValue old value
27547                */
27548               validationFailed: function (rowEntity, colDef, newValue, oldValue) {
27549               }
27550             }
27551           },
27552           methods: {
27553             validate: {
27554               /**
27555                * @ngdoc function
27556                * @name isInvalid
27557                * @methodOf  ui.grid.validate.api:PublicApi
27558                * @description checks if a cell (identified by rowEntity, colDef) is invalid
27559                * @param {object} rowEntity gridOptions.data[] array instance we want to check
27560                * @param {object} colDef the column whose errors we want to check
27561                * @returns {boolean} true if the cell value is not valid
27562                */
27563               isInvalid: function(rowEntity, colDef) {
27564                 return grid.validate.isInvalid(rowEntity, colDef);
27565               },
27566               /**
27567                * @ngdoc function
27568                * @name getErrorMessages
27569                * @methodOf  ui.grid.validate.api:PublicApi
27570                * @description returns an array of i18n-ed error messages.
27571                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27572                * @param {object} colDef the column whose errors we are looking for
27573                * @returns {array} An array of strings containing all the error messages for the cell
27574                */
27575               getErrorMessages: function (rowEntity, colDef) {
27576                 return grid.validate.getErrorMessages(rowEntity, colDef);
27577               },
27578               /**
27579                * @ngdoc function
27580                * @name getFormattedErrors
27581                * @methodOf  ui.grid.validate.api:PublicApi
27582                * @description returns the error i18n-ed and formatted in html to be shown inside the page.
27583                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27584                * @param {object} colDef the column whose errors we are looking for
27585                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27586                * message inside the page (i.e. inside a div)
27587                */
27588               getFormattedErrors: function (rowEntity, colDef) {
27589                 return grid.validate.getFormattedErrors(rowEntity, colDef);
27590               },
27591               /**
27592                * @ngdoc function
27593                * @name getTitleFormattedErrors
27594                * @methodOf  ui.grid.validate.api:PublicApi
27595                * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
27596                * title attribute.
27597                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27598                * @param {object} colDef the column whose errors we are looking for
27599                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27600                * message inside an html title attribute
27601                */
27602               getTitleFormattedErrors: function (rowEntity, colDef) {
27603                 return grid.validate.getTitleFormattedErrors(rowEntity, colDef);
27604               }
27605             } 
27606           }
27607         };
27608         
27609         grid.api.registerEventsFromObject(publicApi.events);
27610         grid.api.registerMethodsFromObject(publicApi.methods);
27611
27612         if (grid.edit) {
27613           grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) {
27614             grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid);
27615           });
27616         }
27617
27618         service.createDefaultValidators();
27619       }
27620       
27621     };
27622   
27623     return service;
27624   }]);
27625   
27626   
27627   /**
27628    *  @ngdoc directive
27629    *  @name ui.grid.validate.directive:uiGridValidate
27630    *  @element div
27631    *  @restrict A
27632    *  @description Adds validating features to the ui-grid directive.
27633    *  @example
27634    <example module="app">
27635    <file name="app.js">
27636    var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']);
27637
27638    app.controller('MainCtrl', ['$scope', function ($scope) {
27639       $scope.data = [
27640         { name: 'Bob', title: 'CEO' },
27641             { name: 'Frank', title: 'Lowly Developer' }
27642       ];
27643
27644       $scope.columnDefs = [
27645         {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'},
27646         {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'}
27647       ];
27648     }]);
27649    </file>
27650    <file name="index.html">
27651    <div ng-controller="MainCtrl">
27652    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div>
27653    </div>
27654    </file>
27655    </example>
27656    */
27657
27658   module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) {
27659     return {
27660       priority: 0,
27661       replace: true,
27662       require: '^uiGrid',
27663       scope: false,
27664       compile: function () {
27665         return {
27666           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
27667             uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid);
27668           },
27669           post: function ($scope, $elm, $attrs, uiGridCtrl) {
27670           }
27671         };
27672       }
27673     };
27674   }]);
27675 })();
27676 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
27677   'use strict';
27678
27679   $templateCache.put('ui-grid/ui-grid-filter',
27680     "<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>"
27681   );
27682
27683
27684   $templateCache.put('ui-grid/ui-grid-footer',
27685     "<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>"
27686   );
27687
27688
27689   $templateCache.put('ui-grid/ui-grid-grid-footer',
27690     "<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>"
27691   );
27692
27693
27694   $templateCache.put('ui-grid/ui-grid-group-panel',
27695     "<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>"
27696   );
27697
27698
27699   $templateCache.put('ui-grid/ui-grid-header',
27700     "<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>"
27701   );
27702
27703
27704   $templateCache.put('ui-grid/ui-grid-menu-button',
27705     "<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>"
27706   );
27707
27708
27709   $templateCache.put('ui-grid/ui-grid-no-header',
27710     "<div class=\"ui-grid-top-panel\"></div>"
27711   );
27712
27713
27714   $templateCache.put('ui-grid/ui-grid-row',
27715     "<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>"
27716   );
27717
27718
27719   $templateCache.put('ui-grid/ui-grid',
27720     "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
27721     "      /* Styles for the grid */\n" +
27722     "    }\n" +
27723     "\n" +
27724     "    .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
27725     "      height: {{ grid.options.rowHeight }}px;\n" +
27726     "    }\n" +
27727     "\n" +
27728     "    .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
27729     "      border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
27730     "    }\n" +
27731     "\n" +
27732     "    {{ grid.verticalScrollbarStyles }}\n" +
27733     "    {{ grid.horizontalScrollbarStyles }}\n" +
27734     "\n" +
27735     "    /*\n" +
27736     "    .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
27737     "      padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
27738     "    }\n" +
27739     "    */\n" +
27740     "\n" +
27741     "    {{ 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>"
27742   );
27743
27744
27745   $templateCache.put('ui-grid/uiGridCell',
27746     "<div class=\"ui-grid-cell-contents\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
27747   );
27748
27749
27750   $templateCache.put('ui-grid/uiGridColumnMenu',
27751     "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
27752     "    <div class=\"inner\" ng-show=\"menuShown\">\n" +
27753     "      <ul>\n" +
27754     "        <div ng-show=\"grid.options.enableSorting\">\n" +
27755     "          <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" +
27756     "          <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" +
27757     "          <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
27758     "        </div>\n" +
27759     "      </ul>\n" +
27760     "    </div>\n" +
27761     "  </div> --></div></div>"
27762   );
27763
27764
27765   $templateCache.put('ui-grid/uiGridFooterCell',
27766     "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationText() + ( col.getAggregationValue() CUSTOM_FILTERS ) }}</div></div>"
27767   );
27768
27769
27770   $templateCache.put('ui-grid/uiGridHeaderCell',
27771     "<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=\"{{isSortPriorityVisible() ? i18n.headerCell.priority + ' ' + col.sort.priority : null}}\" aria-hidden=\"true\"></i> <sub ui-grid-visible=\"isSortPriorityVisible()\" 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>"
27772   );
27773
27774
27775   $templateCache.put('ui-grid/uiGridMenu',
27776     "<div class=\"ui-grid-menu\" ng-if=\"shown\"><style ui-grid-style>{{dynamicStyles}}</style><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>"
27777   );
27778
27779
27780   $templateCache.put('ui-grid/uiGridMenuItem',
27781     "<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>"
27782   );
27783
27784
27785   $templateCache.put('ui-grid/uiGridRenderContainer',
27786     "<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>"
27787   );
27788
27789
27790   $templateCache.put('ui-grid/uiGridViewport',
27791     "<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>"
27792   );
27793
27794
27795   $templateCache.put('ui-grid/cellEditor',
27796     "<div><form name=\"inputForm\"><input type=\"INPUT_TYPE\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
27797   );
27798
27799
27800   $templateCache.put('ui-grid/dropdownEditor',
27801     "<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>"
27802   );
27803
27804
27805   $templateCache.put('ui-grid/fileChooserEditor',
27806     "<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>"
27807   );
27808
27809
27810   $templateCache.put('ui-grid/expandableRow',
27811     "<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>"
27812   );
27813
27814
27815   $templateCache.put('ui-grid/expandableRowHeader',
27816     "<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>"
27817   );
27818
27819
27820   $templateCache.put('ui-grid/expandableScrollFiller',
27821     "<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>"
27822   );
27823
27824
27825   $templateCache.put('ui-grid/expandableTopRowHeader',
27826     "<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>"
27827   );
27828
27829
27830   $templateCache.put('ui-grid/csvLink',
27831     "<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>"
27832   );
27833
27834
27835   $templateCache.put('ui-grid/importerMenuItem',
27836     "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
27837   );
27838
27839
27840   $templateCache.put('ui-grid/importerMenuItemContainer',
27841     "<div ui-grid-importer-menu-item></div>"
27842   );
27843
27844
27845   $templateCache.put('ui-grid/pagination',
27846     "<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>"
27847   );
27848
27849
27850   $templateCache.put('ui-grid/columnResizer',
27851     "<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>"
27852   );
27853
27854
27855   $templateCache.put('ui-grid/gridFooterSelectedItems',
27856     "<span ng-if=\"grid.selection.selectedCount !== 0 && grid.options.enableFooterTotalSelected\">({{\"search.selectedItems\" | t}} {{grid.selection.selectedCount}})</span>"
27857   );
27858
27859
27860   $templateCache.put('ui-grid/selectionHeaderCell',
27861     "<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>"
27862   );
27863
27864
27865   $templateCache.put('ui-grid/selectionRowHeader',
27866     "<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>"
27867   );
27868
27869
27870   $templateCache.put('ui-grid/selectionRowHeaderButtons',
27871     "<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>"
27872   );
27873
27874
27875   $templateCache.put('ui-grid/selectionSelectAllButtons',
27876     "<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>"
27877   );
27878
27879
27880   $templateCache.put('ui-grid/treeBaseExpandAllButtons',
27881     "<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>"
27882   );
27883
27884
27885   $templateCache.put('ui-grid/treeBaseHeaderCell',
27886     "<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>"
27887   );
27888
27889
27890   $templateCache.put('ui-grid/treeBaseRowHeader',
27891     "<div class=\"ui-grid-cell-contents\"><ui-grid-tree-base-row-header-buttons></ui-grid-tree-base-row-header-buttons></div>"
27892   );
27893
27894
27895   $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
27896     "<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>"
27897   );
27898
27899
27900   $templateCache.put('ui-grid/cellTitleValidator',
27901     "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" title=\"{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
27902   );
27903
27904
27905   $templateCache.put('ui-grid/cellTooltipValidator',
27906     "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" tooltip-html-unsafe=\"{{grid.validate.getFormattedErrors(row.entity,col.colDef)}}\" tooltip-enable=\"grid.validate.isInvalid(row.entity,col.colDef)\" tooltip-append-to-body=\"true\" tooltip-placement=\"top\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
27907   );
27908
27909 }]);