Built motion from commit 7767ffc.|0.0.132
[motion.git] / public / bower_components / angular-ui-grid / ui-grid.js
1 /*!
2  * ui-grid - v3.1.1 - 2016-02-09
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) {
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     },
456
457
458     /**
459      * @ngdoc method
460      * @methodOf ui.grid.service:uiGridColumnMenuService
461      * @name getColumnElementPosition
462      * @description  gets the position information needed to place the column
463      * menu below the column header
464      * @param {$scope} $scope the $scope from the uiGridColumnMenu
465      * @param {GridCol} column the column we want to position below
466      * @param {element} $columnElement the column element we want to position below
467      * @returns {hash} containing left, top, offset, height, width
468      *
469      */
470     getColumnElementPosition: function( $scope, column, $columnElement ){
471       var positionData = {};
472       positionData.left = $columnElement[0].offsetLeft;
473       positionData.top = $columnElement[0].offsetTop;
474       positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
475
476       // Get the grid scrollLeft
477       positionData.offset = 0;
478       if (column.grid.options.offsetLeft) {
479         positionData.offset = column.grid.options.offsetLeft;
480       }
481
482       positionData.height = gridUtil.elementHeight($columnElement, true);
483       positionData.width = gridUtil.elementWidth($columnElement, true);
484
485       return positionData;
486     },
487
488
489     /**
490      * @ngdoc method
491      * @methodOf ui.grid.service:uiGridColumnMenuService
492      * @name repositionMenu
493      * @description  Reposition the menu below the new column.  If the menu has no child nodes
494      * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
495      * later to fix it
496      * @param {$scope} $scope the $scope from the uiGridColumnMenu
497      * @param {GridCol} column the column we want to position below
498      * @param {hash} positionData a hash containing left, top, offset, height, width
499      * @param {element} $elm the column menu element that we want to reposition
500      * @param {element} $columnElement the column element that we want to reposition underneath
501      *
502      */
503     repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
504       var menu = $elm[0].querySelectorAll('.ui-grid-menu');
505
506       // It's possible that the render container of the column we're attaching to is
507       // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
508       // between the render container and the grid
509       var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
510       var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
511
512       var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
513
514       // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
515       var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
516       var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
517
518       if ( menu.length !== 0 ){
519         var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
520         if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
521           myWidth = gridUtil.elementWidth(menu, true);
522           $scope.lastMenuWidth = myWidth;
523           column.lastMenuWidth = myWidth;
524
525           // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
526           // Get the column menu right padding
527           paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
528           $scope.lastMenuPaddingRight = paddingRight;
529           column.lastMenuPaddingRight = paddingRight;
530         }
531       }
532
533       var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
534       if (left < positionData.offset){
535         left = positionData.offset;
536       }
537
538       $elm.css('left', left + 'px');
539       $elm.css('top', (positionData.top + positionData.height) + 'px');
540     }
541
542   };
543
544   return service;
545 }])
546
547
548 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
549 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
550 /**
551  * @ngdoc directive
552  * @name ui.grid.directive:uiGridColumnMenu
553  * @description  Provides the column menu framework, leverages uiGridMenu underneath
554  *
555  */
556
557   var uiGridColumnMenu = {
558     priority: 0,
559     scope: true,
560     require: '^uiGrid',
561     templateUrl: 'ui-grid/uiGridColumnMenu',
562     replace: true,
563     link: function ($scope, $elm, $attrs, uiGridCtrl) {
564       uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
565
566       $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
567
568       // Set the menu items for use with the column menu. The user can later add additional items via the watch
569       $scope.menuItems = $scope.defaultMenuItems;
570       uiGridColumnMenuService.setColMenuItemWatch( $scope );
571
572
573       /**
574        * @ngdoc method
575        * @methodOf ui.grid.directive:uiGridColumnMenu
576        * @name showMenu
577        * @description Shows the column menu.  If the menu is already displayed it
578        * calls the menu to ask it to hide (it will animate), then it repositions the menu
579        * to the right place whilst hidden (it will make an assumption on menu width),
580        * then it asks the menu to show (it will animate), then it repositions the menu again
581        * once we can calculate it's size.
582        * @param {GridCol} column the column we want to position below
583        * @param {element} $columnElement the column element we want to position below
584        */
585       $scope.showMenu = function(column, $columnElement, event) {
586         // Swap to this column
587         $scope.col = column;
588
589         // Get the position information for the column element
590         var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
591
592         if ($scope.menuShown) {
593           // we want to hide, then reposition, then show, but we want to wait for animations
594           // we set a variable, and then rely on the menu-hidden event to call the reposition and show
595           $scope.colElement = $columnElement;
596           $scope.colElementPosition = colElementPosition;
597           $scope.hideThenShow = true;
598
599           $scope.$broadcast('hide-menu', { originalEvent: event });
600         } else {
601           $scope.menuShown = true;
602           uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
603
604           $scope.colElement = $columnElement;
605           $scope.colElementPosition = colElementPosition;
606           $scope.$broadcast('show-menu', { originalEvent: event });
607
608         }
609       };
610
611
612       /**
613        * @ngdoc method
614        * @methodOf ui.grid.directive:uiGridColumnMenu
615        * @name hideMenu
616        * @description Hides the column menu.
617        * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
618        * from the menu itself - in which case don't broadcast again as we'll get
619        * an infinite loop
620        */
621       $scope.hideMenu = function( broadcastTrigger ) {
622         $scope.menuShown = false;
623         if ( !broadcastTrigger ){
624           $scope.$broadcast('hide-menu');
625         }
626       };
627
628
629       $scope.$on('menu-hidden', function() {
630         if ( $scope.hideThenShow ){
631           delete $scope.hideThenShow;
632
633           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
634           $scope.$broadcast('show-menu');
635
636           $scope.menuShown = true;
637         } else {
638           $scope.hideMenu( true );
639
640           if ($scope.col) {
641             //Focus on the menu button
642             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
643           }
644         }
645       });
646
647       $scope.$on('menu-shown', function() {
648         $timeout( function() {
649           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
650           //Focus on the first item
651           gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item', true);
652           delete $scope.colElementPosition;
653           delete $scope.columnElement;
654         }, 200);
655       });
656
657
658       /* Column methods */
659       $scope.sortColumn = function (event, dir) {
660         event.stopPropagation();
661
662         $scope.grid.sortColumn($scope.col, dir, true)
663           .then(function () {
664             $scope.grid.refresh();
665             $scope.hideMenu();
666           });
667       };
668
669       $scope.unsortColumn = function () {
670         $scope.col.unsort();
671
672         $scope.grid.refresh();
673         $scope.hideMenu();
674       };
675
676       //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
677       var setFocusOnHideColumn = function(){
678         $timeout(function(){
679           // Get the UID of the first
680           var focusToGridMenu = function(){
681             return gridUtil.focus.byId('grid-menu', $scope.grid);
682           };
683
684           var thisIndex;
685           $scope.grid.columns.some(function(element, index){
686             if (angular.equals(element, $scope.col)) {
687               thisIndex = index;
688               return true;
689             }
690           });
691
692           var previousVisibleCol;
693           // Try and find the next lower or nearest column to focus on
694           $scope.grid.columns.some(function(element, index){
695             if (!element.visible){
696               return false;
697             } // This columns index is below the current column index
698             else if ( index < thisIndex){
699               previousVisibleCol = element;
700             } // This elements index is above this column index and we haven't found one that is lower
701             else if ( index > thisIndex && !previousVisibleCol) {
702               // This is the next best thing
703               previousVisibleCol = element;
704               // We've found one so use it.
705               return true;
706             } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
707             else if (index > thisIndex && previousVisibleCol) {
708               // We are done.
709               return true;
710             }
711           });
712           // If found then focus on it
713           if (previousVisibleCol){
714             var colClass = previousVisibleCol.getColClass();
715             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
716               if (reason !== 'canceled'){ // If this is canceled then don't perform the action
717                 //The fallback action is to focus on the grid menu
718                 return focusToGridMenu();
719               }
720             });
721           } else {
722             // Fallback action to focus on the grid menu
723             focusToGridMenu();
724           }
725         });
726       };
727
728       $scope.hideColumn = function () {
729         $scope.col.colDef.visible = false;
730         $scope.col.visible = false;
731
732         $scope.grid.queueGridRefresh();
733         $scope.hideMenu();
734         $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
735         $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
736
737         // We are hiding so the default action of focusing on the button that opened this menu will fail.
738         setFocusOnHideColumn();
739       };
740     },
741
742
743
744     controller: ['$scope', function ($scope) {
745       var self = this;
746
747       $scope.$watch('menuItems', function (n) {
748         self.menuItems = n;
749       });
750     }]
751   };
752
753   return uiGridColumnMenu;
754
755 }]);
756
757 })();
758
759 (function(){
760   'use strict';
761
762   angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
763
764     return {
765       compile: function() {
766         return {
767           pre: function ($scope, $elm, $attrs, controllers) {
768             $scope.col.updateFilters = function( filterable ){
769               $elm.children().remove();
770               if ( filterable ){
771                 var template = $scope.col.filterHeaderTemplate;
772
773                 $elm.append($compile(template)($scope));
774               }
775             };
776
777             $scope.$on( '$destroy', function() {
778               delete $scope.col.updateFilters;
779             });
780           },
781           post: function ($scope, $elm, $attrs, controllers){
782             $scope.aria = i18nService.getSafeText('headerCell.aria');
783             $scope.removeFilter = function(colFilter, index){
784               colFilter.term = null;
785               //Set the focus to the filter input after the action disables the button
786               gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
787             };
788           }
789         };
790       }
791     };
792   }]);
793 })();
794
795 (function () {
796   'use strict';
797
798   angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
799   function ($timeout, gridUtil, uiGridConstants, $compile) {
800     var uiGridFooterCell = {
801       priority: 0,
802       scope: {
803         col: '=',
804         row: '=',
805         renderIndex: '='
806       },
807       replace: true,
808       require: '^uiGrid',
809       compile: function compile(tElement, tAttrs, transclude) {
810         return {
811           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
812             var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
813             $elm.append(cellFooter);
814           },
815           post: function ($scope, $elm, $attrs, uiGridCtrl) {
816             //$elm.addClass($scope.col.getColClass(false));
817             $scope.grid = uiGridCtrl.grid;
818
819             var initColClass = $scope.col.getColClass(false);
820             $elm.addClass(initColClass);
821
822             // apply any footerCellClass
823             var classAdded;
824             var updateClass = function( grid ){
825               var contents = $elm;
826               if ( classAdded ){
827                 contents.removeClass( classAdded );
828                 classAdded = null;
829               }
830   
831               if (angular.isFunction($scope.col.footerCellClass)) {
832                 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
833               }
834               else {
835                 classAdded = $scope.col.footerCellClass;
836               }
837               contents.addClass(classAdded);
838             };
839   
840             if ($scope.col.footerCellClass) {
841               updateClass();
842             }
843
844             $scope.col.updateAggregationValue();
845
846             // Watch for column changes so we can alter the col cell class properly
847 /* shouldn't be needed any more, given track by col.name
848             $scope.$watch('col', function (n, o) {
849               if (n !== o) {
850                 // See if the column's internal class has changed
851                 var newColClass = $scope.col.getColClass(false);
852                 if (newColClass !== initColClass) {
853                   $elm.removeClass(initColClass);
854                   $elm.addClass(newColClass);
855                   initColClass = newColClass;
856                 }
857               }
858             });
859 */
860
861
862             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
863             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
864             // listen for visible rows change and update aggregation values
865             $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
866             $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
867             $scope.$on( '$destroy', dataChangeDereg );
868           }
869         };
870       }
871     };
872
873     return uiGridFooterCell;
874   }]);
875
876 })();
877
878 (function () {
879   'use strict';
880
881   angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
882
883     return {
884       restrict: 'EA',
885       replace: true,
886       // priority: 1000,
887       require: ['^uiGrid', '^uiGridRenderContainer'],
888       scope: true,
889       compile: function ($elm, $attrs) {
890         return {
891           pre: function ($scope, $elm, $attrs, controllers) {
892             var uiGridCtrl = controllers[0];
893             var containerCtrl = controllers[1];
894
895             $scope.grid = uiGridCtrl.grid;
896             $scope.colContainer = containerCtrl.colContainer;
897
898             containerCtrl.footer = $elm;
899
900             var footerTemplate = $scope.grid.options.footerTemplate;
901             gridUtil.getTemplate(footerTemplate)
902               .then(function (contents) {
903                 var template = angular.element(contents);
904
905                 var newElm = $compile(template)($scope);
906                 $elm.append(newElm);
907
908                 if (containerCtrl) {
909                   // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
910                   var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
911
912                   if (footerViewport) {
913                     containerCtrl.footerViewport = footerViewport;
914                   }
915                 }
916               });
917           },
918
919           post: function ($scope, $elm, $attrs, controllers) {
920             var uiGridCtrl = controllers[0];
921             var containerCtrl = controllers[1];
922
923             // gridUtil.logDebug('ui-grid-footer link');
924
925             var grid = uiGridCtrl.grid;
926
927             // Don't animate footer cells
928             gridUtil.disableAnimations($elm);
929
930             containerCtrl.footer = $elm;
931
932             var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
933             if (footerViewport) {
934               containerCtrl.footerViewport = footerViewport;
935             }
936           }
937         };
938       }
939     };
940   }]);
941
942 })();
943 (function () {
944   'use strict';
945
946   angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
947
948     return {
949       restrict: 'EA',
950       replace: true,
951       // priority: 1000,
952       require: '^uiGrid',
953       scope: true,
954       compile: function ($elm, $attrs) {
955         return {
956           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
957
958             $scope.grid = uiGridCtrl.grid;
959
960
961
962             var footerTemplate = $scope.grid.options.gridFooterTemplate;
963             gridUtil.getTemplate(footerTemplate)
964               .then(function (contents) {
965                 var template = angular.element(contents);
966
967                 var newElm = $compile(template)($scope);
968                 $elm.append(newElm);
969               });
970           },
971
972           post: function ($scope, $elm, $attrs, controllers) {
973
974           }
975         };
976       }
977     };
978   }]);
979
980 })();
981 (function(){
982   'use strict';
983
984   angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
985     var defaultTemplate = 'ui-grid/ui-grid-group-panel';
986
987     return {
988       restrict: 'EA',
989       replace: true,
990       require: '?^uiGrid',
991       scope: false,
992       compile: function($elm, $attrs) {
993         return {
994           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
995             var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
996
997              gridUtil.getTemplate(groupPanelTemplate)
998               .then(function (contents) {
999                 var template = angular.element(contents);
1000                 
1001                 var newElm = $compile(template)($scope);
1002                 $elm.append(newElm);
1003               });
1004           },
1005
1006           post: function ($scope, $elm, $attrs, uiGridCtrl) {
1007             $elm.bind('$destroy', function() {
1008               // scrollUnbinder();
1009             });
1010           }
1011         };
1012       }
1013     };
1014   }]);
1015
1016 })();
1017 (function(){
1018   'use strict';
1019
1020   angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
1021   function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
1022     // Do stuff after mouse has been down this many ms on the header cell
1023     var mousedownTimeout = 500;
1024     var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
1025
1026     var uiGridHeaderCell = {
1027       priority: 0,
1028       scope: {
1029         col: '=',
1030         row: '=',
1031         renderIndex: '='
1032       },
1033       require: ['^uiGrid', '^uiGridRenderContainer'],
1034       replace: true,
1035       compile: function() {
1036         return {
1037           pre: function ($scope, $elm, $attrs) {
1038             var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1039             $elm.append(cellHeader);
1040           },
1041
1042           post: function ($scope, $elm, $attrs, controllers) {
1043             var uiGridCtrl = controllers[0];
1044             var renderContainerCtrl = controllers[1];
1045
1046             $scope.i18n = {
1047               headerCell: i18nService.getSafeText('headerCell'),
1048               sort: i18nService.getSafeText('sort')
1049             };
1050             $scope.isSortPriorityVisible = function() {
1051               //show sort priority if column is sorted and there is at least one other sorted column
1052               return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index){
1053                   return angular.isNumber(element.sort.priority) && element !== $scope.col;
1054                 });
1055             };
1056             $scope.getSortDirectionAriaLabel = function(){
1057               var col = $scope.col;
1058               //Trying to recreate this sort of thing but it was getting messy having it in the template.
1059               //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1060               var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1061               var label = sortDirectionText;
1062
1063               if ($scope.isSortPriorityVisible()) {
1064                 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1065               }
1066               return label;
1067             };
1068
1069             $scope.grid = uiGridCtrl.grid;
1070
1071             $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
1072
1073             var initColClass = $scope.col.getColClass(false);
1074             $elm.addClass(initColClass);
1075
1076             // Hide the menu by default
1077             $scope.menuShown = false;
1078
1079             // Put asc and desc sort directions in scope
1080             $scope.asc = uiGridConstants.ASC;
1081             $scope.desc = uiGridConstants.DESC;
1082
1083             // Store a reference to menu element
1084             var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1085
1086             var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
1087
1088
1089             // apply any headerCellClass
1090             var classAdded;
1091             var previousMouseX;
1092
1093             // filter watchers
1094             var filterDeregisters = [];
1095
1096
1097             /*
1098              * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
1099              * Once we have a down event, we need to work out whether we have a click, a drag, or a
1100              * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
1101              * we ignore it.  A hold would open the menu.
1102              *
1103              * So, on down event, we put in place handlers for move and up events, and a timer.  If the
1104              * timer expires before we see a move or up, then we have a long press and hence a column menu open.
1105              * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
1106              * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
1107              * will handle it.
1108              *
1109              * To deal with touch enabled devices that also have mice, we only create our handlers when
1110              * we get the down event, and we create the corresponding handlers - if we're touchstart then
1111              * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
1112              *
1113              * We also suppress the click action whilst this is happening - otherwise after the mouseup there
1114              * will be a click event and that can cause the column menu to close
1115              *
1116              */
1117
1118             $scope.downFn = function( event ){
1119               event.stopPropagation();
1120
1121               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
1122                 event = event.originalEvent;
1123               }
1124
1125               // Don't show the menu if it's not the left button
1126               if (event.button && event.button !== 0) {
1127                 return;
1128               }
1129               previousMouseX = event.pageX;
1130
1131               $scope.mousedownStartTime = (new Date()).getTime();
1132               $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
1133
1134               $scope.mousedownTimeout.then(function () {
1135                 if ( $scope.colMenu ) {
1136                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
1137                 }
1138               });
1139
1140               uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
1141
1142               $scope.offAllEvents();
1143               if ( event.type === 'touchstart'){
1144                 $document.on('touchend', $scope.upFn);
1145                 $document.on('touchmove', $scope.moveFn);
1146               } else if ( event.type === 'mousedown' ){
1147                 $document.on('mouseup', $scope.upFn);
1148                 $document.on('mousemove', $scope.moveFn);
1149               }
1150             };
1151
1152             $scope.upFn = function( event ){
1153               event.stopPropagation();
1154               $timeout.cancel($scope.mousedownTimeout);
1155               $scope.offAllEvents();
1156               $scope.onDownEvents(event.type);
1157
1158               var mousedownEndTime = (new Date()).getTime();
1159               var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
1160
1161               if (mousedownTime > mousedownTimeout) {
1162                 // long click, handled above with mousedown
1163               }
1164               else {
1165                 // short click
1166                 if ( $scope.sortable ){
1167                   $scope.handleClick(event);
1168                 }
1169               }
1170             };
1171
1172             $scope.moveFn = function( event ){
1173               // Chrome is known to fire some bogus move events.
1174               var changeValue = event.pageX - previousMouseX;
1175               if ( changeValue === 0 ){ return; }
1176
1177               // we're a move, so do nothing and leave for column move (if enabled) to take over
1178               $timeout.cancel($scope.mousedownTimeout);
1179               $scope.offAllEvents();
1180               $scope.onDownEvents(event.type);
1181             };
1182
1183             $scope.clickFn = function ( event ){
1184               event.stopPropagation();
1185               $contentsElm.off('click', $scope.clickFn);
1186             };
1187
1188
1189             $scope.offAllEvents = function(){
1190               $contentsElm.off('touchstart', $scope.downFn);
1191               $contentsElm.off('mousedown', $scope.downFn);
1192
1193               $document.off('touchend', $scope.upFn);
1194               $document.off('mouseup', $scope.upFn);
1195
1196               $document.off('touchmove', $scope.moveFn);
1197               $document.off('mousemove', $scope.moveFn);
1198
1199               $contentsElm.off('click', $scope.clickFn);
1200             };
1201
1202             $scope.onDownEvents = function( type ){
1203               // If there is a previous event, then wait a while before
1204               // activating the other mode - i.e. if the last event was a touch event then
1205               // don't enable mouse events for a wee while (500ms or so)
1206               // Avoids problems with devices that emulate mouse events when you have touch events
1207
1208               switch (type){
1209                 case 'touchmove':
1210                 case 'touchend':
1211                   $contentsElm.on('click', $scope.clickFn);
1212                   $contentsElm.on('touchstart', $scope.downFn);
1213                   $timeout(function(){
1214                     $contentsElm.on('mousedown', $scope.downFn);
1215                   }, changeModeTimeout);
1216                   break;
1217                 case 'mousemove':
1218                 case 'mouseup':
1219                   $contentsElm.on('click', $scope.clickFn);
1220                   $contentsElm.on('mousedown', $scope.downFn);
1221                   $timeout(function(){
1222                     $contentsElm.on('touchstart', $scope.downFn);
1223                   }, changeModeTimeout);
1224                   break;
1225                 default:
1226                   $contentsElm.on('click', $scope.clickFn);
1227                   $contentsElm.on('touchstart', $scope.downFn);
1228                   $contentsElm.on('mousedown', $scope.downFn);
1229               }
1230             };
1231
1232
1233             var updateHeaderOptions = function( grid ){
1234               var contents = $elm;
1235               if ( classAdded ){
1236                 contents.removeClass( classAdded );
1237                 classAdded = null;
1238               }
1239
1240               if (angular.isFunction($scope.col.headerCellClass)) {
1241                 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
1242               }
1243               else {
1244                 classAdded = $scope.col.headerCellClass;
1245               }
1246               contents.addClass(classAdded);
1247
1248               $timeout(function (){
1249                 var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1250                 $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1251               });
1252
1253               // Figure out whether this column is sortable or not
1254               if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
1255                 $scope.sortable = true;
1256               }
1257               else {
1258                 $scope.sortable = false;
1259               }
1260
1261               // Figure out whether this column is filterable or not
1262               var oldFilterable = $scope.filterable;
1263               if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1264                 $scope.filterable = true;
1265               }
1266               else {
1267                 $scope.filterable = false;
1268               }
1269
1270               if ( oldFilterable !== $scope.filterable){
1271                 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1272                   $scope.col.updateFilters($scope.filterable);
1273                 }
1274
1275                 // if column is filterable add a filter watcher
1276                 if ($scope.filterable) {
1277                   $scope.col.filters.forEach( function(filter, i) {
1278                     filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1279                       if (n !== o) {
1280                         uiGridCtrl.grid.api.core.raise.filterChanged();
1281                         uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1282                         uiGridCtrl.grid.queueGridRefresh();
1283                       }
1284                     }));
1285                   });
1286                   $scope.$on('$destroy', function() {
1287                     filterDeregisters.forEach( function(filterDeregister) {
1288                       filterDeregister();
1289                     });
1290                   });
1291                 } else {
1292                   filterDeregisters.forEach( function(filterDeregister) {
1293                     filterDeregister();
1294                   });
1295                 }
1296
1297               }
1298
1299               // figure out whether we support column menus
1300               if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1301                       $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1302                 $scope.colMenu = true;
1303               } else {
1304                 $scope.colMenu = false;
1305               }
1306
1307               /**
1308               * @ngdoc property
1309               * @name enableColumnMenu
1310               * @propertyOf ui.grid.class:GridOptions.columnDef
1311               * @description if column menus are enabled, controls the column menus for this specific
1312               * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1313               * using this option. If gridOptions.enableColumnMenus === false then you get no column
1314               * menus irrespective of the value of this option ).  Defaults to true.
1315               *
1316               */
1317               /**
1318               * @ngdoc property
1319               * @name enableColumnMenus
1320               * @propertyOf ui.grid.class:GridOptions.columnDef
1321               * @description Override for column menus everywhere - if set to false then you get no
1322               * column menus.  Defaults to true.
1323               *
1324               */
1325
1326               $scope.offAllEvents();
1327
1328               if ($scope.sortable || $scope.colMenu) {
1329                 $scope.onDownEvents();
1330
1331                 $scope.$on('$destroy', function () {
1332                   $scope.offAllEvents();
1333                 });
1334               }
1335             };
1336
1337 /*
1338             $scope.$watch('col', function (n, o) {
1339               if (n !== o) {
1340                 // See if the column's internal class has changed
1341                 var newColClass = $scope.col.getColClass(false);
1342                 if (newColClass !== initColClass) {
1343                   $elm.removeClass(initColClass);
1344                   $elm.addClass(newColClass);
1345                   initColClass = newColClass;
1346                 }
1347               }
1348             });
1349 */
1350             updateHeaderOptions();
1351
1352             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
1353             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
1354
1355             $scope.$on( '$destroy', dataChangeDereg );
1356
1357             $scope.handleClick = function(event) {
1358               // If the shift key is being held down, add this column to the sort
1359               var add = false;
1360               if (event.shiftKey) {
1361                 add = true;
1362               }
1363
1364               // Sort this column then rebuild the grid's rows
1365               uiGridCtrl.grid.sortColumn($scope.col, add)
1366                 .then(function () {
1367                   if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
1368                   uiGridCtrl.grid.refresh();
1369                 });
1370             };
1371
1372
1373             $scope.toggleMenu = function(event) {
1374               event.stopPropagation();
1375
1376               // If the menu is already showing...
1377               if (uiGridCtrl.columnMenuScope.menuShown) {
1378                 // ... and we're the column the menu is on...
1379                 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1380                   // ... hide it
1381                   uiGridCtrl.columnMenuScope.hideMenu();
1382                 }
1383                 // ... and we're NOT the column the menu is on
1384                 else {
1385                   // ... move the menu to our column
1386                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1387                 }
1388               }
1389               // If the menu is NOT showing
1390               else {
1391                 // ... show it on our column
1392                 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1393               }
1394             };
1395           }
1396         };
1397       }
1398     };
1399
1400     return uiGridHeaderCell;
1401   }]);
1402
1403 })();
1404
1405 (function(){
1406   'use strict';
1407
1408   angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
1409     function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1410     var defaultTemplate = 'ui-grid/ui-grid-header';
1411     var emptyTemplate = 'ui-grid/ui-grid-no-header';
1412
1413     return {
1414       restrict: 'EA',
1415       // templateUrl: 'ui-grid/ui-grid-header',
1416       replace: true,
1417       // priority: 1000,
1418       require: ['^uiGrid', '^uiGridRenderContainer'],
1419       scope: true,
1420       compile: function($elm, $attrs) {
1421         return {
1422           pre: function ($scope, $elm, $attrs, controllers) {
1423             var uiGridCtrl = controllers[0];
1424             var containerCtrl = controllers[1];
1425
1426             $scope.grid = uiGridCtrl.grid;
1427             $scope.colContainer = containerCtrl.colContainer;
1428
1429             updateHeaderReferences();
1430             
1431             var headerTemplate;
1432             if (!$scope.grid.options.showHeader) {
1433               headerTemplate = emptyTemplate;
1434             }
1435             else {
1436               headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
1437             }
1438
1439             gridUtil.getTemplate(headerTemplate)
1440               .then(function (contents) {
1441                 var template = angular.element(contents);
1442                 
1443                 var newElm = $compile(template)($scope);
1444                 $elm.replaceWith(newElm);
1445
1446                 // And update $elm to be the new element
1447                 $elm = newElm;
1448
1449                 updateHeaderReferences();
1450
1451                 if (containerCtrl) {
1452                   // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1453                   var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1454
1455
1456                   if (headerViewport) {
1457                     containerCtrl.headerViewport = headerViewport;
1458                     angular.element(headerViewport).on('scroll', scrollHandler);
1459                     $scope.$on('$destroy', function () {
1460                       angular.element(headerViewport).off('scroll', scrollHandler);
1461                     });
1462                   }
1463                 }
1464
1465                 $scope.grid.queueRefresh();
1466               });
1467
1468             function updateHeaderReferences() {
1469               containerCtrl.header = containerCtrl.colContainer.header = $elm;
1470
1471               var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
1472
1473               if (headerCanvases.length > 0) {
1474                 containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
1475               }
1476               else {
1477                 containerCtrl.headerCanvas = null;
1478               }
1479             }
1480
1481             function scrollHandler(evt) {
1482               if (uiGridCtrl.grid.isScrollingHorizontally) {
1483                 return;
1484               }
1485               var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
1486               var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
1487
1488               var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
1489               scrollEvent.newScrollLeft = newScrollLeft;
1490               if ( horizScrollPercentage > -1 ){
1491                 scrollEvent.x = { percentage: horizScrollPercentage };
1492               }
1493
1494               uiGridCtrl.grid.scrollContainers(null, scrollEvent);
1495             }
1496           },
1497
1498           post: function ($scope, $elm, $attrs, controllers) {
1499             var uiGridCtrl = controllers[0];
1500             var containerCtrl = controllers[1];
1501
1502             // gridUtil.logDebug('ui-grid-header link');
1503
1504             var grid = uiGridCtrl.grid;
1505
1506             // Don't animate header cells
1507             gridUtil.disableAnimations($elm);
1508
1509             function updateColumnWidths() {
1510               // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
1511               // already being populated correctly
1512
1513               var columnCache = containerCtrl.colContainer.visibleColumnCache;
1514               
1515               // Build the CSS
1516               // uiGridCtrl.grid.columns.forEach(function (column) {
1517               var ret = '';
1518               var canvasWidth = 0;
1519               columnCache.forEach(function (column) {
1520                 ret = ret + column.getColClassDefinition();
1521                 canvasWidth += column.drawnWidth;
1522               });
1523
1524               containerCtrl.colContainer.canvasWidth = canvasWidth;
1525               
1526               // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1527               return ret;
1528             }
1529             
1530             containerCtrl.header = $elm;
1531             
1532             var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1533             if (headerViewport) {
1534               containerCtrl.headerViewport = headerViewport;
1535             }
1536
1537             //todo: remove this if by injecting gridCtrl into unit tests
1538             if (uiGridCtrl) {
1539               uiGridCtrl.grid.registerStyleComputation({
1540                 priority: 15,
1541                 func: updateColumnWidths
1542               });
1543             }
1544           }
1545         };
1546       }
1547     };
1548   }]);
1549
1550 })();
1551
1552 (function(){
1553
1554 angular.module('ui.grid')
1555 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
1556   /**
1557    *  @ngdoc service
1558    *  @name ui.grid.gridMenuService
1559    *
1560    *  @description Methods for working with the grid menu
1561    */
1562
1563   var service = {
1564     /**
1565      * @ngdoc method
1566      * @methodOf ui.grid.gridMenuService
1567      * @name initialize
1568      * @description Sets up the gridMenu. Most importantly, sets our
1569      * scope onto the grid object as grid.gridMenuScope, allowing us
1570      * to operate when passed only the grid.  Second most importantly,
1571      * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1572      * on the core api.
1573      * @param {$scope} $scope the scope of this gridMenu
1574      * @param {Grid} grid the grid to which this gridMenu is associated
1575      */
1576     initialize: function( $scope, grid ){
1577       grid.gridMenuScope = $scope;
1578       $scope.grid = grid;
1579       $scope.registeredMenuItems = [];
1580
1581       // not certain this is needed, but would be bad to create a memory leak
1582       $scope.$on('$destroy', function() {
1583         if ( $scope.grid && $scope.grid.gridMenuScope ){
1584           $scope.grid.gridMenuScope = null;
1585         }
1586         if ( $scope.grid ){
1587           $scope.grid = null;
1588         }
1589         if ( $scope.registeredMenuItems ){
1590           $scope.registeredMenuItems = null;
1591         }
1592       });
1593
1594       $scope.registeredMenuItems = [];
1595
1596       /**
1597        * @ngdoc function
1598        * @name addToGridMenu
1599        * @methodOf ui.grid.core.api:PublicApi
1600        * @description add items to the grid menu.  Used by features
1601        * to add their menu items if they are enabled, can also be used by
1602        * end users to add menu items.  This method has the advantage of allowing
1603        * remove again, which can simplify management of which items are included
1604        * in the menu when.  (Noting that in most cases the shown and active functions
1605        * provide a better way to handle visibility of menu items)
1606        * @param {Grid} grid the grid on which we are acting
1607        * @param {array} items menu items in the format as described in the tutorial, with
1608        * the added note that if you want to use remove you must also specify an `id` field,
1609        * which is provided when you want to remove an item.  The id should be unique.
1610        *
1611        */
1612       grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1613
1614       /**
1615        * @ngdoc function
1616        * @name removeFromGridMenu
1617        * @methodOf ui.grid.core.api:PublicApi
1618        * @description Remove an item from the grid menu based on a provided id. Assumes
1619        * that the id is unique, removes only the last instance of that id. Does nothing if
1620        * the specified id is not found
1621        * @param {Grid} grid the grid on which we are acting
1622        * @param {string} id the id we'd like to remove from the menu
1623        *
1624        */
1625       grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1626     },
1627
1628
1629     /**
1630      * @ngdoc function
1631      * @name addToGridMenu
1632      * @propertyOf ui.grid.gridMenuService
1633      * @description add items to the grid menu.  Used by features
1634      * to add their menu items if they are enabled, can also be used by
1635      * end users to add menu items.  This method has the advantage of allowing
1636      * remove again, which can simplify management of which items are included
1637      * in the menu when.  (Noting that in most cases the shown and active functions
1638      * provide a better way to handle visibility of menu items)
1639      * @param {Grid} grid the grid on which we are acting
1640      * @param {array} items menu items in the format as described in the tutorial, with
1641      * the added note that if you want to use remove you must also specify an `id` field,
1642      * which is provided when you want to remove an item.  The id should be unique.
1643      *
1644      */
1645     addToGridMenu: function( grid, menuItems ) {
1646       if ( !angular.isArray( menuItems ) ) {
1647         gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1648       } else {
1649         if ( grid.gridMenuScope ){
1650           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1651           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1652         } else {
1653           gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
1654         }
1655       }
1656     },
1657
1658
1659     /**
1660      * @ngdoc function
1661      * @name removeFromGridMenu
1662      * @methodOf ui.grid.gridMenuService
1663      * @description Remove an item from the grid menu based on a provided id.  Assumes
1664      * that the id is unique, removes only the last instance of that id.  Does nothing if
1665      * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
1666      * then do nothing silently - the desired result is those menu items not be present and they
1667      * aren't.
1668      * @param {Grid} grid the grid on which we are acting
1669      * @param {string} id the id we'd like to remove from the menu
1670      *
1671      */
1672     removeFromGridMenu: function( grid, id ){
1673       var foundIndex = -1;
1674
1675       if ( grid && grid.gridMenuScope ){
1676         grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1677           if ( value.id === id ){
1678             if (foundIndex > -1) {
1679               gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1680             } else {
1681
1682               foundIndex = index;
1683             }
1684           }
1685         });
1686       }
1687
1688       if ( foundIndex > -1 ){
1689         grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1690       }
1691     },
1692
1693
1694     /**
1695      * @ngdoc array
1696      * @name gridMenuCustomItems
1697      * @propertyOf ui.grid.class:GridOptions
1698      * @description (optional) An array of menu items that should be added to
1699      * the gridMenu.  Follow the format documented in the tutorial for column
1700      * menu customisation.  The context provided to the action function will
1701      * include context.grid.  An alternative if working with dynamic menus is to use the
1702      * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1703      * some of the management of items for you.
1704      *
1705      */
1706     /**
1707      * @ngdoc boolean
1708      * @name gridMenuShowHideColumns
1709      * @propertyOf ui.grid.class:GridOptions
1710      * @description true by default, whether the grid menu should allow hide/show
1711      * of columns
1712      *
1713      */
1714     /**
1715      * @ngdoc method
1716      * @methodOf ui.grid.gridMenuService
1717      * @name getMenuItems
1718      * @description Decides the menu items to show in the menu.  This is a
1719      * combination of:
1720      *
1721      * - the default menu items that are always included,
1722      * - any menu items that have been provided through the addMenuItem api. These
1723      *   are typically added by features within the grid
1724      * - any menu items included in grid.options.gridMenuCustomItems.  These can be
1725      *   changed dynamically, as they're always recalculated whenever we show the
1726      *   menu
1727      * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1728      * the information that we need
1729      * @returns {array} an array of menu items that can be shown
1730      */
1731     getMenuItems: function( $scope ) {
1732       var menuItems = [
1733         // this is where we add any menu items we want to always include
1734       ];
1735
1736       if ( $scope.grid.options.gridMenuCustomItems ){
1737         if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1738           gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1739         } else {
1740           menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1741         }
1742       }
1743
1744       var clearFilters = [{
1745         title: i18nService.getSafeText('gridMenu.clearAllFilters'),
1746         action: function ($event) {
1747           $scope.grid.clearAllFilters(undefined, true, undefined);
1748         },
1749         shown: function() {
1750           return $scope.grid.options.enableFiltering;
1751         },
1752         order: 100
1753       }];
1754       menuItems = menuItems.concat( clearFilters );
1755
1756       menuItems = menuItems.concat( $scope.registeredMenuItems );
1757
1758       if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1759         menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1760       }
1761
1762       menuItems.sort(function(a, b){
1763         return a.order - b.order;
1764       });
1765
1766       return menuItems;
1767     },
1768
1769
1770     /**
1771      * @ngdoc array
1772      * @name gridMenuTitleFilter
1773      * @propertyOf ui.grid.class:GridOptions
1774      * @description (optional) A function that takes a title string
1775      * (usually the col.displayName), and converts it into a display value.  The function
1776      * must return either a string or a promise.
1777      *
1778      * Used for internationalization of the grid menu column names - for angular-translate
1779      * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1780      * function
1781      * @example
1782      * <pre>
1783      *   gridOptions = {
1784      *     gridMenuTitleFilter: $translate
1785      *   }
1786      * </pre>
1787      */
1788     /**
1789      * @ngdoc method
1790      * @methodOf ui.grid.gridMenuService
1791      * @name showHideColumns
1792      * @description Adds two menu items for each of the columns in columnDefs.  One
1793      * menu item for hide, one menu item for show.  Each is visible when appropriate
1794      * (show when column is not visible, hide when column is visible).  Each toggles
1795      * the visible property on the columnDef using toggleColumnVisibility
1796      * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1797      */
1798     showHideColumns: function( $scope ){
1799       var showHideColumns = [];
1800       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1801         return showHideColumns;
1802       }
1803
1804       // add header for columns
1805       showHideColumns.push({
1806         title: i18nService.getSafeText('gridMenu.columns'),
1807         order: 300
1808       });
1809
1810       $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1811
1812       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1813         if ( colDef.enableHiding !== false ){
1814           // add hide menu item - shows an OK icon as we only show when column is already visible
1815           var menuItem = {
1816             icon: 'ui-grid-icon-ok',
1817             action: function($event) {
1818               $event.stopPropagation();
1819               service.toggleColumnVisibility( this.context.gridCol );
1820             },
1821             shown: function() {
1822               return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1823             },
1824             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1825             leaveOpen: true,
1826             order: 301 + index * 2
1827           };
1828           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1829           showHideColumns.push( menuItem );
1830
1831           // add show menu item - shows no icon as we only show when column is invisible
1832           menuItem = {
1833             icon: 'ui-grid-icon-cancel',
1834             action: function($event) {
1835               $event.stopPropagation();
1836               service.toggleColumnVisibility( this.context.gridCol );
1837             },
1838             shown: function() {
1839               return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1840             },
1841             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1842             leaveOpen: true,
1843             order: 301 + index * 2 + 1
1844           };
1845           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1846           showHideColumns.push( menuItem );
1847         }
1848       });
1849       return showHideColumns;
1850     },
1851
1852
1853     /**
1854      * @ngdoc method
1855      * @methodOf ui.grid.gridMenuService
1856      * @name setMenuItemTitle
1857      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1858      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1859      * putting the result into the title
1860      * @param {object} menuItem the menuItem we want to put the title on
1861      * @param {object} colDef the colDef from which we can get displayName, name or field
1862      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1863      *
1864      */
1865     setMenuItemTitle: function( menuItem, colDef, grid ){
1866       var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
1867
1868       if ( typeof(title) === 'string' ){
1869         menuItem.title = title;
1870       } else if ( title.then ){
1871         // must be a promise
1872         menuItem.title = "";
1873         title.then( function( successValue ) {
1874           menuItem.title = successValue;
1875         }, function( errorValue ) {
1876           menuItem.title = errorValue;
1877         });
1878       } else {
1879         gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
1880         menuItem.title = 'badconfig';
1881       }
1882     },
1883
1884     /**
1885      * @ngdoc method
1886      * @methodOf ui.grid.gridMenuService
1887      * @name toggleColumnVisibility
1888      * @description Toggles the visibility of an individual column.  Expects to be
1889      * provided a context that has on it a gridColumn, which is the column that
1890      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
1891      * @param {GridCol} gridCol the column that we want to toggle
1892      *
1893      */
1894     toggleColumnVisibility: function( gridCol ) {
1895       gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
1896
1897       gridCol.grid.refresh();
1898       gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1899       gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
1900     }
1901   };
1902
1903   return service;
1904 }])
1905
1906
1907
1908 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
1909 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
1910
1911   return {
1912     priority: 0,
1913     scope: true,
1914     require: ['^uiGrid'],
1915     templateUrl: 'ui-grid/ui-grid-menu-button',
1916     replace: true,
1917
1918     link: function ($scope, $elm, $attrs, controllers) {
1919       var uiGridCtrl = controllers[0];
1920
1921       // For the aria label
1922       $scope.i18n = {
1923         aria: i18nService.getSafeText('gridMenu.aria')
1924       };
1925
1926       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
1927
1928       $scope.shown = false;
1929
1930       $scope.toggleMenu = function () {
1931         if ( $scope.shown ){
1932           $scope.$broadcast('hide-menu');
1933           $scope.shown = false;
1934         } else {
1935           $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
1936           $scope.$broadcast('show-menu');
1937           $scope.shown = true;
1938         }
1939       };
1940
1941       $scope.$on('menu-hidden', function() {
1942         $scope.shown = false;
1943         gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
1944       });
1945     }
1946   };
1947
1948 }]);
1949
1950 })();
1951
1952 (function(){
1953
1954 /**
1955  * @ngdoc directive
1956  * @name ui.grid.directive:uiGridMenu
1957  * @element style
1958  * @restrict A
1959  *
1960  * @description
1961  * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1962  *
1963  * @example
1964  <doc:example module="app">
1965  <doc:source>
1966  <script>
1967  var app = angular.module('app', ['ui.grid']);
1968
1969  app.controller('MainCtrl', ['$scope', function ($scope) {
1970
1971  }]);
1972  </script>
1973
1974  <div ng-controller="MainCtrl">
1975    <div ui-grid-menu shown="true"  ></div>
1976  </div>
1977  </doc:source>
1978  <doc:scenario>
1979  </doc:scenario>
1980  </doc:example>
1981  */
1982 angular.module('ui.grid')
1983
1984 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
1985 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
1986   var uiGridMenu = {
1987     priority: 0,
1988     scope: {
1989       // shown: '&',
1990       menuItems: '=',
1991       autoHide: '=?'
1992     },
1993     require: '?^uiGrid',
1994     templateUrl: 'ui-grid/uiGridMenu',
1995     replace: false,
1996     link: function ($scope, $elm, $attrs, uiGridCtrl) {
1997       var gridMenuMaxHeight;
1998
1999       $scope.dynamicStyles = '';
2000
2001       if (uiGridCtrl) {
2002         // magic number of 30 because the grid menu displays somewhat below
2003         // the top of the grid. It is approximately 30px.
2004         gridMenuMaxHeight = uiGridCtrl.grid.gridHeight - 30;
2005         $scope.dynamicStyles = [
2006           '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
2007             'max-height: ' + gridMenuMaxHeight + 'px;',
2008           '}'
2009         ].join(' ');
2010       }
2011
2012       $scope.i18n = {
2013         close: i18nService.getSafeText('columnMenu.close')
2014       };
2015
2016     // *** Show/Hide functions ******
2017       $scope.showMenu = function(event, args) {
2018         if ( !$scope.shown ){
2019
2020           /*
2021            * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
2022            * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
2023            * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
2024            * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
2025            * on scroll events.
2026            *
2027            * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
2028            * like they do, but angular has a default $animate provider that is just a stub, and that's what's
2029            * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
2030            * angular-translate.js, it's not using it.  You need to test animations in an external application.
2031            */
2032           $scope.shown = true;
2033
2034           $timeout( function() {
2035             $scope.shownMid = true;
2036             $scope.$emit('menu-shown');
2037           });
2038         } else if ( !$scope.shownMid ) {
2039           // we're probably doing a hide then show, so we don't need to wait for ng-if
2040           $scope.shownMid = true;
2041           $scope.$emit('menu-shown');
2042         }
2043
2044         var docEventType = 'click';
2045         if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
2046           docEventType = args.originalEvent.type;
2047         }
2048
2049         // Turn off an existing document click handler
2050         angular.element(document).off('click touchstart', applyHideMenu);
2051         $elm.off('keyup', checkKeyUp);
2052         $elm.off('keydown', checkKeyDown);
2053
2054         // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
2055         $timeout(function() {
2056           angular.element(document).on(docEventType, applyHideMenu);
2057           $elm.on('keyup', checkKeyUp);
2058           $elm.on('keydown', checkKeyDown);
2059
2060         });
2061         //automatically set the focus to the first button element in the now open menu.
2062         gridUtil.focus.bySelector($elm, 'button[type=button]', true);
2063       };
2064
2065
2066       $scope.hideMenu = function(event) {
2067         if ( $scope.shown ){
2068           /*
2069            * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
2070            * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
2071            * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
2072            *
2073            * The user may have clicked on the menu again whilst
2074            * we're waiting, so we check that the mid isn't shown before applying the ng-if.
2075            */
2076           $scope.shownMid = false;
2077           $timeout( function() {
2078             if ( !$scope.shownMid ){
2079               $scope.shown = false;
2080               $scope.$emit('menu-hidden');
2081             }
2082           }, 200);
2083         }
2084
2085         angular.element(document).off('click touchstart', applyHideMenu);
2086         $elm.off('keyup', checkKeyUp);
2087         $elm.off('keydown', checkKeyDown);
2088       };
2089
2090       $scope.$on('hide-menu', function (event, args) {
2091         $scope.hideMenu(event, args);
2092       });
2093
2094       $scope.$on('show-menu', function (event, args) {
2095         $scope.showMenu(event, args);
2096       });
2097
2098
2099     // *** Auto hide when click elsewhere ******
2100       var applyHideMenu = function(){
2101         if ($scope.shown) {
2102           $scope.$apply(function () {
2103             $scope.hideMenu();
2104           });
2105         }
2106       };
2107
2108       // close menu on ESC and keep tab cyclical
2109       var checkKeyUp = function(event) {
2110         if (event.keyCode === 27) {
2111           $scope.hideMenu();
2112         }
2113       };
2114
2115       var checkKeyDown = function(event) {
2116         var setFocus = function(elm) {
2117           elm.focus();
2118           event.preventDefault();
2119           return false;
2120         };
2121         if (event.keyCode === 9) {
2122           var firstMenuItem, lastMenuItem;
2123           var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
2124           if (menuItemButtons.length > 0) {
2125             firstMenuItem = menuItemButtons[0];
2126             lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
2127             if (event.target === lastMenuItem && !event.shiftKey) {
2128               setFocus(firstMenuItem);
2129             } else if (event.target === firstMenuItem && event.shiftKey) {
2130               setFocus(lastMenuItem);
2131             }
2132           }
2133         }
2134       };
2135
2136       if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
2137         $scope.autoHide = true;
2138       }
2139
2140       if ($scope.autoHide) {
2141         angular.element($window).on('resize', applyHideMenu);
2142       }
2143
2144       $scope.$on('$destroy', function () {
2145         angular.element(document).off('click touchstart', applyHideMenu);
2146       });
2147
2148
2149       $scope.$on('$destroy', function() {
2150         angular.element($window).off('resize', applyHideMenu);
2151       });
2152
2153       if (uiGridCtrl) {
2154        $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
2155       }
2156
2157       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2158     }
2159   };
2160
2161   return uiGridMenu;
2162 }])
2163
2164 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2165   var uiGridMenuItem = {
2166     priority: 0,
2167     scope: {
2168       name: '=',
2169       active: '=',
2170       action: '=',
2171       icon: '=',
2172       shown: '=',
2173       context: '=',
2174       templateUrl: '=',
2175       leaveOpen: '=',
2176       screenReaderOnly: '='
2177     },
2178     require: ['?^uiGrid'],
2179     templateUrl: 'ui-grid/uiGridMenuItem',
2180     replace: false,
2181     compile: function() {
2182       return {
2183         pre: function ($scope, $elm) {
2184           if ($scope.templateUrl) {
2185             gridUtil.getTemplate($scope.templateUrl)
2186                 .then(function (contents) {
2187                   var template = angular.element(contents);
2188
2189                   var newElm = $compile(template)($scope);
2190                   $elm.replaceWith(newElm);
2191                 });
2192           }
2193         },
2194         post: function ($scope, $elm, $attrs, controllers) {
2195           var uiGridCtrl = controllers[0];
2196
2197           // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
2198           // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
2199           //   throw new TypeError("$scope.shown is defined but not a function");
2200           // }
2201           if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
2202             $scope.shown = function() { return true; };
2203           }
2204
2205           $scope.itemShown = function () {
2206             var context = {};
2207             if ($scope.context) {
2208               context.context = $scope.context;
2209             }
2210
2211             if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2212               context.grid = uiGridCtrl.grid;
2213             }
2214
2215             return $scope.shown.call(context);
2216           };
2217
2218           $scope.itemAction = function($event,title) {
2219             gridUtil.logDebug('itemAction');
2220             $event.stopPropagation();
2221
2222             if (typeof($scope.action) === 'function') {
2223               var context = {};
2224
2225               if ($scope.context) {
2226                 context.context = $scope.context;
2227               }
2228
2229               // Add the grid to the function call context if the uiGrid controller is present
2230               if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2231                 context.grid = uiGridCtrl.grid;
2232               }
2233
2234               $scope.action.call(context, $event, title);
2235
2236               if ( !$scope.leaveOpen ){
2237                 $scope.$emit('hide-menu');
2238               } else {
2239                 /*
2240                  * XXX: Fix after column refactor
2241                  * Ideally the focus would remain on the item.
2242                  * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2243                  */
2244                 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2245               }
2246             }
2247           };
2248
2249           $scope.i18n = i18nService.get();
2250         }
2251       };
2252     }
2253   };
2254
2255   return uiGridMenuItem;
2256 }]);
2257
2258 })();
2259
2260 (function(){
2261   'use strict';
2262   /**
2263    * @ngdoc overview
2264    * @name ui.grid.directive:uiGridOneBind
2265    * @summary A group of directives that provide a one time bind to a dom element.
2266    * @description A group of directives that provide a one time bind to a dom element.
2267    * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
2268    * This is done to reduce the number of watchers on the dom.
2269    * <br/>
2270    * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
2271    * <pre>
2272         <div ng-init="imageName = 'myImageDir.jpg'">
2273           <img ui-grid-one-bind-src="imageName"></img>
2274         </div>
2275      </pre>
2276    * Will become:
2277    * <pre>
2278        <div ng-init="imageName = 'myImageDir.jpg'">
2279          <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
2280        </div>
2281      </pre>
2282      </br>
2283      <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
2284    * <pre>
2285         <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
2286      </pre>
2287    * Will become:
2288    * <pre>
2289    <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
2290      </pre>
2291      </br>
2292    * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
2293    * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
2294    *
2295    */
2296   //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2297   var oneBinders = angular.module('ui.grid');
2298   angular.forEach([
2299       /**
2300        * @ngdoc directive
2301        * @name ui.grid.directive:uiGridOneBindSrc
2302        * @memberof ui.grid.directive:uiGridOneBind
2303        * @element img
2304        * @restrict A
2305        * @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>.
2306        * @description One time binding for the src dom tag.
2307        *
2308        */
2309       {tag: 'Src', method: 'attr'},
2310       /**
2311        * @ngdoc directive
2312        * @name ui.grid.directive:uiGridOneBindText
2313        * @element div
2314        * @restrict A
2315        * @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>.
2316        * @description One time binding for the text dom tag.
2317        */
2318       {tag: 'Text', method: 'text'},
2319       /**
2320        * @ngdoc directive
2321        * @name ui.grid.directive:uiGridOneBindHref
2322        * @element div
2323        * @restrict A
2324        * @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>.
2325        * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2326        */
2327       {tag: 'Href', method: 'attr'},
2328       /**
2329        * @ngdoc directive
2330        * @name ui.grid.directive:uiGridOneBindClass
2331        * @element div
2332        * @restrict A
2333        * @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>.
2334        * @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.
2335        *                                    this is to prevent the watcher from being removed before the scope is initialized.
2336        * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
2337        * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2338        */
2339       {tag: 'Class', method: 'addClass'},
2340       /**
2341        * @ngdoc directive
2342        * @name ui.grid.directive:uiGridOneBindHtml
2343        * @element div
2344        * @restrict A
2345        * @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>.
2346        * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
2347        */
2348       {tag: 'Html', method: 'html'},
2349       /**
2350        * @ngdoc directive
2351        * @name ui.grid.directive:uiGridOneBindAlt
2352        * @element div
2353        * @restrict A
2354        * @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>.
2355        * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2356        */
2357       {tag: 'Alt', method: 'attr'},
2358       /**
2359        * @ngdoc directive
2360        * @name ui.grid.directive:uiGridOneBindStyle
2361        * @element div
2362        * @restrict A
2363        * @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>.
2364        * @description One time binding for the style dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2365        */
2366       {tag: 'Style', method: 'css'},
2367       /**
2368        * @ngdoc directive
2369        * @name ui.grid.directive:uiGridOneBindValue
2370        * @element div
2371        * @restrict A
2372        * @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>.
2373        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2374        */
2375       {tag: 'Value', method: 'attr'},
2376       /**
2377        * @ngdoc directive
2378        * @name ui.grid.directive:uiGridOneBindId
2379        * @element div
2380        * @restrict A
2381        * @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>.
2382        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2383        */
2384       {tag: 'Id', method: 'attr'},
2385       /**
2386        * @ngdoc directive
2387        * @name ui.grid.directive:uiGridOneBindIdGrid
2388        * @element div
2389        * @restrict A
2390        * @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>.
2391        * @description One time binding for the id dom tag.
2392        * <h1>Important Note!</h1>
2393        * If the id tag passed as a parameter does <b>not</b> contain the grid id as a substring
2394        * then the directive will search the scope and the parent controller (if it is a uiGridController) for the grid.id value.
2395        * 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.
2396        * This is done in order to ensure uniqueness of id tags across the grid.
2397        * This is to prevent two grids in the same document having duplicate id tags.
2398        */
2399       {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2400       /**
2401        * @ngdoc directive
2402        * @name ui.grid.directive:uiGridOneBindTitle
2403        * @element div
2404        * @restrict A
2405        * @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>.
2406        * @description One time binding for the title dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2407        */
2408       {tag: 'Title', method: 'attr'},
2409       /**
2410        * @ngdoc directive
2411        * @name ui.grid.directive:uiGridOneBindAriaLabel
2412        * @element div
2413        * @restrict A
2414        * @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>.
2415        * @description One time binding for the aria-label dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2416        *<br/>
2417        * <pre>
2418             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text"></div>
2419          </pre>
2420        * Will become:
2421        * <pre>
2422             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
2423          </pre>
2424        */
2425       {tag: 'Label', method: 'attr', aria:true},
2426       /**
2427        * @ngdoc directive
2428        * @name ui.grid.directive:uiGridOneBindAriaLabelledby
2429        * @element div
2430        * @restrict A
2431        * @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>.
2432        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2433        *<br/>
2434        * <pre>
2435             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId"></div>
2436          </pre>
2437        * Will become:
2438        * <pre>
2439             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
2440          </pre>
2441        */
2442       {tag: 'Labelledby', method: 'attr', aria:true},
2443       /**
2444        * @ngdoc directive
2445        * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
2446        * @element div
2447        * @restrict A
2448        * @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>.
2449        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2450        * 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
2451        * grid id to each one.
2452        *<br/>
2453        * <pre>
2454             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId"></div>
2455          </pre>
2456        * Will become ([grid.id] will be replaced by the actual grid id):
2457        * <pre>
2458             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
2459          </pre>
2460        */
2461       {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2462       /**
2463        * @ngdoc directive
2464        * @name ui.grid.directive:uiGridOneBindAriaDescribedby
2465        * @element ANY
2466        * @restrict A
2467        * @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>.
2468        * @description One time binding for the aria-describedby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2469        *<br/>
2470        * <pre>
2471             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId"></div>
2472          </pre>
2473        * Will become:
2474        * <pre>
2475             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
2476          </pre>
2477        */
2478       {tag: 'Describedby', method: 'attr', aria:true},
2479       /**
2480        * @ngdoc directive
2481        * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
2482        * @element ANY
2483        * @restrict A
2484        * @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>.
2485        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2486        * 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
2487        * grid id to each one.
2488        *<br/>
2489        * <pre>
2490             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId"></div>
2491          </pre>
2492        * Will become ([grid.id] will be replaced by the actual grid id):
2493        * <pre>
2494             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
2495          </pre>
2496        */
2497       {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2498     function(v){
2499
2500       var baseDirectiveName = 'uiGridOneBind';
2501       //If it is an aria tag then append the aria label seperately
2502       //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2503       //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.
2504       var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2505       oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2506         return {
2507           restrict: 'A',
2508           require: ['?uiGrid','?^uiGrid'],
2509           link: function(scope, iElement, iAttrs, controllers){
2510             /* Appends the grid id to the beginnig of the value. */
2511             var appendGridId = function(val){
2512               var grid; //Get an instance of the grid if its available
2513               //If its available in the scope then we don't need to try to find it elsewhere
2514               if (scope.grid) {
2515                 grid = scope.grid;
2516               }
2517               //Another possible location to try to find the grid
2518               else if (scope.col && scope.col.grid){
2519                 grid = scope.col.grid;
2520               }
2521               //Last ditch effort: Search through the provided controllers.
2522               else if (!controllers.some( //Go through the controllers till one has the element we need
2523                 function(controller){
2524                   if (controller && controller.grid) {
2525                     grid = controller.grid;
2526                     return true; //We've found the grid
2527                   }
2528               })){
2529                 //We tried our best to find it for you
2530                 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
2531                                  "within the correct scope? Trying to generate id: [gridID]-" + val);
2532                 throw new Error("No valid grid could be found");
2533               }
2534
2535               if (grid){
2536                 var idRegex = new RegExp(grid.id.toString());
2537                 //If the grid id hasn't been appended already in the template declaration
2538                 if (!idRegex.test(val)){
2539                   val = grid.id.toString() + '-' + val;
2540                 }
2541               }
2542               return val;
2543             };
2544
2545             // The watch returns a function to remove itself.
2546             var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2547               if (newV){
2548                 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2549                 if (v.appendGridId) {
2550                   var newIdString = null;
2551                   //Append the id to all of the new ids.
2552                   angular.forEach( newV.split(' '), function(s){
2553                     newIdString = (newIdString ? (newIdString + ' ') : '') +  appendGridId(s);
2554                   });
2555                   newV = newIdString;
2556                 }
2557
2558                 // Append this newValue to the dom element.
2559                 switch (v.method) {
2560                   case 'attr': //The attr method takes two paraams the tag and the value
2561                     if (v.aria) {
2562                       //If it is an aria element then append the aria prefix
2563                       iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
2564                     } else {
2565                       iElement[v.method](v.tag.toLowerCase(),newV);
2566                     }
2567                     break;
2568                   case 'addClass':
2569                     //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2570                     if (angular.isObject(newV) && !angular.isArray(newV)) {
2571                       var results = [];
2572                       var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2573                       angular.forEach(newV, function (value, index) {
2574                         if (value !== null && typeof(value) !== "undefined"){
2575                           nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2576                           if (value) {results.push(index);}
2577                         }
2578                       });
2579                       //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2580                       if (!nonNullFound){
2581                         return; // If not initialized then the watcher should not be removed yet.
2582                       }
2583                       newV = results;
2584                     }
2585
2586                     if (newV) {
2587                       iElement.addClass(angular.isArray(newV) ? newV.join(' ') : newV);
2588                     } else {
2589                       return;
2590                     }
2591                     break;
2592                   default:
2593                     iElement[v.method](newV);
2594                     break;
2595                 }
2596
2597                 //Removes the watcher on itself after the bind
2598                 rmWatcher();
2599               }
2600             // True ensures that equality is determined using angular.equals instead of ===
2601             }, true); //End rm watchers
2602           } //End compile function
2603         }; //End directive return
2604       } // End directive function
2605     ]); //End directive
2606   }); // End angular foreach
2607 })();
2608
2609 (function () {
2610   'use strict';
2611
2612   var module = angular.module('ui.grid');
2613
2614   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2615     function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2616     return {
2617       replace: true,
2618       transclude: true,
2619       templateUrl: 'ui-grid/uiGridRenderContainer',
2620       require: ['^uiGrid', 'uiGridRenderContainer'],
2621       scope: {
2622         containerId: '=',
2623         rowContainerName: '=',
2624         colContainerName: '=',
2625         bindScrollHorizontal: '=',
2626         bindScrollVertical: '=',
2627         enableVerticalScrollbar: '=',
2628         enableHorizontalScrollbar: '='
2629       },
2630       controller: 'uiGridRenderContainer as RenderContainer',
2631       compile: function () {
2632         return {
2633           pre: function prelink($scope, $elm, $attrs, controllers) {
2634
2635             var uiGridCtrl = controllers[0];
2636             var containerCtrl = controllers[1];
2637             var grid = $scope.grid = uiGridCtrl.grid;
2638
2639             // Verify that the render container for this element exists
2640             if (!$scope.rowContainerName) {
2641               throw "No row render container name specified";
2642             }
2643             if (!$scope.colContainerName) {
2644               throw "No column render container name specified";
2645             }
2646
2647             if (!grid.renderContainers[$scope.rowContainerName]) {
2648               throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2649             }
2650             if (!grid.renderContainers[$scope.colContainerName]) {
2651               throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2652             }
2653
2654             var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2655             var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2656
2657             containerCtrl.containerId = $scope.containerId;
2658             containerCtrl.rowContainer = rowContainer;
2659             containerCtrl.colContainer = colContainer;
2660           },
2661           post: function postlink($scope, $elm, $attrs, controllers) {
2662
2663             var uiGridCtrl = controllers[0];
2664             var containerCtrl = controllers[1];
2665
2666             var grid = uiGridCtrl.grid;
2667             var rowContainer = containerCtrl.rowContainer;
2668             var colContainer = containerCtrl.colContainer;
2669             var scrollTop = null;
2670             var scrollLeft = null;
2671
2672
2673             var renderContainer = grid.renderContainers[$scope.containerId];
2674
2675             // Put the container name on this element as a class
2676             $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2677
2678             // Scroll the render container viewport when the mousewheel is used
2679             gridUtil.on.mousewheel($elm, function (event) {
2680               var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2681               if (event.deltaY !== 0) {
2682                 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2683
2684                 scrollTop = containerCtrl.viewport[0].scrollTop;
2685
2686                 // Get the scroll percentage
2687                 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2688                 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2689
2690                 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2691                 //   Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2692                 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2693                   containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2694                 }
2695
2696                 // Keep scrollPercentage within the range 0-1.
2697                 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2698                 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2699
2700                 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2701               }
2702               if (event.deltaX !== 0) {
2703                 var scrollXAmount = event.deltaX * event.deltaFactor;
2704
2705                 // Get the scroll percentage
2706                 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2707                 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2708                 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2709
2710                 // Keep scrollPercentage within the range 0-1.
2711                 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2712                 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2713
2714                 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2715               }
2716
2717               // Let the parent container scroll if the grid is already at the top/bottom
2718               if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2719                   (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2720                 //parent controller scrolls
2721               }
2722               else {
2723                 event.preventDefault();
2724                 event.stopPropagation();
2725                 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2726               }
2727
2728             });
2729
2730             $elm.bind('$destroy', function() {
2731               $elm.unbind('keydown');
2732
2733               ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2734                 $elm.unbind(eventName);
2735               });
2736             });
2737
2738             // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2739             function update() {
2740               var ret = '';
2741
2742               var canvasWidth = colContainer.canvasWidth;
2743               var viewportWidth = colContainer.getViewportWidth();
2744
2745               var canvasHeight = rowContainer.getCanvasHeight();
2746
2747               //add additional height for scrollbar on left and right container
2748               //if ($scope.containerId !== 'body') {
2749               //  canvasHeight -= grid.scrollbarHeight;
2750               //}
2751
2752               var viewportHeight = rowContainer.getViewportHeight();
2753               //shorten the height to make room for a scrollbar placeholder
2754               if (colContainer.needsHScrollbarPlaceholder()) {
2755                 viewportHeight -= grid.scrollbarHeight;
2756               }
2757
2758               var headerViewportWidth,
2759                   footerViewportWidth;
2760               headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2761
2762               // Set canvas dimensions
2763               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2764
2765               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2766
2767               if (renderContainer.explicitHeaderCanvasHeight) {
2768                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2769               }
2770               else {
2771                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2772               }
2773
2774               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2775               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2776
2777               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2778               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2779
2780               return ret;
2781             }
2782
2783             uiGridCtrl.grid.registerStyleComputation({
2784               priority: 6,
2785               func: update
2786             });
2787           }
2788         };
2789       }
2790     };
2791
2792   }]);
2793
2794   module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2795
2796   }]);
2797
2798 })();
2799
2800 (function(){
2801   'use strict';
2802
2803   angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2804     return {
2805       replace: true,
2806       // priority: 2001,
2807       // templateUrl: 'ui-grid/ui-grid-row',
2808       require: ['^uiGrid', '^uiGridRenderContainer'],
2809       scope: {
2810          row: '=uiGridRow',
2811          //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2812          rowRenderIndex: '='
2813       },
2814       compile: function() {
2815         return {
2816           pre: function($scope, $elm, $attrs, controllers) {
2817             var uiGridCtrl = controllers[0];
2818             var containerCtrl = controllers[1];
2819
2820             var grid = uiGridCtrl.grid;
2821
2822             $scope.grid = uiGridCtrl.grid;
2823             $scope.colContainer = containerCtrl.colContainer;
2824
2825             // Function for attaching the template to this scope
2826             var clonedElement, cloneScope;
2827             function compileTemplate() {
2828               $scope.row.getRowTemplateFn.then(function (compiledElementFn) {
2829                 // var compiledElementFn = $scope.row.compiledElementFn;
2830
2831                 // Create a new scope for the contents of this row, so we can destroy it later if need be
2832                 var newScope = $scope.$new();
2833
2834                 compiledElementFn(newScope, function (newElm, scope) {
2835                   // If we already have a cloned element, we need to remove it and destroy its scope
2836                   if (clonedElement) {
2837                     clonedElement.remove();
2838                     cloneScope.$destroy();
2839                   }
2840
2841                   // Empty the row and append the new element
2842                   $elm.empty().append(newElm);
2843
2844                   // Save the new cloned element and scope
2845                   clonedElement = newElm;
2846                   cloneScope = newScope;
2847                 });
2848               });
2849             }
2850
2851             // Initially attach the compiled template to this scope
2852             compileTemplate();
2853
2854             // If the row's compiled element function changes, we need to replace this element's contents with the new compiled template
2855             $scope.$watch('row.getRowTemplateFn', function (newFunc, oldFunc) {
2856               if (newFunc !== oldFunc) {
2857                 compileTemplate();
2858               }
2859             });
2860           },
2861           post: function($scope, $elm, $attrs, controllers) {
2862
2863           }
2864         };
2865       }
2866     };
2867   }]);
2868
2869 })();
2870 (function(){
2871 // 'use strict';
2872
2873   /**
2874    * @ngdoc directive
2875    * @name ui.grid.directive:uiGridStyle
2876    * @element style
2877    * @restrict A
2878    *
2879    * @description
2880    * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2881    *
2882    * @example
2883    <doc:example module="app">
2884    <doc:source>
2885    <script>
2886    var app = angular.module('app', ['ui.grid']);
2887
2888    app.controller('MainCtrl', ['$scope', function ($scope) {
2889           $scope.myStyle = '.blah { border: 1px solid }';
2890         }]);
2891    </script>
2892
2893    <div ng-controller="MainCtrl">
2894    <style ui-grid-style>{{ myStyle }}</style>
2895    <span class="blah">I am in a box.</span>
2896    </div>
2897    </doc:source>
2898    <doc:scenario>
2899       it('should apply the right class to the element', function () {
2900         element(by.css('.blah')).getCssValue('border-top-width')
2901           .then(function(c) {
2902             expect(c).toContain('1px');
2903           });
2904       });
2905    </doc:scenario>
2906    </doc:example>
2907    */
2908
2909
2910   angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
2911     return {
2912       // restrict: 'A',
2913       // priority: 1000,
2914       // require: '?^uiGrid',
2915       link: function($scope, $elm, $attrs, uiGridCtrl) {
2916         // gridUtil.logDebug('ui-grid-style link');
2917         // if (uiGridCtrl === undefined) {
2918         //    gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2919         // }
2920
2921         var interpolateFn = $interpolate($elm.text(), true);
2922
2923         if (interpolateFn) {
2924           $scope.$watch(interpolateFn, function(value) {
2925             $elm.text(value);
2926           });
2927         }
2928
2929           // uiGridCtrl.recalcRowStyles = function() {
2930           //   var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2931           //   var rowHeight = scope.options.rowHeight;
2932
2933           //   var ret = '';
2934           //   var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2935           //   for (var i = 1; i <= rowStyleCount; i++) {
2936           //     ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2937           //     offset = offset + rowHeight;
2938           //   }
2939
2940           //   scope.rowStyles = ret;
2941           // };
2942
2943           // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2944
2945       }
2946     };
2947   }]);
2948
2949 })();
2950
2951 (function(){
2952   'use strict';
2953
2954   angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
2955     function(gridUtil, ScrollEvent, uiGridConstants, $log) {
2956       return {
2957         replace: true,
2958         scope: {},
2959         controllerAs: 'Viewport',
2960         templateUrl: 'ui-grid/uiGridViewport',
2961         require: ['^uiGrid', '^uiGridRenderContainer'],
2962         link: function($scope, $elm, $attrs, controllers) {
2963           // gridUtil.logDebug('viewport post-link');
2964
2965           var uiGridCtrl = controllers[0];
2966           var containerCtrl = controllers[1];
2967
2968           $scope.containerCtrl = containerCtrl;
2969
2970           var rowContainer = containerCtrl.rowContainer;
2971           var colContainer = containerCtrl.colContainer;
2972
2973           var grid = uiGridCtrl.grid;
2974
2975           $scope.grid = uiGridCtrl.grid;
2976
2977           // Put the containers in scope so we can get rows and columns from them
2978           $scope.rowContainer = containerCtrl.rowContainer;
2979           $scope.colContainer = containerCtrl.colContainer;
2980
2981           // Register this viewport with its container
2982           containerCtrl.viewport = $elm;
2983
2984
2985           $elm.on('scroll', scrollHandler);
2986
2987           var ignoreScroll = false;
2988
2989           function scrollHandler(evt) {
2990             //Leaving in this commented code in case it can someday be used
2991             //It does improve performance, but because the horizontal scroll is normalized,
2992             //  using this code will lead to the column header getting slightly out of line with columns
2993             //
2994             //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
2995             //  //don't ask for scrollTop if we just set it
2996             //  ignoreScroll = false;
2997             //  return;
2998             //}
2999             //ignoreScroll = true;
3000
3001             var newScrollTop = $elm[0].scrollTop;
3002             var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
3003
3004             var vertScrollPercentage = rowContainer.scrollVertical(newScrollTop);
3005             var horizScrollPercentage = colContainer.scrollHorizontal(newScrollLeft);
3006
3007             var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
3008             scrollEvent.newScrollLeft = newScrollLeft;
3009             scrollEvent.newScrollTop = newScrollTop;
3010             if ( horizScrollPercentage > -1 ){
3011               scrollEvent.x = { percentage: horizScrollPercentage };
3012             }
3013
3014             if ( vertScrollPercentage > -1 ){
3015               scrollEvent.y = { percentage: vertScrollPercentage };
3016             }
3017
3018             grid.scrollContainers($scope.$parent.containerId, scrollEvent);
3019           }
3020
3021           if ($scope.$parent.bindScrollVertical) {
3022             grid.addVerticalScrollSync($scope.$parent.containerId, syncVerticalScroll);
3023           }
3024
3025           if ($scope.$parent.bindScrollHorizontal) {
3026             grid.addHorizontalScrollSync($scope.$parent.containerId, syncHorizontalScroll);
3027             grid.addHorizontalScrollSync($scope.$parent.containerId + 'header', syncHorizontalHeader);
3028             grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
3029           }
3030
3031           function syncVerticalScroll(scrollEvent){
3032             containerCtrl.prevScrollArgs = scrollEvent;
3033             var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3034             $elm[0].scrollTop = newScrollTop;
3035
3036           }
3037
3038           function syncHorizontalScroll(scrollEvent){
3039             containerCtrl.prevScrollArgs = scrollEvent;
3040             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3041             $elm[0].scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3042           }
3043
3044           function syncHorizontalHeader(scrollEvent){
3045             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3046             if (containerCtrl.headerViewport) {
3047               containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3048             }
3049           }
3050
3051           function syncHorizontalFooter(scrollEvent){
3052             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3053             if (containerCtrl.footerViewport) {
3054               containerCtrl.footerViewport.scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3055             }
3056           }
3057
3058
3059         },
3060         controller: ['$scope', function ($scope) {
3061           this.rowStyle = function (index) {
3062             var rowContainer = $scope.rowContainer;
3063             var colContainer = $scope.colContainer;
3064
3065             var styles = {};
3066
3067             if (index === 0 && rowContainer.currentTopRow !== 0) {
3068               // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
3069               var hiddenRowWidth = (rowContainer.currentTopRow) * rowContainer.grid.options.rowHeight;
3070
3071               // return { 'margin-top': hiddenRowWidth + 'px' };
3072               styles['margin-top'] = hiddenRowWidth + 'px';
3073             }
3074
3075             if (colContainer.currentFirstColumn !== 0) {
3076               if (colContainer.grid.isRTL()) {
3077                 styles['margin-right'] = colContainer.columnOffset + 'px';
3078               }
3079               else {
3080                 styles['margin-left'] = colContainer.columnOffset + 'px';
3081               }
3082             }
3083
3084             return styles;
3085           };
3086         }]
3087       };
3088     }
3089   ]);
3090
3091 })();
3092
3093 (function() {
3094
3095 angular.module('ui.grid')
3096 .directive('uiGridVisible', function uiGridVisibleAction() {
3097   return function ($scope, $elm, $attr) {
3098     $scope.$watch($attr.uiGridVisible, function (visible) {
3099         // $elm.css('visibility', visible ? 'visible' : 'hidden');
3100         $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3101     });
3102   };
3103 });
3104
3105 })();
3106 (function () {
3107   'use strict';
3108
3109   angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3110                     '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3111     function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3112               $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3113       // gridUtil.logDebug('ui-grid controller');
3114
3115       var self = this;
3116
3117       self.grid = gridClassFactory.createGrid($scope.uiGrid);
3118
3119       //assign $scope.$parent if appScope not already assigned
3120       self.grid.appScope = self.grid.appScope || $scope.$parent;
3121
3122       $elm.addClass('grid' + self.grid.id);
3123       self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
3124
3125
3126       // angular.extend(self.grid.options, );
3127
3128       //all properties of grid are available on scope
3129       $scope.grid = self.grid;
3130
3131       if ($attrs.uiGridColumns) {
3132         $attrs.$observe('uiGridColumns', function(value) {
3133           self.grid.options.columnDefs = value;
3134           self.grid.buildColumns()
3135             .then(function(){
3136               self.grid.preCompileCellTemplates();
3137
3138               self.grid.refreshCanvas(true);
3139             });
3140         });
3141       }
3142
3143
3144       // if fastWatch is set we watch only the length and the reference, not every individual object
3145       var deregFunctions = [];
3146       if (self.grid.options.fastWatch) {
3147         self.uiGrid = $scope.uiGrid;
3148         if (angular.isString($scope.uiGrid.data)) {
3149           deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
3150           deregFunctions.push( $scope.$parent.$watch(function() {
3151             if ( self.grid.appScope[$scope.uiGrid.data] ){
3152               return self.grid.appScope[$scope.uiGrid.data].length;
3153             } else {
3154               return undefined;
3155             }
3156           }, dataWatchFunction) );
3157         } else {
3158           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3159           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, function(){ dataWatchFunction($scope.uiGrid.data); }) );
3160         }
3161         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3162         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, function(){ columnDefsWatchFunction($scope.uiGrid.columnDefs); }) );
3163       } else {
3164         if (angular.isString($scope.uiGrid.data)) {
3165           deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
3166         } else {
3167           deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3168         }
3169         deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3170       }
3171
3172
3173       function columnDefsWatchFunction(n, o) {
3174         if (n && n !== o) {
3175           self.grid.options.columnDefs = $scope.uiGrid.columnDefs;
3176           self.grid.buildColumns({ orderByColumnDefs: true })
3177             .then(function(){
3178
3179               self.grid.preCompileCellTemplates();
3180
3181               self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3182             });
3183         }
3184       }
3185
3186       var mostRecentData;
3187
3188       function dataWatchFunction(newData) {
3189         // gridUtil.logDebug('dataWatch fired');
3190         var promises = [];
3191
3192         if ( self.grid.options.fastWatch ){
3193           if (angular.isString($scope.uiGrid.data)) {
3194             newData = self.grid.appScope[$scope.uiGrid.data];
3195           } else {
3196             newData = $scope.uiGrid.data;
3197           }
3198         }
3199
3200         mostRecentData = newData;
3201
3202         if (newData) {
3203           // columns length is greater than the number of row header columns, which don't count because they're created automatically
3204           var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
3205
3206           if (
3207             // If we have no columns
3208             !hasColumns &&
3209             // ... and we don't have a ui-grid-columns attribute, which would define columns for us
3210             !$attrs.uiGridColumns &&
3211             // ... and we have no pre-defined columns
3212             self.grid.options.columnDefs.length === 0 &&
3213             // ... but we DO have data
3214             newData.length > 0
3215           ) {
3216             // ... then build the column definitions from the data that we have
3217             self.grid.buildColumnDefsFromData(newData);
3218           }
3219
3220           // If we haven't built columns before and either have some columns defined or some data defined
3221           if (!hasColumns && (self.grid.options.columnDefs.length > 0 || newData.length > 0)) {
3222             // Build the column set, then pre-compile the column cell templates
3223             promises.push(self.grid.buildColumns()
3224               .then(function() {
3225                 self.grid.preCompileCellTemplates();
3226               }));
3227           }
3228
3229           $q.all(promises).then(function() {
3230             // use most recent data, rather than the potentially outdated data passed into watcher handler
3231             self.grid.modifyRows(mostRecentData)
3232               .then(function () {
3233                 // if (self.viewport) {
3234                   self.grid.redrawInPlace(true);
3235                 // }
3236
3237                 $scope.$evalAsync(function() {
3238                   self.grid.refreshCanvas(true);
3239                   self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
3240                 });
3241               });
3242           });
3243         }
3244       }
3245
3246       var styleWatchDereg = $scope.$watch(function () { return self.grid.styleComputations; }, function() {
3247         self.grid.refreshCanvas(true);
3248       });
3249
3250       $scope.$on('$destroy', function() {
3251         deregFunctions.forEach( function( deregFn ){ deregFn(); });
3252         styleWatchDereg();
3253       });
3254
3255       self.fireEvent = function(eventName, args) {
3256         // Add the grid to the event arguments if it's not there
3257         if (typeof(args) === 'undefined' || args === undefined) {
3258           args = {};
3259         }
3260
3261         if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3262           args.grid = self.grid;
3263         }
3264
3265         $scope.$broadcast(eventName, args);
3266       };
3267
3268       self.innerCompile = function innerCompile(elm) {
3269         $compile(elm)($scope);
3270       };
3271
3272     }]);
3273
3274 /**
3275  *  @ngdoc directive
3276  *  @name ui.grid.directive:uiGrid
3277  *  @element div
3278  *  @restrict EA
3279  *  @param {Object} uiGrid Options for the grid to use
3280  *
3281  *  @description Create a very basic grid.
3282  *
3283  *  @example
3284     <example module="app">
3285       <file name="app.js">
3286         var app = angular.module('app', ['ui.grid']);
3287
3288         app.controller('MainCtrl', ['$scope', function ($scope) {
3289           $scope.data = [
3290             { name: 'Bob', title: 'CEO' },
3291             { name: 'Frank', title: 'Lowly Developer' }
3292           ];
3293         }]);
3294       </file>
3295       <file name="index.html">
3296         <div ng-controller="MainCtrl">
3297           <div ui-grid="{ data: data }"></div>
3298         </div>
3299       </file>
3300     </example>
3301  */
3302 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
3303
3304 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3305 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3306   return {
3307     templateUrl: 'ui-grid/ui-grid',
3308     scope: {
3309       uiGrid: '='
3310     },
3311     replace: true,
3312     transclude: true,
3313     controller: 'uiGridController',
3314     compile: function () {
3315       return {
3316         post: function ($scope, $elm, $attrs, uiGridCtrl) {
3317           var grid = uiGridCtrl.grid;
3318           // Initialize scrollbars (TODO: move to controller??)
3319           uiGridCtrl.scrollbars = [];
3320           grid.element = $elm;
3321
3322
3323           // See if the grid has a rendered width, if not, wait a bit and try again
3324           var sizeCheckInterval = 100; // ms
3325           var maxSizeChecks = 20; // 2 seconds total
3326           var sizeChecks = 0;
3327
3328           // Setup (event listeners) the grid
3329           setup();
3330
3331           // And initialize it
3332           init();
3333
3334           // Mark rendering complete so API events can happen
3335           grid.renderingComplete();
3336
3337           // If the grid doesn't have size currently, wait for a bit to see if it gets size
3338           checkSize();
3339
3340           /*-- Methods --*/
3341
3342           function checkSize() {
3343             // If the grid has no width and we haven't checked more than <maxSizeChecks> times, check again in <sizeCheckInterval> milliseconds
3344             if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
3345               setTimeout(checkSize, sizeCheckInterval);
3346               sizeChecks++;
3347             }
3348             else {
3349               $timeout(init);
3350             }
3351           }
3352
3353           // Setup event listeners and watchers
3354           function setup() {
3355             // Bind to window resize events
3356             angular.element($window).on('resize', gridResize);
3357
3358             // Unbind from window resize events when the grid is destroyed
3359             $elm.on('$destroy', function () {
3360               angular.element($window).off('resize', gridResize);
3361             });
3362
3363             // If we add a left container after render, we need to watch and react
3364             $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3365               if (newValue === oldValue) {
3366                 return;
3367               }
3368               grid.refreshCanvas(true);
3369             });
3370
3371             // If we add a right container after render, we need to watch and react
3372             $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3373               if (newValue === oldValue) {
3374                 return;
3375               }
3376               grid.refreshCanvas(true);
3377             });
3378           }
3379
3380           // Initialize the directive
3381           function init() {
3382             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3383
3384             // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3385             grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3386
3387             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3388
3389             // 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
3390             if (grid.gridHeight <= grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3391               autoAdjustHeight();
3392             }
3393
3394             // Run initial canvas refresh
3395             grid.refreshCanvas(true);
3396           }
3397
3398           // Set the grid's height ourselves in the case that its height would be unusably small
3399           function autoAdjustHeight() {
3400             // Figure out the new height
3401             var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3402             var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
3403             var footerHeight = grid.calcFooterHeight();
3404
3405             var scrollbarHeight = 0;
3406             if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3407               scrollbarHeight = gridUtil.getScrollbarWidth();
3408             }
3409
3410             var maxNumberOfFilters = 0;
3411             // Calculates the maximum number of filters in the columns
3412             angular.forEach(grid.options.columnDefs, function(col) {
3413               if (col.hasOwnProperty('filter')) {
3414                 if (maxNumberOfFilters < 1) {
3415                     maxNumberOfFilters = 1;
3416                 }
3417               }
3418               else if (col.hasOwnProperty('filters')) {
3419                 if (maxNumberOfFilters < col.filters.length) {
3420                     maxNumberOfFilters = col.filters.length;
3421                 }
3422               }
3423             });
3424
3425             if (grid.options.enableFiltering  && !maxNumberOfFilters) {
3426               var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.length && grid.options.columnDefs.every(function(col) {
3427                 return col.enableFiltering === false;
3428               });
3429
3430               if (!allColumnsHaveFilteringTurnedOff) {
3431                 maxNumberOfFilters = 1;
3432               }
3433             }
3434
3435             var filterHeight = maxNumberOfFilters * headerHeight;
3436
3437             var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3438
3439             $elm.css('height', newHeight + 'px');
3440
3441             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3442           }
3443
3444           // Resize the grid on window resize events
3445           function gridResize($event) {
3446             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3447             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3448
3449             grid.refreshCanvas(true);
3450           }
3451         }
3452       };
3453     }
3454   };
3455 }
3456
3457 })();
3458
3459 (function(){
3460   'use strict';
3461
3462   // TODO: rename this file to ui-grid-pinned-container.js
3463
3464   angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3465     return {
3466       restrict: 'EA',
3467       replace: true,
3468       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>',
3469       scope: {
3470         side: '=uiGridPinnedContainer'
3471       },
3472       require: '^uiGrid',
3473       compile: function compile() {
3474         return {
3475           post: function ($scope, $elm, $attrs, uiGridCtrl) {
3476             // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3477
3478             var grid = uiGridCtrl.grid;
3479
3480             var myWidth = 0;
3481
3482             $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3483
3484             // Monkey-patch the viewport width function
3485             if ($scope.side === 'left' || $scope.side === 'right') {
3486               grid.renderContainers[$scope.side].getViewportWidth = monkeyPatchedGetViewportWidth;
3487             }
3488
3489             function monkeyPatchedGetViewportWidth() {
3490               /*jshint validthis: true */
3491               var self = this;
3492
3493               var viewportWidth = 0;
3494               self.visibleColumnCache.forEach(function (column) {
3495                 viewportWidth += column.drawnWidth;
3496               });
3497
3498               var adjustment = self.getViewportAdjustment();
3499
3500               viewportWidth = viewportWidth + adjustment.width;
3501
3502               return viewportWidth;
3503             }
3504
3505             function updateContainerWidth() {
3506               if ($scope.side === 'left' || $scope.side === 'right') {
3507                 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3508                 var width = 0;
3509                 for (var i = 0; i < cols.length; i++) {
3510                   var col = cols[i];
3511                   width += col.drawnWidth || col.width || 0;
3512                 }
3513
3514                 return width;
3515               }
3516             }
3517
3518             function updateContainerDimensions() {
3519               var ret = '';
3520
3521               // Column containers
3522               if ($scope.side === 'left' || $scope.side === 'right') {
3523                 myWidth = updateContainerWidth();
3524
3525                 // gridUtil.logDebug('myWidth', myWidth);
3526
3527                 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3528                 $elm.attr('style', null);
3529
3530              //   var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3531
3532                 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; } ';
3533               }
3534
3535               return ret;
3536             }
3537
3538             grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3539               myWidth = updateContainerWidth();
3540
3541               // Subtract our own width
3542               adjustment.width -= myWidth;
3543               adjustment.side = $scope.side;
3544
3545               return adjustment;
3546             });
3547
3548             // Register style computation to adjust for columns in `side`'s render container
3549             grid.registerStyleComputation({
3550               priority: 15,
3551               func: updateContainerDimensions
3552             });
3553           }
3554         };
3555       }
3556     };
3557   }]);
3558 })();
3559
3560 (function(){
3561
3562 angular.module('ui.grid')
3563 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
3564     function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout, ScrollEvent) {
3565
3566   /**
3567    * @ngdoc object
3568    * @name ui.grid.core.api:PublicApi
3569    * @description Public Api for the core grid features
3570    *
3571    */
3572
3573   /**
3574    * @ngdoc function
3575    * @name ui.grid.class:Grid
3576    * @description Grid is the main viewModel.  Any properties or methods needed to maintain state are defined in
3577    * this prototype.  One instance of Grid is created per Grid directive instance.
3578    * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3579    */
3580   var Grid = function Grid(options) {
3581     var self = this;
3582     // Get the id out of the options, then remove it
3583     if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3584       if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3585         throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3586       }
3587     }
3588     else {
3589       throw new Error('No ID provided. An ID must be given when creating a grid.');
3590     }
3591
3592     self.id = options.id;
3593     delete options.id;
3594
3595     // Get default options
3596     self.options = GridOptions.initialize( options );
3597
3598     /**
3599      * @ngdoc object
3600      * @name appScope
3601      * @propertyOf ui.grid.class:Grid
3602      * @description reference to the application scope (the parent scope of the ui-grid element).  Assigned in ui-grid controller
3603      * <br/>
3604      * use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
3605      */
3606     self.appScope = self.options.appScopeProvider;
3607
3608     self.headerHeight = self.options.headerRowHeight;
3609
3610
3611     /**
3612      * @ngdoc object
3613      * @name footerHeight
3614      * @propertyOf ui.grid.class:Grid
3615      * @description returns the total footer height gridFooter + columnFooter
3616      */
3617     self.footerHeight = self.calcFooterHeight();
3618
3619
3620     /**
3621      * @ngdoc object
3622      * @name columnFooterHeight
3623      * @propertyOf ui.grid.class:Grid
3624      * @description returns the total column footer height
3625      */
3626     self.columnFooterHeight = self.calcColumnFooterHeight();
3627
3628     self.rtl = false;
3629     self.gridHeight = 0;
3630     self.gridWidth = 0;
3631     self.columnBuilders = [];
3632     self.rowBuilders = [];
3633     self.rowsProcessors = [];
3634     self.columnsProcessors = [];
3635     self.styleComputations = [];
3636     self.viewportAdjusters = [];
3637     self.rowHeaderColumns = [];
3638     self.dataChangeCallbacks = {};
3639     self.verticalScrollSyncCallBackFns = {};
3640     self.horizontalScrollSyncCallBackFns = {};
3641
3642     // self.visibleRowCache = [];
3643
3644     // Set of 'render' containers for self grid, which can render sets of rows
3645     self.renderContainers = {};
3646
3647     // Create a
3648     self.renderContainers.body = new GridRenderContainer('body', self);
3649
3650     self.cellValueGetterCache = {};
3651
3652     // Cached function to use with custom row templates
3653     self.getRowTemplateFn = null;
3654
3655
3656     //representation of the rows on the grid.
3657     //these are wrapped references to the actual data rows (options.data)
3658     self.rows = [];
3659
3660     //represents the columns on the grid
3661     self.columns = [];
3662
3663     /**
3664      * @ngdoc boolean
3665      * @name isScrollingVertically
3666      * @propertyOf ui.grid.class:Grid
3667      * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3668      */
3669     self.isScrollingVertically = false;
3670
3671     /**
3672      * @ngdoc boolean
3673      * @name isScrollingHorizontally
3674      * @propertyOf ui.grid.class:Grid
3675      * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3676      */
3677     self.isScrollingHorizontally = false;
3678
3679     /**
3680      * @ngdoc property
3681      * @name scrollDirection
3682      * @propertyOf ui.grid.class:Grid
3683      * @description set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells
3684      * us which direction we are scrolling. Set to NONE via debounced method
3685      */
3686     self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3687
3688     //if true, grid will not respond to any scroll events
3689     self.disableScrolling = false;
3690
3691
3692     function vertical (scrollEvent) {
3693       self.isScrollingVertically = false;
3694       self.api.core.raise.scrollEnd(scrollEvent);
3695       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3696     }
3697
3698     var debouncedVertical = gridUtil.debounce(vertical, self.options.scrollDebounce);
3699     var debouncedVerticalMinDelay = gridUtil.debounce(vertical, 0);
3700
3701     function horizontal (scrollEvent) {
3702       self.isScrollingHorizontally = false;
3703       self.api.core.raise.scrollEnd(scrollEvent);
3704       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3705     }
3706
3707     var debouncedHorizontal = gridUtil.debounce(horizontal, self.options.scrollDebounce);
3708     var debouncedHorizontalMinDelay = gridUtil.debounce(horizontal, 0);
3709
3710
3711     /**
3712      * @ngdoc function
3713      * @name flagScrollingVertically
3714      * @methodOf ui.grid.class:Grid
3715      * @description sets isScrollingVertically to true and sets it to false in a debounced function
3716      */
3717     self.flagScrollingVertically = function(scrollEvent) {
3718       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3719         self.api.core.raise.scrollBegin(scrollEvent);
3720       }
3721       self.isScrollingVertically = true;
3722       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3723         debouncedVerticalMinDelay(scrollEvent);
3724       }
3725       else {
3726         debouncedVertical(scrollEvent);
3727       }
3728     };
3729
3730     /**
3731      * @ngdoc function
3732      * @name flagScrollingHorizontally
3733      * @methodOf ui.grid.class:Grid
3734      * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3735      */
3736     self.flagScrollingHorizontally = function(scrollEvent) {
3737       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3738         self.api.core.raise.scrollBegin(scrollEvent);
3739       }
3740       self.isScrollingHorizontally = true;
3741       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3742         debouncedHorizontalMinDelay(scrollEvent);
3743       }
3744       else {
3745         debouncedHorizontal(scrollEvent);
3746       }
3747     };
3748
3749     self.scrollbarHeight = 0;
3750     self.scrollbarWidth = 0;
3751     if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3752       self.scrollbarHeight = gridUtil.getScrollbarWidth();
3753     }
3754
3755     if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3756       self.scrollbarWidth = gridUtil.getScrollbarWidth();
3757     }
3758
3759
3760
3761     self.api = new GridApi(self);
3762
3763     /**
3764      * @ngdoc function
3765      * @name refresh
3766      * @methodOf ui.grid.core.api:PublicApi
3767      * @description Refresh the rendered grid on screen.
3768      * The refresh method re-runs both the columnProcessors and the
3769      * rowProcessors, as well as calling refreshCanvas to update all
3770      * the grid sizing.  In general you should prefer to use queueGridRefresh
3771      * instead, which is basically a debounced version of refresh.
3772      *
3773      * If you only want to resize the grid, not regenerate all the rows
3774      * and columns, you should consider directly calling refreshCanvas instead.
3775      *
3776      */
3777     self.api.registerMethod( 'core', 'refresh', this.refresh );
3778
3779     /**
3780      * @ngdoc function
3781      * @name queueGridRefresh
3782      * @methodOf ui.grid.core.api:PublicApi
3783      * @description Request a refresh of the rendered grid on screen, if multiple
3784      * calls to queueGridRefresh are made within a digest cycle only one will execute.
3785      * The refresh method re-runs both the columnProcessors and the
3786      * rowProcessors, as well as calling refreshCanvas to update all
3787      * the grid sizing.  In general you should prefer to use queueGridRefresh
3788      * instead, which is basically a debounced version of refresh.
3789      *
3790      */
3791     self.api.registerMethod( 'core', 'queueGridRefresh', this.queueGridRefresh );
3792
3793     /**
3794      * @ngdoc function
3795      * @name refreshRows
3796      * @methodOf ui.grid.core.api:PublicApi
3797      * @description Runs only the rowProcessors, columns remain as they were.
3798      * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
3799      * @returns {promise} promise that is resolved when render completes?
3800      *
3801      */
3802     self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3803
3804     /**
3805      * @ngdoc function
3806      * @name queueRefresh
3807      * @methodOf ui.grid.core.api:PublicApi
3808      * @description Requests execution of refreshCanvas, if multiple requests are made
3809      * during a digest cycle only one will run.  RefreshCanvas updates the grid sizing.
3810      * @returns {promise} promise that is resolved when render completes?
3811      *
3812      */
3813     self.api.registerMethod( 'core', 'queueRefresh', this.queueRefresh );
3814
3815     /**
3816      * @ngdoc function
3817      * @name handleWindowResize
3818      * @methodOf ui.grid.core.api:PublicApi
3819      * @description Trigger a grid resize, normally this would be picked
3820      * up by a watch on window size, but in some circumstances it is necessary
3821      * to call this manually
3822      * @returns {promise} promise that is resolved when render completes?
3823      *
3824      */
3825     self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3826
3827
3828     /**
3829      * @ngdoc function
3830      * @name addRowHeaderColumn
3831      * @methodOf ui.grid.core.api:PublicApi
3832      * @description adds a row header column to the grid
3833      * @param {object} column def
3834      *
3835      */
3836     self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
3837
3838     /**
3839      * @ngdoc function
3840      * @name scrollToIfNecessary
3841      * @methodOf ui.grid.core.api:PublicApi
3842      * @description Scrolls the grid to make a certain row and column combo visible,
3843      *   in the case that it is not completely visible on the screen already.
3844      * @param {GridRow} gridRow row to make visible
3845      * @param {GridCol} gridCol column to make visible
3846      * @returns {promise} a promise that is resolved when scrolling is complete
3847      *
3848      */
3849     self.api.registerMethod( 'core', 'scrollToIfNecessary', function(gridRow, gridCol) { return self.scrollToIfNecessary(gridRow, gridCol);} );
3850
3851     /**
3852      * @ngdoc function
3853      * @name scrollTo
3854      * @methodOf ui.grid.core.api:PublicApi
3855      * @description Scroll the grid such that the specified
3856      * row and column is in view
3857      * @param {object} rowEntity gridOptions.data[] array instance to make visible
3858      * @param {object} colDef to make visible
3859      * @returns {promise} a promise that is resolved after any scrolling is finished
3860      */
3861     self.api.registerMethod( 'core', 'scrollTo', function (rowEntity, colDef) { return self.scrollTo(rowEntity, colDef);}  );
3862
3863     /**
3864      * @ngdoc function
3865      * @name registerRowsProcessor
3866      * @methodOf ui.grid.core.api:PublicApi
3867      * @description
3868      * Register a "rows processor" function. When the rows are updated,
3869      * the grid calls each registered "rows processor", which has a chance
3870      * to alter the set of rows (sorting, etc) as long as the count is not
3871      * modified.
3872      *
3873      * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
3874      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3875      * return the updated rows list, which is passed to the next processor in the chain
3876      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3877      * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
3878      *
3879      * At present allRowsVisible is running at 50, sort manipulations running at 60-65, filter is running at 100,
3880      * sort is at 200, grouping and treeview at 400-410, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
3881      */
3882     self.api.registerMethod( 'core', 'registerRowsProcessor', this.registerRowsProcessor  );
3883
3884     /**
3885      * @ngdoc function
3886      * @name registerColumnsProcessor
3887      * @methodOf ui.grid.core.api:PublicApi
3888      * @description
3889      * Register a "columns processor" function. When the columns are updated,
3890      * the grid calls each registered "columns processor", which has a chance
3891      * to alter the set of columns as long as the count is not
3892      * modified.
3893      *
3894      * @param {function(renderedColumnsToProcess, rows )} processorFunction columns processor function, which
3895      * is run in the context of the grid (i.e. this for the function will be the grid), and must
3896      * return the updated columns list, which is passed to the next processor in the chain
3897      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
3898      * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
3899      *
3900      * 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)
3901      */
3902     self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor  );
3903
3904
3905
3906     /**
3907      * @ngdoc function
3908      * @name sortHandleNulls
3909      * @methodOf ui.grid.core.api:PublicApi
3910      * @description A null handling method that can be used when building custom sort
3911      * functions
3912      * @example
3913      * <pre>
3914      *   mySortFn = function(a, b) {
3915      *   var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3916      *   if ( nulls !== null ){
3917      *     return nulls;
3918      *   } else {
3919      *     // your code for sorting here
3920      *   };
3921      * </pre>
3922      * @param {object} a sort value a
3923      * @param {object} b sort value b
3924      * @returns {number} null if there were no nulls/undefineds, otherwise returns
3925      * a sort value that should be passed back from the sort function
3926      *
3927      */
3928     self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
3929
3930
3931     /**
3932      * @ngdoc function
3933      * @name sortChanged
3934      * @methodOf  ui.grid.core.api:PublicApi
3935      * @description The sort criteria on one or more columns has
3936      * changed.  Provides as parameters the grid and the output of
3937      * getColumnSorting, which is an array of gridColumns
3938      * that have sorting on them, sorted in priority order.
3939      *
3940      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3941      * @param {Function} callBack Will be called when the event is emited. The function passes back the grid and an array of 
3942      * columns with sorts on them, in priority order.
3943      *
3944      * @example
3945      * <pre>
3946      *      gridApi.core.on.sortChanged( $scope, function(grid, sortColumns){
3947      *        // do something
3948      *      });
3949      * </pre>
3950      */
3951     self.api.registerEvent( 'core', 'sortChanged' );
3952
3953       /**
3954      * @ngdoc function
3955      * @name columnVisibilityChanged
3956      * @methodOf  ui.grid.core.api:PublicApi
3957      * @description The visibility of a column has changed,
3958      * the column itself is passed out as a parameter of the event.
3959      *
3960      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3961      * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
3962      *
3963      * @example
3964      * <pre>
3965      *      gridApi.core.on.columnVisibilityChanged( $scope, function (column) {
3966      *        // do something
3967      *      } );
3968      * </pre>
3969      */
3970     self.api.registerEvent( 'core', 'columnVisibilityChanged' );
3971
3972     /**
3973      * @ngdoc method
3974      * @name notifyDataChange
3975      * @methodOf ui.grid.core.api:PublicApi
3976      * @description Notify the grid that a data or config change has occurred,
3977      * where that change isn't something the grid was otherwise noticing.  This
3978      * might be particularly relevant where you've changed values within the data
3979      * and you'd like cell classes to be re-evaluated, or changed config within
3980      * the columnDef and you'd like headerCellClasses to be re-evaluated.
3981      * @param {string} type one of the
3982      * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3983      * us which refreshes to fire.
3984      *
3985      */
3986     self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
3987
3988     /**
3989      * @ngdoc method
3990      * @name clearAllFilters
3991      * @methodOf ui.grid.core.api:PublicApi
3992      * @description Clears all filters and optionally refreshes the visible rows.
3993      * @param {object} refreshRows Defaults to true.
3994      * @param {object} clearConditions Defaults to false.
3995      * @param {object} clearFlags Defaults to false.
3996      * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
3997      */
3998     self.api.registerMethod('core', 'clearAllFilters', this.clearAllFilters);
3999
4000     self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
4001     self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
4002     self.registerDataChangeCallback( self.updateFooterHeightCallback, [uiGridConstants.dataChange.OPTIONS]);
4003
4004     self.registerStyleComputation({
4005       priority: 10,
4006       func: self.getFooterStyles
4007     });
4008   };
4009
4010    Grid.prototype.calcFooterHeight = function () {
4011      if (!this.hasFooter()) {
4012        return 0;
4013      }
4014
4015      var height = 0;
4016      if (this.options.showGridFooter) {
4017        height += this.options.gridFooterHeight;
4018      }
4019
4020      height += this.calcColumnFooterHeight();
4021
4022      return height;
4023    };
4024
4025    Grid.prototype.calcColumnFooterHeight = function () {
4026      var height = 0;
4027
4028      if (this.options.showColumnFooter) {
4029        height += this.options.columnFooterHeight;
4030      }
4031
4032      return height;
4033    };
4034
4035    Grid.prototype.getFooterStyles = function () {
4036      var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }';
4037      style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }';
4038      return style;
4039    };
4040
4041   Grid.prototype.hasFooter = function () {
4042    return this.options.showGridFooter || this.options.showColumnFooter;
4043   };
4044
4045   /**
4046    * @ngdoc function
4047    * @name isRTL
4048    * @methodOf ui.grid.class:Grid
4049    * @description Returns true if grid is RightToLeft
4050    */
4051   Grid.prototype.isRTL = function () {
4052     return this.rtl;
4053   };
4054
4055
4056   /**
4057    * @ngdoc function
4058    * @name registerColumnBuilder
4059    * @methodOf ui.grid.class:Grid
4060    * @description When the build creates columns from column definitions, the columnbuilders will be called to add
4061    * additional properties to the column.
4062    * @param {function(colDef, col, gridOptions)} columnBuilder function to be called
4063    */
4064   Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
4065     this.columnBuilders.push(columnBuilder);
4066   };
4067
4068   /**
4069    * @ngdoc function
4070    * @name buildColumnDefsFromData
4071    * @methodOf ui.grid.class:Grid
4072    * @description Populates columnDefs from the provided data
4073    * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
4074    */
4075   Grid.prototype.buildColumnDefsFromData = function (dataRows){
4076     this.options.columnDefs =  gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
4077   };
4078
4079   /**
4080    * @ngdoc function
4081    * @name registerRowBuilder
4082    * @methodOf ui.grid.class:Grid
4083    * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
4084    * additional properties to the row.
4085    * @param {function(row, gridOptions)} rowBuilder function to be called
4086    */
4087   Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
4088     this.rowBuilders.push(rowBuilder);
4089   };
4090
4091
4092   /**
4093    * @ngdoc function
4094    * @name registerDataChangeCallback
4095    * @methodOf ui.grid.class:Grid
4096    * @description When a data change occurs, the data change callbacks of the specified type
4097    * will be called.  The rules are:
4098    *
4099    * - when the data watch fires, that is considered a ROW change (the data watch only notices
4100    *   added or removed rows)
4101    * - when the api is called to inform us of a change, the declared type of that change is used
4102    * - when a cell edit completes, the EDIT callbacks are triggered
4103    * - when the columnDef watch fires, the COLUMN callbacks are triggered
4104    * - when the options watch fires, the OPTIONS callbacks are triggered
4105    *
4106    * For a given event:
4107    * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks
4108    * - ROW calls ROW and ALL callbacks
4109    * - EDIT calls EDIT and ALL callbacks
4110    * - COLUMN calls COLUMN and ALL callbacks
4111    * - OPTIONS calls OPTIONS and ALL callbacks
4112    *
4113    * @param {function(grid)} callback function to be called
4114    * @param {array} types the types of data change you want to be informed of.  Values from
4115    * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ).  Optional and defaults to
4116    * ALL
4117    * @returns {function} deregister function - a function that can be called to deregister this callback
4118    */
4119   Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4120     var uid = gridUtil.nextUid();
4121     if ( !types ){
4122       types = [uiGridConstants.dataChange.ALL];
4123     }
4124     if ( !Array.isArray(types)){
4125       gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
4126     }
4127     this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4128
4129     var self = this;
4130     var deregisterFunction = function() {
4131       delete self.dataChangeCallbacks[uid];
4132     };
4133     return deregisterFunction;
4134   };
4135
4136   /**
4137    * @ngdoc function
4138    * @name callDataChangeCallbacks
4139    * @methodOf ui.grid.class:Grid
4140    * @description Calls the callbacks based on the type of data change that
4141    * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
4142    * event type is matching, or if the type is ALL.
4143    * @param {number} type the type of event that occurred - one of the
4144    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4145    */
4146   Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4147     angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4148       if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
4149            callback.types.indexOf( type ) !== -1 ||
4150            type === uiGridConstants.dataChange.ALL ) {
4151         if (callback._this) {
4152            callback.callback.apply(callback._this,this);
4153         }
4154         else {
4155           callback.callback( this );
4156         }
4157       }
4158     }, this);
4159   };
4160
4161   /**
4162    * @ngdoc function
4163    * @name notifyDataChange
4164    * @methodOf ui.grid.class:Grid
4165    * @description Notifies us that a data change has occurred, used in the public
4166    * api for users to tell us when they've changed data or some other event that
4167    * our watches cannot pick up
4168    * @param {string} type the type of event that occurred - one of the
4169    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4170    */
4171   Grid.prototype.notifyDataChange = function notifyDataChange(type) {
4172     var constants = uiGridConstants.dataChange;
4173     if ( type === constants.ALL ||
4174          type === constants.COLUMN ||
4175          type === constants.EDIT ||
4176          type === constants.ROW ||
4177          type === constants.OPTIONS ){
4178       this.callDataChangeCallbacks( type );
4179     } else {
4180       gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
4181     }
4182   };
4183
4184
4185   /**
4186    * @ngdoc function
4187    * @name columnRefreshCallback
4188    * @methodOf ui.grid.class:Grid
4189    * @description refreshes the grid when a column refresh
4190    * is notified, which triggers handling of the visible flag.
4191    * This is called on uiGridConstants.dataChange.COLUMN, and is
4192    * registered as a dataChangeCallback in grid.js
4193    * @param {string} name column name
4194    */
4195   Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4196     grid.buildColumns();
4197     grid.queueGridRefresh();
4198   };
4199
4200
4201   /**
4202    * @ngdoc function
4203    * @name processRowsCallback
4204    * @methodOf ui.grid.class:Grid
4205    * @description calls the row processors, specifically
4206    * intended to reset the sorting when an edit is called,
4207    * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4208    * @param {string} name column name
4209    */
4210   Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4211     grid.queueGridRefresh();
4212   };
4213
4214
4215   /**
4216    * @ngdoc function
4217    * @name updateFooterHeightCallback
4218    * @methodOf ui.grid.class:Grid
4219    * @description recalculates the footer height,
4220    * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4221    * @param {string} name column name
4222    */
4223   Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4224     grid.footerHeight = grid.calcFooterHeight();
4225     grid.columnFooterHeight = grid.calcColumnFooterHeight();
4226   };
4227
4228
4229   /**
4230    * @ngdoc function
4231    * @name getColumn
4232    * @methodOf ui.grid.class:Grid
4233    * @description returns a grid column for the column name
4234    * @param {string} name column name
4235    */
4236   Grid.prototype.getColumn = function getColumn(name) {
4237     var columns = this.columns.filter(function (column) {
4238       return column.colDef.name === name;
4239     });
4240     return columns.length > 0 ? columns[0] : null;
4241   };
4242
4243   /**
4244    * @ngdoc function
4245    * @name getColDef
4246    * @methodOf ui.grid.class:Grid
4247    * @description returns a grid colDef for the column name
4248    * @param {string} name column.field
4249    */
4250   Grid.prototype.getColDef = function getColDef(name) {
4251     var colDefs = this.options.columnDefs.filter(function (colDef) {
4252       return colDef.name === name;
4253     });
4254     return colDefs.length > 0 ? colDefs[0] : null;
4255   };
4256
4257   /**
4258    * @ngdoc function
4259    * @name assignTypes
4260    * @methodOf ui.grid.class:Grid
4261    * @description uses the first row of data to assign colDef.type for any types not defined.
4262    */
4263   /**
4264    * @ngdoc property
4265    * @name type
4266    * @propertyOf ui.grid.class:GridOptions.columnDef
4267    * @description the type of the column, used in sorting.  If not provided then the
4268    * grid will guess the type.  Add this only if the grid guessing is not to your
4269    * satisfaction.  One of:
4270    * - 'string'
4271    * - 'boolean'
4272    * - 'number'
4273    * - 'date'
4274    * - 'object'
4275    * - 'numberStr'
4276    * Note that if you choose date, your dates should be in a javascript date type
4277    *
4278    */
4279   Grid.prototype.assignTypes = function(){
4280     var self = this;
4281     self.options.columnDefs.forEach(function (colDef, index) {
4282
4283       //Assign colDef type if not specified
4284       if (!colDef.type) {
4285         var col = new GridColumn(colDef, index, self);
4286         var firstRow = self.rows.length > 0 ? self.rows[0] : null;
4287         if (firstRow) {
4288           colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
4289         }
4290         else {
4291           colDef.type = 'string';
4292         }
4293       }
4294     });
4295   };
4296
4297
4298   /**
4299    * @ngdoc function
4300    * @name isRowHeaderColumn
4301    * @methodOf ui.grid.class:Grid
4302    * @description returns true if the column is a row Header
4303    * @param {object} column column
4304    */
4305   Grid.prototype.isRowHeaderColumn = function isRowHeaderColumn(column) {
4306     return this.rowHeaderColumns.indexOf(column) !== -1;
4307   };
4308
4309   /**
4310   * @ngdoc function
4311   * @name addRowHeaderColumn
4312   * @methodOf ui.grid.class:Grid
4313   * @description adds a row header column to the grid
4314   * @param {object} column def
4315   */
4316   Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
4317     var self = this;
4318     var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
4319     rowHeaderCol.isRowHeader = true;
4320     if (self.isRTL()) {
4321       self.createRightContainer();
4322       rowHeaderCol.renderContainer = 'right';
4323     }
4324     else {
4325       self.createLeftContainer();
4326       rowHeaderCol.renderContainer = 'left';
4327     }
4328
4329     // relies on the default column builder being first in array, as it is instantiated
4330     // as part of grid creation
4331     self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4332       .then(function(){
4333         rowHeaderCol.enableFiltering = false;
4334         rowHeaderCol.enableSorting = false;
4335         rowHeaderCol.enableHiding = false;
4336         self.rowHeaderColumns.push(rowHeaderCol);
4337         self.buildColumns()
4338           .then( function() {
4339             self.preCompileCellTemplates();
4340             self.queueGridRefresh();
4341           });
4342       });
4343   };
4344
4345   /**
4346    * @ngdoc function
4347    * @name getOnlyDataColumns
4348    * @methodOf ui.grid.class:Grid
4349    * @description returns all columns except for rowHeader columns
4350    */
4351   Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4352     var self = this;
4353     var cols = [];
4354     self.columns.forEach(function (col) {
4355       if (self.rowHeaderColumns.indexOf(col) === -1) {
4356         cols.push(col);
4357       }
4358     });
4359     return cols;
4360   };
4361
4362   /**
4363    * @ngdoc function
4364    * @name buildColumns
4365    * @methodOf ui.grid.class:Grid
4366    * @description creates GridColumn objects from the columnDefinition.  Calls each registered
4367    * columnBuilder to further process the column
4368    * @param {object} options  An object contains options to use when building columns
4369    *
4370    * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
4371    *
4372    * @returns {Promise} a promise to load any needed column resources
4373    */
4374   Grid.prototype.buildColumns = function buildColumns(opts) {
4375     var options = {
4376       orderByColumnDefs: false
4377     };
4378
4379     angular.extend(options, opts);
4380
4381     // gridUtil.logDebug('buildColumns');
4382     var self = this;
4383     var builderPromises = [];
4384     var headerOffset = self.rowHeaderColumns.length;
4385     var i;
4386
4387     // Remove any columns for which a columnDef cannot be found
4388     // Deliberately don't use forEach, as it doesn't like splice being called in the middle
4389     // Also don't cache columns.length, as it will change during this operation
4390     for (i = 0; i < self.columns.length; i++){
4391       if (!self.getColDef(self.columns[i].name)) {
4392         self.columns.splice(i, 1);
4393         i--;
4394       }
4395     }
4396
4397     //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4398     self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
4399       self.columns.unshift(rowHeaderColumn);
4400     });
4401
4402
4403     // look at each column def, and update column properties to match.  If the column def
4404     // doesn't have a column, then splice in a new gridCol
4405     self.options.columnDefs.forEach(function (colDef, index) {
4406       self.preprocessColDef(colDef);
4407       var col = self.getColumn(colDef.name);
4408
4409       if (!col) {
4410         col = new GridColumn(colDef, gridUtil.nextUid(), self);
4411         self.columns.splice(index + headerOffset, 0, col);
4412       }
4413       else {
4414         // tell updateColumnDef that the column was pre-existing
4415         col.updateColumnDef(colDef, false);
4416       }
4417
4418       self.columnBuilders.forEach(function (builder) {
4419         builderPromises.push(builder.call(self, colDef, col, self.options));
4420       });
4421     });
4422
4423     /*** Reorder columns if necessary ***/
4424     if (!!options.orderByColumnDefs) {
4425       // Create a shallow copy of the columns as a cache
4426       var columnCache = self.columns.slice(0);
4427
4428       // We need to allow for the "row headers" when mapping from the column defs array to the columns array
4429       //   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]
4430
4431       // 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
4432       // columns will be shorter than columnDefs.  In this situation we'll avoid an error, but the user will still get an unexpected result
4433       var len = Math.min(self.options.columnDefs.length, self.columns.length);
4434       for (i = 0; i < len; i++) {
4435         // If the column at this index has a different name than the column at the same index in the column defs...
4436         if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) {
4437           // Replace the one in the cache with the appropriate column
4438           columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name);
4439         }
4440         else {
4441           // Otherwise just copy over the one from the initial columns
4442           columnCache[i + headerOffset] = self.columns[i + headerOffset];
4443         }
4444       }
4445
4446       // Empty out the columns array, non-destructively
4447       self.columns.length = 0;
4448
4449       // And splice in the updated, ordered columns from the cache
4450       Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
4451     }
4452
4453     return $q.all(builderPromises).then(function(){
4454       if (self.rows.length > 0){
4455         self.assignTypes();
4456       }
4457     });
4458   };
4459
4460 /**
4461  * @ngdoc function
4462  * @name preCompileCellTemplates
4463  * @methodOf ui.grid.class:Grid
4464  * @description precompiles all cell templates
4465  */
4466   Grid.prototype.preCompileCellTemplates = function() {
4467     var self = this;
4468
4469     var preCompileTemplate = function( col ) {
4470       var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4471       html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4472
4473       var compiledElementFn = $compile(html);
4474       col.compiledElementFn = compiledElementFn;
4475
4476       if (col.compiledElementFnDefer) {
4477         col.compiledElementFnDefer.resolve(col.compiledElementFn);
4478       }
4479     };
4480
4481     this.columns.forEach(function (col) {
4482       if ( col.cellTemplate ){
4483         preCompileTemplate( col );
4484       } else if ( col.cellTemplatePromise ){
4485         col.cellTemplatePromise.then( function() {
4486           preCompileTemplate( col );
4487         });
4488       }
4489     });
4490   };
4491
4492   /**
4493    * @ngdoc function
4494    * @name getGridQualifiedColField
4495    * @methodOf ui.grid.class:Grid
4496    * @description Returns the $parse-able accessor for a column within its $scope
4497    * @param {GridColumn} col col object
4498    */
4499   Grid.prototype.getQualifiedColField = function (col) {
4500     return 'row.entity.' + gridUtil.preEval(col.field);
4501   };
4502
4503   /**
4504    * @ngdoc function
4505    * @name createLeftContainer
4506    * @methodOf ui.grid.class:Grid
4507    * @description creates the left render container if it doesn't already exist
4508    */
4509   Grid.prototype.createLeftContainer = function() {
4510     if (!this.hasLeftContainer()) {
4511       this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
4512     }
4513   };
4514
4515   /**
4516    * @ngdoc function
4517    * @name createRightContainer
4518    * @methodOf ui.grid.class:Grid
4519    * @description creates the right render container if it doesn't already exist
4520    */
4521   Grid.prototype.createRightContainer = function() {
4522     if (!this.hasRightContainer()) {
4523       this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
4524     }
4525   };
4526
4527   /**
4528    * @ngdoc function
4529    * @name hasLeftContainer
4530    * @methodOf ui.grid.class:Grid
4531    * @description returns true if leftContainer exists
4532    */
4533   Grid.prototype.hasLeftContainer = function() {
4534     return this.renderContainers.left !== undefined;
4535   };
4536
4537   /**
4538    * @ngdoc function
4539    * @name hasRightContainer
4540    * @methodOf ui.grid.class:Grid
4541    * @description returns true if rightContainer exists
4542    */
4543   Grid.prototype.hasRightContainer = function() {
4544     return this.renderContainers.right !== undefined;
4545   };
4546
4547
4548       /**
4549    * undocumented function
4550    * @name preprocessColDef
4551    * @methodOf ui.grid.class:Grid
4552    * @description defaults the name property from field to maintain backwards compatibility with 2.x
4553    * validates that name or field is present
4554    */
4555   Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
4556     var self = this;
4557
4558     if (!colDef.field && !colDef.name) {
4559       throw new Error('colDef.name or colDef.field property is required');
4560     }
4561
4562     //maintain backwards compatibility with 2.x
4563     //field was required in 2.x.  now name is required
4564     if (colDef.name === undefined && colDef.field !== undefined) {
4565       // See if the column name already exists:
4566       var newName = colDef.field,
4567         counter = 2;
4568       while (self.getColumn(newName)) {
4569         newName = colDef.field + counter.toString();
4570         counter++;
4571       }
4572       colDef.name = newName;
4573     }
4574   };
4575
4576   // 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
4577   Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
4578     var self = this;
4579
4580     var t = [];
4581     for (var i = 0; i < n.length; i++) {
4582       var nV = nAccessor ? n[i][nAccessor] : n[i];
4583
4584       var found = false;
4585       for (var j = 0; j < o.length; j++) {
4586         var oV = oAccessor ? o[j][oAccessor] : o[j];
4587         if (self.options.rowEquality(nV, oV)) {
4588           found = true;
4589           break;
4590         }
4591       }
4592       if (!found) {
4593         t.push(nV);
4594       }
4595     }
4596
4597     return t;
4598   };
4599
4600   /**
4601    * @ngdoc function
4602    * @name getRow
4603    * @methodOf ui.grid.class:Grid
4604    * @description returns the GridRow that contains the rowEntity
4605    * @param {object} rowEntity the gridOptions.data array element instance
4606    * @param {array} rows [optional] the rows to look in - if not provided then
4607    * looks in grid.rows
4608    */
4609   Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
4610     var self = this;
4611
4612     lookInRows = typeof(lookInRows) === 'undefined' ? self.rows : lookInRows;
4613
4614     var rows = lookInRows.filter(function (row) {
4615       return self.options.rowEquality(row.entity, rowEntity);
4616     });
4617     return rows.length > 0 ? rows[0] : null;
4618   };
4619
4620
4621   /**
4622    * @ngdoc function
4623    * @name modifyRows
4624    * @methodOf ui.grid.class:Grid
4625    * @description creates or removes GridRow objects from the newRawData array.  Calls each registered
4626    * rowBuilder to further process the row
4627    * @param {array} newRawData Modified set of data
4628    *
4629    * This method aims to achieve three things:
4630    * 1. the resulting rows array is in the same order as the newRawData, we'll call
4631    * rowsProcessors immediately after to sort the data anyway
4632    * 2. if we have row hashing available, we try to use the rowHash to find the row
4633    * 3. no memory leaks - rows that are no longer in newRawData need to be garbage collected
4634    *
4635    * The basic logic flow makes use of the newRawData, oldRows and oldHash, and creates
4636    * the newRows and newHash
4637    *
4638    * ```
4639    * newRawData.forEach newEntity
4640    *   if (hashing enabled)
4641    *     check oldHash for newEntity
4642    *   else
4643    *     look for old row directly in oldRows
4644    *   if !oldRowFound     // must be a new row
4645    *     create newRow
4646    *   append to the newRows and add to newHash
4647    *   run the processors
4648    * ```
4649    *
4650    * Rows are identified using the hashKey if configured.  If not configured, then rows
4651    * are identified using the gridOptions.rowEquality function
4652    *
4653    * This method is useful when trying to select rows immediately after loading data without
4654    * using a $timeout/$interval, e.g.:
4655    *
4656    *   $scope.gridOptions.data =  someData;
4657    *   $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
4658    *   $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4659    *
4660    * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
4661    * originally selected rows to be re-selected))
4662    */
4663   Grid.prototype.modifyRows = function modifyRows(newRawData) {
4664     var self = this;
4665     var oldRows = self.rows.slice(0);
4666     var oldRowHash = self.rowHashMap || self.createRowHashMap();
4667     self.rowHashMap = self.createRowHashMap();
4668     self.rows.length = 0;
4669
4670     newRawData.forEach( function( newEntity, i ) {
4671       var newRow;
4672       if ( self.options.enableRowHashing ){
4673         // if hashing is enabled, then this row will be in the hash if we already know about it
4674         newRow = oldRowHash.get( newEntity );
4675       } else {
4676         // otherwise, manually search the oldRows to see if we can find this row
4677         newRow = self.getRow(newEntity, oldRows);
4678       }
4679
4680       // if we didn't find the row, it must be new, so create it
4681       if ( !newRow ){
4682         newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
4683       }
4684
4685       self.rows.push( newRow );
4686       self.rowHashMap.put( newEntity, newRow );
4687     });
4688
4689     self.assignTypes();
4690
4691     var p1 = $q.when(self.processRowsProcessors(self.rows))
4692       .then(function (renderableRows) {
4693         return self.setVisibleRows(renderableRows);
4694       });
4695
4696     var p2 = $q.when(self.processColumnsProcessors(self.columns))
4697       .then(function (renderableColumns) {
4698         return self.setVisibleColumns(renderableColumns);
4699       });
4700
4701     return $q.all([p1, p2]);
4702   };
4703
4704
4705   /**
4706    * Private Undocumented Method
4707    * @name addRows
4708    * @methodOf ui.grid.class:Grid
4709    * @description adds the newRawData array of rows to the grid and calls all registered
4710    * rowBuilders. this keyword will reference the grid
4711    */
4712   Grid.prototype.addRows = function addRows(newRawData) {
4713     var self = this;
4714
4715     var existingRowCount = self.rows.length;
4716     for (var i = 0; i < newRawData.length; i++) {
4717       var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4718
4719       if (self.options.enableRowHashing) {
4720         var found = self.rowHashMap.get(newRow.entity);
4721         if (found) {
4722           found.row = newRow;
4723         }
4724       }
4725
4726       self.rows.push(newRow);
4727     }
4728   };
4729
4730   /**
4731    * @ngdoc function
4732    * @name processRowBuilders
4733    * @methodOf ui.grid.class:Grid
4734    * @description processes all RowBuilders for the gridRow
4735    * @param {GridRow} gridRow reference to gridRow
4736    * @returns {GridRow} the gridRow with all additional behavior added
4737    */
4738   Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4739     var self = this;
4740
4741     self.rowBuilders.forEach(function (builder) {
4742       builder.call(self, gridRow, self.options);
4743     });
4744
4745     return gridRow;
4746   };
4747
4748   /**
4749    * @ngdoc function
4750    * @name registerStyleComputation
4751    * @methodOf ui.grid.class:Grid
4752    * @description registered a styleComputation function
4753    *
4754    * If the function returns a value it will be appended into the grid's `<style>` block
4755    * @param {function($scope)} styleComputation function
4756    */
4757   Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4758     this.styleComputations.push(styleComputationInfo);
4759   };
4760
4761
4762   // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4763   // Grid.prototype.registerRowFilter = function(filter) {
4764   //   // TODO(c0bra): validate filter?
4765
4766   //   this.rowFilters.push(filter);
4767   // };
4768
4769   // Grid.prototype.removeRowFilter = function(filter) {
4770   //   var idx = this.rowFilters.indexOf(filter);
4771
4772   //   if (typeof(idx) !== 'undefined' && idx !== undefined) {
4773   //     this.rowFilters.slice(idx, 1);
4774   //   }
4775   // };
4776
4777   // Grid.prototype.processRowFilters = function(rows) {
4778   //   var self = this;
4779   //   self.rowFilters.forEach(function (filter) {
4780   //     filter.call(self, rows);
4781   //   });
4782   // };
4783
4784
4785   /**
4786    * @ngdoc function
4787    * @name registerRowsProcessor
4788    * @methodOf ui.grid.class:Grid
4789    * @description
4790    *
4791    * Register a "rows processor" function. When the rows are updated,
4792    * the grid calls each registered "rows processor", which has a chance
4793    * to alter the set of rows (sorting, etc) as long as the count is not
4794    * modified.
4795    *
4796    * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4797    * is run in the context of the grid (i.e. this for the function will be the grid), and must
4798    * return the updated rows list, which is passed to the next processor in the chain
4799    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4800    * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
4801    *
4802    * 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)
4803    *
4804    */
4805   Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor, priority) {
4806     if (!angular.isFunction(processor)) {
4807       throw 'Attempt to register non-function rows processor: ' + processor;
4808     }
4809
4810     this.rowsProcessors.push({processor: processor, priority: priority});
4811     this.rowsProcessors.sort(function sortByPriority( a, b ){
4812       return a.priority - b.priority;
4813     });
4814   };
4815
4816   /**
4817    * @ngdoc function
4818    * @name removeRowsProcessor
4819    * @methodOf ui.grid.class:Grid
4820    * @param {function(renderableRows)} rows processor function
4821    * @description Remove a registered rows processor
4822    */
4823   Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
4824     var idx = -1;
4825     this.rowsProcessors.forEach(function(rowsProcessor, index){
4826       if ( rowsProcessor.processor === processor ){
4827         idx = index;
4828       }
4829     });
4830
4831     if ( idx !== -1 ) {
4832       this.rowsProcessors.splice(idx, 1);
4833     }
4834   };
4835
4836   /**
4837    * Private Undocumented Method
4838    * @name processRowsProcessors
4839    * @methodOf ui.grid.class:Grid
4840    * @param {Array[GridRow]} The array of "renderable" rows
4841    * @param {Array[GridColumn]} The array of columns
4842    * @description Run all the registered rows processors on the array of renderable rows
4843    */
4844   Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
4845     var self = this;
4846
4847     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4848     var myRenderableRows = renderableRows.slice(0);
4849
4850     // Return myRenderableRows with no processing if we have no rows processors
4851     if (self.rowsProcessors.length === 0) {
4852       return $q.when(myRenderableRows);
4853     }
4854
4855     // Counter for iterating through rows processors
4856     var i = 0;
4857
4858     // Promise for when we're done with all the processors
4859     var finished = $q.defer();
4860
4861     // This function will call the processor in self.rowsProcessors at index 'i', and then
4862     //   when done will call the next processor in the list, using the output from the processor
4863     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4864     //
4865     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4866     //   the result.
4867     function startProcessor(i, renderedRowsToProcess) {
4868       // Get the processor at 'i'
4869       var processor = self.rowsProcessors[i].processor;
4870
4871       // Call the processor, passing in the rows to process and the current columns
4872       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4873       return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
4874         .then(function handleProcessedRows(processedRows) {
4875           // Check for errors
4876           if (!processedRows) {
4877             throw "Processor at index " + i + " did not return a set of renderable rows";
4878           }
4879
4880           if (!angular.isArray(processedRows)) {
4881             throw "Processor at index " + i + " did not return an array";
4882           }
4883
4884           // Processor is done, increment the counter
4885           i++;
4886
4887           // If we're not done with the processors, call the next one
4888           if (i <= self.rowsProcessors.length - 1) {
4889             return startProcessor(i, processedRows);
4890           }
4891           // We're done! Resolve the 'finished' promise
4892           else {
4893             finished.resolve(processedRows);
4894           }
4895         });
4896     }
4897
4898     // Start on the first processor
4899     startProcessor(0, myRenderableRows);
4900
4901     return finished.promise;
4902   };
4903
4904   Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
4905     var self = this;
4906
4907     // Reset all the render container row caches
4908     for (var i in self.renderContainers) {
4909       var container = self.renderContainers[i];
4910
4911       container.canvasHeightShouldUpdate = true;
4912
4913       if ( typeof(container.visibleRowCache) === 'undefined' ){
4914         container.visibleRowCache = [];
4915       } else {
4916         container.visibleRowCache.length = 0;
4917       }
4918     }
4919
4920     // rows.forEach(function (row) {
4921     for (var ri = 0; ri < rows.length; ri++) {
4922       var row = rows[ri];
4923
4924       var targetContainer = (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) ? row.renderContainer : 'body';
4925
4926       // If the row is visible
4927       if (row.visible) {
4928         self.renderContainers[targetContainer].visibleRowCache.push(row);
4929       }
4930     }
4931     self.api.core.raise.rowsRendered(this.api);
4932   };
4933
4934   /**
4935    * @ngdoc function
4936    * @name registerColumnsProcessor
4937    * @methodOf ui.grid.class:Grid
4938    * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
4939    * is run in the context of the grid (i.e. this for the function will be the grid), and
4940    * which must return an updated renderedColumnsToProcess which can be passed to the next processor
4941    * in the chain
4942    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4943    * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
4944    *
4945    * 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)
4946    * @description
4947
4948      Register a "columns processor" function. When the columns are updated,
4949      the grid calls each registered "columns processor", which has a chance
4950      to alter the set of columns, as long as the count is not modified.
4951    */
4952   Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor, priority) {
4953     if (!angular.isFunction(processor)) {
4954       throw 'Attempt to register non-function rows processor: ' + processor;
4955     }
4956
4957     this.columnsProcessors.push({processor: processor, priority: priority});
4958     this.columnsProcessors.sort(function sortByPriority( a, b ){
4959       return a.priority - b.priority;
4960     });
4961   };
4962
4963   Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
4964     var idx = this.columnsProcessors.indexOf(processor);
4965
4966     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4967       this.columnsProcessors.splice(idx, 1);
4968     }
4969   };
4970
4971   Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
4972     var self = this;
4973
4974     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4975     var myRenderableColumns = renderableColumns.slice(0);
4976
4977     // Return myRenderableRows with no processing if we have no rows processors
4978     if (self.columnsProcessors.length === 0) {
4979       return $q.when(myRenderableColumns);
4980     }
4981
4982     // Counter for iterating through rows processors
4983     var i = 0;
4984
4985     // Promise for when we're done with all the processors
4986     var finished = $q.defer();
4987
4988     // This function will call the processor in self.rowsProcessors at index 'i', and then
4989     //   when done will call the next processor in the list, using the output from the processor
4990     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4991     //
4992     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4993     //   the result.
4994     function startProcessor(i, renderedColumnsToProcess) {
4995       // Get the processor at 'i'
4996       var processor = self.columnsProcessors[i].processor;
4997
4998       // Call the processor, passing in the rows to process and the current columns
4999       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
5000       return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
5001         .then(function handleProcessedRows(processedColumns) {
5002           // Check for errors
5003           if (!processedColumns) {
5004             throw "Processor at index " + i + " did not return a set of renderable rows";
5005           }
5006
5007           if (!angular.isArray(processedColumns)) {
5008             throw "Processor at index " + i + " did not return an array";
5009           }
5010
5011           // Processor is done, increment the counter
5012           i++;
5013
5014           // If we're not done with the processors, call the next one
5015           if (i <= self.columnsProcessors.length - 1) {
5016             return startProcessor(i, myRenderableColumns);
5017           }
5018           // We're done! Resolve the 'finished' promise
5019           else {
5020             finished.resolve(myRenderableColumns);
5021           }
5022         });
5023     }
5024
5025     // Start on the first processor
5026     startProcessor(0, myRenderableColumns);
5027
5028     return finished.promise;
5029   };
5030
5031   Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
5032     // gridUtil.logDebug('setVisibleColumns');
5033
5034     var self = this;
5035
5036     // Reset all the render container row caches
5037     for (var i in self.renderContainers) {
5038       var container = self.renderContainers[i];
5039
5040       container.visibleColumnCache.length = 0;
5041     }
5042
5043     for (var ci = 0; ci < columns.length; ci++) {
5044       var column = columns[ci];
5045
5046       // If the column is visible
5047       if (column.visible) {
5048         // If the column has a container specified
5049         if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
5050           self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
5051         }
5052         // If not, put it into the body container
5053         else {
5054           self.renderContainers.body.visibleColumnCache.push(column);
5055         }
5056       }
5057     }
5058   };
5059
5060   /**
5061    * @ngdoc function
5062    * @name handleWindowResize
5063    * @methodOf ui.grid.class:Grid
5064    * @description Triggered when the browser window resizes; automatically resizes the grid
5065    * @returns {Promise} A resolved promise once the window resize has completed.
5066    */
5067   Grid.prototype.handleWindowResize = function handleWindowResize($event) {
5068     var self = this;
5069
5070     self.gridWidth = gridUtil.elementWidth(self.element);
5071     self.gridHeight = gridUtil.elementHeight(self.element);
5072
5073     return self.queueRefresh();
5074   };
5075
5076   /**
5077    * @ngdoc function
5078    * @name queueRefresh
5079    * @methodOf ui.grid.class:Grid
5080    * @description queues a grid refreshCanvas, a way of debouncing all the refreshes we might otherwise issue
5081    */
5082   Grid.prototype.queueRefresh = function queueRefresh() {
5083     var self = this;
5084
5085     if (self.refreshCanceller) {
5086       $timeout.cancel(self.refreshCanceller);
5087     }
5088
5089     self.refreshCanceller = $timeout(function () {
5090       self.refreshCanvas(true);
5091     });
5092
5093     self.refreshCanceller.then(function () {
5094       self.refreshCanceller = null;
5095     });
5096
5097     return self.refreshCanceller;
5098   };
5099
5100
5101   /**
5102    * @ngdoc function
5103    * @name queueGridRefresh
5104    * @methodOf ui.grid.class:Grid
5105    * @description queues a grid refresh, a way of debouncing all the refreshes we might otherwise issue
5106    */
5107   Grid.prototype.queueGridRefresh = function queueGridRefresh() {
5108     var self = this;
5109
5110     if (self.gridRefreshCanceller) {
5111       $timeout.cancel(self.gridRefreshCanceller);
5112     }
5113
5114     self.gridRefreshCanceller = $timeout(function () {
5115       self.refresh(true);
5116     });
5117
5118     self.gridRefreshCanceller.then(function () {
5119       self.gridRefreshCanceller = null;
5120     });
5121
5122     return self.gridRefreshCanceller;
5123   };
5124
5125
5126   /**
5127    * @ngdoc function
5128    * @name updateCanvasHeight
5129    * @methodOf ui.grid.class:Grid
5130    * @description flags all render containers to update their canvas height
5131    */
5132   Grid.prototype.updateCanvasHeight = function updateCanvasHeight() {
5133     var self = this;
5134
5135     for (var containerId in self.renderContainers) {
5136       if (self.renderContainers.hasOwnProperty(containerId)) {
5137         var container = self.renderContainers[containerId];
5138         container.canvasHeightShouldUpdate = true;
5139       }
5140     }
5141   };
5142
5143   /**
5144    * @ngdoc function
5145    * @name buildStyles
5146    * @methodOf ui.grid.class:Grid
5147    * @description calls each styleComputation function
5148    */
5149   // TODO: this used to take $scope, but couldn't see that it was used
5150   Grid.prototype.buildStyles = function buildStyles() {
5151     // gridUtil.logDebug('buildStyles');
5152
5153     var self = this;
5154
5155     self.customStyles = '';
5156
5157     self.styleComputations
5158       .sort(function(a, b) {
5159         if (a.priority === null) { return 1; }
5160         if (b.priority === null) { return -1; }
5161         if (a.priority === null && b.priority === null) { return 0; }
5162         return a.priority - b.priority;
5163       })
5164       .forEach(function (compInfo) {
5165         // this used to provide $scope as a second parameter, but I couldn't find any
5166         // style builders that used it, so removed it as part of moving to grid from controller
5167         var ret = compInfo.func.call(self);
5168
5169         if (angular.isString(ret)) {
5170           self.customStyles += '\n' + ret;
5171         }
5172       });
5173   };
5174
5175
5176   Grid.prototype.minColumnsToRender = function minColumnsToRender() {
5177     var self = this;
5178     var viewport = this.getViewportWidth();
5179
5180     var min = 0;
5181     var totalWidth = 0;
5182     self.columns.forEach(function(col, i) {
5183       if (totalWidth < viewport) {
5184         totalWidth += col.drawnWidth;
5185         min++;
5186       }
5187       else {
5188         var currWidth = 0;
5189         for (var j = i; j >= i - min; j--) {
5190           currWidth += self.columns[j].drawnWidth;
5191         }
5192         if (currWidth < viewport) {
5193           min++;
5194         }
5195       }
5196     });
5197
5198     return min;
5199   };
5200
5201   Grid.prototype.getBodyHeight = function getBodyHeight() {
5202     // Start with the viewportHeight
5203     var bodyHeight = this.getViewportHeight();
5204
5205     // Add the horizontal scrollbar height if there is one
5206     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5207     //  bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5208     //}
5209
5210     return bodyHeight;
5211   };
5212
5213   // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
5214   // TODO(c0bra): account for footer height
5215   Grid.prototype.getViewportHeight = function getViewportHeight() {
5216     var self = this;
5217
5218     var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
5219
5220     // Account for native horizontal scrollbar, if present
5221     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5222     //  viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5223     //}
5224
5225     var adjustment = self.getViewportAdjustment();
5226
5227     viewPortHeight = viewPortHeight + adjustment.height;
5228
5229     //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5230
5231     return viewPortHeight;
5232   };
5233
5234   Grid.prototype.getViewportWidth = function getViewportWidth() {
5235     var self = this;
5236
5237     var viewPortWidth = this.gridWidth;
5238
5239     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5240     //  viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5241     //}
5242
5243     var adjustment = self.getViewportAdjustment();
5244
5245     viewPortWidth = viewPortWidth + adjustment.width;
5246
5247     //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5248
5249     return viewPortWidth;
5250   };
5251
5252   Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5253     var viewPortWidth = this.getViewportWidth();
5254
5255     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5256     //  viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5257     //}
5258
5259     return viewPortWidth;
5260   };
5261
5262   Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
5263     this.verticalScrollSyncCallBackFns[containerId] = callBackFn;
5264   };
5265
5266   Grid.prototype.addHorizontalScrollSync = function (containerId, callBackFn) {
5267     this.horizontalScrollSyncCallBackFns[containerId] = callBackFn;
5268   };
5269
5270 /**
5271  * Scroll needed containers by calling their ScrollSyncs
5272  * @param sourceContainerId the containerId that has already set it's top/left.
5273  *         can be empty string which means all containers need to set top/left
5274  * @param scrollEvent
5275  */
5276   Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5277
5278     if (scrollEvent.y) {
5279       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5280       var verts = ['body','left', 'right'];
5281
5282       this.flagScrollingVertically(scrollEvent);
5283
5284       if (sourceContainerId === 'body') {
5285         verts = ['left', 'right'];
5286       }
5287       else if (sourceContainerId === 'left') {
5288         verts = ['body', 'right'];
5289       }
5290       else if (sourceContainerId === 'right') {
5291         verts = ['body', 'left'];
5292       }
5293
5294       for (var i = 0; i < verts.length; i++) {
5295         var id = verts[i];
5296         if (this.verticalScrollSyncCallBackFns[id]) {
5297           this.verticalScrollSyncCallBackFns[id](scrollEvent);
5298         }
5299       }
5300
5301     }
5302
5303     if (scrollEvent.x) {
5304       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5305       var horizs = ['body','bodyheader', 'bodyfooter'];
5306
5307       this.flagScrollingHorizontally(scrollEvent);
5308       if (sourceContainerId === 'body') {
5309         horizs = ['bodyheader', 'bodyfooter'];
5310       }
5311
5312       for (var j = 0; j < horizs.length; j++) {
5313         var idh = horizs[j];
5314         if (this.horizontalScrollSyncCallBackFns[idh]) {
5315           this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
5316         }
5317       }
5318
5319     }
5320
5321   };
5322
5323   Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5324     this.viewportAdjusters.push(func);
5325   };
5326
5327   Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5328     var idx = this.viewportAdjusters.indexOf(func);
5329
5330     if (typeof(idx) !== 'undefined' && idx !== undefined) {
5331       this.viewportAdjusters.splice(idx, 1);
5332     }
5333   };
5334
5335   Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
5336     var self = this;
5337
5338     var adjustment = { height: 0, width: 0 };
5339
5340     self.viewportAdjusters.forEach(function (func) {
5341       adjustment = func.call(this, adjustment);
5342     });
5343
5344     return adjustment;
5345   };
5346
5347   Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5348     // var count = 0;
5349
5350     // this.rows.forEach(function (row) {
5351     //   if (row.visible) {
5352     //     count++;
5353     //   }
5354     // });
5355
5356     // return this.visibleRowCache.length;
5357     return this.renderContainers.body.visibleRowCache.length;
5358   };
5359
5360    Grid.prototype.getVisibleRows = function getVisibleRows() {
5361     return this.renderContainers.body.visibleRowCache;
5362    };
5363
5364   Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5365     // var count = 0;
5366
5367     // this.rows.forEach(function (row) {
5368     //   if (row.visible) {
5369     //     count++;
5370     //   }
5371     // });
5372
5373     // return this.visibleRowCache.length;
5374     return this.renderContainers.body.visibleColumnCache.length;
5375   };
5376
5377
5378   Grid.prototype.searchRows = function searchRows(renderableRows) {
5379     return rowSearcher.search(this, renderableRows, this.columns);
5380   };
5381
5382   Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
5383     return rowSorter.sort(this, renderableRows, this.columns);
5384   };
5385
5386   /**
5387    * @ngdoc function
5388    * @name getCellValue
5389    * @methodOf ui.grid.class:Grid
5390    * @description Gets the value of a cell for a particular row and column
5391    * @param {GridRow} row Row to access
5392    * @param {GridColumn} col Column to access
5393    */
5394   Grid.prototype.getCellValue = function getCellValue(row, col){
5395     if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
5396       return row.entity[ '$$' + col.uid].rendered;
5397     } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5398       return row.entity[col.field];
5399     } else {
5400       if (!col.cellValueGetterCache) {
5401         col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
5402       }
5403
5404       return col.cellValueGetterCache(row);
5405     }
5406   };
5407
5408   /**
5409    * @ngdoc function
5410    * @name getCellDisplayValue
5411    * @methodOf ui.grid.class:Grid
5412    * @description Gets the displayed value of a cell after applying any the `cellFilter`
5413    * @param {GridRow} row Row to access
5414    * @param {GridColumn} col Column to access
5415    */
5416   Grid.prototype.getCellDisplayValue = function getCellDisplayValue(row, col) {
5417     if ( !col.cellDisplayGetterCache ) {
5418       var custom_filter = col.cellFilter ? " | " + col.cellFilter : "";
5419
5420       if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
5421         col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5422       } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5423         col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5424       } else {
5425         col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
5426       }
5427     }
5428
5429     return col.cellDisplayGetterCache(row);
5430   };
5431
5432
5433   Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
5434     var self = this,
5435         p = 0;
5436
5437     self.columns.forEach(function (col) {
5438       if (col.sort && col.sort.priority !== undefined && col.sort.priority >= p) {
5439         p = col.sort.priority + 1;
5440       }
5441     });
5442
5443     return p;
5444   };
5445
5446   /**
5447    * @ngdoc function
5448    * @name resetColumnSorting
5449    * @methodOf ui.grid.class:Grid
5450    * @description Return the columns that the grid is currently being sorted by
5451    * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
5452    */
5453   Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
5454     var self = this;
5455
5456     self.columns.forEach(function (col) {
5457       if (col !== excludeCol && !col.suppressRemoveSort) {
5458         col.sort = {};
5459       }
5460     });
5461   };
5462
5463   /**
5464    * @ngdoc function
5465    * @name getColumnSorting
5466    * @methodOf ui.grid.class:Grid
5467    * @description Return the columns that the grid is currently being sorted by
5468    * @returns {Array[GridColumn]} An array of GridColumn objects
5469    */
5470   Grid.prototype.getColumnSorting = function getColumnSorting() {
5471     var self = this;
5472
5473     var sortedCols = [], myCols;
5474
5475     // Iterate through all the columns, sorted by priority
5476     // Make local copy of column list, because sorting is in-place and we do not want to
5477     // change the original sequence of columns
5478     myCols = self.columns.slice(0);
5479     myCols.sort(rowSorter.prioritySort).forEach(function (col) {
5480       if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
5481         sortedCols.push(col);
5482       }
5483     });
5484
5485     return sortedCols;
5486   };
5487
5488   /**
5489    * @ngdoc function
5490    * @name sortColumn
5491    * @methodOf ui.grid.class:Grid
5492    * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
5493    * Emits the sortChanged event whenever the sort criteria are changed.
5494    * @param {GridColumn} column Column to set the sorting on
5495    * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
5496    *   If not provided, the column will iterate through the sort directions
5497    *   specified in the {@link ui.grid.class:GridOptions.columnDef#sortDirectionCycle sortDirectionCycle} attribute.
5498    * @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
5499    *   by this column only
5500    * @returns {Promise} A resolved promise that supplies the column.
5501    */
5502
5503   Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
5504     var self = this,
5505         direction = null;
5506
5507     if (typeof(column) === 'undefined' || !column) {
5508       throw new Error('No column parameter provided');
5509     }
5510
5511     // Second argument can either be a direction or whether to add this column to the existing sort.
5512     //   If it's a boolean, it's an add, otherwise, it's a direction
5513     if (typeof(directionOrAdd) === 'boolean') {
5514       add = directionOrAdd;
5515     }
5516     else {
5517       direction = directionOrAdd;
5518     }
5519
5520     if (!add) {
5521       self.resetColumnSorting(column);
5522       column.sort.priority = undefined;
5523       // Get the actual priority since there may be columns which have suppressRemoveSort set
5524       column.sort.priority = self.getNextColumnSortPriority();
5525     }
5526     else if (!column.sort.priority){
5527       column.sort.priority = self.getNextColumnSortPriority();
5528     }
5529
5530     if (!direction) {
5531       // Find the current position in the cycle (or -1).
5532       var i = column.sortDirectionCycle.indexOf(column.sort.direction ? column.sort.direction : null);
5533       // Proceed to the next position in the cycle (or start at the beginning).
5534       i = (i+1) % column.sortDirectionCycle.length;
5535       // If suppressRemoveSort is set, and the next position in the cycle would
5536       // remove the sort, skip it.
5537       if (column.colDef && column.suppressRemoveSort && !column.sortDirectionCycle[i]) {
5538         i = (i+1) % column.sortDirectionCycle.length;
5539       }
5540
5541       if (column.sortDirectionCycle[i]) {
5542         column.sort.direction = column.sortDirectionCycle[i];
5543       } else {
5544         column.sort = {};
5545       }
5546     }
5547     else {
5548       column.sort.direction = direction;
5549     }
5550
5551     self.api.core.raise.sortChanged( self, self.getColumnSorting() );
5552
5553     return $q.when(column);
5554   };
5555
5556   /**
5557    * communicate to outside world that we are done with initial rendering
5558    */
5559   Grid.prototype.renderingComplete = function(){
5560     if (angular.isFunction(this.options.onRegisterApi)) {
5561       this.options.onRegisterApi(this.api);
5562     }
5563     this.api.core.raise.renderingComplete( this.api );
5564   };
5565
5566   Grid.prototype.createRowHashMap = function createRowHashMap() {
5567     var self = this;
5568
5569     var hashMap = new RowHashMap();
5570     hashMap.grid = self;
5571
5572     return hashMap;
5573   };
5574
5575
5576   /**
5577    * @ngdoc function
5578    * @name refresh
5579    * @methodOf ui.grid.class:Grid
5580    * @description Refresh the rendered grid on screen.
5581    * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed.
5582    */
5583   Grid.prototype.refresh = function refresh(rowsAltered) {
5584     var self = this;
5585
5586     var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
5587       self.setVisibleRows(renderableRows);
5588     });
5589
5590     var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
5591       self.setVisibleColumns(renderableColumns);
5592     });
5593
5594     return $q.all([p1, p2]).then(function () {
5595       self.redrawInPlace(rowsAltered);
5596
5597       self.refreshCanvas(true);
5598     });
5599   };
5600
5601   /**
5602    * @ngdoc function
5603    * @name refreshRows
5604    * @methodOf ui.grid.class:Grid
5605    * @description Refresh the rendered rows on screen?  Note: not functional at present
5606    * @returns {promise} promise that is resolved when render completes?
5607    *
5608    */
5609   Grid.prototype.refreshRows = function refreshRows() {
5610     var self = this;
5611
5612     return self.processRowsProcessors(self.rows)
5613       .then(function (renderableRows) {
5614         self.setVisibleRows(renderableRows);
5615
5616         self.redrawInPlace();
5617
5618         self.refreshCanvas( true );
5619       });
5620   };
5621
5622   /**
5623    * @ngdoc function
5624    * @name refreshCanvas
5625    * @methodOf ui.grid.class:Grid
5626    * @description Builds all styles and recalculates much of the grid sizing
5627    * @param {object} buildStyles optional parameter.  Use TBD
5628    * @returns {promise} promise that is resolved when the canvas
5629    * has been refreshed
5630    *
5631    */
5632   Grid.prototype.refreshCanvas = function(buildStyles) {
5633     var self = this;
5634
5635     if (buildStyles) {
5636       self.buildStyles();
5637     }
5638
5639     var p = $q.defer();
5640
5641     // Get all the header heights
5642     var containerHeadersToRecalc = [];
5643     for (var containerId in self.renderContainers) {
5644       if (self.renderContainers.hasOwnProperty(containerId)) {
5645         var container = self.renderContainers[containerId];
5646
5647         // Skip containers that have no canvasWidth set yet
5648         if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5649           continue;
5650         }
5651
5652         if (container.header || container.headerCanvas) {
5653           container.explicitHeaderHeight = container.explicitHeaderHeight || null;
5654           container.explicitHeaderCanvasHeight = container.explicitHeaderCanvasHeight || null;
5655
5656           containerHeadersToRecalc.push(container);
5657         }
5658       }
5659     }
5660
5661     /*
5662      *
5663      * Here we loop through the headers, measuring each element as well as any header "canvas" it has within it.
5664      *
5665      * If any header is less than the largest header height, it will be resized to that so that we don't have headers
5666      * with different heights, which looks like a rendering problem
5667      *
5668      * We'll do the same thing with the header canvases, and give the header CELLS an explicit height if their canvas
5669      * is smaller than the largest canvas height. That was header cells without extra controls like filtering don't
5670      * appear shorter than other cells.
5671      *
5672      */
5673     if (containerHeadersToRecalc.length > 0) {
5674       // Build the styles without the explicit header heights
5675       if (buildStyles) {
5676         self.buildStyles();
5677       }
5678
5679       // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
5680       $timeout(function() {
5681         // var oldHeaderHeight = self.grid.headerHeight;
5682         // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
5683
5684         var rebuildStyles = false;
5685
5686         // Get all the header heights
5687         var maxHeaderHeight = 0;
5688         var maxHeaderCanvasHeight = 0;
5689         var i, container;
5690         var getHeight = function(oldVal, newVal){
5691           if ( oldVal !== newVal){
5692             rebuildStyles = true;
5693           }
5694           return newVal;
5695         };
5696         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5697           container = containerHeadersToRecalc[i];
5698
5699           // Skip containers that have no canvasWidth set yet
5700           if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5701             continue;
5702           }
5703
5704           if (container.header) {
5705             var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5706
5707             // 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
5708             var topBorder = gridUtil.getBorderSize(container.header, 'top');
5709             var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
5710             var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
5711
5712             innerHeaderHeight  = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
5713
5714             container.innerHeaderHeight = innerHeaderHeight;
5715
5716             // If the header doesn't have an explicit height set, save the largest header height for use later
5717             //   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
5718             if (!container.explicitHeaderHeight && innerHeaderHeight > maxHeaderHeight) {
5719               maxHeaderHeight = innerHeaderHeight;
5720             }
5721           }
5722
5723           if (container.headerCanvas) {
5724             var headerCanvasHeight = container.headerCanvasHeight = getHeight(container.headerCanvasHeight, parseInt(gridUtil.outerElementHeight(container.headerCanvas), 10));
5725
5726
5727             // If the header doesn't have an explicit canvas height, save the largest header canvas height for use later
5728             //   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
5729             if (!container.explicitHeaderCanvasHeight && headerCanvasHeight > maxHeaderCanvasHeight) {
5730               maxHeaderCanvasHeight = headerCanvasHeight;
5731             }
5732           }
5733         }
5734
5735         // Go through all the headers
5736         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5737           container = containerHeadersToRecalc[i];
5738
5739           /* If:
5740               1. We have a max header height
5741               2. This container has a header height defined
5742               3. And either this container has an explicit header height set, OR its header height is less than the max
5743
5744               then:
5745
5746               Give this container's header an explicit height so it will line up with the tallest header
5747           */
5748           if (
5749             maxHeaderHeight > 0 && typeof(container.headerHeight) !== 'undefined' && container.headerHeight !== null &&
5750             (container.explicitHeaderHeight || container.headerHeight < maxHeaderHeight)
5751           ) {
5752             container.explicitHeaderHeight = getHeight(container.explicitHeaderHeight, maxHeaderHeight);
5753           }
5754
5755           // Do the same as above except for the header canvas
5756           if (
5757             maxHeaderCanvasHeight > 0 && typeof(container.headerCanvasHeight) !== 'undefined' && container.headerCanvasHeight !== null &&
5758             (container.explicitHeaderCanvasHeight || container.headerCanvasHeight < maxHeaderCanvasHeight)
5759           ) {
5760             container.explicitHeaderCanvasHeight = getHeight(container.explicitHeaderCanvasHeight, maxHeaderCanvasHeight);
5761           }
5762         }
5763
5764         // Rebuild styles if the header height has changed
5765         //   The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
5766         if (buildStyles && rebuildStyles) {
5767           self.buildStyles();
5768         }
5769
5770         p.resolve();
5771       });
5772     }
5773     else {
5774       // Timeout still needs to be here to trigger digest after styles have been rebuilt
5775       $timeout(function() {
5776         p.resolve();
5777       });
5778     }
5779
5780     return p.promise;
5781   };
5782
5783
5784   /**
5785    * @ngdoc function
5786    * @name redrawCanvas
5787    * @methodOf ui.grid.class:Grid
5788    * @description Redraw the rows and columns based on our current scroll position
5789    * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
5790    *
5791    */
5792   Grid.prototype.redrawInPlace = function redrawInPlace(rowsAdded) {
5793     // gridUtil.logDebug('redrawInPlace');
5794
5795     var self = this;
5796
5797     for (var i in self.renderContainers) {
5798       var container = self.renderContainers[i];
5799
5800       // gridUtil.logDebug('redrawing container', i);
5801
5802       if (rowsAdded) {
5803         container.adjustRows(container.prevScrollTop, null);
5804         container.adjustColumns(container.prevScrollLeft, null);
5805       }
5806       else {
5807         container.adjustRows(null, container.prevScrolltopPercentage);
5808         container.adjustColumns(null, container.prevScrollleftPercentage);
5809       }
5810     }
5811   };
5812
5813     /**
5814      * @ngdoc function
5815      * @name hasLeftContainerColumns
5816      * @methodOf ui.grid.class:Grid
5817      * @description returns true if leftContainer has columns
5818      */
5819     Grid.prototype.hasLeftContainerColumns = function () {
5820       return this.hasLeftContainer() && this.renderContainers.left.renderedColumns.length > 0;
5821     };
5822
5823     /**
5824      * @ngdoc function
5825      * @name hasRightContainerColumns
5826      * @methodOf ui.grid.class:Grid
5827      * @description returns true if rightContainer has columns
5828      */
5829     Grid.prototype.hasRightContainerColumns = function () {
5830       return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
5831     };
5832
5833     /**
5834      * @ngdoc method
5835      * @methodOf  ui.grid.class:Grid
5836      * @name scrollToIfNecessary
5837      * @description Scrolls the grid to make a certain row and column combo visible,
5838      *   in the case that it is not completely visible on the screen already.
5839      * @param {GridRow} gridRow row to make visible
5840      * @param {GridCol} gridCol column to make visible
5841      * @returns {promise} a promise that is resolved when scrolling is complete
5842      */
5843     Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
5844       var self = this;
5845
5846       var scrollEvent = new ScrollEvent(self, 'uiGrid.scrollToIfNecessary');
5847
5848       // Alias the visible row and column caches
5849       var visRowCache = self.renderContainers.body.visibleRowCache;
5850       var visColCache = self.renderContainers.body.visibleColumnCache;
5851
5852       /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
5853
5854       // 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
5855       var topBound = self.renderContainers.body.prevScrollTop + self.headerHeight;
5856
5857       // Don't the let top boundary be less than 0
5858       topBound = (topBound < 0) ? 0 : topBound;
5859
5860       // The left boundary is the current X scroll position
5861       var leftBound = self.renderContainers.body.prevScrollLeft;
5862
5863       // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
5864       //   Basically this is the viewport height added on to the scroll position
5865       var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight -  self.scrollbarWidth;
5866
5867       // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
5868       //if (self.horizontalScrollbarHeight) {
5869       //  bottomBound = bottomBound - self.horizontalScrollbarHeight;
5870       //}
5871
5872       // The right position is the current X scroll position minus the grid width
5873       var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
5874
5875       // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
5876       //if (self.verticalScrollbarWidth) {
5877       //  rightBound = rightBound - self.verticalScrollbarWidth;
5878       //}
5879
5880       // We were given a row to scroll to
5881       if (gridRow !== null) {
5882         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5883         var seekRowIndex = visRowCache.indexOf(gridRow);
5884
5885         // Total vertical scroll length of the grid
5886         var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
5887
5888         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5889         //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
5890         //  scrollLength = scrollLength + self.horizontalScrollbarHeight;
5891         //}
5892
5893         // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
5894         var pixelsToSeeRow = (seekRowIndex * self.options.rowHeight + self.headerHeight);
5895
5896         // Don't let the pixels required to see the row be less than zero
5897         pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
5898
5899         var scrollPixels, percentage;
5900
5901         // 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...
5902         if (pixelsToSeeRow < topBound) {
5903           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5904           //   to get the full position we need
5905           scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
5906
5907           // Turn the scroll position into a percentage and make it an argument for a scroll event
5908           percentage = scrollPixels / scrollLength;
5909           scrollEvent.y = { percentage: percentage  };
5910         }
5911         // 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...
5912         else if (pixelsToSeeRow > bottomBound) {
5913           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5914           //   to get the full position we need
5915           scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
5916
5917           // Turn the scroll position into a percentage and make it an argument for a scroll event
5918           percentage = scrollPixels / scrollLength;
5919           scrollEvent.y = { percentage: percentage  };
5920         }
5921       }
5922
5923       // We were given a column to scroll to
5924       if (gridCol !== null) {
5925         // This is the index of the row we want to scroll to, within the list of rows that can be visible
5926         var seekColumnIndex = visColCache.indexOf(gridCol);
5927
5928         // Total vertical scroll length of the grid
5929         var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
5930
5931         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5932         // if (self.verticalScrollbarWidth && self.verticalScrollbarWidth > 0) {
5933         //   horizScrollLength = horizScrollLength + self.verticalScrollbarWidth;
5934         // }
5935
5936         // This is the minimum amount of pixels we need to scroll vertical in order to see this column
5937         var columnLeftEdge = 0;
5938         for (var i = 0; i < seekColumnIndex; i++) {
5939           var col = visColCache[i];
5940           columnLeftEdge += col.drawnWidth;
5941         }
5942         columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
5943
5944         var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
5945
5946         // Don't let the pixels required to see the column be less than zero
5947         columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
5948
5949         var horizScrollPixels, horizPercentage;
5950
5951         // 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...
5952         if (columnLeftEdge < leftBound) {
5953           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
5954           //   to get the full position we need
5955           horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
5956
5957           // Turn the scroll position into a percentage and make it an argument for a scroll event
5958           horizPercentage = horizScrollPixels / horizScrollLength;
5959           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5960           scrollEvent.x = { percentage: horizPercentage  };
5961         }
5962         // 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...
5963         else if (columnRightEdge > rightBound) {
5964           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
5965           //   to get the full position we need
5966           horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
5967
5968           // Turn the scroll position into a percentage and make it an argument for a scroll event
5969           horizPercentage = horizScrollPixels / horizScrollLength;
5970           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5971           scrollEvent.x = { percentage: horizPercentage  };
5972         }
5973       }
5974
5975       var deferred = $q.defer();
5976
5977       // If we need to scroll on either the x or y axes, fire a scroll event
5978       if (scrollEvent.y || scrollEvent.x) {
5979         scrollEvent.withDelay = false;
5980         self.scrollContainers('',scrollEvent);
5981         var dereg = self.api.core.on.scrollEnd(null,function() {
5982           deferred.resolve(scrollEvent);
5983           dereg();
5984         });
5985       }
5986       else {
5987         deferred.resolve();
5988       }
5989
5990       return deferred.promise;
5991     };
5992
5993     /**
5994      * @ngdoc method
5995      * @methodOf ui.grid.class:Grid
5996      * @name scrollTo
5997      * @description Scroll the grid such that the specified
5998      * row and column is in view
5999      * @param {object} rowEntity gridOptions.data[] array instance to make visible
6000      * @param {object} colDef to make visible
6001      * @returns {promise} a promise that is resolved after any scrolling is finished
6002      */
6003     Grid.prototype.scrollTo = function (rowEntity, colDef) {
6004       var gridRow = null, gridCol = null;
6005
6006       if (rowEntity !== null && typeof(rowEntity) !== 'undefined' ) {
6007         gridRow = this.getRow(rowEntity);
6008       }
6009
6010       if (colDef !== null && typeof(colDef) !== 'undefined' ) {
6011         gridCol = this.getColumn(colDef.name ? colDef.name : colDef.field);
6012       }
6013       return this.scrollToIfNecessary(gridRow, gridCol);
6014     };
6015
6016   /**
6017    * @ngdoc function
6018    * @name clearAllFilters
6019    * @methodOf ui.grid.class:Grid
6020    * @description Clears all filters and optionally refreshes the visible rows.
6021    * @param {object} refreshRows Defaults to true.
6022    * @param {object} clearConditions Defaults to false.
6023    * @param {object} clearFlags Defaults to false.
6024    * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
6025    */
6026   Grid.prototype.clearAllFilters = function clearAllFilters(refreshRows, clearConditions, clearFlags) {
6027     // Default `refreshRows` to true because it will be the most commonly desired behaviour.
6028     if (refreshRows === undefined) {
6029       refreshRows = true;
6030     }
6031     if (clearConditions === undefined) {
6032       clearConditions = false;
6033     }
6034     if (clearFlags === undefined) {
6035       clearFlags = false;
6036     }
6037
6038     this.columns.forEach(function(column) {
6039       column.filters.forEach(function(filter) {
6040         filter.term = undefined;
6041
6042         if (clearConditions) {
6043           filter.condition = undefined;
6044         }
6045
6046         if (clearFlags) {
6047           filter.flags = undefined;
6048         }
6049       });
6050     });
6051
6052     if (refreshRows) {
6053       return this.refreshRows();
6054     }
6055   };
6056
6057
6058       // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
6059   function RowHashMap() {}
6060
6061   RowHashMap.prototype = {
6062     /**
6063      * Store key value pair
6064      * @param key key to store can be any type
6065      * @param value value to store can be any type
6066      */
6067     put: function(key, value) {
6068       this[this.grid.options.rowIdentity(key)] = value;
6069     },
6070
6071     /**
6072      * @param key
6073      * @returns {Object} the value for the key
6074      */
6075     get: function(key) {
6076       return this[this.grid.options.rowIdentity(key)];
6077     },
6078
6079     /**
6080      * Remove the key/value pair
6081      * @param key
6082      */
6083     remove: function(key) {
6084       var value = this[key = this.grid.options.rowIdentity(key)];
6085       delete this[key];
6086       return value;
6087     }
6088   };
6089
6090
6091
6092   return Grid;
6093
6094 }]);
6095
6096 })();
6097
6098 (function () {
6099
6100   angular.module('ui.grid')
6101     .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6102       function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6103         /**
6104          * @ngdoc function
6105          * @name ui.grid.class:GridApi
6106          * @description GridApi provides the ability to register public methods events inside the grid and allow
6107          * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6108          * <br/>
6109          * To listen to events, you must add a callback to gridOptions.onRegisterApi
6110          * <pre>
6111          *   $scope.gridOptions.onRegisterApi = function(gridApi){
6112          *      gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6113          *          $log.log('navigation event');
6114          *      });
6115          *   };
6116          * </pre>
6117          * @param {object} grid grid that owns api
6118          */
6119         var GridApi = function GridApi(grid) {
6120           this.grid = grid;
6121           this.listeners = [];
6122           
6123           /**
6124            * @ngdoc function
6125            * @name renderingComplete
6126            * @methodOf  ui.grid.core.api:PublicApi
6127            * @description Rendering is complete, called at the same
6128            * time as `onRegisterApi`, but provides a way to obtain
6129            * that same event within features without stopping end
6130            * users from getting at the onRegisterApi method.
6131            * 
6132            * Included in gridApi so that it's always there - otherwise
6133            * there is still a timing problem with when a feature can
6134            * call this. 
6135            * 
6136            * @param {GridApi} gridApi the grid api, as normally 
6137            * returned in the onRegisterApi method
6138            * 
6139            * @example
6140            * <pre>
6141            *      gridApi.core.on.renderingComplete( grid );
6142            * </pre>
6143            */
6144           this.registerEvent( 'core', 'renderingComplete' );
6145
6146           /**
6147            * @ngdoc event
6148            * @name filterChanged
6149            * @eventOf  ui.grid.core.api:PublicApi
6150            * @description  is raised after the filter is changed.  The nature
6151            * of the watch expression doesn't allow notification of what changed,
6152            * so the receiver of this event will need to re-extract the filter 
6153            * conditions from the columns.
6154            * 
6155            */
6156           this.registerEvent( 'core', 'filterChanged' );
6157
6158           /**
6159            * @ngdoc function
6160            * @name setRowInvisible
6161            * @methodOf  ui.grid.core.api:PublicApi
6162            * @description Sets an override on the row to make it always invisible,
6163            * which will override any filtering or other visibility calculations.  
6164            * If the row is currently visible then sets it to invisible and calls
6165            * both grid refresh and emits the rowsVisibleChanged event
6166            * @param {object} rowEntity gridOptions.data[] array instance
6167            */
6168           this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6169       
6170           /**
6171            * @ngdoc function
6172            * @name clearRowInvisible
6173            * @methodOf  ui.grid.core.api:PublicApi
6174            * @description Clears any override on visibility for the row so that it returns to 
6175            * using normal filtering and other visibility calculations.  
6176            * If the row is currently invisible then sets it to visible and calls
6177            * both grid refresh and emits the rowsVisibleChanged event
6178            * TODO: if a filter is active then we can't just set it to visible?
6179            * @param {object} rowEntity gridOptions.data[] array instance
6180            */
6181           this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6182       
6183           /**
6184            * @ngdoc function
6185            * @name getVisibleRows
6186            * @methodOf  ui.grid.core.api:PublicApi
6187            * @description Returns all visible rows
6188            * @param {Grid} grid the grid you want to get visible rows from
6189            * @returns {array} an array of gridRow
6190            */
6191           this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6192           
6193           /**
6194            * @ngdoc event
6195            * @name rowsVisibleChanged
6196            * @eventOf  ui.grid.core.api:PublicApi
6197            * @description  is raised after the rows that are visible
6198            * change.  The filtering is zero-based, so it isn't possible
6199            * to say which rows changed (unlike in the selection feature).
6200            * We can plausibly know which row was changed when setRowInvisible
6201            * is called, but in that situation the user already knows which row
6202            * they changed.  When a filter runs we don't know what changed,
6203            * and that is the one that would have been useful.
6204            *
6205            */
6206           this.registerEvent( 'core', 'rowsVisibleChanged' );
6207
6208           /**
6209            * @ngdoc event
6210            * @name rowsRendered
6211            * @eventOf  ui.grid.core.api:PublicApi
6212            * @description  is raised after the cache of visible rows is changed.
6213            */
6214           this.registerEvent( 'core', 'rowsRendered' );
6215
6216
6217           /**
6218            * @ngdoc event
6219            * @name scrollBegin
6220            * @eventOf  ui.grid.core.api:PublicApi
6221            * @description  is raised when scroll begins.  Is throttled, so won't be raised too frequently
6222            */
6223           this.registerEvent( 'core', 'scrollBegin' );
6224
6225           /**
6226            * @ngdoc event
6227            * @name scrollEnd
6228            * @eventOf  ui.grid.core.api:PublicApi
6229            * @description  is raised when scroll has finished.  Is throttled, so won't be raised too frequently
6230            */
6231           this.registerEvent( 'core', 'scrollEnd' );
6232
6233           /**
6234            * @ngdoc event
6235            * @name canvasHeightChanged
6236            * @eventOf  ui.grid.core.api:PublicApi
6237            * @description  is raised when the canvas height has changed
6238            * <br/>
6239            * arguments: oldHeight, newHeight
6240            */
6241           this.registerEvent( 'core', 'canvasHeightChanged');
6242         };
6243
6244         /**
6245          * @ngdoc function
6246          * @name ui.grid.class:suppressEvents
6247          * @methodOf ui.grid.class:GridApi
6248          * @description Used to execute a function while disabling the specified event listeners.
6249          * Disables the listenerFunctions, executes the callbackFn, and then enables
6250          * the listenerFunctions again
6251          * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
6252          * functions that were used in the .on.eventName method
6253          * @param {object} callBackFn function to execute
6254          * @example
6255          * <pre>
6256          *    var navigate = function (newRowCol, oldRowCol){
6257          *       //do something on navigate
6258          *    }
6259          *
6260          *    gridApi.cellNav.on.navigate(scope,navigate);
6261          *
6262          *
6263          *    //call the scrollTo event and suppress our navigate listener
6264          *    //scrollTo will still raise the event for other listeners
6265          *    gridApi.suppressEvents(navigate, function(){
6266          *       gridApi.cellNav.scrollTo(aRow, aCol);
6267          *    });
6268          *
6269          * </pre>
6270          */
6271         GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6272           var self = this;
6273           var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6274
6275           //find all registered listeners
6276           var foundListeners = self.listeners.filter(function(listener) {
6277             return listeners.some(function(l) {
6278               return listener.handler === l;
6279             });
6280           });
6281
6282           //deregister all the listeners
6283           foundListeners.forEach(function(l){
6284             l.dereg();
6285           });
6286
6287           callBackFn();
6288
6289           //reregister all the listeners
6290           foundListeners.forEach(function(l){
6291               l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
6292           });
6293
6294         };
6295
6296         /**
6297          * @ngdoc function
6298          * @name registerEvent
6299          * @methodOf ui.grid.class:GridApi
6300          * @description Registers a new event for the given feature.  The event will get a
6301          * .raise and .on prepended to it
6302          * <br>
6303          * .raise.eventName() - takes no arguments
6304          * <br/>
6305          * <br/>
6306          * .on.eventName(scope, callBackFn, _this)
6307          * <br/>
6308          * scope - a scope reference to add a deregister call to the scopes .$on('destroy').  Scope is optional and can be a null value,
6309          * but in this case you must deregister it yourself via the returned deregister function
6310          * <br/>
6311          * callBackFn - The function to call
6312          * <br/>
6313          * _this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
6314          * <br/>
6315          * .on.eventName returns a dereg funtion that will remove the listener.  It's not necessary to use it as the listener
6316          * will be removed when the scope is destroyed.
6317          * @param {string} featureName name of the feature that raises the event
6318          * @param {string} eventName  name of the event
6319          */
6320         GridApi.prototype.registerEvent = function (featureName, eventName) {
6321           var self = this;
6322           if (!self[featureName]) {
6323             self[featureName] = {};
6324           }
6325
6326           var feature = self[featureName];
6327           if (!feature.on) {
6328             feature.on = {};
6329             feature.raise = {};
6330           }
6331
6332           var eventId = self.grid.id + featureName + eventName;
6333
6334           // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
6335           feature.raise[eventName] = function () {
6336             $rootScope.$emit.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
6337           };
6338
6339           // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
6340           feature.on[eventName] = function (scope, handler, _this) {
6341             if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6342               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');
6343               return;
6344             }
6345             var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
6346
6347             //track our listener so we can turn off and on
6348             var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6349             self.listeners.push(listener);
6350
6351             var removeListener = function(){
6352               listener.dereg();
6353               var index = self.listeners.indexOf(listener);
6354               self.listeners.splice(index,1);
6355             };
6356
6357             //destroy tracking when scope is destroyed
6358             if (scope) {
6359               scope.$on('$destroy', function() {
6360                 removeListener();
6361               });
6362             }
6363
6364
6365             return removeListener;
6366           };
6367         };
6368
6369         function registerEventWithAngular(eventId, handler, grid, _this) {
6370           return $rootScope.$on(eventId, function (event) {
6371             var args = Array.prototype.slice.call(arguments);
6372             args.splice(0, 1); //remove evt argument
6373             handler.apply(_this ? _this : grid.api, args);
6374           });
6375         }
6376
6377         /**
6378          * @ngdoc function
6379          * @name registerEventsFromObject
6380          * @methodOf ui.grid.class:GridApi
6381          * @description Registers features and events from a simple objectMap.
6382          * eventObjectMap must be in this format (multiple features allowed)
6383          * <pre>
6384          * {featureName:
6385          *        {
6386          *          eventNameOne:function(args){},
6387          *          eventNameTwo:function(args){}
6388          *        }
6389          *  }
6390          * </pre>
6391          * @param {object} eventObjectMap map of feature/event names
6392          */
6393         GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6394           var self = this;
6395           var features = [];
6396           angular.forEach(eventObjectMap, function (featProp, featPropName) {
6397             var feature = {name: featPropName, events: []};
6398             angular.forEach(featProp, function (prop, propName) {
6399               feature.events.push(propName);
6400             });
6401             features.push(feature);
6402           });
6403
6404           features.forEach(function (feature) {
6405             feature.events.forEach(function (event) {
6406               self.registerEvent(feature.name, event);
6407             });
6408           });
6409
6410         };
6411
6412         /**
6413          * @ngdoc function
6414          * @name registerMethod
6415          * @methodOf ui.grid.class:GridApi
6416          * @description Registers a new event for the given feature
6417          * @param {string} featureName name of the feature
6418          * @param {string} methodName  name of the method
6419          * @param {object} callBackFn function to execute
6420          * @param {object} _this binds callBackFn 'this' to _this.  Defaults to gridApi.grid
6421          */
6422         GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, _this) {
6423           if (!this[featureName]) {
6424             this[featureName] = {};
6425           }
6426
6427           var feature = this[featureName];
6428
6429           feature[methodName] = gridUtil.createBoundedWrapper(_this || this.grid, callBackFn);
6430         };
6431
6432         /**
6433          * @ngdoc function
6434          * @name registerMethodsFromObject
6435          * @methodOf ui.grid.class:GridApi
6436          * @description Registers features and methods from a simple objectMap.
6437          * eventObjectMap must be in this format (multiple features allowed)
6438          * <br>
6439          * {featureName:
6440          *        {
6441          *          methodNameOne:function(args){},
6442          *          methodNameTwo:function(args){}
6443          *        }
6444          * @param {object} eventObjectMap map of feature/event names
6445          * @param {object} _this binds this to _this for all functions.  Defaults to gridApi.grid
6446          */
6447         GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
6448           var self = this;
6449           var features = [];
6450           angular.forEach(methodMap, function (featProp, featPropName) {
6451             var feature = {name: featPropName, methods: []};
6452             angular.forEach(featProp, function (prop, propName) {
6453               feature.methods.push({name: propName, fn: prop});
6454             });
6455             features.push(feature);
6456           });
6457
6458           features.forEach(function (feature) {
6459             feature.methods.forEach(function (method) {
6460               self.registerMethod(feature.name, method.name, method.fn, _this);
6461             });
6462           });
6463
6464         };
6465         
6466         return GridApi;
6467
6468       }]);
6469
6470 })();
6471
6472 (function(){
6473
6474 angular.module('ui.grid')
6475 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
6476
6477   /**
6478    * ******************************************************************************************
6479    * PaulL1: Ugly hack here in documentation.  These properties are clearly properties of GridColumn,
6480    * and need to be noted as such for those extending and building ui-grid itself.
6481    * However, from an end-developer perspective, they interact with all these through columnDefs,
6482    * and they really need to be documented there.  I feel like they're relatively static, and
6483    * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
6484    * comment block.  Ugh.
6485    *
6486    */
6487
6488   /**
6489    * @ngdoc property
6490    * @name name
6491    * @propertyOf ui.grid.class:GridColumn
6492    * @description (mandatory) each column should have a name, although for backward
6493    * compatibility with 2.x name can be omitted if field is present
6494    *
6495    */
6496
6497   /**
6498    * @ngdoc property
6499    * @name name
6500    * @propertyOf ui.grid.class:GridOptions.columnDef
6501    * @description (mandatory) each column should have a name, although for backward
6502    * compatibility with 2.x name can be omitted if field is present
6503    *
6504    */
6505
6506   /**
6507    * @ngdoc property
6508    * @name displayName
6509    * @propertyOf ui.grid.class:GridColumn
6510    * @description Column name that will be shown in the header.  If displayName is not
6511    * provided then one is generated using the name.
6512    *
6513    */
6514
6515   /**
6516    * @ngdoc property
6517    * @name displayName
6518    * @propertyOf ui.grid.class:GridOptions.columnDef
6519    * @description Column name that will be shown in the header.  If displayName is not
6520    * provided then one is generated using the name.
6521    *
6522    */
6523
6524   /**
6525    * @ngdoc property
6526    * @name field
6527    * @propertyOf ui.grid.class:GridColumn
6528    * @description field must be provided if you wish to bind to a
6529    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6530    * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
6531    * See the angular docs on binding expressions.
6532    *
6533    */
6534
6535   /**
6536    * @ngdoc property
6537    * @name field
6538    * @propertyOf ui.grid.class:GridOptions.columnDef
6539    * @description field must be provided if you wish to bind to a
6540    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6541    * 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.    *
6542    */
6543
6544   /**
6545    * @ngdoc property
6546    * @name filter
6547    * @propertyOf ui.grid.class:GridColumn
6548    * @description Filter on this column.
6549    * @example
6550    * <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>
6551    *
6552    */
6553
6554   /**
6555    * @ngdoc object
6556    * @name ui.grid.class:GridColumn
6557    * @description Represents the viewModel for each column.  Any state or methods needed for a Grid Column
6558    * are defined on this prototype
6559    * @param {ColumnDef} colDef the column def to associate with this column
6560    * @param {number} uid the unique and immutable uid we'd like to allocate to this column
6561    * @param {Grid} grid the grid we'd like to create this column in
6562    */
6563   function GridColumn(colDef, uid, grid) {
6564     var self = this;
6565
6566     self.grid = grid;
6567     self.uid = uid;
6568
6569     self.updateColumnDef(colDef, true);
6570
6571     self.aggregationValue = undefined;
6572
6573     // The footer cell registers to listen for the rowsRendered event, and calls this.  Needed to be
6574     // in something with a scope so that the dereg would get called
6575     self.updateAggregationValue = function() {
6576
6577      // gridUtil.logDebug('getAggregationValue for Column ' + self.colDef.name);
6578
6579       /**
6580        * @ngdoc property
6581        * @name aggregationType
6582        * @propertyOf ui.grid.class:GridOptions.columnDef
6583        * @description The aggregation that you'd like to show in the columnFooter for this
6584        * column.  Valid values are in uiGridConstants, and currently include `uiGridConstants.aggregationTypes.count`,
6585        * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
6586        * `uiGridConstants.aggregationTypes.max`.
6587        *
6588        * You can also provide a function as the aggregation type, in this case your function needs to accept the full
6589        * set of visible rows, and return a value that should be shown
6590        */
6591       if (!self.aggregationType) {
6592         self.aggregationValue = undefined;
6593         return;
6594       }
6595
6596       var result = 0;
6597       var visibleRows = self.grid.getVisibleRows();
6598
6599       var cellValues = function(){
6600         var values = [];
6601         visibleRows.forEach(function (row) {
6602           var cellValue = self.grid.getCellValue(row, self);
6603           var cellNumber = Number(cellValue);
6604           if (!isNaN(cellNumber)) {
6605             values.push(cellNumber);
6606           }
6607         });
6608         return values;
6609       };
6610
6611       if (angular.isFunction(self.aggregationType)) {
6612         self.aggregationValue = self.aggregationType(visibleRows, self);
6613       }
6614       else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6615         self.aggregationValue = self.grid.getVisibleRowCount();
6616       }
6617       else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6618         cellValues().forEach(function (value) {
6619           result += value;
6620         });
6621         self.aggregationValue = result;
6622       }
6623       else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6624         cellValues().forEach(function (value) {
6625           result += value;
6626         });
6627         result = result / cellValues().length;
6628         self.aggregationValue = result;
6629       }
6630       else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6631         self.aggregationValue = Math.min.apply(null, cellValues());
6632       }
6633       else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6634         self.aggregationValue = Math.max.apply(null, cellValues());
6635       }
6636       else {
6637         self.aggregationValue = '\u00A0';
6638       }
6639     };
6640
6641 //     var throttledUpdateAggregationValue = gridUtil.throttle(updateAggregationValue, self.grid.options.aggregationCalcThrottle, { trailing: true, context: self.name });
6642
6643     /**
6644      * @ngdoc function
6645      * @name getAggregationValue
6646      * @methodOf ui.grid.class:GridColumn
6647      * @description gets the aggregation value based on the aggregation type for this column.
6648      * Debounced using scrollDebounce option setting
6649      */
6650     this.getAggregationValue =  function() {
6651 //      if (!self.grid.isScrollingVertically && !self.grid.isScrollingHorizontally) {
6652 //        throttledUpdateAggregationValue();
6653 //      }
6654
6655       return self.aggregationValue;
6656     };
6657   }
6658
6659   /**
6660    * @ngdoc function
6661    * @name hideColumn
6662    * @methodOf ui.grid.class:GridColumn
6663    * @description Hides the column by setting colDef.visible = false
6664    */
6665   GridColumn.prototype.hideColumn = function() {
6666     this.colDef.visible = false;
6667   };
6668   
6669
6670   /**
6671    * @ngdoc method
6672    * @methodOf ui.grid.class:GridColumn
6673    * @name setPropertyOrDefault
6674    * @description Sets a property on the column using the passed in columnDef, and
6675    * setting the defaultValue if the value cannot be found on the colDef
6676    * @param {ColumnDef} colDef the column def to look in for the property value
6677    * @param {string} propName the property name we'd like to set
6678    * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
6679    */
6680   GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
6681     var self = this;
6682
6683     // Use the column definition filter if we were passed it
6684     if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
6685       self[propName] = colDef[propName];
6686     }
6687     // Otherwise use our own if it's set
6688     else if (typeof(self[propName]) !== 'undefined') {
6689       self[propName] = self[propName];
6690     }
6691     // Default to empty object for the filter
6692     else {
6693       self[propName] = defaultValue ? defaultValue : {};
6694     }
6695   };
6696
6697
6698
6699   /**
6700    * @ngdoc property
6701    * @name width
6702    * @propertyOf ui.grid.class:GridOptions.columnDef
6703    * @description sets the column width.  Can be either
6704    * a number or a percentage, or an * for auto.
6705    * @example
6706    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
6707    *                                          { field: 'field2', width: '20%'},
6708    *                                          { field: 'field3', width: '*' }]; </pre>
6709    *
6710    */
6711
6712   /**
6713    * @ngdoc property
6714    * @name minWidth
6715    * @propertyOf ui.grid.class:GridOptions.columnDef
6716    * @description sets the minimum column width.  Should be a number.
6717    * @example
6718    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
6719    *
6720    */
6721
6722   /**
6723    * @ngdoc property
6724    * @name maxWidth
6725    * @propertyOf ui.grid.class:GridOptions.columnDef
6726    * @description sets the maximum column width.  Should be a number.
6727    * @example
6728    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
6729    *
6730    */
6731
6732   /**
6733    * @ngdoc property
6734    * @name visible
6735    * @propertyOf ui.grid.class:GridOptions.columnDef
6736    * @description sets whether or not the column is visible
6737    * </br>Default is true
6738    * @example
6739    * <pre>  $scope.gridOptions.columnDefs = [
6740    *     { field: 'field1', visible: true},
6741    *     { field: 'field2', visible: false }
6742    *   ]; </pre>
6743    *
6744    */
6745
6746  /**
6747   * @ngdoc property
6748   * @name sort
6749   * @propertyOf ui.grid.class:GridOptions.columnDef
6750   * @description An object of sort information, attributes are:
6751   *
6752   * - direction: values are uiGridConstants.ASC or uiGridConstants.DESC
6753   * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6754   * - priority: says what order to sort the columns in (lower priority gets sorted first).
6755   * @example
6756   * <pre>
6757   *   $scope.gridOptions.columnDefs = [{
6758   *     field: 'field1',
6759   *     sort: {
6760   *       direction: uiGridConstants.ASC,
6761   *       ignoreSort: true,
6762   *       priority: 0
6763   *      }
6764   *   }];
6765   * </pre>
6766   */
6767
6768
6769   /**
6770    * @ngdoc property
6771    * @name sortingAlgorithm
6772    * @propertyOf ui.grid.class:GridOptions.columnDef
6773    * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
6774    * like any normal sorting function with additional 'rowA', 'rowB', and 'direction' parameters
6775    * that are the row objects and the current direction of the sort respectively.
6776    *
6777    */
6778
6779   /**
6780    * @ngdoc array
6781    * @name filters
6782    * @propertyOf ui.grid.class:GridOptions.columnDef
6783    * @description Specify multiple filter fields.
6784    * @example
6785    * <pre>$scope.gridOptions.columnDefs = [
6786    *   {
6787    *     field: 'field1', filters: [
6788    *       {
6789    *         term: 'aa',
6790    *         condition: uiGridConstants.filter.STARTS_WITH,
6791    *         placeholder: 'starts with...',
6792    *         ariaLabel: 'Filter for field1',
6793    *         flags: { caseSensitive: false },
6794    *         type: uiGridConstants.filter.SELECT,
6795    *         selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6796    *       },
6797    *       {
6798    *         condition: uiGridConstants.filter.ENDS_WITH,
6799    *         placeholder: 'ends with...'
6800    *       }
6801    *     ]
6802    *   }
6803    * ]; </pre>
6804    *
6805    *
6806    */
6807
6808   /**
6809    * @ngdoc array
6810    * @name filters
6811    * @propertyOf ui.grid.class:GridColumn
6812    * @description Filters for this column. Includes 'term' property bound to filter input elements.
6813    * @example
6814    * <pre>[
6815    *   {
6816    *     term: 'foo', // ngModel for <input>
6817    *     condition: uiGridConstants.filter.STARTS_WITH,
6818    *     placeholder: 'starts with...',
6819    *     ariaLabel: 'Filter for foo',
6820    *     flags: { caseSensitive: false },
6821    *     type: uiGridConstants.filter.SELECT,
6822    *     selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
6823    *   },
6824    *   {
6825    *     term: 'baz',
6826    *     condition: uiGridConstants.filter.ENDS_WITH,
6827    *     placeholder: 'ends with...'
6828    *   }
6829    * ] </pre>
6830    *
6831    *
6832    */
6833
6834   /**
6835    * @ngdoc array
6836    * @name menuItems
6837    * @propertyOf ui.grid.class:GridOptions.columnDef
6838    * @description used to add menu items to a column.  Refer to the tutorial on this
6839    * functionality.  A number of settings are supported:
6840    *
6841    * - title: controls the title that is displayed in the menu
6842    * - icon: the icon shown alongside that title
6843    * - action: the method to call when the menu is clicked
6844    * - shown: a function to evaluate to determine whether or not to show the item
6845    * - active: a function to evaluate to determine whether or not the item is currently selected
6846    * - context: context to pass to the action function, available in this.context in your handler
6847    * - leaveOpen: if set to true, the menu should stay open after the action, defaults to false
6848    * @example
6849    * <pre>  $scope.gridOptions.columnDefs = [
6850    *   { field: 'field1', menuItems: [
6851    *     {
6852    *       title: 'Outer Scope Alert',
6853    *       icon: 'ui-grid-icon-info-circled',
6854    *       action: function($event) {
6855    *         this.context.blargh(); // $scope.blargh() would work too, this is just an example
6856    *       },
6857    *       shown: function() { return true; },
6858    *       active: function() { return true; },
6859    *       context: $scope
6860    *     },
6861    *     {
6862    *       title: 'Grid ID',
6863    *       action: function() {
6864    *         alert('Grid ID: ' + this.grid.id);
6865    *       }
6866    *     }
6867    *   ] }]; </pre>
6868    *
6869    */
6870
6871   /**
6872    * @ngdoc method
6873    * @methodOf ui.grid.class:GridColumn
6874    * @name updateColumnDef
6875    * @description Moves settings from the columnDef down onto the column,
6876    * and sets properties as appropriate
6877    * @param {ColumnDef} colDef the column def to look in for the property value
6878    * @param {boolean} isNew whether the column is being newly created, if not
6879    * we're updating an existing column, and some items such as the sort shouldn't
6880    * be copied down
6881    */
6882   GridColumn.prototype.updateColumnDef = function(colDef, isNew) {
6883     var self = this;
6884
6885     self.colDef = colDef;
6886
6887     if (colDef.name === undefined) {
6888       throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
6889     }
6890
6891     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6892
6893     if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
6894       var colDefWidth = colDef.width;
6895       var parseErrorMsg = "Cannot parse column width '" + colDefWidth + "' for column named '" + colDef.name + "'";
6896       self.hasCustomWidth = false;
6897
6898       if (!angular.isString(colDefWidth) && !angular.isNumber(colDefWidth)) {
6899         self.width = '*';
6900       } else if (angular.isString(colDefWidth)) {
6901         // See if it ends with a percent
6902         if (gridUtil.endsWith(colDefWidth, '%')) {
6903           // If so we should be able to parse the non-percent-sign part to a number
6904           var percentStr = colDefWidth.replace(/%/g, '');
6905           var percent = parseInt(percentStr, 10);
6906           if (isNaN(percent)) {
6907             throw new Error(parseErrorMsg);
6908           }
6909           self.width = colDefWidth;
6910         }
6911         // And see if it's a number string
6912         else if (colDefWidth.match(/^(\d+)$/)) {
6913           self.width = parseInt(colDefWidth.match(/^(\d+)$/)[1], 10);
6914         }
6915         // Otherwise it should be a string of asterisks
6916         else if (colDefWidth.match(/^\*+$/)) {
6917           self.width = colDefWidth;
6918         }
6919         // No idea, throw an Error
6920         else {
6921           throw new Error(parseErrorMsg);
6922         }
6923       }
6924       // Is a number, use it as the width
6925       else {
6926         self.width = colDefWidth;
6927       }
6928     }
6929
6930     ['minWidth', 'maxWidth'].forEach(function (name) {
6931       var minOrMaxWidth = colDef[name];
6932       var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
6933
6934       if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
6935         //Sets default minWidth and maxWidth values
6936         self[name] = ((name === 'minWidth') ? 30 : 9000);
6937       } else if (angular.isString(minOrMaxWidth)) {
6938         if (minOrMaxWidth.match(/^(\d+)$/)) {
6939           self[name] = parseInt(minOrMaxWidth.match(/^(\d+)$/)[1], 10);
6940         } else {
6941           throw new Error(parseErrorMsg);
6942         }
6943       } else {
6944         self[name] = minOrMaxWidth;
6945       }
6946     });
6947
6948     //use field if it is defined; name if it is not
6949     self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
6950
6951     if ( typeof( self.field ) !== 'string' ){
6952       gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
6953     }
6954
6955     self.name = colDef.name;
6956
6957     // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
6958     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6959
6960     //self.originalIndex = index;
6961
6962     self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
6963     self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
6964
6965     /**
6966      * @ngdoc property
6967      * @name cellTooltip
6968      * @propertyOf ui.grid.class:GridOptions.columnDef
6969      * @description Whether or not to show a tooltip when a user hovers over the cell.
6970      * If set to false, no tooltip.  If true, the cell value is shown in the tooltip (useful
6971      * if you have long values in your cells), if a function then that function is called
6972      * passing in the row and the col `cellTooltip( row, col )`, and the return value is shown in the tooltip,
6973      * if it is a static string then displays that static string.
6974      *
6975      * Defaults to false
6976      *
6977      */
6978     if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
6979       self.cellTooltip = false;
6980     } else if ( colDef.cellTooltip === true ){
6981       self.cellTooltip = function(row, col) {
6982         return self.grid.getCellValue( row, col );
6983       };
6984     } else if (typeof(colDef.cellTooltip) === 'function' ){
6985       self.cellTooltip = colDef.cellTooltip;
6986     } else {
6987       self.cellTooltip = function ( row, col ){
6988         return col.colDef.cellTooltip;
6989       };
6990     }
6991
6992     /**
6993      * @ngdoc property
6994      * @name headerTooltip
6995      * @propertyOf ui.grid.class:GridOptions.columnDef
6996      * @description Whether or not to show a tooltip when a user hovers over the header cell.
6997      * If set to false, no tooltip.  If true, the displayName is shown in the tooltip (useful
6998      * if you have long values in your headers), if a function then that function is called
6999      * passing in the row and the col `headerTooltip( col )`, and the return value is shown in the tooltip,
7000      * if a static string then shows that static string.
7001      *
7002      * Defaults to false
7003      *
7004      */
7005     if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
7006       self.headerTooltip = false;
7007     } else if ( colDef.headerTooltip === true ){
7008       self.headerTooltip = function(col) {
7009         return col.displayName;
7010       };
7011     } else if (typeof(colDef.headerTooltip) === 'function' ){
7012       self.headerTooltip = colDef.headerTooltip;
7013     } else {
7014       self.headerTooltip = function ( col ) {
7015         return col.colDef.headerTooltip;
7016       };
7017     }
7018
7019
7020     /**
7021      * @ngdoc property
7022      * @name footerCellClass
7023      * @propertyOf ui.grid.class:GridOptions.columnDef
7024      * @description footerCellClass can be a string specifying the class to append to a cell
7025      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7026      *
7027      */
7028     self.footerCellClass = colDef.footerCellClass;
7029
7030     /**
7031      * @ngdoc property
7032      * @name cellClass
7033      * @propertyOf ui.grid.class:GridOptions.columnDef
7034      * @description cellClass can be a string specifying the class to append to a cell
7035      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7036      *
7037      */
7038     self.cellClass = colDef.cellClass;
7039
7040     /**
7041      * @ngdoc property
7042      * @name headerCellClass
7043      * @propertyOf ui.grid.class:GridOptions.columnDef
7044      * @description headerCellClass can be a string specifying the class to append to a cell
7045      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7046      *
7047      */
7048     self.headerCellClass = colDef.headerCellClass;
7049
7050     /**
7051      * @ngdoc property
7052      * @name cellFilter
7053      * @propertyOf ui.grid.class:GridOptions.columnDef
7054      * @description cellFilter is a filter to apply to the content of each cell
7055      * @example
7056      * <pre>
7057      *   gridOptions.columnDefs[0].cellFilter = 'date'
7058      *
7059      */
7060     self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
7061
7062     /**
7063      * @ngdoc boolean
7064      * @name sortCellFiltered
7065      * @propertyOf ui.grid.class:GridOptions.columnDef
7066      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7067      * sorting the data. Note that when using this option uiGrid will assume that the displayed value is
7068      * a string, and use the {@link ui.grid.class:RowSorter#sortAlpha sortAlpha} `sortFn`. It is possible
7069      * 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}
7070      * for the column which hanldes the returned type. You may specify one of the `sortingAlgorithms`
7071      * found in the {@link ui.grid.RowSorter rowSorter} service.
7072      */
7073     self.sortCellFiltered = colDef.sortCellFiltered ? true : false;
7074
7075     /**
7076      * @ngdoc boolean
7077      * @name filterCellFiltered
7078      * @propertyOf ui.grid.class:GridOptions.columnDef
7079      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7080      * applying "search" `filters`.
7081      */
7082     self.filterCellFiltered = colDef.filterCellFiltered ? true : false;
7083
7084     /**
7085      * @ngdoc property
7086      * @name headerCellFilter
7087      * @propertyOf ui.grid.class:GridOptions.columnDef
7088      * @description headerCellFilter is a filter to apply to the content of the column header
7089      * @example
7090      * <pre>
7091      *   gridOptions.columnDefs[0].headerCellFilter = 'translate'
7092      *
7093      */
7094     self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
7095
7096     /**
7097      * @ngdoc property
7098      * @name footerCellFilter
7099      * @propertyOf ui.grid.class:GridOptions.columnDef
7100      * @description footerCellFilter is a filter to apply to the content of the column footer
7101      * @example
7102      * <pre>
7103      *   gridOptions.columnDefs[0].footerCellFilter = 'date'
7104      *
7105      */
7106     self.footerCellFilter = colDef.footerCellFilter ? colDef.footerCellFilter : "";
7107
7108     self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
7109
7110     self.headerClass = colDef.headerClass;
7111     //self.cursor = self.sortable ? 'pointer' : 'default';
7112
7113     // Turn on sorting by default
7114     self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
7115     self.sortingAlgorithm = colDef.sortingAlgorithm;
7116
7117     /**
7118      * @ngdoc property
7119      * @name sortDirectionCycle
7120      * @propertyOf ui.grid.class:GridOptions.columnDef
7121      * @description (optional) An array of sort directions, specifying the order that they
7122      * should cycle through as the user repeatedly clicks on the column heading.
7123      * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
7124      * refers to the unsorted state. This does not affect the initial sort
7125      * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
7126      * property for that. If
7127      * {@link ui.grid.class:GridOptions.columnDef#suppressRemoveSort suppressRemoveSort}
7128      * is also set, the unsorted state will be skipped even if it is listed here.
7129      * Each direction may not appear in the list more than once (e.g. `[ASC,
7130      * DESC, DESC]` is not allowed), and the list may not be empty.
7131      */
7132     self.sortDirectionCycle = typeof(colDef.sortDirectionCycle) !== 'undefined' ?
7133       colDef.sortDirectionCycle :
7134       [null, uiGridConstants.ASC, uiGridConstants.DESC];
7135
7136     /**
7137      * @ngdoc boolean
7138      * @name suppressRemoveSort
7139      * @propertyOf ui.grid.class:GridOptions.columnDef
7140      * @description (optional) False by default. When enabled, this setting hides the removeSort option
7141      * in the menu, and prevents users from manually removing the sort
7142      */
7143     if ( typeof(self.suppressRemoveSort) === 'undefined'){
7144       self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
7145     }
7146
7147     /**
7148      * @ngdoc property
7149      * @name enableFiltering
7150      * @propertyOf ui.grid.class:GridOptions.columnDef
7151      * @description turn off filtering for an individual column, where
7152      * you've turned on filtering for the overall grid
7153      * @example
7154      * <pre>
7155      *   gridOptions.columnDefs[0].enableFiltering = false;
7156      *
7157      */
7158     // Turn on filtering by default (it's disabled by default at the Grid level)
7159     self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
7160
7161     // self.menuItems = colDef.menuItems;
7162     self.setPropertyOrDefault(colDef, 'menuItems', []);
7163
7164     // Use the column definition sort if we were passed it, but only if this is a newly added column
7165     if ( isNew ){
7166       self.setPropertyOrDefault(colDef, 'sort');
7167     }
7168
7169     // Set up default filters array for when one is not provided.
7170     //   In other words, this (in column def):
7171     //
7172     //       filter: { term: 'something', flags: {}, condition: [CONDITION] }
7173     //
7174     //   is just shorthand for this:
7175     //
7176     //       filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
7177     //
7178     var defaultFilters = [];
7179     if (colDef.filter) {
7180       defaultFilters.push(colDef.filter);
7181     }
7182     else if ( colDef.filters ){
7183       defaultFilters = colDef.filters;
7184     } else {
7185       // Add an empty filter definition object, which will
7186       // translate to a guessed condition and no pre-populated
7187       // value for the filter <input>.
7188       defaultFilters.push({});
7189     }
7190
7191     /**
7192      * @ngdoc property
7193      * @name filter
7194      * @propertyOf ui.grid.class:GridOptions.columnDef
7195      * @description Specify a single filter field on this column.
7196      *
7197      * A filter consists of a condition, a term, and a placeholder:
7198      *
7199      * - condition defines how rows are chosen as matching the filter term. This can be set to
7200      * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
7201      * that gets passed the following arguments: [searchTerm, cellValue, row, column].
7202      * - term: If set, the filter field will be pre-populated
7203      * with this value.
7204      * - placeholder: String that will be set to the `<input>.placeholder` attribute.
7205      * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
7206      * - noTerm: set this to true if you have defined a custom function in condition, and
7207      * your custom function doesn't require a term (so it can run even when the term is null)
7208      * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
7209      * case sensitive matching
7210      * - type: defaults to uiGridConstants.filter.INPUT, which gives a text box.  If set to uiGridConstants.filter.SELECT
7211      * then a select box will be shown with options selectOptions
7212      * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`.  No i18n filter is provided, you need
7213      * to perform the i18n on the values before you provide them
7214      * - disableCancelFilterButton: defaults to false. If set to true then the 'x' button that cancels/clears the filter
7215      * will not be shown.
7216      * @example
7217      * <pre>$scope.gridOptions.columnDefs = [
7218      *   {
7219      *     field: 'field1',
7220      *     filter: {
7221      *       term: 'xx',
7222      *       condition: uiGridConstants.filter.STARTS_WITH,
7223      *       placeholder: 'starts with...',
7224      *       ariaLabel: 'Starts with filter for field1',
7225      *       flags: { caseSensitive: false },
7226      *       type: uiGridConstants.filter.SELECT,
7227      *       selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ],
7228      *       disableCancelFilterButton: true
7229      *     }
7230      *   }
7231      * ]; </pre>
7232      *
7233      */
7234
7235     /*
7236
7237
7238     /*
7239
7240       self.filters = [
7241         {
7242           term: 'search term'
7243           condition: uiGridConstants.filter.CONTAINS,
7244           placeholder: 'my placeholder',
7245           ariaLabel: 'Starts with filter for field1',
7246           flags: {
7247             caseSensitive: true
7248           }
7249         }
7250       ]
7251
7252     */
7253
7254     // Only set filter if this is a newly added column, if we're updating an existing
7255     // column then we don't want to put the default filter back if the user may have already
7256     // removed it.
7257     // However, we do want to keep the settings if they change, just not the term
7258     if ( isNew ) {
7259       self.setPropertyOrDefault(colDef, 'filter');
7260       self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
7261     } else if ( self.filters.length === defaultFilters.length ) {
7262       self.filters.forEach( function( filter, index ){
7263         if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
7264           filter.placeholder = defaultFilters[index].placeholder;
7265         }
7266         if (typeof(defaultFilters[index].ariaLabel) !== 'undefined') {
7267           filter.ariaLabel = defaultFilters[index].ariaLabel;
7268         }
7269         if (typeof(defaultFilters[index].flags) !== 'undefined') {
7270           filter.flags = defaultFilters[index].flags;
7271         }
7272         if (typeof(defaultFilters[index].type) !== 'undefined') {
7273           filter.type = defaultFilters[index].type;
7274         }
7275         if (typeof(defaultFilters[index].selectOptions) !== 'undefined') {
7276           filter.selectOptions = defaultFilters[index].selectOptions;
7277         }
7278       });
7279     }
7280   };
7281
7282   /**
7283    * @ngdoc function
7284    * @name unsort
7285    * @methodOf ui.grid.class:GridColumn
7286    * @description Removes column from the grid sorting
7287    */
7288   GridColumn.prototype.unsort = function () {
7289     this.sort = {};
7290     this.grid.api.core.raise.sortChanged( this.grid, this.grid.getColumnSorting() );
7291   };
7292
7293
7294   /**
7295    * @ngdoc function
7296    * @name getColClass
7297    * @methodOf ui.grid.class:GridColumn
7298    * @description Returns the class name for the column
7299    * @param {bool} prefixDot  if true, will return .className instead of className
7300    */
7301   GridColumn.prototype.getColClass = function (prefixDot) {
7302     var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
7303
7304     return prefixDot ? '.' + cls : cls;
7305   };
7306
7307     /**
7308      * @ngdoc function
7309      * @name isPinnedLeft
7310      * @methodOf ui.grid.class:GridColumn
7311      * @description Returns true if column is in the left render container
7312      */
7313     GridColumn.prototype.isPinnedLeft = function () {
7314       return this.renderContainer === 'left';
7315     };
7316
7317     /**
7318      * @ngdoc function
7319      * @name isPinnedRight
7320      * @methodOf ui.grid.class:GridColumn
7321      * @description Returns true if column is in the right render container
7322      */
7323     GridColumn.prototype.isPinnedRight = function () {
7324       return this.renderContainer === 'right';
7325     };
7326
7327
7328     /**
7329    * @ngdoc function
7330    * @name getColClassDefinition
7331    * @methodOf ui.grid.class:GridColumn
7332    * @description Returns the class definition for th column
7333    */
7334   GridColumn.prototype.getColClassDefinition = function () {
7335     return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { min-width: ' + this.drawnWidth + 'px; max-width: ' + this.drawnWidth + 'px; }';
7336   };
7337
7338   /**
7339    * @ngdoc function
7340    * @name getRenderContainer
7341    * @methodOf ui.grid.class:GridColumn
7342    * @description Returns the render container object that this column belongs to.
7343    *
7344    * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
7345    */
7346   GridColumn.prototype.getRenderContainer = function getRenderContainer() {
7347     var self = this;
7348
7349     var containerId = self.renderContainer;
7350
7351     if (containerId === null || containerId === '' || containerId === undefined) {
7352       containerId = 'body';
7353     }
7354
7355     return self.grid.renderContainers[containerId];
7356   };
7357
7358   /**
7359    * @ngdoc function
7360    * @name showColumn
7361    * @methodOf ui.grid.class:GridColumn
7362    * @description Makes the column visible by setting colDef.visible = true
7363    */
7364   GridColumn.prototype.showColumn = function() {
7365       this.colDef.visible = true;
7366   };
7367
7368
7369   /**
7370    * @ngdoc property
7371    * @name aggregationHideLabel
7372    * @propertyOf ui.grid.class:GridOptions.columnDef
7373    * @description defaults to false, if set to true hides the label text
7374    * in the aggregation footer, so only the value is displayed.
7375    *
7376    */
7377   /**
7378    * @ngdoc function
7379    * @name getAggregationText
7380    * @methodOf ui.grid.class:GridColumn
7381    * @description Gets the aggregation label from colDef.aggregationLabel if
7382    * specified or by using i18n, including deciding whether or not to display
7383    * based on colDef.aggregationHideLabel.
7384    *
7385    * @param {string} label the i18n lookup value to use for the column label
7386    *
7387    */
7388   GridColumn.prototype.getAggregationText = function () {
7389     var self = this;
7390     if ( self.colDef.aggregationHideLabel ){
7391       return '';
7392     }
7393     else if ( self.colDef.aggregationLabel ) {
7394       return self.colDef.aggregationLabel;
7395     }
7396     else {
7397       switch ( self.colDef.aggregationType ){
7398         case uiGridConstants.aggregationTypes.count:
7399           return i18nService.getSafeText('aggregation.count');
7400         case uiGridConstants.aggregationTypes.sum:
7401           return i18nService.getSafeText('aggregation.sum');
7402         case uiGridConstants.aggregationTypes.avg:
7403           return i18nService.getSafeText('aggregation.avg');
7404         case uiGridConstants.aggregationTypes.min:
7405           return i18nService.getSafeText('aggregation.min');
7406         case uiGridConstants.aggregationTypes.max:
7407           return i18nService.getSafeText('aggregation.max');
7408         default:
7409           return '';
7410       }
7411     }
7412   };
7413
7414   GridColumn.prototype.getCellTemplate = function () {
7415     var self = this;
7416
7417     return self.cellTemplatePromise;
7418   };
7419
7420   GridColumn.prototype.getCompiledElementFn = function () {
7421     var self = this;
7422
7423     return self.compiledElementFnDefer.promise;
7424   };
7425
7426   return GridColumn;
7427 }]);
7428
7429 })();
7430
7431   (function(){
7432
7433 angular.module('ui.grid')
7434 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7435
7436   /**
7437    * @ngdoc function
7438    * @name ui.grid.class:GridOptions
7439    * @description Default GridOptions class.  GridOptions are defined by the application developer and overlaid
7440    * over this object.  Setting gridOptions within your controller is the most common method for an application
7441    * developer to configure the behaviour of their ui-grid
7442    *
7443    * @example To define your gridOptions within your controller:
7444    * <pre>$scope.gridOptions = {
7445    *   data: $scope.myData,
7446    *   columnDefs: [
7447    *     { name: 'field1', displayName: 'pretty display name' },
7448    *     { name: 'field2', visible: false }
7449    *  ]
7450    * };</pre>
7451    *
7452    * You can then use this within your html template, when you define your grid:
7453    * <pre>&lt;div ui-grid="gridOptions"&gt;&lt;/div&gt;</pre>
7454    *
7455    * To provide default options for all of the grids within your application, use an angular
7456    * decorator to modify the GridOptions factory.
7457    * <pre>
7458    * app.config(function($provide){
7459    *   $provide.decorator('GridOptions',function($delegate){
7460    *     var gridOptions;
7461    *     gridOptions = angular.copy($delegate);
7462    *     gridOptions.initialize = function(options) {
7463    *       var initOptions;
7464    *       initOptions = $delegate.initialize(options);
7465    *       initOptions.enableColumnMenus = false;
7466    *       return initOptions;
7467    *     };
7468    *     return gridOptions;
7469    *   });
7470    * });
7471    * </pre>
7472    */
7473   return {
7474     initialize: function( baseOptions ){
7475       /**
7476        * @ngdoc function
7477        * @name onRegisterApi
7478        * @propertyOf ui.grid.class:GridOptions
7479        * @description A callback that returns the gridApi once the grid is instantiated, which is
7480        * then used to interact with the grid programatically.
7481        *
7482        * Note that the gridApi.core.renderingComplete event is identical to this
7483        * callback, but has the advantage that it can be called from multiple places
7484        * if needed
7485        *
7486        * @example
7487        * <pre>
7488        *   $scope.gridOptions.onRegisterApi = function ( gridApi ) {
7489        *     $scope.gridApi = gridApi;
7490        *     $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid );
7491        *   };
7492        * </pre>
7493        *
7494        */
7495       baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
7496
7497       /**
7498        * @ngdoc object
7499        * @name data
7500        * @propertyOf ui.grid.class:GridOptions
7501        * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
7502        * the grid.
7503        *
7504        * Most commonly the data is an array of objects, where each object has a number of attributes.
7505        * Each attribute automatically becomes a column in your grid.  This array could, for example, be sourced from
7506        * an angularJS $resource query request.  The array can also contain complex objects, refer the binding tutorial
7507        * for examples of that.
7508        *
7509        * The most flexible usage is to set your data on $scope:
7510        *
7511        * `$scope.data = data;`
7512        *
7513        * And then direct the grid to resolve whatever is in $scope.data:
7514        *
7515        * `$scope.gridOptions.data = 'data';`
7516        *
7517        * This is the most flexible approach as it allows you to replace $scope.data whenever you feel like it without
7518        * getting pointer issues.
7519        *
7520        * Alternatively you can directly set the data array:
7521        *
7522        * `$scope.gridOptions.data = [ ];`
7523        * or
7524        *
7525        * `$http.get('/data/100.json')
7526        * .success(function(data) {
7527        *   $scope.myData = data;
7528        *   $scope.gridOptions.data = $scope.myData;
7529        *  });`
7530        *
7531        * Where you do this, you need to take care in updating the data - you can't just update `$scope.myData` to some other
7532        * array, you need to update $scope.gridOptions.data to point to that new array as well.
7533        *
7534        */
7535       baseOptions.data = baseOptions.data || [];
7536
7537       /**
7538        * @ngdoc array
7539        * @name columnDefs
7540        * @propertyOf  ui.grid.class:GridOptions
7541        * @description Array of columnDef objects.  Only required property is name.
7542        * The individual options available in columnDefs are documented in the
7543        * {@link ui.grid.class:GridOptions.columnDef columnDef} section
7544        * </br>_field property can be used in place of name for backwards compatibility with 2.x_
7545        *  @example
7546        *
7547        * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7548        *
7549        */
7550       baseOptions.columnDefs = baseOptions.columnDefs || [];
7551
7552       /**
7553        * @ngdoc object
7554        * @name ui.grid.class:GridOptions.columnDef
7555        * @description Definition / configuration of an individual column, which would typically be
7556        * one of many column definitions within the gridOptions.columnDefs array
7557        * @example
7558        * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7559        *
7560        */
7561
7562
7563       /**
7564        * @ngdoc array
7565        * @name excludeProperties
7566        * @propertyOf  ui.grid.class:GridOptions
7567        * @description Array of property names in data to ignore when auto-generating column names.  Provides the
7568        * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
7569        * to exclude.
7570        *
7571        * If columnDefs is defined, this will be ignored.
7572        *
7573        * Defaults to ['$$hashKey']
7574        */
7575
7576       baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
7577
7578       /**
7579        * @ngdoc boolean
7580        * @name enableRowHashing
7581        * @propertyOf ui.grid.class:GridOptions
7582        * @description True by default. When enabled, this setting allows uiGrid to add
7583        * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
7584        * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
7585        *
7586        * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
7587        * 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
7588        * and are altering the data set often.
7589        */
7590       baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
7591
7592       /**
7593        * @ngdoc function
7594        * @name rowIdentity
7595        * @methodOf ui.grid.class:GridOptions
7596        * @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).
7597        *
7598        * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
7599        */
7600       baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
7601         return gridUtil.hashKey(row);
7602       };
7603
7604       /**
7605        * @ngdoc function
7606        * @name getRowIdentity
7607        * @methodOf ui.grid.class:GridOptions
7608        * @description This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
7609        *
7610        * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
7611        */
7612       baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
7613         return row.$$hashKey;
7614       };
7615
7616       /**
7617        * @ngdoc property
7618        * @name flatEntityAccess
7619        * @propertyOf ui.grid.class:GridOptions
7620        * @description Set to true if your columns are all related directly to fields in a flat object structure - i.e.
7621        * each of your columns associate directly with a property on each of the entities in your data array.
7622        *
7623        * In that situation we can avoid all the logic associated with complex binding to functions or to properties of sub-objects,
7624        * which can provide a significant speed improvement with large data sets when filtering or sorting.
7625        *
7626        * By default false
7627        */
7628       baseOptions.flatEntityAccess = baseOptions.flatEntityAccess === true;
7629
7630       /**
7631        * @ngdoc property
7632        * @name showHeader
7633        * @propertyOf ui.grid.class:GridOptions
7634        * @description True by default. When set to false, this setting will replace the
7635        * standard header template with '<div></div>', resulting in no header being shown.
7636        */
7637       baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
7638
7639       /* (NOTE): Don't show this in the docs. We only use it internally
7640        * @ngdoc property
7641        * @name headerRowHeight
7642        * @propertyOf ui.grid.class:GridOptions
7643        * @description The height of the header in pixels, defaults to 30
7644        *
7645        */
7646       if (!baseOptions.showHeader) {
7647         baseOptions.headerRowHeight = 0;
7648       }
7649       else {
7650         baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
7651       }
7652
7653       /**
7654        * @ngdoc property
7655        * @name rowHeight
7656        * @propertyOf ui.grid.class:GridOptions
7657        * @description The height of the row in pixels, defaults to 30
7658        *
7659        */
7660       baseOptions.rowHeight = baseOptions.rowHeight || 30;
7661
7662       /**
7663        * @ngdoc integer
7664        * @name minRowsToShow
7665        * @propertyOf ui.grid.class:GridOptions
7666        * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
7667        */
7668       baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
7669
7670       /**
7671        * @ngdoc property
7672        * @name showGridFooter
7673        * @propertyOf ui.grid.class:GridOptions
7674        * @description Whether or not to show the footer, defaults to false
7675        * The footer display Total Rows and Visible Rows (filtered rows)
7676        */
7677       baseOptions.showGridFooter = baseOptions.showGridFooter === true;
7678
7679       /**
7680        * @ngdoc property
7681        * @name showColumnFooter
7682        * @propertyOf ui.grid.class:GridOptions
7683        * @description Whether or not to show the column footer, defaults to false
7684        * The column footer displays column aggregates
7685        */
7686       baseOptions.showColumnFooter = baseOptions.showColumnFooter === true;
7687
7688       /**
7689        * @ngdoc property
7690        * @name columnFooterHeight
7691        * @propertyOf ui.grid.class:GridOptions
7692        * @description The height of the footer rows (column footer and grid footer) in pixels
7693        *
7694        */
7695       baseOptions.columnFooterHeight = typeof(baseOptions.columnFooterHeight) !== "undefined" ? baseOptions.columnFooterHeight : 30;
7696       baseOptions.gridFooterHeight = typeof(baseOptions.gridFooterHeight) !== "undefined" ? baseOptions.gridFooterHeight : 30;
7697
7698       baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
7699
7700       /**
7701        * @ngdoc property
7702        * @name maxVisibleColumnCount
7703        * @propertyOf ui.grid.class:GridOptions
7704        * @description Defaults to 200
7705        *
7706        */
7707       baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
7708
7709       /**
7710        * @ngdoc property
7711        * @name virtualizationThreshold
7712        * @propertyOf ui.grid.class:GridOptions
7713        * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
7714        */
7715       baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
7716
7717       /**
7718        * @ngdoc property
7719        * @name columnVirtualizationThreshold
7720        * @propertyOf ui.grid.class:GridOptions
7721        * @description Turn virtualization on when number of columns goes over this number, defaults to 10
7722        */
7723       baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
7724
7725       /**
7726        * @ngdoc property
7727        * @name excessRows
7728        * @propertyOf ui.grid.class:GridOptions
7729        * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
7730        * Defaults to 4
7731        */
7732       baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7733       /**
7734        * @ngdoc property
7735        * @name scrollThreshold
7736        * @propertyOf ui.grid.class:GridOptions
7737        * @description Defaults to 4
7738        */
7739       baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
7740
7741       /**
7742        * @ngdoc property
7743        * @name excessColumns
7744        * @propertyOf ui.grid.class:GridOptions
7745        * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
7746        * Defaults to 4
7747        */
7748       baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7749       /**
7750        * @ngdoc property
7751        * @name horizontalScrollThreshold
7752        * @propertyOf ui.grid.class:GridOptions
7753        * @description Defaults to 4
7754        */
7755       baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
7756
7757
7758       /**
7759        * @ngdoc property
7760        * @name aggregationCalcThrottle
7761        * @propertyOf ui.grid.class:GridOptions
7762        * @description Default time in milliseconds to throttle aggregation calcuations, defaults to 500ms
7763        */
7764       baseOptions.aggregationCalcThrottle = typeof(baseOptions.aggregationCalcThrottle) !== "undefined" ? baseOptions.aggregationCalcThrottle : 500;
7765
7766       /**
7767        * @ngdoc property
7768        * @name wheelScrollThrottle
7769        * @propertyOf ui.grid.class:GridOptions
7770        * @description Default time in milliseconds to throttle scroll events to, defaults to 70ms
7771        */
7772       baseOptions.wheelScrollThrottle = typeof(baseOptions.wheelScrollThrottle) !== "undefined" ? baseOptions.wheelScrollThrottle : 70;
7773
7774
7775       /**
7776        * @ngdoc property
7777        * @name scrollDebounce
7778        * @propertyOf ui.grid.class:GridOptions
7779        * @description Default time in milliseconds to debounce scroll events, defaults to 300ms
7780        */
7781       baseOptions.scrollDebounce = typeof(baseOptions.scrollDebounce) !== "undefined" ? baseOptions.scrollDebounce : 300;
7782
7783       /**
7784        * @ngdoc boolean
7785        * @name enableSorting
7786        * @propertyOf ui.grid.class:GridOptions
7787        * @description True by default. When enabled, this setting adds sort
7788        * widgets to the column headers, allowing sorting of the data for the entire grid.
7789        * Sorting can then be disabled on individual columns using the columnDefs.
7790        */
7791       baseOptions.enableSorting = baseOptions.enableSorting !== false;
7792
7793       /**
7794        * @ngdoc boolean
7795        * @name enableFiltering
7796        * @propertyOf ui.grid.class:GridOptions
7797        * @description False by default. When enabled, this setting adds filter
7798        * boxes to each column header, allowing filtering within the column for the entire grid.
7799        * Filtering can then be disabled on individual columns using the columnDefs.
7800        */
7801       baseOptions.enableFiltering = baseOptions.enableFiltering === true;
7802
7803       /**
7804        * @ngdoc boolean
7805        * @name enableColumnMenus
7806        * @propertyOf ui.grid.class:GridOptions
7807        * @description True by default. When enabled, this setting displays a column
7808        * menu within each column.
7809        */
7810       baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
7811
7812       /**
7813        * @ngdoc boolean
7814        * @name enableVerticalScrollbar
7815        * @propertyOf ui.grid.class:GridOptions
7816        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
7817        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7818        */
7819       baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7820
7821       /**
7822        * @ngdoc boolean
7823        * @name enableHorizontalScrollbar
7824        * @propertyOf ui.grid.class:GridOptions
7825        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
7826        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
7827        */
7828       baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
7829
7830       /**
7831        * @ngdoc boolean
7832        * @name enableMinHeightCheck
7833        * @propertyOf ui.grid.class:GridOptions
7834        * @description True by default. When enabled, a newly initialized grid will check to see if it is tall enough to display
7835        * at least one row of data.  If the grid is not tall enough, it will resize the DOM element to display minRowsToShow number
7836        * of rows.
7837        */
7838        baseOptions.enableMinHeightCheck = baseOptions.enableMinHeightCheck !== false;
7839
7840       /**
7841        * @ngdoc boolean
7842        * @name minimumColumnSize
7843        * @propertyOf ui.grid.class:GridOptions
7844        * @description Columns can't be smaller than this, defaults to 10 pixels
7845        */
7846       baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
7847
7848       /**
7849        * @ngdoc function
7850        * @name rowEquality
7851        * @methodOf ui.grid.class:GridOptions
7852        * @description By default, rows are compared using object equality.  This option can be overridden
7853        * to compare on any data item property or function
7854        * @param {object} entityA First Data Item to compare
7855        * @param {object} entityB Second Data Item to compare
7856        */
7857       baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
7858         return entityA === entityB;
7859       };
7860
7861       /**
7862        * @ngdoc string
7863        * @name headerTemplate
7864        * @propertyOf ui.grid.class:GridOptions
7865        * @description Null by default. When provided, this setting uses a custom header
7866        * template, rather than the default template. Can be set to either the name of a template file:
7867        * <pre>  $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
7868        * inline html
7869        * <pre>  $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
7870        * or the id of a precompiled template (TBD how to use this).
7871        * </br>Refer to the custom header tutorial for more information.
7872        * If you want no header at all, you can set to an empty div:
7873        * <pre>  $scope.gridOptions.headerTemplate = '<div></div>';</pre>
7874        *
7875        * If you want to only have a static header, then you can set to static content.  If
7876        * you want to tailor the existing column headers, then you should look at the
7877        * current 'ui-grid-header.html' template in github as your starting point.
7878        *
7879        */
7880       baseOptions.headerTemplate = baseOptions.headerTemplate || null;
7881
7882       /**
7883        * @ngdoc string
7884        * @name footerTemplate
7885        * @propertyOf ui.grid.class:GridOptions
7886        * @description (optional) ui-grid/ui-grid-footer by default.  This footer shows the per-column
7887        * aggregation totals.
7888        * 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
7889        * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
7890        * of a precompiled template (TBD how to use this).  Refer to the custom footer tutorial for more information.
7891        */
7892       baseOptions.footerTemplate = baseOptions.footerTemplate || 'ui-grid/ui-grid-footer';
7893
7894       /**
7895        * @ngdoc string
7896        * @name gridFooterTemplate
7897        * @propertyOf ui.grid.class:GridOptions
7898        * @description (optional) ui-grid/ui-grid-grid-footer by default. This template by default shows the
7899        * total items at the bottom of the grid, and the selected items if selection is enabled.
7900        */
7901       baseOptions.gridFooterTemplate = baseOptions.gridFooterTemplate || 'ui-grid/ui-grid-grid-footer';
7902
7903       /**
7904        * @ngdoc string
7905        * @name rowTemplate
7906        * @propertyOf ui.grid.class:GridOptions
7907        * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
7908        * custom row template.  Can be set to either the name of a template file:
7909        * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
7910        * inline html
7911        * <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>
7912        * or the id of a precompiled template (TBD how to use this) can be provided.
7913        * </br>Refer to the custom row template tutorial for more information.
7914        */
7915       baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
7916
7917       /**
7918        * @ngdoc object
7919        * @name appScopeProvider
7920        * @propertyOf ui.grid.class:GridOptions
7921        * @description by default, the parent scope of the ui-grid element will be assigned to grid.appScope
7922        * this property allows you to assign any reference you want to grid.appScope
7923        */
7924       baseOptions.appScopeProvider = baseOptions.appScopeProvider || null;
7925
7926       return baseOptions;
7927     }
7928   };
7929
7930
7931 }]);
7932
7933 })();
7934
7935 (function(){
7936
7937 angular.module('ui.grid')
7938
7939   /**
7940    * @ngdoc function
7941    * @name ui.grid.class:GridRenderContainer
7942    * @description The grid has render containers, allowing the ability to have pinned columns.  If the grid
7943    * is right-to-left then there may be a right render container, if left-to-right then there may
7944    * be a left render container.  There is always a body render container.
7945    * @param {string} name The name of the render container ('body', 'left', or 'right')
7946    * @param {Grid} grid the grid the render container is in
7947    * @param {object} options the render container options
7948    */
7949 .factory('GridRenderContainer', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
7950   function GridRenderContainer(name, grid, options) {
7951     var self = this;
7952
7953     // if (gridUtil.type(grid) !== 'Grid') {
7954     //   throw new Error('Grid argument is not a Grid object');
7955     // }
7956
7957     self.name = name;
7958
7959     self.grid = grid;
7960
7961     // self.rowCache = [];
7962     // self.columnCache = [];
7963
7964     self.visibleRowCache = [];
7965     self.visibleColumnCache = [];
7966
7967     self.renderedRows = [];
7968     self.renderedColumns = [];
7969
7970     self.prevScrollTop = 0;
7971     self.prevScrolltopPercentage = 0;
7972     self.prevRowScrollIndex = 0;
7973
7974     self.prevScrollLeft = 0;
7975     self.prevScrollleftPercentage = 0;
7976     self.prevColumnScrollIndex = 0;
7977
7978     self.columnStyles = "";
7979
7980     self.viewportAdjusters = [];
7981
7982     /**
7983      *  @ngdoc boolean
7984      *  @name hasHScrollbar
7985      *  @propertyOf  ui.grid.class:GridRenderContainer
7986      *  @description flag to signal that container has a horizontal scrollbar
7987      */
7988     self.hasHScrollbar = false;
7989
7990     /**
7991      *  @ngdoc boolean
7992      *  @name hasVScrollbar
7993      *  @propertyOf  ui.grid.class:GridRenderContainer
7994      *  @description flag to signal that container has a vertical scrollbar
7995      */
7996     self.hasVScrollbar = false;
7997
7998     /**
7999      *  @ngdoc boolean
8000      *  @name canvasHeightShouldUpdate
8001      *  @propertyOf  ui.grid.class:GridRenderContainer
8002      *  @description flag to signal that container should recalculate the canvas size
8003      */
8004     self.canvasHeightShouldUpdate = true;
8005
8006     /**
8007      *  @ngdoc boolean
8008      *  @name canvasHeight
8009      *  @propertyOf  ui.grid.class:GridRenderContainer
8010      *  @description last calculated canvas height value
8011      */
8012     self.$$canvasHeight = 0;
8013
8014     if (options && angular.isObject(options)) {
8015       angular.extend(self, options);
8016     }
8017
8018     grid.registerStyleComputation({
8019       priority: 5,
8020       func: function () {
8021         self.updateColumnWidths();
8022         return self.columnStyles;
8023       }
8024     });
8025   }
8026
8027
8028   GridRenderContainer.prototype.reset = function reset() {
8029     // this.rowCache.length = 0;
8030     // this.columnCache.length = 0;
8031
8032     this.visibleColumnCache.length = 0;
8033     this.visibleRowCache.length = 0;
8034
8035     this.renderedRows.length = 0;
8036     this.renderedColumns.length = 0;
8037   };
8038
8039   // TODO(c0bra): calculate size?? Should this be in a stackable directive?
8040
8041
8042   GridRenderContainer.prototype.containsColumn = function (col) {
8043      return this.visibleColumnCache.indexOf(col) !== -1;
8044   };
8045
8046   GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
8047     var self = this;
8048     var minRows = 0;
8049     var rowAddedHeight = 0;
8050     var viewPortHeight = self.getViewportHeight();
8051     for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
8052       rowAddedHeight += self.visibleRowCache[i].height;
8053       minRows++;
8054     }
8055     return minRows;
8056   };
8057
8058   GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
8059     var self = this;
8060     var viewportWidth = this.getViewportWidth();
8061
8062     var min = 0;
8063     var totalWidth = 0;
8064     // self.columns.forEach(function(col, i) {
8065     for (var i = 0; i < self.visibleColumnCache.length; i++) {
8066       var col = self.visibleColumnCache[i];
8067
8068       if (totalWidth < viewportWidth) {
8069         totalWidth += col.drawnWidth ? col.drawnWidth : 0;
8070         min++;
8071       }
8072       else {
8073         var currWidth = 0;
8074         for (var j = i; j >= i - min; j--) {
8075           currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
8076         }
8077         if (currWidth < viewportWidth) {
8078           min++;
8079         }
8080       }
8081     }
8082
8083     return min;
8084   };
8085
8086   GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
8087     return this.visibleRowCache.length;
8088   };
8089
8090   /**
8091    * @ngdoc function
8092    * @name registerViewportAdjuster
8093    * @methodOf ui.grid.class:GridRenderContainer
8094    * @description Registers an adjuster to the render container's available width or height.  Adjusters are used
8095    * to tell the render container that there is something else consuming space, and to adjust it's size
8096    * appropriately.
8097    * @param {function} func the adjuster function we want to register
8098    */
8099
8100   GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
8101     this.viewportAdjusters.push(func);
8102   };
8103
8104   /**
8105    * @ngdoc function
8106    * @name removeViewportAdjuster
8107    * @methodOf ui.grid.class:GridRenderContainer
8108    * @description Removes an adjuster, should be used when your element is destroyed
8109    * @param {function} func the adjuster function we want to remove
8110    */
8111   GridRenderContainer.prototype.removeViewportAdjuster = function removeViewportAdjuster(func) {
8112     var idx = this.viewportAdjusters.indexOf(func);
8113
8114     if (idx > -1) {
8115       this.viewportAdjusters.splice(idx, 1);
8116     }
8117   };
8118
8119   /**
8120    * @ngdoc function
8121    * @name getViewportAdjustment
8122    * @methodOf ui.grid.class:GridRenderContainer
8123    * @description Gets the adjustment based on the viewportAdjusters.
8124    * @returns {object} a hash of { height: x, width: y }.  Usually the values will be negative
8125    */
8126   GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
8127     var self = this;
8128
8129     var adjustment = { height: 0, width: 0 };
8130
8131     self.viewportAdjusters.forEach(function (func) {
8132       adjustment = func.call(this, adjustment);
8133     });
8134
8135     return adjustment;
8136   };
8137
8138   GridRenderContainer.prototype.getMargin = function getMargin(side) {
8139     var self = this;
8140
8141     var amount = 0;
8142
8143     self.viewportAdjusters.forEach(function (func) {
8144       var adjustment = func.call(this, { height: 0, width: 0 });
8145
8146       if (adjustment.side && adjustment.side === side) {
8147         amount += adjustment.width * -1;
8148       }
8149     });
8150
8151     return amount;
8152   };
8153
8154   GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
8155     var self = this;
8156
8157     var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
8158
8159     var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
8160
8161
8162     var adjustment = self.getViewportAdjustment();
8163
8164     viewPortHeight = viewPortHeight + adjustment.height;
8165
8166     return viewPortHeight;
8167   };
8168
8169   GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
8170     var self = this;
8171
8172     var viewportWidth = self.grid.gridWidth;
8173
8174     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8175     //  viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8176     //}
8177
8178     // var viewportWidth = 0;\
8179     // self.visibleColumnCache.forEach(function (column) {
8180     //   viewportWidth += column.drawnWidth;
8181     // });
8182
8183     var adjustment = self.getViewportAdjustment();
8184
8185     viewportWidth = viewportWidth + adjustment.width;
8186
8187     return viewportWidth;
8188   };
8189
8190   GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8191     var self = this;
8192
8193     var viewportWidth = this.getViewportWidth();
8194
8195     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8196     //  viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8197     //}
8198
8199     // var adjustment = self.getViewportAdjustment();
8200     // viewPortWidth = viewPortWidth + adjustment.width;
8201
8202     return viewportWidth;
8203   };
8204
8205
8206   /**
8207    * @ngdoc function
8208    * @name getCanvasHeight
8209    * @methodOf ui.grid.class:GridRenderContainer
8210    * @description Returns the total canvas height.   Only recalculates if canvasHeightShouldUpdate = false
8211    * @returns {number} total height of all the visible rows in the container
8212    */
8213   GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
8214     var self = this;
8215
8216     if (!self.canvasHeightShouldUpdate) {
8217       return self.$$canvasHeight;
8218     }
8219
8220     var oldCanvasHeight = self.$$canvasHeight;
8221
8222     self.$$canvasHeight =  0;
8223
8224     self.visibleRowCache.forEach(function(row){
8225       self.$$canvasHeight += row.height;
8226     });
8227
8228
8229     self.canvasHeightShouldUpdate = false;
8230
8231     self.grid.api.core.raise.canvasHeightChanged(oldCanvasHeight, self.$$canvasHeight);
8232
8233     return self.$$canvasHeight;
8234   };
8235
8236   GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8237     return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight;
8238   };
8239
8240   GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
8241     var self = this;
8242
8243     var ret = self.canvasWidth;
8244
8245     return ret;
8246   };
8247
8248   GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
8249     this.renderedRows.length = newRows.length;
8250     for (var i = 0; i < newRows.length; i++) {
8251       this.renderedRows[i] = newRows[i];
8252     }
8253   };
8254
8255   GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8256     var self = this;
8257
8258     // OLD:
8259     this.renderedColumns.length = newColumns.length;
8260     for (var i = 0; i < newColumns.length; i++) {
8261       this.renderedColumns[i] = newColumns[i];
8262     }
8263
8264     this.updateColumnOffset();
8265   };
8266
8267   GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
8268     // Calculate the width of the columns on the left side that are no longer rendered.
8269     //  That will be the offset for the columns as we scroll horizontally.
8270     var hiddenColumnsWidth = 0;
8271     for (var i = 0; i < this.currentFirstColumn; i++) {
8272       hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
8273     }
8274
8275     this.columnOffset = hiddenColumnsWidth;
8276   };
8277
8278   GridRenderContainer.prototype.scrollVertical = function (newScrollTop) {
8279     var vertScrollPercentage = -1;
8280
8281     if (newScrollTop !== this.prevScrollTop) {
8282       var yDiff = newScrollTop - this.prevScrollTop;
8283
8284       if (yDiff > 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; }
8285       if (yDiff < 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.UP; }
8286
8287       var vertScrollLength = this.getVerticalScrollLength();
8288
8289       vertScrollPercentage = newScrollTop / vertScrollLength;
8290
8291       // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8292
8293       if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
8294       if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
8295
8296       this.adjustScrollVertical(newScrollTop, vertScrollPercentage);
8297       return vertScrollPercentage;
8298     }
8299   };
8300
8301   GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8302     var horizScrollPercentage = -1;
8303
8304     // Handle RTL here
8305
8306     if (newScrollLeft !== this.prevScrollLeft) {
8307       var xDiff = newScrollLeft - this.prevScrollLeft;
8308
8309       if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
8310       if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
8311
8312       var horizScrollLength = (this.canvasWidth - this.getViewportWidth());
8313       if (horizScrollLength !== 0) {
8314         horizScrollPercentage = newScrollLeft / horizScrollLength;
8315       }
8316       else {
8317         horizScrollPercentage = 0;
8318       }
8319
8320       this.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
8321       return horizScrollPercentage;
8322     }
8323   };
8324
8325   GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
8326     if (this.prevScrollTop === scrollTop && !force) {
8327       return;
8328     }
8329
8330     if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
8331       scrollTop = (this.getCanvasHeight() - this.getViewportHeight()) * scrollPercentage;
8332     }
8333
8334     this.adjustRows(scrollTop, scrollPercentage, false);
8335
8336     this.prevScrollTop = scrollTop;
8337     this.prevScrolltopPercentage = scrollPercentage;
8338
8339     this.grid.queueRefresh();
8340   };
8341
8342   GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
8343     if (this.prevScrollLeft === scrollLeft && !force) {
8344       return;
8345     }
8346
8347     if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
8348       scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
8349     }
8350
8351     this.adjustColumns(scrollLeft, scrollPercentage);
8352
8353     this.prevScrollLeft = scrollLeft;
8354     this.prevScrollleftPercentage = scrollPercentage;
8355
8356     this.grid.queueRefresh();
8357   };
8358
8359   GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) {
8360     var self = this;
8361
8362     var minRows = self.minRowsToRender();
8363
8364     var rowCache = self.visibleRowCache;
8365
8366     var maxRowIndex = rowCache.length - minRows;
8367
8368     // console.log('scroll%1', scrollPercentage);
8369
8370     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
8371     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
8372       scrollPercentage = scrollTop / self.getVerticalScrollLength();
8373     }
8374
8375     var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8376
8377     // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
8378
8379     // Define a max row index that we can't scroll past
8380     if (rowIndex > maxRowIndex) {
8381       rowIndex = maxRowIndex;
8382     }
8383
8384     var newRange = [];
8385     if (rowCache.length > self.grid.options.virtualizationThreshold) {
8386       if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) {
8387         // Have we hit the threshold going down?
8388         if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8389           return;
8390         }
8391         //Have we hit the threshold going up?
8392         if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8393           return;
8394         }
8395       }
8396       var rangeStart = {};
8397       var rangeEnd = {};
8398
8399       rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8400       rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8401
8402       newRange = [rangeStart, rangeEnd];
8403     }
8404     else {
8405       var maxLen = self.visibleRowCache.length;
8406       newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
8407     }
8408
8409     self.updateViewableRowRange(newRange);
8410
8411     self.prevRowScrollIndex = rowIndex;
8412   };
8413
8414   GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
8415     var self = this;
8416
8417     var minCols = self.minColumnsToRender();
8418
8419     var columnCache = self.visibleColumnCache;
8420     var maxColumnIndex = columnCache.length - minCols;
8421
8422     // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
8423     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8424       var horizScrollLength = (self.getCanvasWidth() - self.getViewportWidth());
8425       scrollPercentage = scrollLeft / horizScrollLength;
8426     }
8427
8428     var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
8429
8430     // Define a max row index that we can't scroll past
8431     if (colIndex > maxColumnIndex) {
8432       colIndex = maxColumnIndex;
8433     }
8434
8435     var newRange = [];
8436     if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8437       /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8438        * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8439       // Have we hit the threshold going down?
8440       if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8441         return;
8442       }
8443       //Have we hit the threshold going up?
8444       if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8445         return;
8446       }*/
8447
8448       var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
8449       var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
8450
8451       newRange = [rangeStart, rangeEnd];
8452     }
8453     else {
8454       var maxLen = self.visibleColumnCache.length;
8455
8456       newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
8457     }
8458
8459     self.updateViewableColumnRange(newRange);
8460
8461     self.prevColumnScrollIndex = colIndex;
8462   };
8463
8464   // Method for updating the visible rows
8465   GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
8466     // Slice out the range of rows from the data
8467     // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
8468     var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
8469
8470     // Define the top-most rendered row
8471     this.currentTopRow = renderedRange[0];
8472
8473     this.setRenderedRows(rowArr);
8474   };
8475
8476   // Method for updating the visible columns
8477   GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
8478     // Slice out the range of rows from the data
8479     // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
8480     var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
8481
8482     // Define the left-most rendered columns
8483     this.currentFirstColumn = renderedRange[0];
8484
8485     this.setRenderedColumns(columnArr);
8486   };
8487
8488   GridRenderContainer.prototype.headerCellWrapperStyle = function () {
8489     var self = this;
8490
8491     if (self.currentFirstColumn !== 0) {
8492       var offset = self.columnOffset;
8493
8494       if (self.grid.isRTL()) {
8495         return { 'margin-right': offset + 'px' };
8496       }
8497       else {
8498         return { 'margin-left': offset + 'px' };
8499       }
8500     }
8501
8502     return null;
8503   };
8504
8505     /**
8506      *  @ngdoc boolean
8507      *  @name updateColumnWidths
8508      *  @propertyOf  ui.grid.class:GridRenderContainer
8509      *  @description Determine the appropriate column width of each column across all render containers.
8510      *
8511      *  Column width is easy when each column has a specified width.  When columns are variable width (i.e.
8512      *  have an * or % of the viewport) then we try to calculate so that things fit in.  The problem is that
8513      *  we have multiple render containers, and we don't want one render container to just take the whole viewport
8514      *  when it doesn't need to - we want things to balance out across the render containers.
8515      *
8516      *  To do this, we use this method to calculate all the renderContainers, recognising that in a given render
8517      *  cycle it'll get called once per render container, so it needs to return the same values each time.
8518      *
8519      *  The constraints on this method are therefore:
8520      *  - must return the same value when called multiple times, to do this it needs to rely on properties of the
8521      *    columns, but not properties that change when this is called (so it shouldn't rely on drawnWidth)
8522      *
8523      *  The general logic of this method is:
8524      *  - calculate our total available width
8525      *  - look at all the columns across all render containers, and work out which have widths and which have
8526      *    constraints such as % or * or something else
8527      *  - for those with *, count the total number of * we see and add it onto a running total, add this column to an * array
8528      *  - for those with a %, allocate the % as a percentage of the viewport, having consideration of min and max
8529      *  - for those with manual width (in pixels) we set the drawnWidth to the specified width
8530      *  - we end up with an asterisks array still to process
8531      *  - we look at our remaining width.  If it's greater than zero, we divide it up among the asterisk columns, then process
8532      *    them for min and max width constraints
8533      *  - if it's zero or less, we set the asterisk columns to their minimum widths
8534      *  - we use parseInt quite a bit, as we try to make all our column widths integers
8535      */
8536   GridRenderContainer.prototype.updateColumnWidths = function () {
8537     var self = this;
8538
8539     var asterisksArray = [],
8540         asteriskNum = 0,
8541         usedWidthSum = 0,
8542         ret = '';
8543
8544     // Get the width of the viewport
8545     var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
8546
8547     // get all the columns across all render containers, we have to calculate them all or one render container
8548     // could consume the whole viewport
8549     var columnCache = [];
8550     angular.forEach(self.grid.renderContainers, function( container, name){
8551       columnCache = columnCache.concat(container.visibleColumnCache);
8552     });
8553
8554     // look at each column, process any manual values or %, put the * into an array to look at later
8555     columnCache.forEach(function(column, i) {
8556       var width = 0;
8557       // Skip hidden columns
8558       if (!column.visible) { return; }
8559
8560       if (angular.isNumber(column.width)) {
8561         // pixel width, set to this value
8562         width = parseInt(column.width, 10);
8563         usedWidthSum = usedWidthSum + width;
8564         column.drawnWidth = width;
8565
8566       } else if (gridUtil.endsWith(column.width, "%")) {
8567         // percentage width, set to percentage of the viewport
8568         width = parseInt(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8569
8570         if ( width > column.maxWidth ){
8571           width = column.maxWidth;
8572         }
8573
8574         if ( width < column.minWidth ){
8575           width = column.minWidth;
8576         }
8577
8578         usedWidthSum = usedWidthSum + width;
8579         column.drawnWidth = width;
8580       } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
8581         // is an asterisk column, the gridColumn already checked the string consists only of '****'
8582         asteriskNum = asteriskNum + column.width.length;
8583         asterisksArray.push(column);
8584       }
8585     });
8586
8587     // Get the remaining width (available width subtracted by the used widths sum)
8588     var remainingWidth = availableWidth - usedWidthSum;
8589
8590     var i, column, colWidth;
8591
8592     if (asterisksArray.length > 0) {
8593       // the width that each asterisk value would be assigned (this can be negative)
8594       var asteriskVal = remainingWidth / asteriskNum;
8595
8596       asterisksArray.forEach(function( column ){
8597         var width = parseInt(column.width.length * asteriskVal, 10);
8598
8599         if ( width > column.maxWidth ){
8600           width = column.maxWidth;
8601         }
8602
8603         if ( width < column.minWidth ){
8604           width = column.minWidth;
8605         }
8606
8607         usedWidthSum = usedWidthSum + width;
8608         column.drawnWidth = width;
8609       });
8610     }
8611
8612     // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8613     // calculated widths would have the grid narrower than the available space,
8614     // dole the remainder out one by one to make everything fit
8615     var processColumnUpwards = function(column){
8616       if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8617         column.drawnWidth++;
8618         usedWidthSum++;
8619         leftoverWidth--;
8620         columnsToChange = true;
8621       }
8622     };
8623
8624     var leftoverWidth = availableWidth - usedWidthSum;
8625     var columnsToChange = true;
8626
8627     while (leftoverWidth > 0 && columnsToChange) {
8628       columnsToChange = false;
8629       asterisksArray.forEach(processColumnUpwards);
8630     }
8631
8632     // We can end up with too much width even though some columns aren't at their max width, in this situation
8633     // we can trim the columns a little
8634     var processColumnDownwards = function(column){
8635       if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8636         column.drawnWidth--;
8637         usedWidthSum--;
8638         excessWidth--;
8639         columnsToChange = true;
8640       }
8641     };
8642
8643     var excessWidth =  usedWidthSum - availableWidth;
8644     columnsToChange = true;
8645
8646     while (excessWidth > 0 && columnsToChange) {
8647       columnsToChange = false;
8648       asterisksArray.forEach(processColumnDownwards);
8649     }
8650
8651
8652     // all that was across all the renderContainers, now we need to work out what that calculation decided for
8653     // our renderContainer
8654     var canvasWidth = 0;
8655     self.visibleColumnCache.forEach(function(column){
8656       if ( column.visible ){
8657         canvasWidth = canvasWidth + column.drawnWidth;
8658       }
8659     });
8660
8661     // Build the CSS
8662     columnCache.forEach(function (column) {
8663       ret = ret + column.getColClassDefinition();
8664     });
8665
8666     self.canvasWidth = canvasWidth;
8667
8668     // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
8669     // return ret;
8670
8671     // Set this render container's column styles so they can be used in style computation
8672     this.columnStyles = ret;
8673   };
8674
8675   GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8676     return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8677   };
8678
8679   GridRenderContainer.prototype.getViewportStyle = function () {
8680     var self = this;
8681     var styles = {};
8682
8683     self.hasHScrollbar = false;
8684     self.hasVScrollbar = false;
8685
8686     if (self.grid.disableScrolling) {
8687       styles['overflow-x'] = 'hidden';
8688       styles['overflow-y'] = 'hidden';
8689       return styles;
8690     }
8691
8692     if (self.name === 'body') {
8693       self.hasHScrollbar = self.grid.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER;
8694       if (!self.grid.isRTL()) {
8695         if (!self.grid.hasRightContainerColumns()) {
8696           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8697         }
8698       }
8699       else {
8700         if (!self.grid.hasLeftContainerColumns()) {
8701           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8702         }
8703       }
8704     }
8705     else if (self.name === 'left') {
8706       self.hasVScrollbar = self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8707     }
8708     else {
8709       self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8710     }
8711
8712     styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8713     styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8714
8715
8716     return styles;
8717
8718
8719   };
8720
8721   return GridRenderContainer;
8722 }]);
8723
8724 })();
8725
8726 (function(){
8727
8728 angular.module('ui.grid')
8729 .factory('GridRow', ['gridUtil', function(gridUtil) {
8730
8731    /**
8732    * @ngdoc function
8733    * @name ui.grid.class:GridRow
8734    * @description GridRow is the viewModel for one logical row on the grid.  A grid Row is not necessarily a one-to-one
8735    * relation to gridOptions.data.
8736    * @param {object} entity the array item from GridOptions.data
8737    * @param {number} index the current position of the row in the array
8738    * @param {Grid} reference to the parent grid
8739    */
8740   function GridRow(entity, index, grid) {
8741
8742      /**
8743       *  @ngdoc object
8744       *  @name grid
8745       *  @propertyOf  ui.grid.class:GridRow
8746       *  @description A reference back to the grid
8747       */
8748      this.grid = grid;
8749
8750      /**
8751       *  @ngdoc object
8752       *  @name entity
8753       *  @propertyOf  ui.grid.class:GridRow
8754       *  @description A reference to an item in gridOptions.data[]
8755       */
8756     this.entity = entity;
8757
8758      /**
8759       *  @ngdoc object
8760       *  @name uid
8761       *  @propertyOf  ui.grid.class:GridRow
8762       *  @description  UniqueId of row
8763       */
8764      this.uid = gridUtil.nextUid();
8765
8766      /**
8767       *  @ngdoc object
8768       *  @name visible
8769       *  @propertyOf  ui.grid.class:GridRow
8770       *  @description If true, the row will be rendered
8771       */
8772     // Default to true
8773     this.visible = true;
8774
8775
8776     this.$$height = grid.options.rowHeight;
8777
8778   }
8779
8780     /**
8781      *  @ngdoc object
8782      *  @name height
8783      *  @propertyOf  ui.grid.class:GridRow
8784      *  @description height of each individual row. changing the height will flag all
8785      *  row renderContainers to recalculate their canvas height
8786      */
8787     Object.defineProperty(GridRow.prototype, 'height', {
8788       get: function() {
8789         return this.$$height;
8790       },
8791       set: function(height) {
8792         if (height !== this.$$height) {
8793           this.grid.updateCanvasHeight();
8794           this.$$height = height;
8795         }
8796       }
8797     });
8798
8799   /**
8800    * @ngdoc function
8801    * @name getQualifiedColField
8802    * @methodOf ui.grid.class:GridRow
8803    * @description returns the qualified field name as it exists on scope
8804    * ie: row.entity.fieldA
8805    * @param {GridCol} col column instance
8806    * @returns {string} resulting name that can be evaluated on scope
8807    */
8808     GridRow.prototype.getQualifiedColField = function(col) {
8809       return 'row.' + this.getEntityQualifiedColField(col);
8810     };
8811
8812     /**
8813      * @ngdoc function
8814      * @name getEntityQualifiedColField
8815      * @methodOf ui.grid.class:GridRow
8816      * @description returns the qualified field name minus the row path
8817      * ie: entity.fieldA
8818      * @param {GridCol} col column instance
8819      * @returns {string} resulting name that can be evaluated against a row
8820      */
8821   GridRow.prototype.getEntityQualifiedColField = function(col) {
8822     return gridUtil.preEval('entity.' + col.field);
8823   };
8824   
8825   
8826   /**
8827    * @ngdoc function
8828    * @name setRowInvisible
8829    * @methodOf  ui.grid.class:GridRow
8830    * @description Sets an override on the row that forces it to always
8831    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8832    * 
8833    * This method can be called from the api, passing in the gridRow we want
8834    * altered.  It should really work by calling gridRow.setRowInvisible, but that's
8835    * not the way I coded it, and too late to change now.  Changed to just call
8836    * the internal function row.setThisRowInvisible().
8837    * 
8838    * @param {GridRow} row the row we want to set to invisible
8839    * 
8840    */
8841   GridRow.prototype.setRowInvisible = function ( row ) {
8842     if (row && row.setThisRowInvisible){
8843       row.setThisRowInvisible( 'user' );
8844     }
8845   };
8846   
8847   
8848   /**
8849    * @ngdoc function
8850    * @name clearRowInvisible
8851    * @methodOf  ui.grid.class:GridRow
8852    * @description Clears an override on the row that forces it to always
8853    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8854    * 
8855    * This method can be called from the api, passing in the gridRow we want
8856    * altered.  It should really work by calling gridRow.clearRowInvisible, but that's
8857    * not the way I coded it, and too late to change now.  Changed to just call
8858    * the internal function row.clearThisRowInvisible().
8859    * 
8860    * @param {GridRow} row the row we want to clear the invisible flag
8861    * 
8862    */
8863   GridRow.prototype.clearRowInvisible = function ( row ) {
8864     if (row && row.clearThisRowInvisible){
8865       row.clearThisRowInvisible( 'user' );
8866     }
8867   };
8868   
8869   
8870   /**
8871    * @ngdoc function
8872    * @name setThisRowInvisible
8873    * @methodOf  ui.grid.class:GridRow
8874    * @description Sets an override on the row that forces it to always
8875    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility
8876    *
8877    * @param {string} reason the reason (usually the module) for the row to be invisible.
8878    * E.g. grouping, user, filter
8879    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8880    */
8881   GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
8882     if ( !this.invisibleReason ){
8883       this.invisibleReason = {};
8884     }
8885     this.invisibleReason[reason] = true;
8886     this.evaluateRowVisibility( fromRowsProcessor);
8887   };
8888
8889
8890   /**
8891    * @ngdoc function
8892    * @name clearRowInvisible
8893    * @methodOf ui.grid.class:GridRow
8894    * @description Clears any override on the row visibility, returning it 
8895    * to normal visibility calculations.  Emits the rowsVisibleChanged
8896    * event
8897    * 
8898    * @param {string} reason the reason (usually the module) for the row to be invisible.
8899    * E.g. grouping, user, filter
8900    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
8901    */
8902   GridRow.prototype.clearThisRowInvisible = function ( reason, fromRowsProcessor ) {
8903     if (typeof(this.invisibleReason) !== 'undefined' ) {
8904       delete this.invisibleReason[reason];
8905     }
8906     this.evaluateRowVisibility( fromRowsProcessor );
8907   };
8908
8909
8910   /**
8911    * @ngdoc function
8912    * @name evaluateRowVisibility
8913    * @methodOf ui.grid.class:GridRow
8914    * @description Determines whether the row should be visible based on invisibleReason, 
8915    * and if it changes the row visibility, then emits the rowsVisibleChanged event.
8916    * 
8917    * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
8918    * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
8919    * row processor does that already
8920    */
8921   GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
8922     var newVisibility = true;
8923     if ( typeof(this.invisibleReason) !== 'undefined' ){
8924       angular.forEach(this.invisibleReason, function( value, key ){
8925         if ( value ){
8926           newVisibility = false;
8927         }
8928       });
8929     }
8930     
8931     if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
8932       this.visible = newVisibility;
8933       if ( !fromRowProcessor ){
8934         this.grid.queueGridRefresh();
8935         this.grid.api.core.raise.rowsVisibleChanged(this);
8936       }
8937     }
8938   };
8939   
8940
8941   return GridRow;
8942 }]);
8943
8944 })();
8945
8946 (function(){
8947   'use strict';
8948   /**
8949    * @ngdoc object
8950    * @name ui.grid.class:GridRowColumn
8951    * @param {GridRow} row The row for this pair
8952    * @param {GridColumn} column The column for this pair
8953    * @description A row and column pair that represents the intersection of these two entities.
8954    * Must be instantiated as a constructor using the `new` keyword.
8955    */
8956   angular.module('ui.grid')
8957   .factory('GridRowColumn', ['$parse', '$filter',
8958     function GridRowColumnFactory($parse, $filter){
8959       var GridRowColumn = function GridRowColumn(row, col) {
8960         if ( !(this instanceof GridRowColumn)){
8961           throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
8962         }
8963
8964         /**
8965          * @ngdoc object
8966          * @name row
8967          * @propertyOf ui.grid.class:GridRowColumn
8968          * @description {@link ui.grid.class:GridRow }
8969          */
8970         this.row = row;
8971         /**
8972          * @ngdoc object
8973          * @name col
8974          * @propertyOf ui.grid.class:GridRowColumn
8975          * @description {@link ui.grid.class:GridColumn }
8976          */
8977         this.col = col;
8978       };
8979
8980       /**
8981        * @ngdoc function
8982        * @name getIntersectionValueRaw
8983        * @methodOf ui.grid.class:GridRowColumn
8984        * @description Gets the intersection of where the row and column meet.
8985        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8986        *          If the column has a cellFilter this will NOT return the filtered value.
8987        */
8988       GridRowColumn.prototype.getIntersectionValueRaw = function(){
8989         var getter = $parse(this.row.getEntityQualifiedColField(this.col));
8990         var context = this.row;
8991         return getter(context);
8992       };
8993       /**
8994        * @ngdoc function
8995        * @name getIntersectionValueFiltered
8996        * @methodOf ui.grid.class:GridRowColumn
8997        * @description Gets the intersection of where the row and column meet.
8998        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8999        *          If the column has a cellFilter this will also apply the filter to it and return the value that the filter displays.
9000        */
9001       GridRowColumn.prototype.getIntersectionValueFiltered = function(){
9002         var value = this.getIntersectionValueRaw();
9003         if (this.col.cellFilter && this.col.cellFilter !== ''){
9004           var getFilterIfExists = function(filterName){
9005             try {
9006               return $filter(filterName);
9007             } catch (e){
9008               return null;
9009             }
9010           };
9011           var filter = getFilterIfExists(this.col.cellFilter);
9012           if (filter) { // Check if this is filter name or a filter string
9013             value = filter(value);
9014           } else { // We have the template version of a filter so we need to parse it apart
9015             // Get the filter params out using a regex
9016             // Test out this regex here https://regex101.com/r/rC5eR5/2
9017             var re = /([^:]*):([^:]*):?([\s\S]+)?/;
9018             var matches;
9019             if ((matches = re.exec(this.col.cellFilter)) !== null) {
9020                 // View your result using the matches-variable.
9021                 // eg matches[0] etc.
9022                 value = $filter(matches[1])(value, matches[2], matches[3]);
9023             }
9024           }
9025         }
9026         return value;
9027       };
9028       return GridRowColumn;
9029     }
9030   ]);
9031 })();
9032
9033 (function () {
9034   angular.module('ui.grid')
9035     .factory('ScrollEvent', ['gridUtil', function (gridUtil) {
9036
9037       /**
9038        * @ngdoc function
9039        * @name ui.grid.class:ScrollEvent
9040        * @description Model for all scrollEvents
9041        * @param {Grid} grid that owns the scroll event
9042        * @param {GridRenderContainer} sourceRowContainer that owns the scroll event. Can be null
9043        * @param {GridRenderContainer} sourceColContainer that owns the scroll event. Can be null
9044        * @param {string} source the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
9045        */
9046       function ScrollEvent(grid, sourceRowContainer, sourceColContainer, source) {
9047         var self = this;
9048         if (!grid) {
9049           throw new Error("grid argument is required");
9050         }
9051
9052         /**
9053          *  @ngdoc object
9054          *  @name grid
9055          *  @propertyOf  ui.grid.class:ScrollEvent
9056          *  @description A reference back to the grid
9057          */
9058          self.grid = grid;
9059
9060
9061
9062         /**
9063          *  @ngdoc object
9064          *  @name source
9065          *  @propertyOf  ui.grid.class:ScrollEvent
9066          *  @description the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
9067          */
9068         self.source = source;
9069
9070
9071         /**
9072          *  @ngdoc object
9073          *  @name noDelay
9074          *  @propertyOf  ui.grid.class:ScrollEvent
9075          *  @description most scroll events from the mouse or trackpad require delay to operate properly
9076          *  set to false to eliminate delay.  Useful for scroll events that the grid causes, such as scrolling to make a row visible.
9077          */
9078         self.withDelay = true;
9079
9080         self.sourceRowContainer = sourceRowContainer;
9081         self.sourceColContainer = sourceColContainer;
9082
9083         self.newScrollLeft = null;
9084         self.newScrollTop = null;
9085         self.x = null;
9086         self.y = null;
9087
9088         self.verticalScrollLength = -9999999;
9089         self.horizontalScrollLength = -999999;
9090
9091
9092         /**
9093          *  @ngdoc function
9094          *  @name fireThrottledScrollingEvent
9095          *  @methodOf  ui.grid.class:ScrollEvent
9096          *  @description fires a throttled event using grid.api.core.raise.scrollEvent
9097          */
9098         self.fireThrottledScrollingEvent = gridUtil.throttle(function(sourceContainerId) {
9099           self.grid.scrollContainers(sourceContainerId, self);
9100         }, self.grid.options.wheelScrollThrottle, {trailing: true});
9101
9102       }
9103
9104
9105       /**
9106        *  @ngdoc function
9107        *  @name getNewScrollLeft
9108        *  @methodOf  ui.grid.class:ScrollEvent
9109        *  @description returns newScrollLeft property if available; calculates a new value if it isn't
9110        */
9111       ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9112         var self = this;
9113
9114         if (!self.newScrollLeft){
9115           var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
9116
9117           var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
9118
9119           var scrollXPercentage;
9120           if (typeof(self.x.percentage) !== 'undefined' && self.x.percentage !== undefined) {
9121             scrollXPercentage = self.x.percentage;
9122           }
9123           else if (typeof(self.x.pixels) !== 'undefined' && self.x.pixels !== undefined) {
9124             scrollXPercentage = self.x.percentage = (oldScrollLeft + self.x.pixels) / scrollWidth;
9125           }
9126           else {
9127             throw new Error("No percentage or pixel value provided for scroll event X axis");
9128           }
9129
9130           return Math.max(0, scrollXPercentage * scrollWidth);
9131         }
9132
9133         return self.newScrollLeft;
9134       };
9135
9136
9137       /**
9138        *  @ngdoc function
9139        *  @name getNewScrollTop
9140        *  @methodOf  ui.grid.class:ScrollEvent
9141        *  @description returns newScrollTop property if available; calculates a new value if it isn't
9142        */
9143       ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9144         var self = this;
9145
9146
9147         if (!self.newScrollTop){
9148           var scrollLength = rowContainer.getVerticalScrollLength();
9149
9150           var oldScrollTop = viewport[0].scrollTop;
9151
9152           var scrollYPercentage;
9153           if (typeof(self.y.percentage) !== 'undefined' && self.y.percentage !== undefined) {
9154             scrollYPercentage = self.y.percentage;
9155           }
9156           else if (typeof(self.y.pixels) !== 'undefined' && self.y.pixels !== undefined) {
9157             scrollYPercentage = self.y.percentage = (oldScrollTop + self.y.pixels) / scrollLength;
9158           }
9159           else {
9160             throw new Error("No percentage or pixel value provided for scroll event Y axis");
9161           }
9162
9163           return Math.max(0, scrollYPercentage * scrollLength);
9164         }
9165
9166         return self.newScrollTop;
9167       };
9168
9169       ScrollEvent.prototype.atTop = function(scrollTop) {
9170         return (this.y && (this.y.percentage === 0 || this.verticalScrollLength < 0) && scrollTop === 0);
9171       };
9172
9173       ScrollEvent.prototype.atBottom = function(scrollTop) {
9174         return (this.y && (this.y.percentage === 1 || this.verticalScrollLength === 0) && scrollTop > 0);
9175       };
9176
9177       ScrollEvent.prototype.atLeft = function(scrollLeft) {
9178         return (this.x && (this.x.percentage === 0 || this.horizontalScrollLength < 0) && scrollLeft === 0);
9179       };
9180
9181       ScrollEvent.prototype.atRight = function(scrollLeft) {
9182         return (this.x && (this.x.percentage === 1 || this.horizontalScrollLength ===0) && scrollLeft > 0);
9183       };
9184
9185
9186       ScrollEvent.Sources = {
9187         ViewPortScroll: 'ViewPortScroll',
9188         RenderContainerMouseWheel: 'RenderContainerMouseWheel',
9189         RenderContainerTouchMove: 'RenderContainerTouchMove',
9190         Other: 99
9191       };
9192
9193       return ScrollEvent;
9194     }]);
9195
9196
9197
9198 })();
9199
9200 (function () {
9201   'use strict';
9202   /**
9203    *  @ngdoc object
9204    *  @name ui.grid.service:gridClassFactory
9205    *
9206    *  @description factory to return dom specific instances of a grid
9207    *
9208    */
9209   angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
9210     function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
9211
9212       var service = {
9213         /**
9214          * @ngdoc method
9215          * @name createGrid
9216          * @methodOf ui.grid.service:gridClassFactory
9217          * @description Creates a new grid instance. Each instance will have a unique id
9218          * @param {object} options An object map of options to pass into the created grid instance.
9219          * @returns {Grid} grid
9220          */
9221         createGrid : function(options) {
9222           options = (typeof(options) !== 'undefined') ? options : {};
9223           options.id = gridUtil.newId();
9224           var grid = new Grid(options);
9225
9226           // NOTE/TODO: rowTemplate should always be defined...
9227           if (grid.options.rowTemplate) {
9228             var rowTemplateFnPromise = $q.defer();
9229             grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9230             
9231             gridUtil.getTemplate(grid.options.rowTemplate)
9232               .then(
9233                 function (template) {
9234                   var rowTemplateFn = $compile(template);
9235                   rowTemplateFnPromise.resolve(rowTemplateFn);
9236                 },
9237                 function (res) {
9238                   // Todo handle response error here?
9239                   throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9240                 });
9241           }
9242
9243           grid.registerColumnBuilder(service.defaultColumnBuilder);
9244
9245           // Row builder for custom row templates
9246           grid.registerRowBuilder(service.rowTemplateAssigner);
9247
9248           // Reset all rows to visible initially
9249           grid.registerRowsProcessor(function allRowsVisible(rows) {
9250             rows.forEach(function (row) {
9251               row.evaluateRowVisibility( true );
9252             }, 50);
9253
9254             return rows;
9255           });
9256
9257           grid.registerColumnsProcessor(function allColumnsVisible(columns) {
9258             columns.forEach(function (column) {
9259               column.visible = true;
9260             });
9261
9262             return columns;
9263           }, 50);
9264
9265           grid.registerColumnsProcessor(function(renderableColumns) {
9266               renderableColumns.forEach(function (column) {
9267                   if (column.colDef.visible === false) {
9268                       column.visible = false;
9269                   }
9270               });
9271
9272               return renderableColumns;
9273           }, 50);
9274
9275
9276           grid.registerRowsProcessor(grid.searchRows, 100);
9277
9278           // Register the default row processor, it sorts rows by selected columns
9279           if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
9280             grid.registerRowsProcessor(grid.options.externalSort, 200);
9281           }
9282           else {
9283             grid.registerRowsProcessor(grid.sortByColumn, 200);
9284           }
9285
9286           return grid;
9287         },
9288
9289         /**
9290          * @ngdoc function
9291          * @name defaultColumnBuilder
9292          * @methodOf ui.grid.service:gridClassFactory
9293          * @description Processes designTime column definitions and applies them to col for the
9294          *              core grid features
9295          * @param {object} colDef reference to column definition
9296          * @param {GridColumn} col reference to gridCol
9297          * @param {object} gridOptions reference to grid options
9298          */
9299         defaultColumnBuilder: function (colDef, col, gridOptions) {
9300
9301           var templateGetPromises = [];
9302
9303           // Abstracts the standard template processing we do for every template type.
9304           var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9305             if ( !colDef[templateType] ){
9306               col[providedType] = defaultTemplate;
9307             } else {
9308               col[providedType] = colDef[templateType];
9309             }
9310  
9311              templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9312                 .then(
9313                 function (template) {
9314                   if ( angular.isFunction(template) ) { template = template(); }
9315                   var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9316                   if ( tooltipType && col[tooltipType] === false ){
9317                     template = template.replace(uiGridConstants.TOOLTIP, '');
9318                   } else if ( tooltipType && col[tooltipType] ){
9319                     template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
9320                   }
9321
9322                   if ( filterType ){
9323                     col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
9324                       return col[filterType] ? "|" + col[filterType] : "";
9325                     });
9326                   } else {
9327                     col[templateType] = template;
9328                   }
9329                 },
9330                 function (res) {
9331                   throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9332                 })
9333             );
9334
9335           };
9336
9337
9338           /**
9339            * @ngdoc property
9340            * @name cellTemplate
9341            * @propertyOf ui.grid.class:GridOptions.columnDef
9342            * @description a custom template for each cell in this column.  The default
9343            * is ui-grid/uiGridCell.  If you are using the cellNav feature, this template
9344            * must contain a div that can receive focus.
9345            *
9346            */
9347           processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9348           col.cellTemplatePromise = templateGetPromises[0];
9349
9350           /**
9351            * @ngdoc property
9352            * @name headerCellTemplate
9353            * @propertyOf ui.grid.class:GridOptions.columnDef
9354            * @description a custom template for the header for this column.  The default
9355            * is ui-grid/uiGridHeaderCell
9356            *
9357            */
9358           processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9359
9360           /**
9361            * @ngdoc property
9362            * @name footerCellTemplate
9363            * @propertyOf ui.grid.class:GridOptions.columnDef
9364            * @description a custom template for the footer for this column.  The default
9365            * is ui-grid/uiGridFooterCell
9366            *
9367            */
9368           processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9369
9370           /**
9371            * @ngdoc property
9372            * @name filterHeaderTemplate
9373            * @propertyOf ui.grid.class:GridOptions.columnDef
9374            * @description a custom template for the filter input.  The default is ui-grid/ui-grid-filter
9375            *
9376            */
9377           processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9378
9379           // Create a promise for the compiled element function
9380           col.compiledElementFnDefer = $q.defer();
9381
9382           return $q.all(templateGetPromises);
9383         },
9384         
9385
9386         rowTemplateAssigner: function rowTemplateAssigner(row) {
9387           var grid = this;
9388
9389           // Row has no template assigned to it
9390           if (!row.rowTemplate) {
9391             // Use the default row template from the grid
9392             row.rowTemplate = grid.options.rowTemplate;
9393
9394             // Use the grid's function for fetching the compiled row template function
9395             row.getRowTemplateFn = grid.getRowTemplateFn;
9396           }
9397           // Row has its own template assigned
9398           else {
9399             // Create a promise for the compiled row template function
9400             var perRowTemplateFnPromise = $q.defer();
9401             row.getRowTemplateFn = perRowTemplateFnPromise.promise;
9402
9403             // Get the row template
9404             gridUtil.getTemplate(row.rowTemplate)
9405               .then(function (template) {
9406                 // Compile the template
9407                 var rowTemplateFn = $compile(template);
9408                 
9409                 // Resolve the compiled template function promise
9410                 perRowTemplateFnPromise.resolve(rowTemplateFn);
9411               },
9412               function (res) {
9413                 // Todo handle response error here?
9414                 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
9415               });
9416           }
9417
9418           return row.getRowTemplateFn;
9419         }
9420       };
9421
9422       //class definitions (moved to separate factories)
9423
9424       return service;
9425     }]);
9426
9427 })();
9428
9429 (function() {
9430
9431 var module = angular.module('ui.grid');
9432
9433 function escapeRegExp(str) {
9434   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
9435 }
9436
9437
9438 /**
9439  *  @ngdoc service
9440  *  @name ui.grid.service:rowSearcher
9441  *
9442  *  @description Service for searching/filtering rows based on column value conditions.
9443  */
9444 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
9445   var defaultCondition = uiGridConstants.filter.CONTAINS;
9446
9447   var rowSearcher = {};
9448
9449   /**
9450    * @ngdoc function
9451    * @name getTerm
9452    * @methodOf ui.grid.service:rowSearcher
9453    * @description Get the term from a filter
9454    * Trims leading and trailing whitespace
9455    * @param {object} filter object to use
9456    * @returns {object} Parsed term
9457    */
9458   rowSearcher.getTerm = function getTerm(filter) {
9459     if (typeof(filter.term) === 'undefined') { return filter.term; }
9460     
9461     var term = filter.term;
9462
9463     // Strip leading and trailing whitespace if the term is a string
9464     if (typeof(term) === 'string') {
9465       term = term.trim();
9466     }
9467
9468     return term;
9469   };
9470
9471   /**
9472    * @ngdoc function
9473    * @name stripTerm
9474    * @methodOf ui.grid.service:rowSearcher
9475    * @description Remove leading and trailing asterisk (*) from the filter's term
9476    * @param {object} filter object to use
9477    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9478    */
9479   rowSearcher.stripTerm = function stripTerm(filter) {
9480     var term = rowSearcher.getTerm(filter);
9481
9482     if (typeof(term) === 'string') {
9483       return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
9484     }
9485     else {
9486       return term;
9487     }
9488   };
9489   
9490
9491   /**
9492    * @ngdoc function
9493    * @name guessCondition
9494    * @methodOf ui.grid.service:rowSearcher
9495    * @description Guess the condition for a filter based on its term
9496    * <br>
9497    * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
9498    * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
9499    * @param {object} filter object to use
9500    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9501    */
9502   rowSearcher.guessCondition = function guessCondition(filter) {
9503     if (typeof(filter.term) === 'undefined' || !filter.term) {
9504       return defaultCondition;
9505     }
9506
9507     var term = rowSearcher.getTerm(filter);
9508     
9509     if (/\*/.test(term)) {
9510       var regexpFlags = '';
9511       if (!filter.flags || !filter.flags.caseSensitive) {
9512         regexpFlags += 'i';
9513       }
9514
9515       var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
9516       return new RegExp('^' + reText + '$', regexpFlags);
9517     }
9518     // Otherwise default to default condition
9519     else {
9520       return defaultCondition;
9521     }
9522   };
9523   
9524   
9525   /**
9526    * @ngdoc function
9527    * @name setupFilters
9528    * @methodOf ui.grid.service:rowSearcher
9529    * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
9530    * do all the parsing and pre-processing and store that data into a new filters object.  The object
9531    * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9532    * 
9533    * We could use a forEach in here, since it's much less performance sensitive, but since we're using 
9534    * for loops everywhere else in this module...
9535    * 
9536    * @param {array} filters the filters from the column (col.filters or [col.filter])
9537    * @returns {array} An array of parsed/preprocessed filters
9538    */
9539   rowSearcher.setupFilters = function setupFilters( filters ){
9540     var newFilters = [];
9541     
9542     var filtersLength = filters.length;
9543     for ( var i = 0; i < filtersLength; i++ ){
9544       var filter = filters[i];
9545       
9546       if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9547         var newFilter = {};
9548         
9549         var regexpFlags = '';
9550         if (!filter.flags || !filter.flags.caseSensitive) {
9551           regexpFlags += 'i';
9552         }
9553     
9554         if ( !gridUtil.isNullOrUndefined(filter.term) ){
9555           // it is possible to have noTerm.  We don't need to copy that across, it was just a flag to avoid
9556           // getting the filter ignored if the filter was a function that didn't use a term
9557           newFilter.term = rowSearcher.stripTerm(filter);
9558         }
9559         
9560         if ( filter.condition ){
9561           newFilter.condition = filter.condition;
9562         } else {
9563           newFilter.condition = rowSearcher.guessCondition(filter);
9564         }
9565
9566         newFilter.flags = angular.extend( { caseSensitive: false, date: false }, filter.flags );
9567
9568         if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
9569           newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
9570         }
9571         
9572          if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
9573           newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
9574         }
9575
9576         if (newFilter.condition === uiGridConstants.filter.CONTAINS) {
9577           newFilter.containsRE = new RegExp(newFilter.term, regexpFlags);
9578         }
9579
9580         if (newFilter.condition === uiGridConstants.filter.EXACT) {
9581           newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
9582         }
9583         
9584         newFilters.push(newFilter);
9585       }
9586     }
9587     return newFilters;
9588   };
9589   
9590
9591   /**
9592    * @ngdoc function
9593    * @name runColumnFilter
9594    * @methodOf ui.grid.service:rowSearcher
9595    * @description Runs a single pre-parsed filter against a cell, returning true
9596    * if the cell matches that one filter.
9597    * 
9598    * @param {Grid} grid the grid we're working against
9599    * @param {GridRow} row the row we're matching against
9600    * @param {GridCol} column the column that we're working against
9601    * @param {object} filter the specific, preparsed, filter that we want to test
9602    * @returns {boolean} true if we match (row stays visible)
9603    */
9604   rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, filter) {
9605     // Cache typeof condition
9606     var conditionType = typeof(filter.condition);
9607
9608     // Term to search for.
9609     var term = filter.term;
9610
9611     // Get the column value for this row
9612     var value;
9613     if ( column.filterCellFiltered ){
9614       value = grid.getCellDisplayValue(row, column);
9615     } else {
9616       value = grid.getCellValue(row, column);
9617     }
9618
9619
9620     // If the filter's condition is a RegExp, then use it
9621     if (filter.condition instanceof RegExp) {
9622       return filter.condition.test(value);
9623     }
9624
9625     // If the filter's condition is a function, run it
9626     if (conditionType === 'function') {
9627       return filter.condition(term, value, row, column);
9628     }
9629
9630     if (filter.startswithRE) {
9631       return filter.startswithRE.test(value);
9632     }
9633
9634     if (filter.endswithRE) {
9635       return filter.endswithRE.test(value);
9636     }
9637
9638     if (filter.containsRE) {
9639       return filter.containsRE.test(value);
9640     }
9641
9642     if (filter.exactRE) {
9643       return filter.exactRE.test(value);
9644     }
9645
9646     if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
9647       var regex = new RegExp('^' + term + '$');
9648       return !regex.exec(value);
9649     }
9650
9651     if (typeof(value) === 'number' && typeof(term) === 'string' ){
9652       // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
9653       // the same for negative numbers
9654       // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
9655       var tempFloat = parseFloat(term.replace(/\\\./,'.').replace(/\\\-/,'-'));
9656       if (!isNaN(tempFloat)) {
9657         term = tempFloat;
9658       }
9659     }
9660
9661     if (filter.flags.date === true) {
9662       value = new Date(value);
9663       // If the term has a dash in it, it comes through as '\-' -- we need to take out the '\'.
9664       term = new Date(term.replace(/\\/g, ''));
9665     }
9666
9667     if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
9668       return (value > term);
9669     }
9670
9671     if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
9672       return (value >= term);
9673     }
9674
9675     if (filter.condition === uiGridConstants.filter.LESS_THAN) {
9676       return (value < term);
9677     }
9678
9679     if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
9680       return (value <= term);
9681     }
9682
9683     return true;
9684   };
9685
9686
9687   /**
9688    * @ngdoc boolean
9689    * @name useExternalFiltering
9690    * @propertyOf ui.grid.class:GridOptions
9691    * @description False by default. When enabled, this setting suppresses the internal filtering.
9692    * All UI logic will still operate, allowing filter conditions to be set and modified.
9693    * 
9694    * The external filter logic can listen for the `filterChange` event, which fires whenever
9695    * a filter has been adjusted.
9696    */
9697   /**
9698    * @ngdoc function
9699    * @name searchColumn
9700    * @methodOf ui.grid.service:rowSearcher
9701    * @description Process provided filters on provided column against a given row. If the row meets 
9702    * the conditions on all the filters, return true.
9703    * @param {Grid} grid Grid to search in
9704    * @param {GridRow} row Row to search on
9705    * @param {GridCol} column Column with the filters to use
9706    * @param {array} filters array of pre-parsed/preprocessed filters to apply
9707    * @returns {boolean} Whether the column matches or not.
9708    */
9709   rowSearcher.searchColumn = function searchColumn(grid, row, column, filters) {
9710     if (grid.options.useExternalFiltering) {
9711       return true;
9712     }
9713
9714     var filtersLength = filters.length;
9715     for (var i = 0; i < filtersLength; i++) {
9716       var filter = filters[i];
9717
9718       var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9719       if (!ret) {
9720         return false;
9721       }
9722     }
9723
9724     return true;
9725   };
9726
9727
9728   /**
9729    * @ngdoc function
9730    * @name search
9731    * @methodOf ui.grid.service:rowSearcher
9732    * @description Run a search across the given rows and columns, marking any rows that don't 
9733    * match the stored col.filters or col.filter as invisible.
9734    * @param {Grid} grid Grid instance to search inside
9735    * @param {Array[GridRow]} rows GridRows to filter
9736    * @param {Array[GridColumn]} columns GridColumns with filters to process
9737    */
9738   rowSearcher.search = function search(grid, rows, columns) {
9739     /*
9740      * Added performance optimisations into this code base, as this logic creates deeply nested
9741      * loops and is therefore very performance sensitive.  In particular, avoiding forEach as
9742      * this impacts some browser optimisers (particularly Chrome), using iterators instead
9743      */
9744
9745     // Don't do anything if we weren't passed any rows
9746     if (!rows) {
9747       return;
9748     }
9749
9750     // don't filter if filtering currently disabled
9751     if (!grid.options.enableFiltering){
9752       return rows;
9753     }
9754
9755     // Build list of filters to apply
9756     var filterData = [];
9757
9758     var colsLength = columns.length;
9759
9760     var hasTerm = function( filters ) {
9761       var hasTerm = false;
9762
9763       filters.forEach( function (filter) {
9764         if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9765           hasTerm = true;
9766         }
9767       });
9768
9769       return hasTerm;
9770     };
9771
9772     for (var i = 0; i < colsLength; i++) {
9773       var col = columns[i];
9774
9775       if (typeof(col.filters) !== 'undefined' && hasTerm(col.filters) ) {
9776         filterData.push( { col: col, filters: rowSearcher.setupFilters(col.filters) } );
9777       }
9778     }
9779
9780     if (filterData.length > 0) {
9781       // define functions outside the loop, performance optimisation
9782       var foreachRow = function(grid, row, col, filters){
9783         if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
9784           row.visible = false;
9785         }
9786       };
9787
9788       var foreachFilterCol = function(grid, filterData){
9789         var rowsLength = rows.length;
9790         for ( var i = 0; i < rowsLength; i++){
9791           foreachRow(grid, rows[i], filterData.col, filterData.filters);  
9792         }
9793       };
9794
9795       // nested loop itself - foreachFilterCol, which in turn calls foreachRow
9796       var filterDataLength = filterData.length;
9797       for ( var j = 0; j < filterDataLength; j++){
9798         foreachFilterCol( grid, filterData[j] );  
9799       }
9800
9801       if (grid.api.core.raise.rowsVisibleChanged) {
9802         grid.api.core.raise.rowsVisibleChanged();
9803       }
9804
9805       // drop any invisible rows
9806       // 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
9807       // rows = rows.filter(function(row){ return row.visible; });
9808
9809     }
9810
9811     return rows;
9812   };
9813
9814   return rowSearcher;
9815 }]);
9816
9817 })();
9818
9819 (function() {
9820
9821 var module = angular.module('ui.grid');
9822
9823 /**
9824  * @ngdoc object
9825  * @name ui.grid.class:RowSorter
9826  * @description RowSorter provides the default sorting mechanisms,
9827  * including guessing column types and applying appropriate sort
9828  * algorithms
9829  *
9830  */
9831
9832 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
9833   var currencyRegexStr =
9834     '(' +
9835     uiGridConstants.CURRENCY_SYMBOLS
9836       .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
9837       .join('|') + // Join all the symbols together with |s
9838     ')?';
9839
9840   // /^[-+]?[£$¤¥]?[\d,.]+%?$/
9841   var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
9842
9843   var rowSorter = {
9844     // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
9845     //   this takes a piece of data from the cell and tries to determine its type and what sorting
9846     //   function to use for it
9847     colSortFnCache: {}
9848   };
9849
9850
9851   /**
9852    * @ngdoc method
9853    * @methodOf ui.grid.class:RowSorter
9854    * @name guessSortFn
9855    * @description Assigns a sort function to use based on the itemType in the column
9856    * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'.  And
9857    * error will be thrown for any other type.
9858    * @returns {function} a sort function that will sort that type
9859    */
9860   rowSorter.guessSortFn = function guessSortFn(itemType) {
9861     switch (itemType) {
9862       case "number":
9863         return rowSorter.sortNumber;
9864       case "numberStr":
9865         return rowSorter.sortNumberStr;
9866       case "boolean":
9867         return rowSorter.sortBool;
9868       case "string":
9869         return rowSorter.sortAlpha;
9870       case "date":
9871         return rowSorter.sortDate;
9872       case "object":
9873         return rowSorter.basicSort;
9874       default:
9875         throw new Error('No sorting function found for type:' + itemType);
9876     }
9877   };
9878
9879
9880   /**
9881    * @ngdoc method
9882    * @methodOf ui.grid.class:RowSorter
9883    * @name handleNulls
9884    * @description Sorts nulls and undefined to the bottom (top when
9885    * descending).  Called by each of the internal sorters before
9886    * attempting to sort.  Note that this method is available on the core api
9887    * via gridApi.core.sortHandleNulls
9888    * @param {object} a sort value a
9889    * @param {object} b sort value b
9890    * @returns {number} null if there were no nulls/undefineds, otherwise returns
9891    * a sort value that should be passed back from the sort function
9892    */
9893   rowSorter.handleNulls = function handleNulls(a, b) {
9894     // We want to allow zero values and false values to be evaluated in the sort function
9895     if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
9896       // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
9897       if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
9898         return 0;
9899       }
9900       else if (!a && a !== 0 && a !== false) {
9901         return 1;
9902       }
9903       else if (!b && b !== 0 && b !== false) {
9904         return -1;
9905       }
9906     }
9907     return null;
9908   };
9909
9910
9911   /**
9912    * @ngdoc method
9913    * @methodOf ui.grid.class:RowSorter
9914    * @name basicSort
9915    * @description Sorts any values that provide the < method, including strings
9916    * or numbers.  Handles nulls and undefined through calling handleNulls
9917    * @param {object} a sort value a
9918    * @param {object} b sort value b
9919    * @returns {number} normal sort function, returns -ve, 0, +ve
9920    */
9921   rowSorter.basicSort = function basicSort(a, b) {
9922     var nulls = rowSorter.handleNulls(a, b);
9923     if ( nulls !== null ){
9924       return nulls;
9925     } else {
9926       if (a === b) {
9927         return 0;
9928       }
9929       if (a < b) {
9930         return -1;
9931       }
9932       return 1;
9933     }
9934   };
9935
9936
9937   /**
9938    * @ngdoc method
9939    * @methodOf ui.grid.class:RowSorter
9940    * @name sortNumber
9941    * @description Sorts numerical values.  Handles nulls and undefined through calling handleNulls
9942    * @param {object} a sort value a
9943    * @param {object} b sort value b
9944    * @returns {number} normal sort function, returns -ve, 0, +ve
9945    */
9946   rowSorter.sortNumber = function sortNumber(a, b) {
9947     var nulls = rowSorter.handleNulls(a, b);
9948     if ( nulls !== null ){
9949       return nulls;
9950     } else {
9951       return a - b;
9952     }
9953   };
9954
9955
9956   /**
9957    * @ngdoc method
9958    * @methodOf ui.grid.class:RowSorter
9959    * @name sortNumberStr
9960    * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
9961    * Handles nulls and undefined through calling handleNulls
9962    * @param {object} a sort value a
9963    * @param {object} b sort value b
9964    * @returns {number} normal sort function, returns -ve, 0, +ve
9965    */
9966   rowSorter.sortNumberStr = function sortNumberStr(a, b) {
9967     var nulls = rowSorter.handleNulls(a, b);
9968     if ( nulls !== null ){
9969       return nulls;
9970     } else {
9971       var numA, // The parsed number form of 'a'
9972           numB, // The parsed number form of 'b'
9973           badA = false,
9974           badB = false;
9975
9976       // Try to parse 'a' to a float
9977       numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
9978
9979       // If 'a' couldn't be parsed to float, flag it as bad
9980       if (isNaN(numA)) {
9981           badA = true;
9982       }
9983
9984       // Try to parse 'b' to a float
9985       numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
9986
9987       // If 'b' couldn't be parsed to float, flag it as bad
9988       if (isNaN(numB)) {
9989           badB = true;
9990       }
9991
9992       // We want bad ones to get pushed to the bottom... which effectively is "greater than"
9993       if (badA && badB) {
9994           return 0;
9995       }
9996
9997       if (badA) {
9998           return 1;
9999       }
10000
10001       if (badB) {
10002           return -1;
10003       }
10004
10005       return numA - numB;
10006     }
10007   };
10008
10009
10010   /**
10011    * @ngdoc method
10012    * @methodOf ui.grid.class:RowSorter
10013    * @name sortAlpha
10014    * @description Sorts string values. Handles nulls and undefined through calling handleNulls
10015    * @param {object} a sort value a
10016    * @param {object} b sort value b
10017    * @returns {number} normal sort function, returns -ve, 0, +ve
10018    */
10019   rowSorter.sortAlpha = function sortAlpha(a, b) {
10020     var nulls = rowSorter.handleNulls(a, b);
10021     if ( nulls !== null ){
10022       return nulls;
10023     } else {
10024       var strA = a.toString().toLowerCase(),
10025           strB = b.toString().toLowerCase();
10026
10027       return strA === strB ? 0 : strA.localeCompare(strB);
10028     }
10029   };
10030
10031
10032   /**
10033    * @ngdoc method
10034    * @methodOf ui.grid.class:RowSorter
10035    * @name sortDate
10036    * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
10037    * Handles date strings by converting to Date object if not already an instance of Date
10038    * @param {object} a sort value a
10039    * @param {object} b sort value b
10040    * @returns {number} normal sort function, returns -ve, 0, +ve
10041    */
10042   rowSorter.sortDate = function sortDate(a, b) {
10043     var nulls = rowSorter.handleNulls(a, b);
10044     if ( nulls !== null ){
10045       return nulls;
10046     } else {
10047       if (!(a instanceof Date)) {
10048         a = new Date(a);
10049       }
10050       if (!(b instanceof Date)){
10051         b = new Date(b);
10052       }
10053       var timeA = a.getTime(),
10054           timeB = b.getTime();
10055
10056       return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
10057     }
10058   };
10059
10060
10061   /**
10062    * @ngdoc method
10063    * @methodOf ui.grid.class:RowSorter
10064    * @name sortBool
10065    * @description Sorts boolean values, true is considered larger than false.
10066    * Handles nulls and undefined through calling handleNulls
10067    * @param {object} a sort value a
10068    * @param {object} b sort value b
10069    * @returns {number} normal sort function, returns -ve, 0, +ve
10070    */
10071   rowSorter.sortBool = function sortBool(a, b) {
10072     var nulls = rowSorter.handleNulls(a, b);
10073     if ( nulls !== null ){
10074       return nulls;
10075     } else {
10076       if (a && b) {
10077         return 0;
10078       }
10079
10080       if (!a && !b) {
10081         return 0;
10082       }
10083       else {
10084         return a ? 1 : -1;
10085       }
10086     }
10087   };
10088
10089
10090   /**
10091    * @ngdoc method
10092    * @methodOf ui.grid.class:RowSorter
10093    * @name getSortFn
10094    * @description Get the sort function for the column.  Looks first in
10095    * rowSorter.colSortFnCache using the column name, failing that it
10096    * looks at col.sortingAlgorithm (and puts it in the cache), failing that
10097    * it guesses the sort algorithm based on the data type.
10098    *
10099    * The cache currently seems a bit pointless, as none of the work we do is
10100    * processor intensive enough to need caching.  Presumably in future we might
10101    * inspect the row data itself to guess the sort function, and in that case
10102    * it would make sense to have a cache, the infrastructure is in place to allow
10103    * that.
10104    *
10105    * @param {Grid} grid the grid to consider
10106    * @param {GridCol} col the column to find a function for
10107    * @param {array} rows an array of grid rows.  Currently unused, but presumably in future
10108    * we might inspect the rows themselves to decide what sort of data might be there
10109    * @returns {function} the sort function chosen for the column
10110    */
10111   rowSorter.getSortFn = function getSortFn(grid, col, rows) {
10112     var sortFn, item;
10113
10114     // See if we already figured out what to use to sort the column and have it in the cache
10115     if (rowSorter.colSortFnCache[col.colDef.name]) {
10116       sortFn = rowSorter.colSortFnCache[col.colDef.name];
10117     }
10118     // If the column has its OWN sorting algorithm, use that
10119     else if (col.sortingAlgorithm !== undefined) {
10120       sortFn = col.sortingAlgorithm;
10121       rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
10122     }
10123     // Always default to sortAlpha when sorting after a cellFilter
10124     else if ( col.sortCellFiltered && col.cellFilter ){
10125       sortFn = rowSorter.sortAlpha;
10126       rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10127     }
10128     // Try and guess what sort function to use
10129     else {
10130       // Guess the sort function
10131       sortFn = rowSorter.guessSortFn(col.colDef.type);
10132
10133       // If we found a sort function, cache it
10134       if (sortFn) {
10135         rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10136       }
10137       else {
10138         // We assign the alpha sort because anything that is null/undefined will never get passed to
10139         // the actual sorting function. It will get caught in our null check and returned to be sorted
10140         // down to the bottom
10141         sortFn = rowSorter.sortAlpha;
10142       }
10143     }
10144
10145     return sortFn;
10146   };
10147
10148
10149
10150   /**
10151    * @ngdoc method
10152    * @methodOf ui.grid.class:RowSorter
10153    * @name prioritySort
10154    * @description Used where multiple columns are present in the sort criteria,
10155    * we determine which column should take precedence in the sort by sorting
10156    * the columns based on their sort.priority
10157    *
10158    * @param {gridColumn} a column a
10159    * @param {gridColumn} b column b
10160    * @returns {number} normal sort function, returns -ve, 0, +ve
10161    */
10162   rowSorter.prioritySort = function (a, b) {
10163     // Both columns have a sort priority
10164     if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
10165       // A is higher priority
10166       if (a.sort.priority < b.sort.priority) {
10167         return -1;
10168       }
10169       // Equal
10170       else if (a.sort.priority === b.sort.priority) {
10171         return 0;
10172       }
10173       // B is higher
10174       else {
10175         return 1;
10176       }
10177     }
10178     // Only A has a priority
10179     else if (a.sort.priority || a.sort.priority === undefined) {
10180       return -1;
10181     }
10182     // Only B has a priority
10183     else if (b.sort.priority || b.sort.priority === undefined) {
10184       return 1;
10185     }
10186     // Neither has a priority
10187     else {
10188       return 0;
10189     }
10190   };
10191
10192
10193   /**
10194    * @ngdoc object
10195    * @name useExternalSorting
10196    * @propertyOf ui.grid.class:GridOptions
10197    * @description Prevents the internal sorting from executing.  Events will
10198    * still be fired when the sort changes, and the sort information on
10199    * the columns will be updated, allowing an external sorter (for example,
10200    * server sorting) to be implemented.  Defaults to false.
10201    *
10202    */
10203   /**
10204    * @ngdoc method
10205    * @methodOf ui.grid.class:RowSorter
10206    * @name sort
10207    * @description sorts the grid
10208    * @param {Object} grid the grid itself
10209    * @param {array} rows the rows to be sorted
10210    * @param {array} columns the columns in which to look
10211    * for sort criteria
10212    * @returns {array} sorted rows
10213    */
10214   rowSorter.sort = function rowSorterSort(grid, rows, columns) {
10215     // first make sure we are even supposed to do work
10216     if (!rows) {
10217       return;
10218     }
10219
10220     if (grid.options.useExternalSorting){
10221       return rows;
10222     }
10223
10224     // Build the list of columns to sort by
10225     var sortCols = [];
10226     columns.forEach(function (col) {
10227       if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10228         sortCols.push(col);
10229       }
10230     });
10231
10232     // Sort the "sort columns" by their sort priority
10233     sortCols = sortCols.sort(rowSorter.prioritySort);
10234
10235     // Now rows to sort by, maintain original order
10236     if (sortCols.length === 0) {
10237       return rows;
10238     }
10239
10240     // Re-usable variables
10241     var col, direction;
10242
10243     // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10244     var setIndex = function( row, idx ){
10245       row.entity.$$uiGridIndex = idx;
10246     };
10247     rows.forEach(setIndex);
10248
10249     // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
10250     // var d = data.slice(0);
10251     var r = rows.slice(0);
10252
10253     // Now actually sort the data
10254     var rowSortFn = function (rowA, rowB) {
10255       var tem = 0,
10256           idx = 0,
10257           sortFn;
10258
10259       while (tem === 0 && idx < sortCols.length) {
10260         // grab the metadata for the rest of the logic
10261         col = sortCols[idx];
10262         direction = sortCols[idx].sort.direction;
10263
10264         sortFn = rowSorter.getSortFn(grid, col, r);
10265
10266         var propA, propB;
10267
10268         if ( col.sortCellFiltered ){
10269           propA = grid.getCellDisplayValue(rowA, col);
10270           propB = grid.getCellDisplayValue(rowB, col);
10271         } else {
10272           propA = grid.getCellValue(rowA, col);
10273           propB = grid.getCellValue(rowB, col);
10274         }
10275
10276         tem = sortFn(propA, propB, rowA, rowB, direction);
10277
10278         idx++;
10279       }
10280
10281       // Chrome doesn't implement a stable sort function.  If our sort returns 0
10282       // (i.e. the items are equal), and we're at the last sort column in the list,
10283       // then return the previous order using our custom
10284       // index variable
10285       if (tem === 0 ) {
10286         return rowA.entity.$$uiGridIndex - rowB.entity.$$uiGridIndex;
10287       }
10288
10289       // Made it this far, we don't have to worry about null & undefined
10290       if (direction === uiGridConstants.ASC) {
10291         return tem;
10292       } else {
10293         return 0 - tem;
10294       }
10295     };
10296
10297     var newRows = rows.sort(rowSortFn);
10298
10299     // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10300     var clearIndex = function( row, idx ){
10301        delete row.entity.$$uiGridIndex;
10302     };
10303     rows.forEach(clearIndex);
10304
10305     return newRows;
10306   };
10307
10308   return rowSorter;
10309 }]);
10310
10311 })();
10312
10313 (function() {
10314
10315 var module = angular.module('ui.grid');
10316
10317 var bindPolyfill;
10318 if (typeof Function.prototype.bind !== "function") {
10319   bindPolyfill = function() {
10320     var slice = Array.prototype.slice;
10321     return function(context) {
10322       var fn = this,
10323         args = slice.call(arguments, 1);
10324       if (args.length) {
10325         return function() {
10326           return arguments.length ? fn.apply(context, args.concat(slice.call(arguments))) : fn.apply(context, args);
10327         };
10328       }
10329       return function() {
10330         return arguments.length ? fn.apply(context, arguments) : fn.call(context);
10331       };
10332     };
10333   };
10334 }
10335
10336 function  getStyles (elem) {
10337   var e = elem;
10338   if (typeof(e.length) !== 'undefined' && e.length) {
10339     e = elem[0];
10340   }
10341
10342   return e.ownerDocument.defaultView.getComputedStyle(e, null);
10343 }
10344
10345 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
10346     // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
10347     // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
10348     rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
10349     cssShow = { position: "absolute", visibility: "hidden", display: "block" };
10350
10351 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
10352   var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
10353           // If we already have the right measurement, avoid augmentation
10354           4 :
10355           // Otherwise initialize for horizontal or vertical properties
10356           name === 'width' ? 1 : 0,
10357
10358           val = 0;
10359
10360   var sides = ['Top', 'Right', 'Bottom', 'Left'];
10361
10362   for ( ; i < 4; i += 2 ) {
10363     var side = sides[i];
10364     // dump('side', side);
10365
10366     // both box models exclude margin, so add it if we want it
10367     if ( extra === 'margin' ) {
10368       var marg = parseFloat(styles[extra + side]);
10369       if (!isNaN(marg)) {
10370         val += marg;
10371       }
10372     }
10373     // dump('val1', val);
10374
10375     if ( isBorderBox ) {
10376       // border-box includes padding, so remove it if we want content
10377       if ( extra === 'content' ) {
10378         var padd = parseFloat(styles['padding' + side]);
10379         if (!isNaN(padd)) {
10380           val -= padd;
10381           // dump('val2', val);
10382         }
10383       }
10384
10385       // at this point, extra isn't border nor margin, so remove border
10386       if ( extra !== 'margin' ) {
10387         var bordermarg = parseFloat(styles['border' + side + 'Width']);
10388         if (!isNaN(bordermarg)) {
10389           val -= bordermarg;
10390           // dump('val3', val);
10391         }
10392       }
10393     }
10394     else {
10395       // at this point, extra isn't content, so add padding
10396       var nocontentPad = parseFloat(styles['padding' + side]);
10397       if (!isNaN(nocontentPad)) {
10398         val += nocontentPad;
10399         // dump('val4', val);
10400       }
10401
10402       // at this point, extra isn't content nor padding, so add border
10403       if ( extra !== 'padding') {
10404         var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
10405         if (!isNaN(nocontentnopad)) {
10406           val += nocontentnopad;
10407           // dump('val5', val);
10408         }
10409       }
10410     }
10411   }
10412
10413   // dump('augVal', val);
10414
10415   return val;
10416 }
10417
10418 function getWidthOrHeight( elem, name, extra ) {
10419   // Start with offset property, which is equivalent to the border-box value
10420   var valueIsBorderBox = true,
10421           val, // = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
10422           styles = getStyles(elem),
10423           isBorderBox = styles['boxSizing'] === 'border-box';
10424
10425   // some non-html elements return undefined for offsetWidth, so check for null/undefined
10426   // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
10427   // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
10428   if ( val <= 0 || val == null ) {
10429     // Fall back to computed then uncomputed css if necessary
10430     val = styles[name];
10431     if ( val < 0 || val == null ) {
10432       val = elem.style[ name ];
10433     }
10434
10435     // Computed unit is not pixels. Stop here and return.
10436     if ( rnumnonpx.test(val) ) {
10437       return val;
10438     }
10439
10440     // we need the check for style in case a browser which returns unreliable values
10441     // for getComputedStyle silently falls back to the reliable elem.style
10442     valueIsBorderBox = isBorderBox &&
10443             ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
10444
10445     // Normalize "", auto, and prepare for extra
10446     val = parseFloat( val ) || 0;
10447   }
10448
10449   // use the active box-sizing model to add/subtract irrelevant styles
10450   var ret = ( val +
10451     augmentWidthOrHeight(
10452       elem,
10453       name,
10454       extra || ( isBorderBox ? "border" : "content" ),
10455       valueIsBorderBox,
10456       styles
10457     )
10458   );
10459
10460   // dump('ret', ret, val);
10461   return ret;
10462 }
10463
10464 function getLineHeight(elm) {
10465   elm = angular.element(elm)[0];
10466   var parent = elm.parentElement;
10467
10468   if (!parent) {
10469     parent = document.getElementsByTagName('body')[0];
10470   }
10471
10472   return parseInt( getStyles(parent).fontSize ) || parseInt( getStyles(elm).fontSize ) || 16;
10473 }
10474
10475 var uid = ['0', '0', '0', '0'];
10476 var uidPrefix = 'uiGrid-';
10477
10478 /**
10479  *  @ngdoc service
10480  *  @name ui.grid.service:GridUtil
10481  *
10482  *  @description Grid utility functions
10483  */
10484 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$interval', '$injector', '$q', '$interpolate', 'uiGridConstants',
10485   function ($log, $window, $document, $http, $templateCache, $timeout, $interval, $injector, $q, $interpolate, uiGridConstants) {
10486   var s = {
10487
10488     augmentWidthOrHeight: augmentWidthOrHeight,
10489
10490     getStyles: getStyles,
10491
10492     /**
10493      * @ngdoc method
10494      * @name createBoundedWrapper
10495      * @methodOf ui.grid.service:GridUtil
10496      *
10497      * @param {object} Object to bind 'this' to
10498      * @param {method} Method to bind
10499      * @returns {Function} The wrapper that performs the binding
10500      *
10501      * @description
10502      * Binds given method to given object.
10503      *
10504      * By means of a wrapper, ensures that ``method`` is always bound to
10505      * ``object`` regardless of its calling environment.
10506      * Iow, inside ``method``, ``this`` always points to ``object``.
10507      *
10508      * See http://alistapart.com/article/getoutbindingsituations
10509      *
10510      */
10511     createBoundedWrapper: function(object, method) {
10512         return function() {
10513             return method.apply(object, arguments);
10514         };
10515     },
10516
10517
10518     /**
10519      * @ngdoc method
10520      * @name readableColumnName
10521      * @methodOf ui.grid.service:GridUtil
10522      *
10523      * @param {string} columnName Column name as a string
10524      * @returns {string} Column name appropriately capitalized and split apart
10525      *
10526        @example
10527        <example module="app">
10528         <file name="app.js">
10529           var app = angular.module('app', ['ui.grid']);
10530
10531           app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
10532             $scope.name = 'firstName';
10533             $scope.columnName = function(name) {
10534               return gridUtil.readableColumnName(name);
10535             };
10536           }]);
10537         </file>
10538         <file name="index.html">
10539           <div ng-controller="MainCtrl">
10540             <strong>Column name:</strong> <input ng-model="name" />
10541             <br>
10542             <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
10543           </div>
10544         </file>
10545       </example>
10546      */
10547     readableColumnName: function (columnName) {
10548       // Convert underscores to spaces
10549       if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
10550
10551       if (typeof(columnName) !== 'string') {
10552         columnName = String(columnName);
10553       }
10554
10555       return columnName.replace(/_+/g, ' ')
10556         // Replace a completely all-capsed word with a first-letter-capitalized version
10557         .replace(/^[A-Z]+$/, function (match) {
10558           return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10559         })
10560         // Capitalize the first letter of words
10561         .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10562           return angular.uppercase(match.charAt(0)) + match.slice(1);
10563         })
10564         // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
10565         // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
10566         // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
10567         .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
10568     },
10569
10570     /**
10571      * @ngdoc method
10572      * @name getColumnsFromData
10573      * @methodOf ui.grid.service:GridUtil
10574      * @description Return a list of column names, given a data set
10575      *
10576      * @param {string} data Data array for grid
10577      * @returns {Object} Column definitions with field accessor and column name
10578      *
10579      * @example
10580        <pre>
10581          var data = [
10582            { firstName: 'Bob', lastName: 'Jones' },
10583            { firstName: 'Frank', lastName: 'Smith' }
10584          ];
10585
10586          var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
10587
10588          columnDefs == [
10589           {
10590             field: 'firstName',
10591             name: 'First Name'
10592           },
10593           {
10594             field: 'lastName',
10595             name: 'Last Name'
10596           }
10597          ];
10598        </pre>
10599      */
10600     getColumnsFromData: function (data, excludeProperties) {
10601       var columnDefs = [];
10602
10603       if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
10604       if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
10605
10606       var item = data[0];
10607
10608       angular.forEach(item,function (prop, propName) {
10609         if ( excludeProperties.indexOf(propName) === -1){
10610           columnDefs.push({
10611             name: propName
10612           });
10613         }
10614       });
10615
10616       return columnDefs;
10617     },
10618
10619     /**
10620      * @ngdoc method
10621      * @name newId
10622      * @methodOf ui.grid.service:GridUtil
10623      * @description Return a unique ID string
10624      *
10625      * @returns {string} Unique string
10626      *
10627      * @example
10628        <pre>
10629         var id = GridUtil.newId();
10630
10631         # 1387305700482;
10632        </pre>
10633      */
10634     newId: (function() {
10635       var seedId = new Date().getTime();
10636       return function() {
10637           return seedId += 1;
10638       };
10639     })(),
10640
10641
10642     /**
10643      * @ngdoc method
10644      * @name getTemplate
10645      * @methodOf ui.grid.service:GridUtil
10646      * @description Get's template from cache / element / url
10647      *
10648      * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10649      *   an jQuery/Angualr element, or a promise that returns the template contents to use.
10650      * @returns {object} a promise resolving to template contents
10651      *
10652      * @example
10653      <pre>
10654      GridUtil.getTemplate(url).then(function (contents) {
10655           alert(contents);
10656         })
10657      </pre>
10658      */
10659     getTemplate: function (template) {
10660       // Try to fetch the template out of the templateCache
10661       if ($templateCache.get(template)) {
10662         return s.postProcessTemplate($templateCache.get(template));
10663       }
10664
10665       // See if the template is itself a promise
10666       if (template.hasOwnProperty('then')) {
10667         return template.then(s.postProcessTemplate);
10668       }
10669
10670       // If the template is an element, return the element
10671       try {
10672         if (angular.element(template).length > 0) {
10673           return $q.when(template).then(s.postProcessTemplate);
10674         }
10675       }
10676       catch (err){
10677         //do nothing; not valid html
10678       }
10679
10680       s.logDebug('fetching url', template);
10681
10682       // Default to trying to fetch the template as a url with $http
10683       return $http({ method: 'GET', url: template})
10684         .then(
10685           function (result) {
10686             var templateHtml = result.data.trim();
10687             //put in templateCache for next call
10688             $templateCache.put(template, templateHtml);
10689             return templateHtml;
10690           },
10691           function (err) {
10692             throw new Error("Could not get template " + template + ": " + err);
10693           }
10694         )
10695         .then(s.postProcessTemplate);
10696     },
10697
10698     //
10699     postProcessTemplate: function (template) {
10700       var startSym = $interpolate.startSymbol(),
10701           endSym = $interpolate.endSymbol();
10702
10703       // If either of the interpolation symbols have been changed, we need to alter this template
10704       if (startSym !== '{{' || endSym !== '}}') {
10705         template = template.replace(/\{\{/g, startSym);
10706         template = template.replace(/\}\}/g, endSym);
10707       }
10708
10709       return $q.when(template);
10710     },
10711
10712     /**
10713      * @ngdoc method
10714      * @name guessType
10715      * @methodOf ui.grid.service:GridUtil
10716      * @description guesses the type of an argument
10717      *
10718      * @param {string/number/bool/object} item variable to examine
10719      * @returns {string} one of the following
10720      * - 'string'
10721      * - 'boolean'
10722      * - 'number'
10723      * - 'date'
10724      * - 'object'
10725      */
10726     guessType : function (item) {
10727       var itemType = typeof(item);
10728
10729       // Check for numbers and booleans
10730       switch (itemType) {
10731         case "number":
10732         case "boolean":
10733         case "string":
10734           return itemType;
10735         default:
10736           if (angular.isDate(item)) {
10737             return "date";
10738           }
10739           return "object";
10740       }
10741     },
10742
10743
10744   /**
10745     * @ngdoc method
10746     * @name elementWidth
10747     * @methodOf ui.grid.service:GridUtil
10748     *
10749     * @param {element} element DOM element
10750     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10751     *
10752     * @returns {number} Element width in pixels, accounting for any borders, etc.
10753     */
10754     elementWidth: function (elem) {
10755
10756     },
10757
10758     /**
10759     * @ngdoc method
10760     * @name elementHeight
10761     * @methodOf ui.grid.service:GridUtil
10762     *
10763     * @param {element} element DOM element
10764     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10765     *
10766     * @returns {number} Element height in pixels, accounting for any borders, etc.
10767     */
10768     elementHeight: function (elem) {
10769
10770     },
10771
10772     // Thanks to http://stackoverflow.com/a/13382873/888165
10773     getScrollbarWidth: function() {
10774         var outer = document.createElement("div");
10775         outer.style.visibility = "hidden";
10776         outer.style.width = "100px";
10777         outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
10778
10779         document.body.appendChild(outer);
10780
10781         var widthNoScroll = outer.offsetWidth;
10782         // force scrollbars
10783         outer.style.overflow = "scroll";
10784
10785         // add innerdiv
10786         var inner = document.createElement("div");
10787         inner.style.width = "100%";
10788         outer.appendChild(inner);
10789
10790         var widthWithScroll = inner.offsetWidth;
10791
10792         // remove divs
10793         outer.parentNode.removeChild(outer);
10794
10795         return widthNoScroll - widthWithScroll;
10796     },
10797
10798     swap: function( elem, options, callback, args ) {
10799       var ret, name,
10800               old = {};
10801
10802       // Remember the old values, and insert the new ones
10803       for ( name in options ) {
10804         old[ name ] = elem.style[ name ];
10805         elem.style[ name ] = options[ name ];
10806       }
10807
10808       ret = callback.apply( elem, args || [] );
10809
10810       // Revert the old values
10811       for ( name in options ) {
10812         elem.style[ name ] = old[ name ];
10813       }
10814
10815       return ret;
10816     },
10817
10818     fakeElement: function( elem, options, callback, args ) {
10819       var ret, name,
10820           newElement = angular.element(elem).clone()[0];
10821
10822       for ( name in options ) {
10823         newElement.style[ name ] = options[ name ];
10824       }
10825
10826       angular.element(document.body).append(newElement);
10827
10828       ret = callback.call( newElement, newElement );
10829
10830       angular.element(newElement).remove();
10831
10832       return ret;
10833     },
10834
10835     /**
10836     * @ngdoc method
10837     * @name normalizeWheelEvent
10838     * @methodOf ui.grid.service:GridUtil
10839     *
10840     * @param {event} event A mouse wheel event
10841     *
10842     * @returns {event} A normalized event
10843     *
10844     * @description
10845     * Given an event from this list:
10846     *
10847     * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
10848     *
10849     * "normalize" it
10850     * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
10851     */
10852     normalizeWheelEvent: function (event) {
10853       // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
10854       // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
10855       var lowestDelta, lowestDeltaXY;
10856
10857       var orgEvent   = event || window.event,
10858           args       = [].slice.call(arguments, 1),
10859           delta      = 0,
10860           deltaX     = 0,
10861           deltaY     = 0,
10862           absDelta   = 0,
10863           absDeltaXY = 0,
10864           fn;
10865
10866       // event = $.event.fix(orgEvent);
10867       // event.type = 'mousewheel';
10868
10869       // NOTE: jQuery masks the event and stores it in the event as originalEvent
10870       if (orgEvent.originalEvent) {
10871         orgEvent = orgEvent.originalEvent;
10872       }
10873
10874       // Old school scrollwheel delta
10875       if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
10876       if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
10877
10878       // At a minimum, setup the deltaY to be delta
10879       deltaY = delta;
10880
10881       // Firefox < 17 related to DOMMouseScroll event
10882       if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
10883           deltaY = 0;
10884           deltaX = delta * -1;
10885       }
10886
10887       // New school wheel delta (wheel event)
10888       if ( orgEvent.deltaY ) {
10889           deltaY = orgEvent.deltaY * -1;
10890           delta  = deltaY;
10891       }
10892       if ( orgEvent.deltaX ) {
10893           deltaX = orgEvent.deltaX;
10894           delta  = deltaX * -1;
10895       }
10896
10897       // Webkit
10898       if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
10899       if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
10900
10901       // Look for lowest delta to normalize the delta values
10902       absDelta = Math.abs(delta);
10903       if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
10904       absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
10905       if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
10906
10907       // Get a whole value for the deltas
10908       fn     = delta > 0 ? 'floor' : 'ceil';
10909       delta  = Math[fn](delta  / lowestDelta);
10910       deltaX = Math[fn](deltaX / lowestDeltaXY);
10911       deltaY = Math[fn](deltaY / lowestDeltaXY);
10912
10913       return {
10914         delta: delta,
10915         deltaX: deltaX,
10916         deltaY: deltaY
10917       };
10918     },
10919
10920     // Stolen from Modernizr
10921     // TODO: make this, and everythign that flows from it, robust
10922     //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
10923     isTouchEnabled: function() {
10924       var bool;
10925
10926       if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
10927         bool = true;
10928       }
10929
10930       return bool;
10931     },
10932
10933     isNullOrUndefined: function(obj) {
10934       if (obj === undefined || obj === null) {
10935         return true;
10936       }
10937       return false;
10938     },
10939
10940     endsWith: function(str, suffix) {
10941       if (!str || !suffix || typeof str !== "string") {
10942         return false;
10943       }
10944       return str.indexOf(suffix, str.length - suffix.length) !== -1;
10945     },
10946
10947     arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
10948         var found = false;
10949         angular.forEach(array, function (object) {
10950             if (object[propertyName] === propertyValue) {
10951                 found = true;
10952             }
10953         });
10954         return found;
10955     },
10956
10957     //// Shim requestAnimationFrame
10958     //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
10959     //                       $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
10960     //                       function(fn) {
10961     //                         return $timeout(fn, 10, false);
10962     //                       },
10963
10964     numericAndNullSort: function (a, b) {
10965       if (a === null) { return 1; }
10966       if (b === null) { return -1; }
10967       if (a === null && b === null) { return 0; }
10968       return a - b;
10969     },
10970
10971     // Disable ngAnimate animations on an element
10972     disableAnimations: function (element) {
10973       var $animate;
10974       try {
10975         $animate = $injector.get('$animate');
10976         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10977         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10978           $animate.enabled(element, false);
10979         } else {
10980           $animate.enabled(false, element);
10981         }
10982       }
10983       catch (e) {}
10984     },
10985
10986     enableAnimations: function (element) {
10987       var $animate;
10988       try {
10989         $animate = $injector.get('$animate');
10990         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
10991         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
10992           $animate.enabled(element, true);
10993         } else {
10994           $animate.enabled(true, element);
10995         }
10996         return $animate;
10997       }
10998       catch (e) {}
10999     },
11000
11001     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11002     nextUid: function nextUid() {
11003       var index = uid.length;
11004       var digit;
11005
11006       while (index) {
11007         index--;
11008         digit = uid[index].charCodeAt(0);
11009         if (digit === 57 /*'9'*/) {
11010           uid[index] = 'A';
11011           return uidPrefix + uid.join('');
11012         }
11013         if (digit === 90  /*'Z'*/) {
11014           uid[index] = '0';
11015         } else {
11016           uid[index] = String.fromCharCode(digit + 1);
11017           return uidPrefix + uid.join('');
11018         }
11019       }
11020       uid.unshift('0');
11021
11022       return uidPrefix + uid.join('');
11023     },
11024
11025     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11026     hashKey: function hashKey(obj) {
11027       var objType = typeof obj,
11028           key;
11029
11030       if (objType === 'object' && obj !== null) {
11031         if (typeof (key = obj.$$hashKey) === 'function') {
11032           // must invoke on object to keep the right this
11033           key = obj.$$hashKey();
11034         }
11035         else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
11036           key = obj.$$hashKey;
11037         }
11038         else if (key === undefined) {
11039           key = obj.$$hashKey = s.nextUid();
11040         }
11041       }
11042       else {
11043         key = obj;
11044       }
11045
11046       return objType + ':' + key;
11047     },
11048
11049     resetUids: function () {
11050       uid = ['0', '0', '0'];
11051     },
11052
11053     /**
11054      * @ngdoc method
11055      * @methodOf ui.grid.service:GridUtil
11056      * @name logError
11057      * @description wraps the $log method, allowing us to choose different
11058      * treatment within ui-grid if we so desired.  At present we only log
11059      * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
11060      * @param {string} logMessage message to be logged to the console
11061      *
11062      */
11063     logError: function( logMessage ){
11064       if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11065         $log.error( logMessage );
11066       }
11067     },
11068
11069     /**
11070      * @ngdoc method
11071      * @methodOf ui.grid.service:GridUtil
11072      * @name logWarn
11073      * @description wraps the $log method, allowing us to choose different
11074      * treatment within ui-grid if we so desired.  At present we only log
11075      * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
11076      * @param {string} logMessage message to be logged to the console
11077      *
11078      */
11079     logWarn: function( logMessage ){
11080       if ( uiGridConstants.LOG_WARN_MESSAGES ){
11081         $log.warn( logMessage );
11082       }
11083     },
11084
11085     /**
11086      * @ngdoc method
11087      * @methodOf ui.grid.service:GridUtil
11088      * @name logDebug
11089      * @description wraps the $log method, allowing us to choose different
11090      * treatment within ui-grid if we so desired.  At present we only log
11091      * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
11092      *
11093      */
11094     logDebug: function() {
11095       if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11096         $log.debug.apply($log, arguments);
11097       }
11098     }
11099
11100   };
11101
11102   /**
11103    * @ngdoc object
11104    * @name focus
11105    * @propertyOf ui.grid.service:GridUtil
11106    * @description Provies a set of methods to set the document focus inside the grid.
11107    * See {@link ui.grid.service:GridUtil.focus} for more information.
11108    */
11109
11110   /**
11111    * @ngdoc object
11112    * @name ui.grid.service:GridUtil.focus
11113    * @description Provies a set of methods to set the document focus inside the grid.
11114    * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
11115    * e.g. click events that need to run before the focus or
11116    * inputs elements that are in a disabled state but are enabled when those events
11117    * are triggered.
11118    */
11119   s.focus = {
11120     queue: [],
11121     //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11122     /**
11123      * @ngdoc method
11124      * @methodOf ui.grid.service:GridUtil.focus
11125      * @name byId
11126      * @description Sets the focus of the document to the given id value.
11127      * If provided with the grid object it will automatically append the grid id.
11128      * This is done to encourage unique dom id's as it allows for multiple grids on a
11129      * page.
11130      * @param {String} id the id of the dom element to set the focus on
11131      * @param {Object=} Grid the grid object for this grid instance. See: {@link ui.grid.class:Grid}
11132      * @param {Number} Grid.id the unique id for this grid. Already set on an initialized grid object.
11133      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11134      * then the promise will fail with the `'canceled'` reason.
11135      */
11136     byId: function (id, Grid) {
11137       this._purgeQueue();
11138       var promise = $timeout(function() {
11139         var elementID = (Grid && Grid.id ? Grid.id + '-' : '') + id;
11140         var element = $window.document.getElementById(elementID);
11141         if (element) {
11142           element.focus();
11143         } else {
11144           s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
11145         }
11146       });
11147       this.queue.push(promise);
11148       return promise;
11149     },
11150
11151     /**
11152      * @ngdoc method
11153      * @methodOf ui.grid.service:GridUtil.focus
11154      * @name byElement
11155      * @description Sets the focus of the document to the given dom element.
11156      * @param {(element|angular.element)} element the DOM element to set the focus on
11157      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11158      * then the promise will fail with the `'canceled'` reason.
11159      */
11160     byElement: function(element){
11161       if (!angular.isElement(element)){
11162         s.logWarn("Trying to focus on an element that isn\'t an element.");
11163         return $q.reject('not-element');
11164       }
11165       element = angular.element(element);
11166       this._purgeQueue();
11167       var promise = $timeout(function(){
11168         if (element){
11169           element[0].focus();
11170         }
11171       });
11172       this.queue.push(promise);
11173       return promise;
11174     },
11175     /**
11176      * @ngdoc method
11177      * @methodOf ui.grid.service:GridUtil.focus
11178      * @name bySelector
11179      * @description Sets the focus of the document to the given dom element.
11180      * @param {(element|angular.element)} parentElement the parent/ancestor of the dom element that you are selecting using the query selector
11181      * @param {String} querySelector finds the dom element using the {@link http://www.w3schools.com/jsref/met_document_queryselector.asp querySelector}
11182      * @param {boolean} [aSync=false] If true then the selector will be querried inside of a timeout. Otherwise the selector will be querried imidately
11183      * then the focus will be called.
11184      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11185      * then the promise will fail with the `'canceled'` reason.
11186      */
11187     bySelector: function(parentElement, querySelector, aSync){
11188       var self = this;
11189       if (!angular.isElement(parentElement)){
11190         throw new Error("The parent element is not an element.");
11191       }
11192       // Ensure that this is an angular element.
11193       // It is fine if this is already an angular element.
11194       parentElement = angular.element(parentElement);
11195       var focusBySelector = function(){
11196         var element = parentElement[0].querySelector(querySelector);
11197         return self.byElement(element);
11198       };
11199       this._purgeQueue();
11200       if (aSync){ //Do this asynchronysly
11201         var promise = $timeout(focusBySelector);
11202         this.queue.push($timeout(focusBySelector));
11203         return promise;
11204       } else {
11205         return focusBySelector();
11206       }
11207     },
11208     _purgeQueue: function(){
11209       this.queue.forEach(function(element){
11210         $timeout.cancel(element);
11211       });
11212       this.queue = [];
11213     }
11214   };
11215
11216
11217   ['width', 'height'].forEach(function (name) {
11218     var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11219     s['element' + capsName] = function (elem, extra) {
11220       var e = elem;
11221       if (e && typeof(e.length) !== 'undefined' && e.length) {
11222         e = elem[0];
11223       }
11224
11225       if (e) {
11226         var styles = getStyles(e);
11227         return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11228                   s.swap(e, cssShow, function() {
11229                     return getWidthOrHeight(e, name, extra );
11230                   }) :
11231                   getWidthOrHeight( e, name, extra );
11232       }
11233       else {
11234         return null;
11235       }
11236     };
11237
11238     s['outerElement' + capsName] = function (elem, margin) {
11239       return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
11240     };
11241   });
11242
11243   // http://stackoverflow.com/a/24107550/888165
11244   s.closestElm = function closestElm(el, selector) {
11245     if (typeof(el.length) !== 'undefined' && el.length) {
11246       el = el[0];
11247     }
11248
11249     var matchesFn;
11250
11251     // find vendor prefix
11252     ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
11253         if (typeof document.body[fn] === 'function') {
11254             matchesFn = fn;
11255             return true;
11256         }
11257         return false;
11258     });
11259
11260     // traverse parents
11261     var parent;
11262     while (el !== null) {
11263       parent = el.parentElement;
11264       if (parent !== null && parent[matchesFn](selector)) {
11265           return parent;
11266       }
11267       el = parent;
11268     }
11269
11270     return null;
11271   };
11272
11273   s.type = function (obj) {
11274     var text = Function.prototype.toString.call(obj.constructor);
11275     return text.match(/function (.*?)\(/)[1];
11276   };
11277
11278   s.getBorderSize = function getBorderSize(elem, borderType) {
11279     if (typeof(elem.length) !== 'undefined' && elem.length) {
11280       elem = elem[0];
11281     }
11282
11283     var styles = getStyles(elem);
11284
11285     // If a specific border is supplied, like 'top', read the 'borderTop' style property
11286     if (borderType) {
11287       borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
11288     }
11289     else {
11290       borderType = 'border';
11291     }
11292
11293     borderType += 'Width';
11294
11295     var val = parseInt(styles[borderType], 10);
11296
11297     if (isNaN(val)) {
11298       return 0;
11299     }
11300     else {
11301       return val;
11302     }
11303   };
11304
11305   // http://stackoverflow.com/a/22948274/888165
11306   // TODO: Opera? Mobile?
11307   s.detectBrowser = function detectBrowser() {
11308     var userAgent = $window.navigator.userAgent;
11309
11310     var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
11311
11312     for (var key in browsers) {
11313       if (browsers[key].test(userAgent)) {
11314         return key;
11315       }
11316     }
11317
11318     return 'unknown';
11319   };
11320
11321   // Borrowed from https://github.com/othree/jquery.rtl-scroll-type
11322   // Determine the scroll "type" this browser is using for RTL
11323   s.rtlScrollType = function rtlScrollType() {
11324     if (rtlScrollType.type) {
11325       return rtlScrollType.type;
11326     }
11327
11328     var definer = angular.element('<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>')[0],
11329         type = 'reverse';
11330
11331     document.body.appendChild(definer);
11332
11333     if (definer.scrollLeft > 0) {
11334       type = 'default';
11335     }
11336     else {
11337       definer.scrollLeft = 1;
11338       if (definer.scrollLeft === 0) {
11339         type = 'negative';
11340       }
11341     }
11342
11343     angular.element(definer).remove();
11344     rtlScrollType.type = type;
11345
11346     return type;
11347   };
11348
11349     /**
11350      * @ngdoc method
11351      * @name normalizeScrollLeft
11352      * @methodOf ui.grid.service:GridUtil
11353      *
11354      * @param {element} element The element to get the `scrollLeft` from.
11355      * @param {grid} grid -  grid used to normalize (uses the rtl property)
11356      *
11357      * @returns {number} A normalized scrollLeft value for the current browser.
11358      *
11359      * @description
11360      * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
11361      */
11362   s.normalizeScrollLeft = function normalizeScrollLeft(element, grid) {
11363     if (typeof(element.length) !== 'undefined' && element.length) {
11364       element = element[0];
11365     }
11366
11367     var scrollLeft = element.scrollLeft;
11368
11369     if (grid.isRTL()) {
11370       switch (s.rtlScrollType()) {
11371         case 'default':
11372           return element.scrollWidth - scrollLeft - element.clientWidth;
11373         case 'negative':
11374           return Math.abs(scrollLeft);
11375         case 'reverse':
11376           return scrollLeft;
11377       }
11378     }
11379
11380     return scrollLeft;
11381   };
11382
11383   /**
11384   * @ngdoc method
11385   * @name denormalizeScrollLeft
11386   * @methodOf ui.grid.service:GridUtil
11387   *
11388   * @param {element} element The element to normalize the `scrollLeft` value for
11389   * @param {number} scrollLeft The `scrollLeft` value to denormalize.
11390   * @param {grid} grid The grid that owns the scroll event.
11391   *
11392   * @returns {number} A normalized scrollLeft value for the current browser.
11393   *
11394   * @description
11395   * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
11396   */
11397   s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft, grid) {
11398     if (typeof(element.length) !== 'undefined' && element.length) {
11399       element = element[0];
11400     }
11401
11402     if (grid.isRTL()) {
11403       switch (s.rtlScrollType()) {
11404         case 'default':
11405           // Get the max scroll for the element
11406           var maxScrollLeft = element.scrollWidth - element.clientWidth;
11407
11408           // Subtract the current scroll amount from the max scroll
11409           return maxScrollLeft - scrollLeft;
11410         case 'negative':
11411           return scrollLeft * -1;
11412         case 'reverse':
11413           return scrollLeft;
11414       }
11415     }
11416
11417     return scrollLeft;
11418   };
11419
11420     /**
11421      * @ngdoc method
11422      * @name preEval
11423      * @methodOf ui.grid.service:GridUtil
11424      *
11425      * @param {string} path Path to evaluate
11426      *
11427      * @returns {string} A path that is normalized.
11428      *
11429      * @description
11430      * Takes a field path and converts it to bracket notation to allow for special characters in path
11431      * @example
11432      * <pre>
11433      * gridUtil.preEval('property') == 'property'
11434      * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
11435      * </pre>
11436      */
11437   s.preEval = function (path) {
11438     var m = uiGridConstants.BRACKET_REGEXP.exec(path);
11439     if (m) {
11440       return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
11441     } else {
11442       path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
11443       var parts = path.split(uiGridConstants.DOT_REGEXP);
11444       var preparsed = [parts.shift()];    // first item must be var notation, thus skip
11445       angular.forEach(parts, function (part) {
11446         preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
11447       });
11448       return preparsed.join('[\'');
11449     }
11450   };
11451
11452   /**
11453    * @ngdoc method
11454    * @name debounce
11455    * @methodOf ui.grid.service:GridUtil
11456    *
11457    * @param {function} func function to debounce
11458    * @param {number} wait milliseconds to delay
11459    * @param {boolean} immediate execute before delay
11460    *
11461    * @returns {function} A function that can be executed as debounced function
11462    *
11463    * @description
11464    * Copied from https://github.com/shahata/angular-debounce
11465    * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
11466    * @example
11467    * <pre>
11468    * var debouncedFunc =  gridUtil.debounce(function(){alert('debounced');}, 500);
11469    * debouncedFunc();
11470    * debouncedFunc();
11471    * debouncedFunc();
11472    * </pre>
11473    */
11474   s.debounce =  function (func, wait, immediate) {
11475     var timeout, args, context, result;
11476     function debounce() {
11477       /* jshint validthis:true */
11478       context = this;
11479       args = arguments;
11480       var later = function () {
11481         timeout = null;
11482         if (!immediate) {
11483           result = func.apply(context, args);
11484         }
11485       };
11486       var callNow = immediate && !timeout;
11487       if (timeout) {
11488         $timeout.cancel(timeout);
11489       }
11490       timeout = $timeout(later, wait, false);
11491       if (callNow) {
11492         result = func.apply(context, args);
11493       }
11494       return result;
11495     }
11496     debounce.cancel = function () {
11497       $timeout.cancel(timeout);
11498       timeout = null;
11499     };
11500     return debounce;
11501   };
11502
11503   /**
11504    * @ngdoc method
11505    * @name throttle
11506    * @methodOf ui.grid.service:GridUtil
11507    *
11508    * @param {function} func function to throttle
11509    * @param {number} wait milliseconds to delay after first trigger
11510    * @param {Object} params to use in throttle.
11511    *
11512    * @returns {function} A function that can be executed as throttled function
11513    *
11514    * @description
11515    * Adapted from debounce function (above)
11516    * Potential keys for Params Object are:
11517    *    trailing (bool) - whether to trigger after throttle time ends if called multiple times
11518    * Updated to use $interval rather than $timeout, as protractor (e2e tests) is able to work with $interval,
11519    * but not with $timeout
11520    *
11521    * Note that when using throttle, you need to use throttle to create a new function upfront, then use the function
11522    * return from that call each time you need to call throttle.  If you call throttle itself repeatedly, the lastCall
11523    * variable will get overwritten and the throttling won't work
11524    *
11525    * @example
11526    * <pre>
11527    * var throttledFunc =  gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11528    * throttledFunc(); //=> logs throttled
11529    * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
11530    * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
11531    * </pre>
11532    */
11533   s.throttle = function(func, wait, options){
11534     options = options || {};
11535     var lastCall = 0, queued = null, context, args;
11536
11537     function runFunc(endDate){
11538       lastCall = +new Date();
11539       func.apply(context, args);
11540       $interval(function(){ queued = null; }, 0, 1, false);
11541     }
11542
11543     return function(){
11544       /* jshint validthis:true */
11545       context = this;
11546       args = arguments;
11547       if (queued === null){
11548         var sinceLast = +new Date() - lastCall;
11549         if (sinceLast > wait){
11550           runFunc();
11551         }
11552         else if (options.trailing){
11553           queued = $interval(runFunc, wait - sinceLast, 1, false);
11554         }
11555       }
11556     };
11557   };
11558
11559   s.on = {};
11560   s.off = {};
11561   s._events = {};
11562
11563   s.addOff = function (eventName) {
11564     s.off[eventName] = function (elm, fn) {
11565       var idx = s._events[eventName].indexOf(fn);
11566       if (idx > 0) {
11567         s._events[eventName].removeAt(idx);
11568       }
11569     };
11570   };
11571
11572   var mouseWheeltoBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
11573       nullLowestDeltaTimeout,
11574       lowestDelta;
11575
11576   s.on.mousewheel = function (elm, fn) {
11577     if (!elm || !fn) { return; }
11578
11579     var $elm = angular.element(elm);
11580
11581     // Store the line height and page height for this particular element
11582     $elm.data('mousewheel-line-height', getLineHeight($elm));
11583     $elm.data('mousewheel-page-height', s.elementHeight($elm));
11584     if (!$elm.data('mousewheel-callbacks')) { $elm.data('mousewheel-callbacks', {}); }
11585
11586     var cbs = $elm.data('mousewheel-callbacks');
11587     cbs[fn] = (Function.prototype.bind || bindPolyfill).call(mousewheelHandler, $elm[0], fn);
11588
11589     // Bind all the mousew heel events
11590     for ( var i = mouseWheeltoBind.length; i; ) {
11591       $elm.on(mouseWheeltoBind[--i], cbs[fn]);
11592     }
11593   };
11594   s.off.mousewheel = function (elm, fn) {
11595     var $elm = angular.element(elm);
11596
11597     var cbs = $elm.data('mousewheel-callbacks');
11598     var handler = cbs[fn];
11599
11600     if (handler) {
11601       for ( var i = mouseWheeltoBind.length; i; ) {
11602         $elm.off(mouseWheeltoBind[--i], handler);
11603       }
11604     }
11605
11606     delete cbs[fn];
11607
11608     if (Object.keys(cbs).length === 0) {
11609       $elm.removeData('mousewheel-line-height');
11610       $elm.removeData('mousewheel-page-height');
11611       $elm.removeData('mousewheel-callbacks');
11612     }
11613   };
11614
11615   function mousewheelHandler(fn, event) {
11616     var $elm = angular.element(this);
11617
11618     var delta      = 0,
11619         deltaX     = 0,
11620         deltaY     = 0,
11621         absDelta   = 0,
11622         offsetX    = 0,
11623         offsetY    = 0;
11624
11625     // jQuery masks events
11626     if (event.originalEvent) { event = event.originalEvent; }
11627
11628     if ( 'detail'      in event ) { deltaY = event.detail * -1;      }
11629     if ( 'wheelDelta'  in event ) { deltaY = event.wheelDelta;       }
11630     if ( 'wheelDeltaY' in event ) { deltaY = event.wheelDeltaY;      }
11631     if ( 'wheelDeltaX' in event ) { deltaX = event.wheelDeltaX * -1; }
11632
11633     // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
11634     if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
11635       deltaX = deltaY * -1;
11636       deltaY = 0;
11637     }
11638
11639     // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
11640     delta = deltaY === 0 ? deltaX : deltaY;
11641
11642     // New school wheel delta (wheel event)
11643     if ( 'deltaY' in event ) {
11644       deltaY = event.deltaY * -1;
11645       delta  = deltaY;
11646     }
11647     if ( 'deltaX' in event ) {
11648       deltaX = event.deltaX;
11649       if ( deltaY === 0 ) { delta  = deltaX * -1; }
11650     }
11651
11652     // No change actually happened, no reason to go any further
11653     if ( deltaY === 0 && deltaX === 0 ) { return; }
11654
11655     // Need to convert lines and pages to pixels if we aren't already in pixels
11656     // There are three delta modes:
11657     //   * deltaMode 0 is by pixels, nothing to do
11658     //   * deltaMode 1 is by lines
11659     //   * deltaMode 2 is by pages
11660     if ( event.deltaMode === 1 ) {
11661         var lineHeight = $elm.data('mousewheel-line-height');
11662         delta  *= lineHeight;
11663         deltaY *= lineHeight;
11664         deltaX *= lineHeight;
11665     }
11666     else if ( event.deltaMode === 2 ) {
11667         var pageHeight = $elm.data('mousewheel-page-height');
11668         delta  *= pageHeight;
11669         deltaY *= pageHeight;
11670         deltaX *= pageHeight;
11671     }
11672
11673     // Store lowest absolute delta to normalize the delta values
11674     absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
11675
11676     if ( !lowestDelta || absDelta < lowestDelta ) {
11677       lowestDelta = absDelta;
11678
11679       // Adjust older deltas if necessary
11680       if ( shouldAdjustOldDeltas(event, absDelta) ) {
11681         lowestDelta /= 40;
11682       }
11683     }
11684
11685     // Get a whole, normalized value for the deltas
11686     delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
11687     deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
11688     deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
11689
11690     event.deltaMode = 0;
11691
11692     // Normalise offsetX and offsetY properties
11693     // if ($elm[0].getBoundingClientRect ) {
11694     //   var boundingRect = $(elm)[0].getBoundingClientRect();
11695     //   offsetX = event.clientX - boundingRect.left;
11696     //   offsetY = event.clientY - boundingRect.top;
11697     // }
11698
11699     // event.deltaX = deltaX;
11700     // event.deltaY = deltaY;
11701     // event.deltaFactor = lowestDelta;
11702
11703     var newEvent = {
11704       originalEvent: event,
11705       deltaX: deltaX,
11706       deltaY: deltaY,
11707       deltaFactor: lowestDelta,
11708       preventDefault: function () { event.preventDefault(); },
11709       stopPropagation: function () { event.stopPropagation(); }
11710     };
11711
11712     // Clearout lowestDelta after sometime to better
11713     // handle multiple device types that give
11714     // a different lowestDelta
11715     // Ex: trackpad = 3 and mouse wheel = 120
11716     if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
11717     nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
11718
11719     fn.call($elm[0], newEvent);
11720   }
11721
11722   function nullLowestDelta() {
11723     lowestDelta = null;
11724   }
11725
11726   function shouldAdjustOldDeltas(orgEvent, absDelta) {
11727     // If this is an older event and the delta is divisable by 120,
11728     // then we are assuming that the browser is treating this as an
11729     // older mouse wheel event and that we should divide the deltas
11730     // by 40 to try and get a more usable deltaFactor.
11731     // Side note, this actually impacts the reported scroll distance
11732     // in older browsers and can cause scrolling to be slower than native.
11733     // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
11734     return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
11735   }
11736
11737   return s;
11738 }]);
11739
11740 // Add 'px' to the end of a number string if it doesn't have it already
11741 module.filter('px', function() {
11742   return function(str) {
11743     if (str.match(/^[\d\.]+$/)) {
11744       return str + 'px';
11745     }
11746     else {
11747       return str;
11748     }
11749   };
11750 });
11751
11752 })();
11753
11754 (function () {
11755   angular.module('ui.grid').config(['$provide', function($provide) {
11756     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11757       var lang = {
11758               aggregate: {
11759                   label: 'položky'
11760               },
11761               groupPanel: {
11762                   description: 'Přesuňte záhlaví zde pro vytvoření skupiny dle sloupce.'
11763               },
11764               search: {
11765                   placeholder: 'Hledat...',
11766                   showingItems: 'Zobrazuji položky:',
11767                   selectedItems: 'Vybrané položky:',
11768                   totalItems: 'Celkem položek:',
11769                   size: 'Velikost strany:',
11770                   first: 'První strana',
11771                   next: 'Další strana',
11772                   previous: 'Předchozí strana',
11773                   last: 'Poslední strana'
11774               },
11775               menu: {
11776                   text: 'Vyberte sloupec:'
11777               },
11778               sort: {
11779                   ascending: 'Seřadit od A-Z',
11780                   descending: 'Seřadit od Z-A',
11781                   remove: 'Odebrat seřazení'
11782               },
11783               column: {
11784                   hide: 'Schovat sloupec'
11785               },
11786               aggregation: {
11787                   count: 'celkem řádků: ',
11788                   sum: 'celkem: ',
11789                   avg: 'avg: ',
11790                   min: 'min.: ',
11791                   max: 'max.: '
11792               },
11793               pinning: {
11794                   pinLeft: 'Zamknout vlevo',
11795                   pinRight: 'Zamknout vpravo',
11796                   unpin: 'Odemknout'
11797               },
11798               gridMenu: {
11799                   columns: 'Sloupce:',
11800                   importerTitle: 'Importovat soubor',
11801                   exporterAllAsCsv: 'Exportovat všechna data do csv',
11802                   exporterVisibleAsCsv: 'Exportovat viditelná data do csv',
11803                   exporterSelectedAsCsv: 'Exportovat vybraná data do csv',
11804                   exporterAllAsPdf: 'Exportovat všechna data do pdf',
11805                   exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
11806                   exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
11807                   clearAllFilters: 'Odstranit všechny filtry'
11808               },
11809               importer: {
11810                   noHeaders: 'Názvy sloupců se nepodařilo získat, obsahuje soubor záhlaví?',
11811                   noObjects: 'Data se nepodařilo zpracovat, obsahuje soubor řádky mimo záhlaví?',
11812                   invalidCsv: 'Soubor nelze zpracovat, jedná se o CSV?',
11813                   invalidJson: 'Soubor nelze zpracovat, je to JSON?',
11814                   jsonNotArray: 'Soubor musí obsahovat json. Ukončuji..'
11815               },
11816               pagination: {
11817                   sizes: 'položek na stránku',
11818                   totalItems: 'položek'
11819               },
11820               grouping: {
11821                   group: 'Seskupit',
11822                   ungroup: 'Odebrat seskupení',
11823                   aggregate_count: 'Agregace: Count',
11824                   aggregate_sum: 'Agregace: Sum',
11825                   aggregate_max: 'Agregace: Max',
11826                   aggregate_min: 'Agregace: Min',
11827                   aggregate_avg: 'Agregace: Avg',
11828                   aggregate_remove: 'Agregace: Odebrat'
11829               }
11830           };
11831
11832           // support varianty of different czech keys.
11833           $delegate.add('cs', lang);
11834           $delegate.add('cz', lang);
11835           $delegate.add('cs-cz', lang);
11836           $delegate.add('cs-CZ', lang);
11837       return $delegate;
11838     }]);
11839   }]);
11840 })();
11841
11842 (function(){
11843   angular.module('ui.grid').config(['$provide', function($provide) {
11844     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11845       $delegate.add('da', {
11846         aggregate:{
11847           label: 'artikler'
11848         },
11849         groupPanel:{
11850           description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
11851         },
11852         search:{
11853           placeholder: 'Søg...',
11854           showingItems: 'Viste rækker:',
11855           selectedItems: 'Valgte rækker:',
11856           totalItems: 'Rækker totalt:',
11857           size: 'Side størrelse:',
11858           first: 'Første side',
11859           next: 'Næste side',
11860           previous: 'Forrige side',
11861           last: 'Sidste side'
11862         },
11863         menu:{
11864           text: 'Vælg kolonner:'
11865         },
11866         sort: {
11867           ascending: 'Sorter stigende',
11868           descending: 'Sorter faldende',
11869           none: 'Sorter ingen',
11870           remove: 'Fjern sortering'
11871         },
11872         column: {
11873           hide: 'Skjul kolonne'
11874         },
11875         aggregation: {
11876           count: 'antal rækker: ',
11877           sum: 'sum: ',
11878           avg: 'gns: ',
11879           min: 'min: ',
11880           max: 'max: '
11881         },
11882         gridMenu: {
11883           columns: 'Columns:',
11884           importerTitle: 'Import file',
11885           exporterAllAsCsv: 'Export all data as csv',
11886           exporterVisibleAsCsv: 'Export visible data as csv',
11887           exporterSelectedAsCsv: 'Export selected data as csv',
11888           exporterAllAsPdf: 'Export all data as pdf',
11889           exporterVisibleAsPdf: 'Export visible data as pdf',
11890           exporterSelectedAsPdf: 'Export selected data as pdf',
11891           clearAllFilters: 'Clear all filters'
11892         },
11893         importer: {
11894           noHeaders: 'Column names were unable to be derived, does the file have a header?',
11895           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
11896           invalidCsv: 'File was unable to be processed, is it valid CSV?',
11897           invalidJson: 'File was unable to be processed, is it valid Json?',
11898           jsonNotArray: 'Imported json file must contain an array, aborting.'
11899         }
11900       });
11901       return $delegate;
11902     }]);
11903   }]);
11904 })();
11905
11906 (function () {
11907   angular.module('ui.grid').config(['$provide', function ($provide) {
11908     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
11909       $delegate.add('de', {
11910         headerCell: {
11911           aria: {
11912             defaultFilterLabel: 'Filter für Spalte',
11913             removeFilter: 'Filter löschen',
11914             columnMenuButtonLabel: 'Spaltenmenü'
11915           },
11916           priority: 'Priorität:',
11917           filterLabel: "Filter für Spalte: "
11918         },
11919         aggregate: {
11920           label: 'Eintrag'
11921         },
11922         groupPanel: {
11923           description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
11924         },
11925         search: {
11926           placeholder: 'Suche...',
11927           showingItems: 'Zeige Einträge:',
11928           selectedItems: 'Ausgewählte Einträge:',
11929           totalItems: 'Einträge gesamt:',
11930           size: 'Einträge pro Seite:',
11931           first: 'Erste Seite',
11932           next: 'Nächste Seite',
11933           previous: 'Vorherige Seite',
11934           last: 'Letzte Seite'
11935         },
11936         menu: {
11937           text: 'Spalten auswählen:'
11938         },
11939         sort: {
11940           ascending: 'aufsteigend sortieren',
11941           descending: 'absteigend sortieren',
11942           none: 'keine Sortierung',
11943           remove: 'Sortierung zurücksetzen'
11944         },
11945         column: {
11946           hide: 'Spalte ausblenden'
11947         },
11948         aggregation: {
11949           count: 'Zeilen insgesamt: ',
11950           sum: 'gesamt: ',
11951           avg: 'Durchschnitt: ',
11952           min: 'min: ',
11953           max: 'max: '
11954         },
11955         pinning: {
11956             pinLeft: 'Links anheften',
11957             pinRight: 'Rechts anheften',
11958             unpin: 'Lösen'
11959         },
11960         columnMenu: {
11961           close: 'Schließen'
11962         },
11963         gridMenu: {
11964           aria: {
11965             buttonLabel: 'Tabellenmenü'
11966           },
11967           columns: 'Spalten:',
11968           importerTitle: 'Datei importieren',
11969           exporterAllAsCsv: 'Alle Daten als CSV exportieren',
11970           exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
11971           exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
11972           exporterAllAsPdf: 'Alle Daten als PDF exportieren',
11973           exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
11974           exporterSelectedAsPdf: 'markierte Daten als CSV exportieren',
11975           clearAllFilters: 'Alle Filter zurücksetzen'
11976         },
11977         importer: {
11978           noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
11979           noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
11980           invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
11981           invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
11982           jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
11983         },
11984         pagination: {
11985           aria: {
11986             pageToFirst: 'Zum Anfang',
11987             pageBack: 'Seite zurück',
11988             pageSelected: 'Ausgwählte Seite',
11989             pageForward: 'Seite vor',
11990             pageToLast: 'Zum Ende'
11991           },
11992           sizes: 'Einträge pro Seite',
11993           totalItems: 'Einträge',
11994           through: 'bis',
11995           of: 'von'
11996         },
11997         grouping: {
11998             group: 'Gruppieren',
11999             ungroup: 'Gruppierung aufheben',
12000             aggregate_count: 'Agg: Anzahl',
12001             aggregate_sum: 'Agg: Summe',
12002             aggregate_max: 'Agg: Maximum',
12003             aggregate_min: 'Agg: Minimum',
12004             aggregate_avg: 'Agg: Mittelwert',
12005             aggregate_remove: 'Aggregation entfernen'
12006         }
12007       });
12008       return $delegate;
12009     }]);
12010   }]);
12011 })();
12012
12013 (function () {
12014   angular.module('ui.grid').config(['$provide', function($provide) {
12015     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12016       $delegate.add('en', {
12017         headerCell: {
12018           aria: {
12019             defaultFilterLabel: 'Filter for column',
12020             removeFilter: 'Remove Filter',
12021             columnMenuButtonLabel: 'Column Menu'
12022           },
12023           priority: 'Priority:',
12024           filterLabel: "Filter for column: "
12025         },
12026         aggregate: {
12027           label: 'items'
12028         },
12029         groupPanel: {
12030           description: 'Drag a column header here and drop it to group by that column.'
12031         },
12032         search: {
12033           placeholder: 'Search...',
12034           showingItems: 'Showing Items:',
12035           selectedItems: 'Selected Items:',
12036           totalItems: 'Total Items:',
12037           size: 'Page Size:',
12038           first: 'First Page',
12039           next: 'Next Page',
12040           previous: 'Previous Page',
12041           last: 'Last Page'
12042         },
12043         menu: {
12044           text: 'Choose Columns:'
12045         },
12046         sort: {
12047           ascending: 'Sort Ascending',
12048           descending: 'Sort Descending',
12049           none: 'Sort None',
12050           remove: 'Remove Sort'
12051         },
12052         column: {
12053           hide: 'Hide Column'
12054         },
12055         aggregation: {
12056           count: 'total rows: ',
12057           sum: 'total: ',
12058           avg: 'avg: ',
12059           min: 'min: ',
12060           max: 'max: '
12061         },
12062         pinning: {
12063           pinLeft: 'Pin Left',
12064           pinRight: 'Pin Right',
12065           unpin: 'Unpin'
12066         },
12067         columnMenu: {
12068           close: 'Close'
12069         },
12070         gridMenu: {
12071           aria: {
12072             buttonLabel: 'Grid Menu'
12073           },
12074           columns: 'Columns:',
12075           importerTitle: 'Import file',
12076           exporterAllAsCsv: 'Export all data as csv',
12077           exporterVisibleAsCsv: 'Export visible data as csv',
12078           exporterSelectedAsCsv: 'Export selected data as csv',
12079           exporterAllAsPdf: 'Export all data as pdf',
12080           exporterVisibleAsPdf: 'Export visible data as pdf',
12081           exporterSelectedAsPdf: 'Export selected data as pdf',
12082           clearAllFilters: 'Clear all filters'
12083         },
12084         importer: {
12085           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12086           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12087           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12088           invalidJson: 'File was unable to be processed, is it valid Json?',
12089           jsonNotArray: 'Imported json file must contain an array, aborting.'
12090         },
12091         pagination: {
12092           aria: {
12093             pageToFirst: 'Page to first',
12094             pageBack: 'Page back',
12095             pageSelected: 'Selected page',
12096             pageForward: 'Page forward',
12097             pageToLast: 'Page to last'
12098           },
12099           sizes: 'items per page',
12100           totalItems: 'items',
12101           through: 'through',
12102           of: 'of'
12103         },
12104         grouping: {
12105           group: 'Group',
12106           ungroup: 'Ungroup',
12107           aggregate_count: 'Agg: Count',
12108           aggregate_sum: 'Agg: Sum',
12109           aggregate_max: 'Agg: Max',
12110           aggregate_min: 'Agg: Min',
12111           aggregate_avg: 'Agg: Avg',
12112           aggregate_remove: 'Agg: Remove'
12113         },
12114         validate: {
12115           error: 'Error:',
12116           minLength: 'Value should be at least THRESHOLD characters long.',
12117           maxLength: 'Value should be at most THRESHOLD characters long.',
12118           required: 'A value is needed.'
12119         }
12120       });
12121       return $delegate;
12122     }]);
12123   }]);
12124 })();
12125
12126 (function () {
12127   angular.module('ui.grid').config(['$provide', function($provide) {
12128     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12129       $delegate.add('es', {
12130         aggregate: {
12131           label: 'Artículos'
12132         },
12133         groupPanel: {
12134           description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
12135         },
12136         search: {
12137           placeholder: 'Buscar...',
12138           showingItems: 'Artículos Mostrados:',
12139           selectedItems: 'Artículos Seleccionados:',
12140           totalItems: 'Artículos Totales:',
12141           size: 'Tamaño de Página:',
12142           first: 'Primera Página',
12143           next: 'Página Siguiente',
12144           previous: 'Página Anterior',
12145           last: 'Última Página'
12146         },
12147         menu: {
12148           text: 'Elegir columnas:'
12149         },
12150         sort: {
12151           ascending: 'Orden Ascendente',
12152           descending: 'Orden Descendente',
12153           remove: 'Sin Ordenar'
12154         },
12155         column: {
12156           hide: 'Ocultar la columna'
12157         },
12158         aggregation: {
12159           count: 'filas totales: ',
12160           sum: 'total: ',
12161           avg: 'media: ',
12162           min: 'min: ',
12163           max: 'max: '
12164         },
12165         pinning: {
12166           pinLeft: 'Fijar a la Izquierda',
12167           pinRight: 'Fijar a la Derecha',
12168           unpin: 'Quitar Fijación'
12169         },
12170         gridMenu: {
12171           columns: 'Columnas:',
12172           importerTitle: 'Importar archivo',
12173           exporterAllAsCsv: 'Exportar todo como csv',
12174           exporterVisibleAsCsv: 'Exportar vista como csv',
12175           exporterSelectedAsCsv: 'Exportar selección como csv',
12176           exporterAllAsPdf: 'Exportar todo como pdf',
12177           exporterVisibleAsPdf: 'Exportar vista como pdf',
12178           exporterSelectedAsPdf: 'Exportar selección como pdf',
12179           clearAllFilters: 'Limpiar todos los filtros'
12180         },
12181         importer: {
12182           noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
12183           noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
12184           invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
12185           invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
12186           jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
12187         },
12188         pagination: {
12189           sizes: 'registros por página',
12190           totalItems: 'registros',
12191           of: 'de'
12192         },
12193         grouping: {
12194           group: 'Agrupar',
12195           ungroup: 'Desagrupar',
12196           aggregate_count: 'Agr: Cont',
12197           aggregate_sum: 'Agr: Sum',
12198           aggregate_max: 'Agr: Máx',
12199           aggregate_min: 'Agr: Min',
12200           aggregate_avg: 'Agr: Prom',
12201           aggregate_remove: 'Agr: Quitar'
12202         }
12203       });
12204       return $delegate;
12205     }]);
12206 }]);
12207 })();
12208
12209 /**
12210  * Translated by: R. Salarmehr
12211  *                M. Hosseynzade
12212  *                Using Vajje.com online dictionary.
12213  */
12214 (function () {
12215   angular.module('ui.grid').config(['$provide', function ($provide) {
12216     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12217       $delegate.add('fa', {
12218         aggregate: {
12219           label: 'قلم'
12220         },
12221         groupPanel: {
12222           description: 'عنوان یک ستون را بگیر و به گروهی از آن ستون رها کن.'
12223         },
12224         search: {
12225           placeholder: 'جستجو...',
12226           showingItems: 'نمایش اقلام:',
12227           selectedItems: 'قلم\u200cهای انتخاب شده:',
12228           totalItems: 'مجموع اقلام:',
12229           size: 'اندازه\u200cی صفحه:',
12230           first: 'اولین صفحه',
12231           next: 'صفحه\u200cی\u200cبعدی',
12232           previous: 'صفحه\u200cی\u200c قبلی',
12233           last: 'آخرین صفحه'
12234         },
12235         menu: {
12236           text: 'ستون\u200cهای انتخابی:'
12237         },
12238         sort: {
12239           ascending: 'ترتیب صعودی',
12240           descending: 'ترتیب نزولی',
12241           remove: 'حذف مرتب کردن'
12242         },
12243         column: {
12244           hide: 'پنهان\u200cکردن ستون'
12245         },
12246         aggregation: {
12247           count: 'تعداد: ',
12248           sum: 'مجموع: ',
12249           avg: 'میانگین: ',
12250           min: 'کمترین: ',
12251           max: 'بیشترین: '
12252         },
12253         pinning: {
12254           pinLeft: 'پین کردن سمت چپ',
12255           pinRight: 'پین کردن سمت راست',
12256           unpin: 'حذف پین'
12257         },
12258         gridMenu: {
12259           columns: 'ستون\u200cها:',
12260           importerTitle: 'وارد کردن فایل',
12261           exporterAllAsCsv: 'خروجی تمام داده\u200cها در فایل csv',
12262           exporterVisibleAsCsv: 'خروجی داده\u200cهای قابل مشاهده در فایل csv',
12263           exporterSelectedAsCsv: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل csv',
12264           exporterAllAsPdf: 'خروجی تمام داده\u200cها در فایل pdf',
12265           exporterVisibleAsPdf: 'خروجی داده\u200cهای قابل مشاهده در فایل pdf',
12266           exporterSelectedAsPdf: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل pdf',
12267           clearAllFilters: 'پاک کردن تمام فیلتر'
12268         },
12269         importer: {
12270           noHeaders: 'نام ستون قابل استخراج نیست. آیا فایل عنوان دارد؟',
12271           noObjects: 'اشیا قابل استخراج نیستند. آیا به جز عنوان\u200cها در فایل داده وجود دارد؟',
12272           invalidCsv: 'فایل قابل پردازش نیست. آیا فرمت  csv  معتبر است؟',
12273           invalidJson: 'فایل قابل پردازش نیست. آیا فرمت json   معتبر است؟',
12274           jsonNotArray: 'فایل json وارد شده باید حاوی آرایه باشد. عملیات ساقط شد.'
12275         },
12276         pagination: {
12277           sizes: 'اقلام در هر صفحه',
12278           totalItems: 'اقلام',
12279           of: 'از'
12280         },
12281         grouping: {
12282           group: 'گروه\u200cبندی',
12283           ungroup: 'حذف گروه\u200cبندی',
12284           aggregate_count: 'Agg: تعداد',
12285           aggregate_sum: 'Agg: جمع',
12286           aggregate_max: 'Agg: بیشینه',
12287           aggregate_min: 'Agg: کمینه',
12288           aggregate_avg: 'Agg: میانگین',
12289           aggregate_remove: 'Agg: حذف'
12290         }
12291       });
12292       return $delegate;
12293     }]);
12294   }]);
12295 })();
12296
12297 (function () {
12298   angular.module('ui.grid').config(['$provide', function($provide) {
12299     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12300       $delegate.add('fi', {
12301         aggregate: {
12302           label: 'rivit'
12303         },
12304         groupPanel: {
12305           description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
12306         },
12307         search: {
12308           placeholder: 'Hae...',
12309           showingItems: 'Näytetään rivejä:',
12310           selectedItems: 'Valitut rivit:',
12311           totalItems: 'Rivejä yht.:',
12312           size: 'Näytä:',
12313           first: 'Ensimmäinen sivu',
12314           next: 'Seuraava sivu',
12315           previous: 'Edellinen sivu',
12316           last: 'Viimeinen sivu'
12317         },
12318         menu: {
12319           text: 'Valitse sarakkeet:'
12320         },
12321         sort: {
12322           ascending: 'Järjestä nouseva',
12323           descending: 'Järjestä laskeva',
12324           remove: 'Poista järjestys'
12325         },
12326         column: {
12327           hide: 'Piilota sarake'
12328         },
12329         aggregation: {
12330           count: 'Rivejä yht.: ',
12331           sum: 'Summa: ',
12332           avg: 'K.a.: ',
12333           min: 'Min: ',
12334           max: 'Max: '
12335         },
12336         pinning: {
12337          pinLeft: 'Lukitse vasemmalle',
12338           pinRight: 'Lukitse oikealle',
12339           unpin: 'Poista lukitus'
12340         },
12341         gridMenu: {
12342           columns: 'Sarakkeet:',
12343           importerTitle: 'Tuo tiedosto',
12344           exporterAllAsCsv: 'Vie tiedot csv-muodossa',
12345           exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
12346           exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
12347           exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
12348           exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
12349           exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12350           clearAllFilters: 'Puhdista kaikki suodattimet'
12351         },
12352         importer: {
12353           noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
12354           noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
12355           invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
12356           invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
12357           jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
12358         }
12359       });
12360       return $delegate;
12361     }]);
12362   }]);
12363 })();
12364
12365 (function () {
12366   angular.module('ui.grid').config(['$provide', function($provide) {
12367     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12368       $delegate.add('fr', {
12369         aggregate: {
12370           label: 'éléments'
12371         },
12372         groupPanel: {
12373           description: 'Faites glisser une en-tête de colonne ici pour créer un groupe de colonnes.'
12374         },
12375         search: {
12376           placeholder: 'Recherche...',
12377           showingItems: 'Affichage des éléments :',
12378           selectedItems: 'Éléments sélectionnés :',
12379           totalItems: 'Nombre total d\'éléments:',
12380           size: 'Taille de page:',
12381           first: 'Première page',
12382           next: 'Page Suivante',
12383           previous: 'Page précédente',
12384           last: 'Dernière page'
12385         },
12386         menu: {
12387           text: 'Choisir des colonnes :'
12388         },
12389         sort: {
12390           ascending: 'Trier par ordre croissant',
12391           descending: 'Trier par ordre décroissant',
12392           remove: 'Enlever le tri'
12393         },
12394         column: {
12395           hide: 'Cacher la colonne'
12396         },
12397         aggregation: {
12398           count: 'lignes totales: ',
12399           sum: 'total: ',
12400           avg: 'moy: ',
12401           min: 'min: ',
12402           max: 'max: '
12403         },
12404         pinning: {
12405           pinLeft: 'Épingler à gauche',
12406           pinRight: 'Épingler à droite',
12407           unpin: 'Détacher'
12408         },
12409         gridMenu: {
12410           columns: 'Colonnes:',
12411           importerTitle: 'Importer un fichier',
12412           exporterAllAsCsv: 'Exporter toutes les données en CSV',
12413           exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
12414           exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
12415           exporterAllAsPdf: 'Exporter toutes les données en PDF',
12416           exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
12417           exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
12418           clearAllFilters: 'Nettoyez tous les filtres'
12419         },
12420         importer: {
12421           noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il une en-tête ?',
12422           noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
12423           invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
12424           invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
12425           jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
12426         },
12427         pagination: {
12428           sizes: 'éléments par page',
12429           totalItems: 'éléments',
12430           of: 'sur'
12431         },
12432         grouping: {
12433           group: 'Grouper',
12434           ungroup: 'Dégrouper',
12435           aggregate_count: 'Agg: Compte',
12436           aggregate_sum: 'Agg: Somme',
12437           aggregate_max: 'Agg: Max',
12438           aggregate_min: 'Agg: Min',
12439           aggregate_avg: 'Agg: Moy',
12440           aggregate_remove: 'Agg: Retirer'
12441         }
12442       });
12443       return $delegate;
12444     }]);
12445   }]);
12446 })();
12447
12448 (function () {
12449   angular.module('ui.grid').config(['$provide', function ($provide) {
12450     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12451       $delegate.add('he', {
12452         aggregate: {
12453           label: 'items'
12454         },
12455         groupPanel: {
12456           description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
12457         },
12458         search: {
12459           placeholder: 'חפש...',
12460           showingItems: 'מציג:',
12461           selectedItems: 'סה"כ נבחרו:',
12462           totalItems: 'סה"כ רשומות:',
12463           size: 'תוצאות בדף:',
12464           first: 'דף ראשון',
12465           next: 'דף הבא',
12466           previous: 'דף קודם',
12467           last: 'דף אחרון'
12468         },
12469         menu: {
12470           text: 'בחר עמודות:'
12471         },
12472         sort: {
12473           ascending: 'סדר עולה',
12474           descending: 'סדר יורד',
12475           remove: 'בטל'
12476         },
12477         column: {
12478           hide: 'טור הסתר'
12479         },
12480         aggregation: {
12481           count: 'total rows: ',
12482           sum: 'total: ',
12483           avg: 'avg: ',
12484           min: 'min: ',
12485           max: 'max: '
12486         },
12487         gridMenu: {
12488           columns: 'Columns:',
12489           importerTitle: 'Import file',
12490           exporterAllAsCsv: 'Export all data as csv',
12491           exporterVisibleAsCsv: 'Export visible data as csv',
12492           exporterSelectedAsCsv: 'Export selected data as csv',
12493           exporterAllAsPdf: 'Export all data as pdf',
12494           exporterVisibleAsPdf: 'Export visible data as pdf',
12495           exporterSelectedAsPdf: 'Export selected data as pdf',
12496           clearAllFilters: 'Clean all filters'
12497         },
12498         importer: {
12499           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12500           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12501           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12502           invalidJson: 'File was unable to be processed, is it valid Json?',
12503           jsonNotArray: 'Imported json file must contain an array, aborting.'
12504         }
12505       });
12506       return $delegate;
12507     }]);
12508   }]);
12509 })();
12510
12511 (function () {
12512   angular.module('ui.grid').config(['$provide', function($provide) {
12513     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12514       $delegate.add('hy', {
12515         aggregate: {
12516           label: 'տվյալներ'
12517         },
12518         groupPanel: {
12519           description: 'Ըստ սյան խմբավորելու համար քաշեք և գցեք վերնագիրն այստեղ։'
12520         },
12521         search: {
12522           placeholder: 'Փնտրում...',
12523           showingItems: 'Ցուցադրված տվյալներ՝',
12524           selectedItems: 'Ընտրված:',
12525           totalItems: 'Ընդամենը՝',
12526           size: 'Տողերի քանակը էջում՝',
12527           first: 'Առաջին էջ',
12528           next: 'Հաջորդ էջ',
12529           previous: 'Նախորդ էջ',
12530           last: 'Վերջին էջ'
12531         },
12532         menu: {
12533           text: 'Ընտրել սյուները:'
12534         },
12535         sort: {
12536           ascending: 'Աճման կարգով',
12537           descending: 'Նվազման կարգով',
12538           remove: 'Հանել '
12539         },
12540         column: {
12541           hide: 'Թաքցնել սյունը'
12542         },
12543         aggregation: {
12544           count: 'ընդամենը տող՝ ',
12545           sum: 'ընդամենը՝ ',
12546           avg: 'միջին՝ ',
12547           min: 'մին՝ ',
12548           max: 'մաքս՝ '
12549         },
12550         pinning: {
12551           pinLeft: 'Կպցնել ձախ կողմում',
12552           pinRight: 'Կպցնել աջ կողմում',
12553           unpin: 'Արձակել'
12554         },
12555         gridMenu: {
12556           columns: 'Սյուներ:',
12557           importerTitle: 'Ներմուծել ֆայլ',
12558           exporterAllAsCsv: 'Արտահանել ամբողջը CSV',
12559           exporterVisibleAsCsv: 'Արտահանել երևացող տվյալները CSV',
12560           exporterSelectedAsCsv: 'Արտահանել ընտրված տվյալները CSV',
12561           exporterAllAsPdf: 'Արտահանել PDF',
12562           exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
12563           exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
12564           clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
12565         },
12566         importer: {
12567           noHeaders: 'Հնարավոր չեղավ որոշել սյան վերնագրերը։ Արդյո՞ք ֆայլը ունի վերնագրեր։',
12568           noObjects: 'Հնարավոր չեղավ կարդալ տվյալները։ Արդյո՞ք ֆայլում կան տվյալներ։',
12569           invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
12570           invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
12571           jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
12572         }
12573       });
12574       return $delegate;
12575     }]);
12576   }]);
12577 })();
12578
12579 (function () {
12580   angular.module('ui.grid').config(['$provide', function($provide) {
12581     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12582       $delegate.add('it', {
12583         aggregate: {
12584           label: 'elementi'
12585         },
12586         groupPanel: {
12587           description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
12588         },
12589         search: {
12590           placeholder: 'Ricerca...',
12591           showingItems: 'Mostra:',
12592           selectedItems: 'Selezionati:',
12593           totalItems: 'Totali:',
12594           size: 'Tot Pagine:',
12595           first: 'Prima',
12596           next: 'Prossima',
12597           previous: 'Precedente',
12598           last: 'Ultima'
12599         },
12600         menu: {
12601           text: 'Scegli le colonne:'
12602         },
12603         sort: {
12604           ascending: 'Asc.',
12605           descending: 'Desc.',
12606           remove: 'Annulla ordinamento'
12607         },
12608         column: {
12609           hide: 'Nascondi'
12610         },
12611         aggregation: {
12612           count: 'righe totali: ',
12613           sum: 'tot: ',
12614           avg: 'media: ',
12615           min: 'minimo: ',
12616           max: 'massimo: '
12617         },
12618         pinning: {
12619          pinLeft: 'Blocca a sx',
12620           pinRight: 'Blocca a dx',
12621           unpin: 'Blocca in alto'
12622         },
12623         gridMenu: {
12624           columns: 'Colonne:',
12625           importerTitle: 'Importa',
12626           exporterAllAsCsv: 'Esporta tutti i dati in CSV',
12627           exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
12628           exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
12629           exporterAllAsPdf: 'Esporta tutti i dati in PDF',
12630           exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
12631           exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
12632           clearAllFilters: 'Pulire tutti i filtri'
12633         },
12634         importer: {
12635           noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
12636           noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
12637           invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
12638           invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
12639           jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
12640         },
12641         grouping: {
12642           group: 'Raggruppa',
12643           ungroup: 'Separa',
12644           aggregate_count: 'Agg: N. Elem.',
12645           aggregate_sum: 'Agg: Somma',
12646           aggregate_max: 'Agg: Massimo',
12647           aggregate_min: 'Agg: Minimo',
12648           aggregate_avg: 'Agg: Media',
12649           aggregate_remove: 'Agg: Rimuovi'
12650         },
12651         validate: {
12652           error: 'Errore:',
12653           minLength: 'Lunghezza minima pari a THRESHOLD caratteri.',
12654           maxLength: 'Lunghezza massima pari a THRESHOLD caratteri.',
12655           required: 'Necessario inserire un valore.'
12656         }
12657       });
12658       return $delegate;
12659     }]);
12660   }]);
12661 })();
12662
12663 (function() {
12664   angular.module('ui.grid').config(['$provide', function($provide) {
12665     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12666       $delegate.add('ja', {
12667         aggregate: {
12668           label: '項目'
12669         },
12670         groupPanel: {
12671           description: 'ここに列ヘッダをドラッグアンドドロップして、その列でグループ化します。'
12672         },
12673         search: {
12674           placeholder: '検索...',
12675           showingItems: '表示中の項目:',
12676           selectedItems: '選択した項目:',
12677           totalItems: '項目の総数:',
12678           size: 'ページサイズ:',
12679           first: '最初のページ',
12680           next: '次のページ',
12681           previous: '前のページ',
12682           last: '前のページ'
12683         },
12684         menu: {
12685           text: '列の選択:'
12686         },
12687         sort: {
12688           ascending: '昇順に並べ替え',
12689           descending: '降順に並べ替え',
12690           remove: '並べ替えの解除'
12691         },
12692         column: {
12693           hide: '列の非表示'
12694         },
12695         aggregation: {
12696           count: '合計行数: ',
12697           sum: '合計: ',
12698           avg: '平均: ',
12699           min: '最小: ',
12700           max: '最大: '
12701         },
12702         pinning: {
12703           pinLeft: '左に固定',
12704           pinRight: '右に固定',
12705           unpin: '固定解除'
12706         },
12707         gridMenu: {
12708           columns: '列:',
12709           importerTitle: 'ファイルのインポート',
12710           exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
12711           exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
12712           exporterSelectedAsCsv: '選択したデータをCSV形式でエクスポート',
12713           exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
12714           exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
12715           exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12716           clearAllFilters: 'すべてのフィルタを清掃してください'
12717         },
12718         importer: {
12719           noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
12720           noObjects: 'オブジェクトを取得できません。ファイルにヘッダ以外のデータが含まれていることを確認してください。',
12721           invalidCsv: 'ファイルを処理できません。ファイルが有効なCSV形式であることを確認してください。',
12722           invalidJson: 'ファイルを処理できません。ファイルが有効なJSON形式であることを確認してください。',
12723           jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
12724         },
12725         pagination: {
12726           aria: {
12727             pageToFirst: '最初のページ',
12728             pageBack: '前のページ',
12729             pageSelected: '現在のページ',
12730             pageForward: '次のページ',
12731             pageToLast: '最後のページ'
12732           },
12733           sizes: '項目/ページ',
12734           totalItems: '項目',
12735           through: 'から',
12736           of: '項目/全'
12737         }
12738       });
12739       return $delegate;
12740     }]);
12741   }]);
12742 })();
12743
12744 (function () {
12745   angular.module('ui.grid').config(['$provide', function($provide) {
12746     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12747       $delegate.add('ko', {
12748         aggregate: {
12749           label: '아이템'
12750         },
12751         groupPanel: {
12752           description: '컬럼으로 그룹핑하기 위해서는 컬럼 헤더를 끌어 떨어뜨려 주세요.'
12753         },
12754         search: {
12755           placeholder: '검색...',
12756           showingItems: '항목 보여주기:',
12757           selectedItems: '선택 항목:',
12758           totalItems: '전체 항목:',
12759           size: '페이지 크기:',
12760           first: '첫번째 페이지',
12761           next: '다음 페이지',
12762           previous: '이전 페이지',
12763           last: '마지막 페이지'
12764         },
12765         menu: {
12766           text: '컬럼을 선택하세요:'
12767         },
12768         sort: {
12769           ascending: '오름차순 정렬',
12770           descending: '내림차순 정렬',
12771           remove: '소팅 제거'
12772         },
12773         column: {
12774           hide: '컬럼 제거'
12775         },
12776         aggregation: {
12777           count: '전체 갯수: ',
12778           sum: '전체: ',
12779           avg: '평균: ',
12780           min: '최소: ',
12781           max: '최대: '
12782         },
12783         pinning: {
12784          pinLeft: '왼쪽 핀',
12785           pinRight: '오른쪽 핀',
12786           unpin: '핀 제거'
12787         },
12788         gridMenu: {
12789           columns: '컬럼:',
12790           importerTitle: '파일 가져오기',
12791           exporterAllAsCsv: 'csv로 모든 데이터 내보내기',
12792           exporterVisibleAsCsv: 'csv로 보이는 데이터 내보내기',
12793           exporterSelectedAsCsv: 'csv로 선택된 데이터 내보내기',
12794           exporterAllAsPdf: 'pdf로 모든 데이터 내보내기',
12795           exporterVisibleAsPdf: 'pdf로 보이는 데이터 내보내기',
12796           exporterSelectedAsPdf: 'pdf로 선택 데이터 내보내기',
12797           clearAllFilters: '모든 필터를 청소'
12798         },
12799         importer: {
12800           noHeaders: '컬럼명이 지정되어 있지 않습니다. 파일에 헤더가 명시되어 있는지 확인해 주세요.',
12801           noObjects: '데이터가 지정되어 있지 않습니다. 데이터가 파일에 있는지 확인해 주세요.',
12802           invalidCsv: '파일을 처리할 수 없습니다. 올바른 csv인지 확인해 주세요.',
12803           invalidJson: '파일을 처리할 수 없습니다. 올바른 json인지 확인해 주세요.',
12804           jsonNotArray: 'json 파일은 배열을 포함해야 합니다.'
12805         },
12806         pagination: {
12807           sizes: '페이지당 항목',
12808           totalItems: '전체 항목'
12809         }
12810       });
12811       return $delegate;
12812     }]);
12813   }]);
12814 })();
12815
12816 (function () {
12817   angular.module('ui.grid').config(['$provide', function($provide) {
12818     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12819       $delegate.add('nl', {
12820         aggregate: {
12821           label: 'items'
12822         },
12823         groupPanel: {
12824           description: 'Sleep hier een kolomnaam heen om op te groeperen.'
12825         },
12826         search: {
12827           placeholder: 'Zoeken...',
12828           showingItems: 'Getoonde items:',
12829           selectedItems: 'Geselecteerde items:',
12830           totalItems: 'Totaal aantal items:',
12831           size: 'Items per pagina:',
12832           first: 'Eerste pagina',
12833           next: 'Volgende pagina',
12834           previous: 'Vorige pagina',
12835           last: 'Laatste pagina'
12836         },
12837         menu: {
12838           text: 'Kies kolommen:'
12839         },
12840         sort: {
12841           ascending: 'Sorteer oplopend',
12842           descending: 'Sorteer aflopend',
12843           remove: 'Verwijder sortering'
12844         },
12845         column: {
12846           hide: 'Verberg kolom'
12847         },
12848         aggregation: {
12849           count: 'Aantal rijen: ',
12850           sum: 'Som: ',
12851           avg: 'Gemiddelde: ',
12852           min: 'Min: ',
12853           max: 'Max: '
12854         },
12855         pinning: {
12856           pinLeft: 'Zet links vast',
12857           pinRight: 'Zet rechts vast',
12858           unpin: 'Maak los'
12859         },
12860         gridMenu: {
12861           columns: 'Kolommen:',
12862           importerTitle: 'Importeer bestand',
12863           exporterAllAsCsv: 'Exporteer alle data als csv',
12864           exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
12865           exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
12866           exporterAllAsPdf: 'Exporteer alle data als pdf',
12867           exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
12868           exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
12869           clearAllFilters: 'Reinig alle filters'
12870         },
12871         importer: {
12872           noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
12873           noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
12874           invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
12875           invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
12876           jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
12877         },
12878         pagination: {
12879             sizes: 'items per pagina',
12880             totalItems: 'items',
12881             of: 'van de'
12882         },
12883         grouping: {
12884             group: 'Groepeer',
12885             ungroup: 'Groepering opheffen',
12886             aggregate_count: 'Agg: Aantal',
12887             aggregate_sum: 'Agg: Som',
12888             aggregate_max: 'Agg: Max',
12889             aggregate_min: 'Agg: Min',
12890             aggregate_avg: 'Agg: Gem',
12891             aggregate_remove: 'Agg: Verwijder'
12892         }
12893       });
12894       return $delegate;
12895     }]);
12896   }]);
12897 })();
12898
12899 (function () {
12900   angular.module('ui.grid').config(['$provide', function($provide) {
12901     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12902       $delegate.add('pl', {
12903         headerCell: {
12904           aria: {
12905             defaultFilterLabel: 'Filter dla kolumny',
12906             removeFilter: 'Usuń filter',
12907             columnMenuButtonLabel: 'Menu kolumny'
12908           },
12909           priority: 'Prioritet:',
12910           filterLabel: "Filtr dla kolumny: "
12911         },
12912         aggregate: {
12913           label: 'pozycji'
12914         },
12915         groupPanel: {
12916           description: 'Przeciągnij nagłówek kolumny tutaj, aby pogrupować według niej.'
12917         },
12918         search: {
12919           placeholder: 'Szukaj...',
12920           showingItems: 'Widoczne pozycje:',
12921           selectedItems: 'Zaznaczone pozycje:',
12922           totalItems: 'Wszystkich pozycji:',
12923           size: 'Rozmiar strony:',
12924           first: 'Pierwsza strona',
12925           next: 'Następna strona',
12926           previous: 'Poprzednia strona',
12927           last: 'Ostatnia strona'
12928         },
12929         menu: {
12930           text: 'Wybierz kolumny:'
12931         },
12932         sort: {
12933           ascending: 'Sortuj rosnąco',
12934           descending: 'Sortuj malejąco',
12935           none: 'Brak sortowania',
12936           remove: 'Wyłącz sortowanie'
12937         },
12938         column: {
12939           hide: 'Ukryj kolumne'
12940         },
12941         aggregation: {
12942           count: 'Razem pozycji: ',
12943             sum: 'Razem: ',
12944             avg: 'Średnia: ',
12945             min: 'Min: ',
12946             max: 'Max: '
12947         },
12948         pinning: {
12949           pinLeft: 'Przypnij do lewej',
12950           pinRight: 'Przypnij do prawej',
12951           unpin: 'Odepnij'
12952         },
12953         columnMenu: {
12954           close: 'Zamknij'
12955         },
12956         gridMenu: {
12957           aria: {
12958             buttonLabel: 'Menu Grida'
12959           },
12960           columns: 'Kolumny:',
12961           importerTitle: 'Importuj plik',
12962           exporterAllAsCsv: 'Eksportuj wszystkie dane do csv',
12963           exporterVisibleAsCsv: 'Eksportuj widoczne dane do csv',
12964           exporterSelectedAsCsv: 'Eksportuj zaznaczone dane do csv',
12965           exporterAllAsPdf: 'Eksportuj wszystkie dane do pdf',
12966           exporterVisibleAsPdf: 'Eksportuj widoczne dane do pdf',
12967           exporterSelectedAsPdf: 'Eksportuj zaznaczone dane do pdf',
12968           clearAllFilters: 'Wyczyść filtry'
12969         },
12970         importer: {
12971           noHeaders: 'Nie udało się wczytać nazw kolumn. Czy plik posiada nagłówek?',
12972           noObjects: 'Nie udalo się wczytać pozycji. Czy plik zawiera dane??',
12973           invalidCsv: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik CSV??',
12974           invalidJson: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik Json?',
12975           jsonNotArray: 'Importowany plik json musi zawierać tablicę, importowanie przerwane.'
12976         },
12977         pagination: {
12978           aria: {
12979             pageToFirst: 'Pierwsza strona',
12980             pageBack: 'Poprzednia strona',
12981             pageSelected: 'Wybrana strona',
12982             pageForward: 'Następna strona',
12983             pageToLast: 'Ostatnia strona'
12984           },
12985           sizes: 'pozycji na stronę',
12986           totalItems: 'pozycji',
12987           through: 'do',
12988           of: 'z'
12989         },
12990         grouping: {
12991           group: 'Grupuj',
12992           ungroup: 'Rozgrupuj',
12993           aggregate_count: 'Zbiorczo: Razem',
12994           aggregate_sum: 'Zbiorczo: Suma',
12995           aggregate_max: 'Zbiorczo: Max',
12996           aggregate_min: 'Zbiorczo: Min',
12997           aggregate_avg: 'Zbiorczo: Średnia',
12998           aggregate_remove: 'Zbiorczo: Usuń'
12999         }
13000       });
13001       return $delegate;
13002     }]);
13003   }]);
13004 })();
13005
13006 (function () {
13007   angular.module('ui.grid').config(['$provide', function($provide) {
13008     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13009       $delegate.add('pt-br', {
13010         headerCell: {
13011           aria: {
13012             defaultFilterLabel: 'Filtro por coluna',
13013             removeFilter: 'Remover filtro',
13014             columnMenuButtonLabel: 'Menu coluna'
13015           },
13016           priority: 'Prioridade:',
13017           filterLabel: "Filtro por coluna: "
13018         },
13019         aggregate: {
13020           label: 'itens'
13021         },
13022         groupPanel: {
13023           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13024         },
13025         search: {
13026           placeholder: 'Procurar...',
13027           showingItems: 'Mostrando os Itens:',
13028           selectedItems: 'Items Selecionados:',
13029           totalItems: 'Total de Itens:',
13030           size: 'Tamanho da Página:',
13031           first: 'Primeira Página',
13032           next: 'Próxima Página',
13033           previous: 'Página Anterior',
13034           last: 'Última Página'
13035         },
13036         menu: {
13037           text: 'Selecione as colunas:'
13038         },
13039         sort: {
13040           ascending: 'Ordenar Ascendente',
13041           descending: 'Ordenar Descendente',
13042           none: 'Nenhuma Ordem',
13043           remove: 'Remover Ordenação'
13044         },
13045         column: {
13046           hide: 'Esconder coluna'
13047         },
13048         aggregation: {
13049           count: 'total de linhas: ',
13050           sum: 'total: ',
13051           avg: 'med: ',
13052           min: 'min: ',
13053           max: 'max: '
13054         },
13055         pinning: {
13056           pinLeft: 'Fixar Esquerda',
13057           pinRight: 'Fixar Direita',
13058           unpin: 'Desprender'
13059         },
13060         columnMenu: {
13061           close: 'Fechar'
13062         },
13063         gridMenu: {
13064           aria: {
13065             buttonLabel: 'Menu Grid'
13066           },
13067           columns: 'Colunas:',
13068           importerTitle: 'Importar arquivo',
13069           exporterAllAsCsv: 'Exportar todos os dados como csv',
13070           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13071           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13072           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13073           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13074           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13075           clearAllFilters: 'Limpar todos os filtros'
13076         },
13077         importer: {
13078           noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
13079           noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
13080           invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
13081           invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
13082           jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
13083         },
13084         pagination: {
13085           aria: {
13086             pageToFirst: 'Primeira página',
13087             pageBack: 'Página anterior',
13088             pageSelected: 'Página Selecionada',
13089             pageForward: 'Proxima',
13090             pageToLast: 'Anterior'
13091           },
13092           sizes: 'itens por página',
13093           totalItems: 'itens',
13094           through: 'através dos',
13095           of: 'de'
13096         },
13097         grouping: {
13098           group: 'Agrupar',
13099           ungroup: 'Desagrupar',
13100           aggregate_count: 'Agr: Contar',
13101           aggregate_sum: 'Agr: Soma',
13102           aggregate_max: 'Agr: Max',
13103           aggregate_min: 'Agr: Min',
13104           aggregate_avg: 'Agr: Med',
13105           aggregate_remove: 'Agr: Remover'
13106         }
13107       });
13108       return $delegate;
13109     }]);
13110 }]);
13111 })();
13112
13113 (function () {
13114   angular.module('ui.grid').config(['$provide', function($provide) {
13115     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13116       $delegate.add('pt', {
13117         headerCell: {
13118           aria: {
13119             defaultFilterLabel: 'Filtro por coluna',
13120             removeFilter: 'Remover filtro',
13121             columnMenuButtonLabel: 'Menu coluna'
13122           },
13123           priority: 'Prioridade:',
13124           filterLabel: "Filtro por coluna: "
13125         },
13126         aggregate: {
13127           label: 'itens'
13128         },
13129         groupPanel: {
13130           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13131         },
13132         search: {
13133           placeholder: 'Procurar...',
13134           showingItems: 'Mostrando os Itens:',
13135           selectedItems: 'Itens Selecionados:',
13136           totalItems: 'Total de Itens:',
13137           size: 'Tamanho da Página:',
13138           first: 'Primeira Página',
13139           next: 'Próxima Página',
13140           previous: 'Página Anterior',
13141           last: 'Última Página'
13142         },
13143         menu: {
13144           text: 'Selecione as colunas:'
13145         },
13146         sort: {
13147           ascending: 'Ordenar Ascendente',
13148           descending: 'Ordenar Descendente',
13149           none: 'Nenhuma Ordem',
13150           remove: 'Remover Ordenação'
13151         },
13152         column: {
13153           hide: 'Esconder coluna'
13154         },
13155         aggregation: {
13156           count: 'total de linhas: ',
13157           sum: 'total: ',
13158           avg: 'med: ',
13159           min: 'min: ',
13160           max: 'max: '
13161         },
13162         pinning: {
13163           pinLeft: 'Fixar Esquerda',
13164           pinRight: 'Fixar Direita',
13165           unpin: 'Desprender'
13166         },
13167         columnMenu: {
13168           close: 'Fechar'
13169         },
13170         gridMenu: {
13171           aria: {
13172             buttonLabel: 'Menu Grid'
13173           },
13174           columns: 'Colunas:',
13175           importerTitle: 'Importar ficheiro',
13176           exporterAllAsCsv: 'Exportar todos os dados como csv',
13177           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13178           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13179           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13180           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13181           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13182           clearAllFilters: 'Limpar todos os filtros'
13183         },
13184         importer: {
13185           noHeaders: 'Nomes de colunas não puderam ser derivados. O ficheiro tem um cabeçalho?',
13186           noObjects: 'Objetos não puderam ser derivados. Havia dados no ficheiro, além dos cabeçalhos?',
13187           invalidCsv: 'Ficheiro não pode ser processado. É um CSV válido?',
13188           invalidJson: 'Ficheiro não pode ser processado. É um Json válido?',
13189           jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
13190         },
13191         pagination: {
13192           aria: {
13193             pageToFirst: 'Primeira página',
13194             pageBack: 'Página anterior',
13195             pageSelected: 'Página Selecionada',
13196             pageForward: 'Próxima',
13197             pageToLast: 'Anterior'
13198           },
13199           sizes: 'itens por página',
13200           totalItems: 'itens',
13201           through: 'através dos',
13202           of: 'de'
13203         },
13204         grouping: {
13205           group: 'Agrupar',
13206           ungroup: 'Desagrupar',
13207           aggregate_count: 'Agr: Contar',
13208           aggregate_sum: 'Agr: Soma',
13209           aggregate_max: 'Agr: Max',
13210           aggregate_min: 'Agr: Min',
13211           aggregate_avg: 'Agr: Med',
13212           aggregate_remove: 'Agr: Remover'
13213         }
13214       });
13215       return $delegate;
13216     }]);
13217 }]);
13218 })();
13219
13220 (function () {
13221   angular.module('ui.grid').config(['$provide', function($provide) {
13222     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13223       $delegate.add('ro', {
13224         headerCell: {
13225           aria: {
13226             defaultFilterLabel: 'Filtru pentru coloana',
13227             removeFilter: 'Sterge filtru',
13228             columnMenuButtonLabel: 'Column Menu'
13229           },
13230           priority: 'Prioritate:',
13231           filterLabel: "Filtru pentru coloana:"
13232         },
13233         aggregate: {
13234           label: 'Elemente'
13235         },
13236         groupPanel: {
13237           description: 'Trage un cap de coloana aici pentru a grupa elementele dupa coloana respectiva'
13238         },
13239         search: {
13240           placeholder: 'Cauta...',
13241           showingItems: 'Arata elementele:',
13242           selectedItems: 'Elementele selectate:',
13243           totalItems: 'Total elemente:',
13244           size: 'Marime pagina:',
13245           first: 'Prima pagina',
13246           next: 'Pagina urmatoare',
13247           previous: 'Pagina anterioara',
13248           last: 'Ultima pagina'
13249         },
13250         menu: {
13251           text: 'Alege coloane:'
13252         },
13253         sort: {
13254           ascending: 'Ordoneaza crescator',
13255           descending: 'Ordoneaza descrescator',
13256           none: 'Fara ordonare',
13257           remove: 'Sterge ordonarea'
13258         },
13259         column: {
13260           hide: 'Ascunde coloana'
13261         },
13262         aggregation: {
13263           count: 'total linii: ',
13264           sum: 'total: ',
13265           avg: 'medie: ',
13266           min: 'min: ',
13267           max: 'max: '
13268         },
13269         pinning: {
13270           pinLeft: 'Pin la stanga',
13271           pinRight: 'Pin la dreapta',
13272           unpin: 'Sterge pinul'
13273         },
13274         columnMenu: {
13275           close: 'Inchide'
13276         },
13277         gridMenu: {
13278           aria: {
13279             buttonLabel: 'Grid Menu'
13280           },
13281           columns: 'Coloane:',
13282           importerTitle: 'Incarca fisier',
13283           exporterAllAsCsv: 'Exporta toate datele ca csv',
13284           exporterVisibleAsCsv: 'Exporta datele vizibile ca csv',
13285           exporterSelectedAsCsv: 'Exporta datele selectate ca csv',
13286           exporterAllAsPdf: 'Exporta toate datele ca pdf',
13287           exporterVisibleAsPdf: 'Exporta datele vizibile ca pdf',
13288           exporterSelectedAsPdf: 'Exporta datele selectate ca csv pdf',
13289           clearAllFilters: 'Sterge toate filtrele'
13290         },
13291         importer: {
13292           noHeaders: 'Numele coloanelor nu a putut fi incarcat, acest fisier are un header?',
13293           noObjects: 'Datele nu au putut fi incarcate, exista date in fisier in afara numelor de coloane?',
13294           invalidCsv: 'Fisierul nu a putut fi procesat, ati incarcat un CSV valid ?',
13295           invalidJson: 'Fisierul nu a putut fi procesat, ati incarcat un Json valid?',
13296           jsonNotArray: 'Json-ul incarcat trebuie sa contina un array, inchidere.'
13297         },
13298         pagination: {
13299           aria: {
13300             pageToFirst: 'Prima pagina',
13301             pageBack: 'O pagina inapoi',
13302             pageSelected: 'Pagina selectata',
13303             pageForward: 'O pagina inainte',
13304             pageToLast: 'Ultima pagina'
13305           },
13306           sizes: 'Elemente per pagina',
13307           totalItems: 'elemente',
13308           through: 'prin',
13309           of: 'of'
13310         },
13311         grouping: {
13312           group: 'Grupeaza',
13313           ungroup: 'Opreste gruparea',
13314           aggregate_count: 'Agg: Count',
13315           aggregate_sum: 'Agg: Sum',
13316           aggregate_max: 'Agg: Max',
13317           aggregate_min: 'Agg: Min',
13318           aggregate_avg: 'Agg: Avg',
13319           aggregate_remove: 'Agg: Remove'
13320         }
13321       });
13322       return $delegate;
13323     }]);
13324   }]);
13325 })();
13326
13327 (function () {
13328   angular.module('ui.grid').config(['$provide', function($provide) {
13329     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13330       $delegate.add('ru', {
13331         headerCell: {
13332           aria: {
13333             defaultFilterLabel: 'Фильтр столбца',
13334             removeFilter: 'Удалить фильтр',
13335             columnMenuButtonLabel: 'Меню столбца'
13336           },
13337           priority: 'Приоритет:',
13338           filterLabel: "Фильтр столбца: "
13339         },
13340         aggregate: {
13341           label: 'элементы'
13342         },
13343         groupPanel: {
13344           description: 'Для группировки по столбцу перетащите сюда его название.'
13345         },
13346         search: {
13347           placeholder: 'Поиск...',
13348           showingItems: 'Показать элементы:',
13349           selectedItems: 'Выбранные элементы:',
13350           totalItems: 'Всего элементов:',
13351           size: 'Размер страницы:',
13352           first: 'Первая страница',
13353           next: 'Следующая страница',
13354           previous: 'Предыдущая страница',
13355           last: 'Последняя страница'
13356         },
13357         menu: {
13358           text: 'Выбрать столбцы:'
13359         },
13360         sort: {
13361           ascending: 'По возрастанию',
13362           descending: 'По убыванию',
13363           none: 'Без сортировки',
13364           remove: 'Убрать сортировку'
13365         },
13366         column: {
13367           hide: 'Спрятать столбец'
13368         },
13369         aggregation: {
13370           count: 'всего строк: ',
13371           sum: 'итого: ',
13372           avg: 'среднее: ',
13373           min: 'мин: ',
13374           max: 'макс: '
13375         },
13376                                 pinning: {
13377                                         pinLeft: 'Закрепить слева',
13378                                         pinRight: 'Закрепить справа',
13379                                         unpin: 'Открепить'
13380                                 },
13381         columnMenu: {
13382           close: 'Закрыть'
13383         },
13384         gridMenu: {
13385           aria: {
13386             buttonLabel: 'Меню'
13387           },
13388           columns: 'Столбцы:',
13389           importerTitle: 'Импортировать файл',
13390           exporterAllAsCsv: 'Экспортировать всё в CSV',
13391           exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
13392           exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
13393           exporterAllAsPdf: 'Экспортировать всё в PDF',
13394           exporterVisibleAsPdf: 'Экспортировать видимые данные в PDF',
13395           exporterSelectedAsPdf: 'Экспортировать выбранные данные в PDF',
13396           clearAllFilters: 'Очистите все фильтры'
13397         },
13398         importer: {
13399           noHeaders: 'Не удалось получить названия столбцов, есть ли в файле заголовок?',
13400           noObjects: 'Не удалось получить данные, есть ли в файле строки кроме заголовка?',
13401           invalidCsv: 'Не удалось обработать файл, это правильный CSV-файл?',
13402           invalidJson: 'Не удалось обработать файл, это правильный JSON?',
13403           jsonNotArray: 'Импортируемый JSON-файл должен содержать массив, операция отменена.'
13404         },
13405         pagination: {
13406           aria: {
13407             pageToFirst: 'Первая страница',
13408             pageBack: 'Предыдущая страница',
13409             pageSelected: 'Выбранная страница',
13410             pageForward: 'Следующая страница',
13411             pageToLast: 'Последняя страница'
13412           },
13413           sizes: 'строк на страницу',
13414           totalItems: 'строк',
13415           through: 'по',
13416           of: 'из'
13417         },
13418         grouping: {
13419           group: 'Группировать',
13420           ungroup: 'Разгруппировать',
13421           aggregate_count: 'Группировать: Count',
13422           aggregate_sum: 'Для группы: Сумма',
13423           aggregate_max: 'Для группы: Максимум',
13424           aggregate_min: 'Для группы: Минимум',
13425           aggregate_avg: 'Для группы: Среднее',
13426           aggregate_remove: 'Для группы: Пусто'
13427         }
13428       });
13429       return $delegate;
13430     }]);
13431   }]);
13432 })();
13433
13434 (function () {
13435   angular.module('ui.grid').config(['$provide', function($provide) {
13436     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13437       $delegate.add('sk', {
13438         aggregate: {
13439           label: 'items'
13440         },
13441         groupPanel: {
13442           description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13443         },
13444         search: {
13445           placeholder: 'Hľadaj...',
13446           showingItems: 'Zobrazujem položky:',
13447           selectedItems: 'Vybraté položky:',
13448           totalItems: 'Počet položiek:',
13449           size: 'Počet:',
13450           first: 'Prvá strana',
13451           next: 'Ďalšia strana',
13452           previous: 'Predchádzajúca strana',
13453           last: 'Posledná strana'
13454         },
13455         menu: {
13456           text: 'Vyberte stĺpce:'
13457         },
13458         sort: {
13459           ascending: 'Zotriediť vzostupne',
13460           descending: 'Zotriediť zostupne',
13461           remove: 'Vymazať triedenie'
13462         },
13463         aggregation: {
13464           count: 'total rows: ',
13465           sum: 'total: ',
13466           avg: 'avg: ',
13467           min: 'min: ',
13468           max: 'max: '
13469         },
13470         gridMenu: {
13471           columns: 'Columns:',
13472           importerTitle: 'Import file',
13473           exporterAllAsCsv: 'Export all data as csv',
13474           exporterVisibleAsCsv: 'Export visible data as csv',
13475           exporterSelectedAsCsv: 'Export selected data as csv',
13476           exporterAllAsPdf: 'Export all data as pdf',
13477           exporterVisibleAsPdf: 'Export visible data as pdf',
13478           exporterSelectedAsPdf: 'Export selected data as pdf',
13479           clearAllFilters: 'Clear all filters'
13480         },
13481         importer: {
13482           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13483           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13484           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13485           invalidJson: 'File was unable to be processed, is it valid Json?',
13486           jsonNotArray: 'Imported json file must contain an array, aborting.'
13487         }
13488       });
13489       return $delegate;
13490     }]);
13491   }]);
13492 })();
13493
13494 (function () {
13495   angular.module('ui.grid').config(['$provide', function($provide) {
13496     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13497       $delegate.add('sv', {
13498         aggregate: {
13499           label: 'Artiklar'
13500         },
13501         groupPanel: {
13502           description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
13503         },
13504         search: {
13505           placeholder: 'Sök...',
13506           showingItems: 'Visar artiklar:',
13507           selectedItems: 'Valda artiklar:',
13508           totalItems: 'Antal artiklar:',
13509           size: 'Sidstorlek:',
13510           first: 'Första sidan',
13511           next: 'Nästa sida',
13512           previous: 'Föregående sida',
13513           last: 'Sista sidan'
13514         },
13515         menu: {
13516           text: 'Välj kolumner:'
13517         },
13518         sort: {
13519           ascending: 'Sortera stigande',
13520           descending: 'Sortera fallande',
13521           remove: 'Inaktivera sortering'
13522         },
13523         column: {
13524           hide: 'Göm kolumn'
13525         },
13526         aggregation: {
13527           count: 'Antal rader: ',
13528           sum: 'Summa: ',
13529           avg: 'Genomsnitt: ',
13530           min: 'Min: ',
13531           max: 'Max: '
13532         },
13533         pinning: {
13534           pinLeft: 'Fäst vänster',
13535           pinRight: 'Fäst höger',
13536           unpin: 'Lösgör'
13537         },
13538         gridMenu: {
13539           columns: 'Kolumner:',
13540           importerTitle: 'Importera fil',
13541           exporterAllAsCsv: 'Exportera all data som CSV',
13542           exporterVisibleAsCsv: 'Exportera synlig data som CSV',
13543           exporterSelectedAsCsv: 'Exportera markerad data som CSV',
13544           exporterAllAsPdf: 'Exportera all data som PDF',
13545           exporterVisibleAsPdf: 'Exportera synlig data som PDF',
13546           exporterSelectedAsPdf: 'Exportera markerad data som PDF',
13547           clearAllFilters: 'Rengör alla filter'
13548         },
13549         importer: {
13550           noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
13551           noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
13552           invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
13553           invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
13554           jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
13555         },
13556         pagination: {
13557           sizes: 'Artiklar per sida',
13558           totalItems: 'Artiklar'
13559         }
13560       });
13561       return $delegate;
13562     }]);
13563   }]);
13564 })();
13565
13566 (function () {
13567   angular.module('ui.grid').config(['$provide', function($provide) {
13568     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13569       $delegate.add('ta', {
13570         aggregate: {
13571           label: 'உருப்படிகள்'
13572         },
13573         groupPanel: {
13574           description: 'ஒரு பத்தியை குழுவாக அமைக்க அப்பத்தியின் தலைப்பை இங்கே  இழுத்து வரவும் '
13575         },
13576         search: {
13577           placeholder: 'தேடல் ...',
13578           showingItems: 'உருப்படிகளை காண்பித்தல்:',
13579           selectedItems: 'தேர்ந்தெடுக்கப்பட்ட  உருப்படிகள்:',
13580           totalItems: 'மொத்த உருப்படிகள்:',
13581           size: 'பக்க அளவு: ',
13582           first: 'முதல் பக்கம்',
13583           next: 'அடுத்த பக்கம்',
13584           previous: 'முந்தைய பக்கம் ',
13585           last: 'இறுதி பக்கம்'
13586         },
13587         menu: {
13588           text: 'பத்திகளை தேர்ந்தெடு:'
13589         },
13590         sort: {
13591           ascending: 'மேலிருந்து கீழாக',
13592           descending: 'கீழிருந்து மேலாக',
13593           remove: 'வரிசையை நீக்கு'
13594         },
13595         column: {
13596           hide: 'பத்தியை மறைத்து வை '
13597         },
13598         aggregation: {
13599           count: 'மொத்த வரிகள்:',
13600           sum: 'மொத்தம்: ',
13601           avg: 'சராசரி: ',
13602           min: 'குறைந்தபட்ச: ',
13603           max: 'அதிகபட்ச: '
13604         },
13605         pinning: {
13606          pinLeft: 'இடதுபுறமாக தைக்க ',
13607           pinRight: 'வலதுபுறமாக தைக்க',
13608           unpin: 'பிரி'
13609         },
13610         gridMenu: {
13611           columns: 'பத்திகள்:',
13612           importerTitle: 'கோப்பு : படித்தல்',
13613           exporterAllAsCsv: 'எல்லா தரவுகளையும் கோப்பாக்கு: csv',
13614           exporterVisibleAsCsv: 'இருக்கும் தரவுகளை கோப்பாக்கு: csv',
13615           exporterSelectedAsCsv: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: csv',
13616           exporterAllAsPdf: 'எல்லா தரவுகளையும் கோப்பாக்கு: pdf',
13617           exporterVisibleAsPdf: 'இருக்கும் தரவுகளை கோப்பாக்கு: pdf',
13618           exporterSelectedAsPdf: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: pdf',
13619           clearAllFilters: 'Clear all filters'
13620         },
13621         importer: {
13622           noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
13623           noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
13624           invalidCsv:   'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
13625           invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
13626           jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
13627         },
13628         pagination: {
13629           sizes         : 'உருப்படிகள் / பக்கம்',
13630           totalItems    : 'உருப்படிகள் '
13631         },
13632         grouping: {
13633           group : 'குழு',
13634           ungroup : 'பிரி',
13635           aggregate_count       : 'மதிப்பீட்டு : எண்ணு',
13636           aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
13637           aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
13638           aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
13639           aggregate_avg : 'மதிப்பீட்டு : சராசரி',
13640           aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
13641         }
13642       });
13643       return $delegate;
13644     }]);
13645   }]);
13646 })();
13647
13648 (function () {
13649   angular.module('ui.grid').config(['$provide', function($provide) {
13650     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13651       $delegate.add('tr', {
13652         headerCell: {
13653           aria: {
13654             defaultFilterLabel: 'Sütun için filtre',
13655             removeFilter: 'Filtreyi Kaldır',
13656             columnMenuButtonLabel: 'Sütun Menüsü'
13657           },
13658           priority: 'Öncelik:',
13659           filterLabel: "Sütun için filtre: "
13660         },
13661         aggregate: {
13662           label: 'kayıtlar'
13663         },
13664         groupPanel: {
13665           description: 'Sütuna göre gruplamak için sütun başlığını buraya sürükleyin ve bırakın.'
13666         },
13667         search: {
13668           placeholder: 'Arama...',
13669           showingItems: 'Gösterilen Kayıt:',
13670           selectedItems: 'Seçili Kayıt:',
13671           totalItems: 'Toplam Kayıt:',
13672           size: 'Sayfa Boyutu:',
13673           first: 'İlk Sayfa',
13674           next: 'Sonraki Sayfa',
13675           previous: 'Önceki Sayfa',
13676           last: 'Son Sayfa'
13677         },
13678         menu: {
13679           text: 'Sütunları Seç:'
13680         },
13681         sort: {
13682           ascending: 'Artan Sırada Sırala',
13683           descending: 'Azalan Sırada Sırala',
13684           none: 'Sıralama Yapma',
13685           remove: 'Sıralamayı Kaldır'
13686         },
13687         column: {
13688           hide: 'Sütunu Gizle'
13689         },
13690         aggregation: {
13691           count: 'toplam satır: ',
13692           sum: 'toplam: ',
13693           avg: 'ort: ',
13694           min: 'min: ',
13695           max: 'maks: '
13696         },
13697         pinning: {
13698           pinLeft: 'Sola Sabitle',
13699           pinRight: 'Sağa Sabitle',
13700           unpin: 'Sabitlemeyi Kaldır'
13701         },
13702         columnMenu: {
13703           close: 'Kapat'
13704         },
13705         gridMenu: {
13706           aria: {
13707             buttonLabel: 'Tablo Menü'
13708           },
13709           columns: 'Sütunlar:',
13710           importerTitle: 'Dosya içeri aktar',
13711           exporterAllAsCsv: 'Bütün veriyi CSV olarak dışarı aktar',
13712           exporterVisibleAsCsv: 'Görünen veriyi CSV olarak dışarı aktar',
13713           exporterSelectedAsCsv: 'Seçili veriyi CSV olarak dışarı aktar',
13714           exporterAllAsPdf: 'Bütün veriyi PDF olarak dışarı aktar',
13715           exporterVisibleAsPdf: 'Görünen veriyi PDF olarak dışarı aktar',
13716           exporterSelectedAsPdf: 'Seçili veriyi PDF olarak dışarı aktar',
13717           clearAllFilters: 'Bütün filtreleri kaldır'
13718         },
13719         importer: {
13720           noHeaders: 'Sütun isimleri üretilemiyor, dosyanın bir başlığı var mı?',
13721           noObjects: 'Nesneler üretilemiyor, dosyada başlıktan başka bir veri var mı?',
13722           invalidCsv: 'Dosya işlenemedi, geçerli bir CSV dosyası mı?',
13723           invalidJson: 'Dosya işlenemedi, geçerli bir Json dosyası mı?',
13724           jsonNotArray: 'Alınan Json dosyasında bir dizi bulunmalıdır, işlem iptal ediliyor.'
13725         },
13726         pagination: {
13727           aria: {
13728             pageToFirst: 'İlk sayfaya',
13729             pageBack: 'Geri git',
13730             pageSelected: 'Seçili sayfa',
13731             pageForward: 'İleri git',
13732             pageToLast: 'Sona git'
13733           },
13734           sizes: 'Sayfadaki nesne sayısı',
13735           totalItems: 'kayıtlar',
13736           through: '', //note(fsw) : turkish dont have this preposition 
13737           of: '' //note(fsw) : turkish dont have this preposition
13738         },
13739         grouping: {
13740           group: 'Grupla',
13741           ungroup: 'Gruplama',
13742           aggregate_count: 'Yekun: Sayı',
13743           aggregate_sum: 'Yekun: Toplam',
13744           aggregate_max: 'Yekun: Maks',
13745           aggregate_min: 'Yekun: Min',
13746           aggregate_avg: 'Yekun: Ort',
13747           aggregate_remove: 'Yekun: Sil'
13748         }
13749       });
13750       return $delegate;
13751     }]);
13752   }]);
13753 })();
13754 /**
13755  * @ngdoc overview
13756  * @name ui.grid.i18n
13757  * @description
13758  *
13759  *  # ui.grid.i18n
13760  * This module provides i18n functions to ui.grid and any application that wants to use it
13761
13762  *
13763  * <div doc-module-components="ui.grid.i18n"></div>
13764  */
13765
13766 (function () {
13767   var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
13768   var FILTER_ALIASES = ['t', 'uiTranslate'];
13769
13770   var module = angular.module('ui.grid.i18n');
13771
13772
13773   /**
13774    *  @ngdoc object
13775    *  @name ui.grid.i18n.constant:i18nConstants
13776    *
13777    *  @description constants available in i18n module
13778    */
13779   module.constant('i18nConstants', {
13780     MISSING: '[MISSING]',
13781     UPDATE_EVENT: '$uiI18n',
13782
13783     LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
13784     // default to english
13785     DEFAULT_LANG: 'en'
13786   });
13787
13788 //    module.config(['$provide', function($provide) {
13789 //        $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
13790
13791   /**
13792    *  @ngdoc service
13793    *  @name ui.grid.i18n.service:i18nService
13794    *
13795    *  @description Services for i18n
13796    */
13797   module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
13798     function ($log, i18nConstants, $rootScope) {
13799
13800       var langCache = {
13801         _langs: {},
13802         current: null,
13803         get: function (lang) {
13804           return this._langs[lang.toLowerCase()];
13805         },
13806         add: function (lang, strings) {
13807           var lower = lang.toLowerCase();
13808           if (!this._langs[lower]) {
13809             this._langs[lower] = {};
13810           }
13811           angular.extend(this._langs[lower], strings);
13812         },
13813         getAllLangs: function () {
13814           var langs = [];
13815           if (!this._langs) {
13816             return langs;
13817           }
13818
13819           for (var key in this._langs) {
13820             langs.push(key);
13821           }
13822
13823           return langs;
13824         },
13825         setCurrent: function (lang) {
13826           this.current = lang.toLowerCase();
13827         },
13828         getCurrentLang: function () {
13829           return this.current;
13830         }
13831       };
13832
13833       var service = {
13834
13835         /**
13836          * @ngdoc service
13837          * @name add
13838          * @methodOf ui.grid.i18n.service:i18nService
13839          * @description  Adds the languages and strings to the cache. Decorate this service to
13840          * add more translation strings
13841          * @param {string} lang language to add
13842          * @param {object} stringMaps of strings to add grouped by property names
13843          * @example
13844          * <pre>
13845          *      i18nService.add('en', {
13846          *         aggregate: {
13847          *                 label1: 'items',
13848          *                 label2: 'some more items'
13849          *                 }
13850          *         },
13851          *         groupPanel: {
13852          *              description: 'Drag a column header here and drop it to group by that column.'
13853          *           }
13854          *      }
13855          * </pre>
13856          */
13857         add: function (langs, stringMaps) {
13858           if (typeof(langs) === 'object') {
13859             angular.forEach(langs, function (lang) {
13860               if (lang) {
13861                 langCache.add(lang, stringMaps);
13862               }
13863             });
13864           } else {
13865             langCache.add(langs, stringMaps);
13866           }
13867         },
13868
13869         /**
13870          * @ngdoc service
13871          * @name getAllLangs
13872          * @methodOf ui.grid.i18n.service:i18nService
13873          * @description  return all currently loaded languages
13874          * @returns {array} string
13875          */
13876         getAllLangs: function () {
13877           return langCache.getAllLangs();
13878         },
13879
13880         /**
13881          * @ngdoc service
13882          * @name get
13883          * @methodOf ui.grid.i18n.service:i18nService
13884          * @description  return all currently loaded languages
13885          * @param {string} lang to return.  If not specified, returns current language
13886          * @returns {object} the translation string maps for the language
13887          */
13888         get: function (lang) {
13889           var language = lang ? lang : service.getCurrentLang();
13890           return langCache.get(language);
13891         },
13892
13893         /**
13894          * @ngdoc service
13895          * @name getSafeText
13896          * @methodOf ui.grid.i18n.service:i18nService
13897          * @description  returns the text specified in the path or a Missing text if text is not found
13898          * @param {string} path property path to use for retrieving text from string map
13899          * @param {string} lang to return.  If not specified, returns current language
13900          * @returns {object} the translation for the path
13901          * @example
13902          * <pre>
13903          * i18nService.getSafeText('sort.ascending')
13904          * </pre>
13905          */
13906         getSafeText: function (path, lang) {
13907           var language = lang ? lang : service.getCurrentLang();
13908           var trans = langCache.get(language);
13909
13910           if (!trans) {
13911             return i18nConstants.MISSING;
13912           }
13913
13914           var paths = path.split('.');
13915           var current = trans;
13916
13917           for (var i = 0; i < paths.length; ++i) {
13918             if (current[paths[i]] === undefined || current[paths[i]] === null) {
13919               return i18nConstants.MISSING;
13920             } else {
13921               current = current[paths[i]];
13922             }
13923           }
13924
13925           return current;
13926
13927         },
13928
13929         /**
13930          * @ngdoc service
13931          * @name setCurrentLang
13932          * @methodOf ui.grid.i18n.service:i18nService
13933          * @description sets the current language to use in the application
13934          * $broadcasts the Update_Event on the $rootScope
13935          * @param {string} lang to set
13936          * @example
13937          * <pre>
13938          * i18nService.setCurrentLang('fr');
13939          * </pre>
13940          */
13941
13942         setCurrentLang: function (lang) {
13943           if (lang) {
13944             langCache.setCurrent(lang);
13945             $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
13946           }
13947         },
13948
13949         /**
13950          * @ngdoc service
13951          * @name getCurrentLang
13952          * @methodOf ui.grid.i18n.service:i18nService
13953          * @description returns the current language used in the application
13954          */
13955         getCurrentLang: function () {
13956           var lang = langCache.getCurrentLang();
13957           if (!lang) {
13958             lang = i18nConstants.DEFAULT_LANG;
13959             langCache.setCurrent(lang);
13960           }
13961           return lang;
13962         }
13963
13964       };
13965
13966       return service;
13967
13968     }]);
13969
13970   var localeDirective = function (i18nService, i18nConstants) {
13971     return {
13972       compile: function () {
13973         return {
13974           pre: function ($scope, $elm, $attrs) {
13975             var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
13976             // check for watchable property
13977             var lang = $scope.$eval($attrs[alias]);
13978             if (lang) {
13979               $scope.$watch($attrs[alias], function () {
13980                 i18nService.setCurrentLang(lang);
13981               });
13982             } else if ($attrs.$$observers) {
13983               $attrs.$observe(alias, function () {
13984                 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
13985               });
13986             }
13987           }
13988         };
13989       }
13990     };
13991   };
13992
13993   module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
13994
13995   // directive syntax
13996   var uitDirective = function ($parse, i18nService, i18nConstants) {
13997     return {
13998       restrict: 'EA',
13999       compile: function () {
14000         return {
14001           pre: function ($scope, $elm, $attrs) {
14002             var alias1 = DIRECTIVE_ALIASES[0],
14003               alias2 = DIRECTIVE_ALIASES[1];
14004             var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
14005             var missing = i18nConstants.MISSING + token;
14006             var observer;
14007             if ($attrs.$$observers) {
14008               var prop = $attrs[alias1] ? alias1 : alias2;
14009               observer = $attrs.$observe(prop, function (result) {
14010                 if (result) {
14011                   $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
14012                 }
14013               });
14014             }
14015             var getter = $parse(token);
14016             var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
14017               if (observer) {
14018                 observer($attrs[alias1] || $attrs[alias2]);
14019               } else {
14020                 // set text based on i18n current language
14021                 $elm.html(getter(i18nService.get()) || missing);
14022               }
14023             });
14024             $scope.$on('$destroy', listener);
14025
14026             $elm.html(getter(i18nService.get()) || missing);
14027           }
14028         };
14029       }
14030     };
14031   };
14032
14033   angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
14034     module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
14035   } );
14036
14037   // optional filter syntax
14038   var uitFilter = function ($parse, i18nService, i18nConstants) {
14039     return function (data) {
14040       var getter = $parse(data);
14041       // set text based on i18n current language
14042       return getter(i18nService.get()) || i18nConstants.MISSING + data;
14043     };
14044   };
14045
14046   angular.forEach( FILTER_ALIASES, function ( alias ) {
14047     module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
14048   } );
14049
14050
14051 })();
14052 (function() {
14053   angular.module('ui.grid').config(['$provide', function($provide) {
14054     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14055       $delegate.add('zh-cn', {
14056         headerCell: {
14057           aria: {
14058             defaultFilterLabel: '列过滤器',
14059             removeFilter: '移除过滤器',
14060             columnMenuButtonLabel: '列菜单'
14061           },
14062           priority: '优先级:',
14063           filterLabel: "列过滤器: "
14064         },
14065         aggregate: {
14066           label: '行'
14067         },
14068         groupPanel: {
14069           description: '拖曳表头到此处进行分组'
14070         },
14071         search: {
14072           placeholder: '查找',
14073           showingItems: '已显示行数:',
14074           selectedItems: '已选择行数:',
14075           totalItems: '总行数:',
14076           size: '每页显示行数:',
14077           first: '首页',
14078           next: '下一页',
14079           previous: '上一页',
14080           last: '末页'
14081         },
14082         menu: {
14083           text: '选择列:'
14084         },
14085         sort: {
14086           ascending: '升序',
14087           descending: '降序',
14088           none: '无序',
14089           remove: '取消排序'
14090         },
14091         column: {
14092           hide: '隐藏列'
14093         },
14094         aggregation: {
14095           count: '计数:',
14096           sum: '求和:',
14097           avg: '均值:',
14098           min: '最小值:',
14099           max: '最大值:'
14100         },
14101         pinning: {
14102           pinLeft: '左侧固定',
14103           pinRight: '右侧固定',
14104           unpin: '取消固定'
14105         },
14106         columnMenu: {
14107           close: '关闭'
14108         },
14109         gridMenu: {
14110           aria: {
14111             buttonLabel: '表格菜单'
14112           },
14113           columns: '列:',
14114           importerTitle: '导入文件',
14115           exporterAllAsCsv: '导出全部数据到CSV',
14116           exporterVisibleAsCsv: '导出可见数据到CSV',
14117           exporterSelectedAsCsv: '导出已选数据到CSV',
14118           exporterAllAsPdf: '导出全部数据到PDF',
14119           exporterVisibleAsPdf: '导出可见数据到PDF',
14120           exporterSelectedAsPdf: '导出已选数据到PDF',
14121           clearAllFilters: '清除所有过滤器'
14122         },
14123         importer: {
14124           noHeaders: '无法获取列名,确定文件包含表头?',
14125           noObjects: '无法获取数据,确定文件包含数据?',
14126           invalidCsv: '无法处理文件,确定是合法的CSV文件?',
14127           invalidJson: '无法处理文件,确定是合法的JSON文件?',
14128           jsonNotArray: '导入的文件不是JSON数组!'
14129         },
14130         pagination: {
14131           aria: {
14132             pageToFirst: '第一页',
14133             pageBack: '上一页',
14134             pageSelected: '当前页',
14135             pageForward: '下一页',
14136             pageToLast: '最后一页'
14137           },
14138           sizes: '行每页',
14139           totalItems: '行',
14140           through: '至',
14141           of: '共'
14142         },
14143         grouping: {
14144           group: '分组',
14145           ungroup: '取消分组',
14146           aggregate_count: '合计: 计数',
14147           aggregate_sum: '合计: 求和',
14148           aggregate_max: '合计: 最大',
14149           aggregate_min: '合计: 最小',
14150           aggregate_avg: '合计: 平均',
14151           aggregate_remove: '合计: 移除'
14152         }
14153       });
14154       return $delegate;
14155     }]);
14156   }]);
14157 })();
14158
14159 (function() {
14160   angular.module('ui.grid').config(['$provide', function($provide) {
14161     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14162       $delegate.add('zh-tw', {
14163         aggregate: {
14164           label: '行'
14165         },
14166         groupPanel: {
14167           description: '拖曳表頭到此處進行分組'
14168         },
14169         search: {
14170           placeholder: '查找',
14171           showingItems: '已顯示行數:',
14172           selectedItems: '已選擇行數:',
14173           totalItems: '總行數:',
14174           size: '每頁顯示行數:',
14175           first: '首頁',
14176           next: '下壹頁',
14177           previous: '上壹頁',
14178           last: '末頁'
14179         },
14180         menu: {
14181           text: '選擇列:'
14182         },
14183         sort: {
14184           ascending: '升序',
14185           descending: '降序',
14186           remove: '取消排序'
14187         },
14188         column: {
14189           hide: '隱藏列'
14190         },
14191         aggregation: {
14192           count: '計數:',
14193           sum: '求和:',
14194           avg: '均值:',
14195           min: '最小值:',
14196           max: '最大值:'
14197         },
14198         pinning: {
14199           pinLeft: '左側固定',
14200           pinRight: '右側固定',
14201           unpin: '取消固定'
14202         },
14203         gridMenu: {
14204           columns: '列:',
14205           importerTitle: '導入文件',
14206           exporterAllAsCsv: '導出全部數據到CSV',
14207           exporterVisibleAsCsv: '導出可見數據到CSV',
14208           exporterSelectedAsCsv: '導出已選數據到CSV',
14209           exporterAllAsPdf: '導出全部數據到PDF',
14210           exporterVisibleAsPdf: '導出可見數據到PDF',
14211           exporterSelectedAsPdf: '導出已選數據到PDF',
14212           clearAllFilters: '清除所有过滤器'
14213         },
14214         importer: {
14215           noHeaders: '無法獲取列名,確定文件包含表頭?',
14216           noObjects: '無法獲取數據,確定文件包含數據?',
14217           invalidCsv: '無法處理文件,確定是合法的CSV文件?',
14218           invalidJson: '無法處理文件,確定是合法的JSON文件?',
14219           jsonNotArray: '導入的文件不是JSON數組!'
14220         },
14221         pagination: {
14222           sizes: '行每頁',
14223           totalItems: '行'
14224         }
14225       });
14226       return $delegate;
14227     }]);
14228   }]);
14229 })();
14230
14231 (function() {
14232   'use strict';
14233   /**
14234    *  @ngdoc overview
14235    *  @name ui.grid.autoResize
14236    *
14237    *  @description
14238    *
14239    *  #ui.grid.autoResize
14240    *
14241    *  <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>
14242    *
14243    *  This module provides auto-resizing functionality to UI-Grid.
14244    */
14245   var module = angular.module('ui.grid.autoResize', ['ui.grid']);
14246
14247
14248   module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
14249     return {
14250       require: 'uiGrid',
14251       scope: false,
14252       link: function ($scope, $elm, $attrs, uiGridCtrl) {
14253         var prevGridWidth, prevGridHeight;
14254
14255         function getDimensions() {
14256           prevGridHeight = gridUtil.elementHeight($elm);
14257           prevGridWidth = gridUtil.elementWidth($elm);
14258         }
14259
14260         // Initialize the dimensions
14261         getDimensions();
14262
14263         var resizeTimeoutId;
14264         function startTimeout() {
14265           clearTimeout(resizeTimeoutId);
14266
14267           resizeTimeoutId = setTimeout(function () {
14268             var newGridHeight = gridUtil.elementHeight($elm);
14269             var newGridWidth = gridUtil.elementWidth($elm);
14270
14271             if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
14272               uiGridCtrl.grid.gridHeight = newGridHeight;
14273               uiGridCtrl.grid.gridWidth = newGridWidth;
14274
14275               $scope.$apply(function () {
14276                 uiGridCtrl.grid.refresh()
14277                   .then(function () {
14278                     getDimensions();
14279
14280                     startTimeout();
14281                   });
14282               });
14283             }
14284             else {
14285               startTimeout();
14286             }
14287           }, 250);
14288         }
14289
14290         startTimeout();
14291
14292         $scope.$on('$destroy', function() {
14293           clearTimeout(resizeTimeoutId);
14294         });
14295       }
14296     };
14297   }]);
14298 })();
14299
14300 (function () {
14301   'use strict';
14302
14303   /**
14304    *  @ngdoc overview
14305    *  @name ui.grid.cellNav
14306    *
14307    *  @description
14308
14309       #ui.grid.cellNav
14310
14311       <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>
14312
14313       This module provides auto-resizing functionality to UI-Grid.
14314    */
14315   var module = angular.module('ui.grid.cellNav', ['ui.grid']);
14316
14317   /**
14318    *  @ngdoc object
14319    *  @name ui.grid.cellNav.constant:uiGridCellNavConstants
14320    *
14321    *  @description constants available in cellNav
14322    */
14323   module.constant('uiGridCellNavConstants', {
14324     FEATURE_NAME: 'gridCellNav',
14325     CELL_NAV_EVENT: 'cellNav',
14326     direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
14327     EVENT_TYPE: {
14328       KEYDOWN: 0,
14329       CLICK: 1,
14330       CLEAR: 2
14331     }
14332   });
14333
14334
14335   module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
14336     function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
14337       /**
14338        *  @ngdoc object
14339        *  @name ui.grid.cellNav.object:CellNav
14340        *  @description returns a CellNav prototype function
14341        *  @param {object} rowContainer container for rows
14342        *  @param {object} colContainer parent column container
14343        *  @param {object} leftColContainer column container to the left of parent
14344        *  @param {object} rightColContainer column container to the right of parent
14345        */
14346       var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
14347         this.rows = rowContainer.visibleRowCache;
14348         this.columns = colContainer.visibleColumnCache;
14349         this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
14350         this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
14351         this.bodyContainer = rowContainer;
14352       };
14353
14354       /** returns focusable columns of all containers */
14355       UiGridCellNav.prototype.getFocusableCols = function () {
14356         var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
14357
14358         return allColumns.filter(function (col) {
14359           return col.colDef.allowCellFocus;
14360         });
14361       };
14362
14363       /**
14364        *  @ngdoc object
14365        *  @name ui.grid.cellNav.api:GridRow
14366        *
14367        *  @description GridRow settings for cellNav feature, these are available to be
14368        *  set only internally (for example, by other features)
14369        */
14370
14371       /**
14372        *  @ngdoc object
14373        *  @name allowCellFocus
14374        *  @propertyOf  ui.grid.cellNav.api:GridRow
14375        *  @description Enable focus on a cell within this row.  If set to false then no cells
14376        *  in this row can be focused - group header rows as an example would set this to false.
14377        *  <br/>Defaults to true
14378        */
14379       /** returns focusable rows */
14380       UiGridCellNav.prototype.getFocusableRows = function () {
14381         return this.rows.filter(function(row) {
14382           return row.allowCellFocus !== false;
14383         });
14384       };
14385
14386       UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
14387         switch (direction) {
14388           case uiGridCellNavConstants.direction.LEFT:
14389             return this.getRowColLeft(curRow, curCol);
14390           case uiGridCellNavConstants.direction.RIGHT:
14391             return this.getRowColRight(curRow, curCol);
14392           case uiGridCellNavConstants.direction.UP:
14393             return this.getRowColUp(curRow, curCol);
14394           case uiGridCellNavConstants.direction.DOWN:
14395             return this.getRowColDown(curRow, curCol);
14396           case uiGridCellNavConstants.direction.PG_UP:
14397             return this.getRowColPageUp(curRow, curCol);
14398           case uiGridCellNavConstants.direction.PG_DOWN:
14399             return this.getRowColPageDown(curRow, curCol);
14400         }
14401
14402       };
14403
14404       UiGridCellNav.prototype.initializeSelection = function () {
14405         var focusableCols = this.getFocusableCols();
14406         var focusableRows = this.getFocusableRows();
14407         if (focusableCols.length === 0 || focusableRows.length === 0) {
14408           return null;
14409         }
14410
14411         var curRowIndex = 0;
14412         var curColIndex = 0;
14413         return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
14414       };
14415
14416       UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
14417         var focusableCols = this.getFocusableCols();
14418         var focusableRows = this.getFocusableRows();
14419         var curColIndex = focusableCols.indexOf(curCol);
14420         var curRowIndex = focusableRows.indexOf(curRow);
14421
14422         //could not find column in focusable Columns so set it to 1
14423         if (curColIndex === -1) {
14424           curColIndex = 1;
14425         }
14426
14427         var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
14428
14429         //get column to left
14430         if (nextColIndex > curColIndex) {
14431           // On the first row
14432           // if (curRowIndex === 0 && curColIndex === 0) {
14433           //   return null;
14434           // }
14435           if (curRowIndex === 0) {
14436             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14437           }
14438           else {
14439             //up one row and far right column
14440             return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
14441           }
14442         }
14443         else {
14444           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14445         }
14446       };
14447
14448
14449
14450       UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
14451         var focusableCols = this.getFocusableCols();
14452         var focusableRows = this.getFocusableRows();
14453         var curColIndex = focusableCols.indexOf(curCol);
14454         var curRowIndex = focusableRows.indexOf(curRow);
14455
14456         //could not find column in focusable Columns so set it to 0
14457         if (curColIndex === -1) {
14458           curColIndex = 0;
14459         }
14460         var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
14461
14462         if (nextColIndex < curColIndex) {
14463           if (curRowIndex === focusableRows.length - 1) {
14464             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14465           }
14466           else {
14467             //down one row and far left column
14468             return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
14469           }
14470         }
14471         else {
14472           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14473         }
14474       };
14475
14476       UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
14477         var focusableCols = this.getFocusableCols();
14478         var focusableRows = this.getFocusableRows();
14479         var curColIndex = focusableCols.indexOf(curCol);
14480         var curRowIndex = focusableRows.indexOf(curRow);
14481
14482         //could not find column in focusable Columns so set it to 0
14483         if (curColIndex === -1) {
14484           curColIndex = 0;
14485         }
14486
14487         if (curRowIndex === focusableRows.length - 1) {
14488           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14489         }
14490         else {
14491           //down one row
14492           return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
14493         }
14494       };
14495
14496       UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
14497         var focusableCols = this.getFocusableCols();
14498         var focusableRows = this.getFocusableRows();
14499         var curColIndex = focusableCols.indexOf(curCol);
14500         var curRowIndex = focusableRows.indexOf(curRow);
14501
14502         //could not find column in focusable Columns so set it to 0
14503         if (curColIndex === -1) {
14504           curColIndex = 0;
14505         }
14506
14507         var pageSize = this.bodyContainer.minRowsToRender();
14508         if (curRowIndex >= focusableRows.length - pageSize) {
14509           return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
14510         }
14511         else {
14512           //down one page
14513           return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
14514         }
14515       };
14516
14517       UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
14518         var focusableCols = this.getFocusableCols();
14519         var focusableRows = this.getFocusableRows();
14520         var curColIndex = focusableCols.indexOf(curCol);
14521         var curRowIndex = focusableRows.indexOf(curRow);
14522
14523         //could not find column in focusable Columns so set it to 0
14524         if (curColIndex === -1) {
14525           curColIndex = 0;
14526         }
14527
14528         if (curRowIndex === 0) {
14529           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14530         }
14531         else {
14532           //up one row
14533           return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
14534         }
14535       };
14536
14537       UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
14538         var focusableCols = this.getFocusableCols();
14539         var focusableRows = this.getFocusableRows();
14540         var curColIndex = focusableCols.indexOf(curCol);
14541         var curRowIndex = focusableRows.indexOf(curRow);
14542
14543         //could not find column in focusable Columns so set it to 0
14544         if (curColIndex === -1) {
14545           curColIndex = 0;
14546         }
14547
14548         var pageSize = this.bodyContainer.minRowsToRender();
14549         if (curRowIndex - pageSize < 0) {
14550           return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
14551         }
14552         else {
14553           //up one page
14554           return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
14555         }
14556       };
14557       return UiGridCellNav;
14558     }]);
14559
14560   /**
14561    *  @ngdoc service
14562    *  @name ui.grid.cellNav.service:uiGridCellNavService
14563    *
14564    *  @description Services for cell navigation features. If you don't like the key maps we use,
14565    *  or the direction cells navigation, override with a service decorator (see angular docs)
14566    */
14567   module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
14568     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
14569
14570       var service = {
14571
14572         initializeGrid: function (grid) {
14573           grid.registerColumnBuilder(service.cellNavColumnBuilder);
14574
14575
14576           /**
14577            *  @ngdoc object
14578            *  @name ui.grid.cellNav:Grid.cellNav
14579            * @description cellNav properties added to grid class
14580            */
14581           grid.cellNav = {};
14582           grid.cellNav.lastRowCol = null;
14583           grid.cellNav.focusedCells = [];
14584
14585           service.defaultGridOptions(grid.options);
14586
14587           /**
14588            *  @ngdoc object
14589            *  @name ui.grid.cellNav.api:PublicApi
14590            *
14591            *  @description Public Api for cellNav feature
14592            */
14593           var publicApi = {
14594             events: {
14595               cellNav: {
14596                 /**
14597                  * @ngdoc event
14598                  * @name navigate
14599                  * @eventOf  ui.grid.cellNav.api:PublicApi
14600                  * @description raised when the active cell is changed
14601                  * <pre>
14602                  *      gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
14603                  * </pre>
14604                  * @param {object} newRowCol new position
14605                  * @param {object} oldRowCol old position
14606                  */
14607                 navigate: function (newRowCol, oldRowCol) {},
14608                 /**
14609                  * @ngdoc event
14610                  * @name viewPortKeyDown
14611                  * @eventOf  ui.grid.cellNav.api:PublicApi
14612                  * @description  is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
14613                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14614                  * event whenever you need a keydown event on a cell
14615                  * <br/>
14616                  * @param {object} event keydown event
14617                  * @param {object} rowCol current rowCol position
14618                  */
14619                 viewPortKeyDown: function (event, rowCol) {},
14620
14621                 /**
14622                  * @ngdoc event
14623                  * @name viewPortKeyPress
14624                  * @eventOf  ui.grid.cellNav.api:PublicApi
14625                  * @description  is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
14626                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
14627                  * event whenever you need a keypress event on a cell
14628                  * <br/>
14629                  * @param {object} event keypress event
14630                  * @param {object} rowCol current rowCol position
14631                  */
14632                 viewPortKeyPress: function (event, rowCol) {}
14633               }
14634             },
14635             methods: {
14636               cellNav: {
14637                 /**
14638                  * @ngdoc function
14639                  * @name scrollToFocus
14640                  * @methodOf  ui.grid.cellNav.api:PublicApi
14641                  * @description brings the specified row and column into view, and sets focus
14642                  * to that cell
14643                  * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
14644                  * @param {object} colDef to make visible and set focus
14645                  * @returns {promise} a promise that is resolved after any scrolling is finished
14646                  */
14647                 scrollToFocus: function (rowEntity, colDef) {
14648                   return service.scrollToFocus(grid, rowEntity, colDef);
14649                 },
14650
14651                 /**
14652                  * @ngdoc function
14653                  * @name getFocusedCell
14654                  * @methodOf  ui.grid.cellNav.api:PublicApi
14655                  * @description returns the current (or last if Grid does not have focus) focused row and column
14656                  * <br> value is null if no selection has occurred
14657                  */
14658                 getFocusedCell: function () {
14659                   return grid.cellNav.lastRowCol;
14660                 },
14661
14662                 /**
14663                  * @ngdoc function
14664                  * @name getCurrentSelection
14665                  * @methodOf  ui.grid.cellNav.api:PublicApi
14666                  * @description returns an array containing the current selection
14667                  * <br> array is empty if no selection has occurred
14668                  */
14669                 getCurrentSelection: function () {
14670                   return grid.cellNav.focusedCells;
14671                 },
14672
14673                 /**
14674                  * @ngdoc function
14675                  * @name rowColSelectIndex
14676                  * @methodOf  ui.grid.cellNav.api:PublicApi
14677                  * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
14678                  * isn't selected
14679                  * @param {object} rowCol the rowCol to evaluate
14680                  */
14681                 rowColSelectIndex: function (rowCol) {
14682                   //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
14683                   var index = -1;
14684                   for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
14685                     if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
14686                       grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
14687                       index = i;
14688                       break;
14689                     }
14690                   }
14691                   return index;
14692                 }
14693               }
14694             }
14695           };
14696
14697           grid.api.registerEventsFromObject(publicApi.events);
14698
14699           grid.api.registerMethodsFromObject(publicApi.methods);
14700
14701         },
14702
14703         defaultGridOptions: function (gridOptions) {
14704           /**
14705            *  @ngdoc object
14706            *  @name ui.grid.cellNav.api:GridOptions
14707            *
14708            *  @description GridOptions for cellNav feature, these are available to be
14709            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14710            */
14711
14712           /**
14713            *  @ngdoc object
14714            *  @name modifierKeysToMultiSelectCells
14715            *  @propertyOf  ui.grid.cellNav.api:GridOptions
14716            *  @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
14717            *  <br/>Defaults to false
14718            */
14719           gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
14720
14721         },
14722
14723         /**
14724          * @ngdoc service
14725          * @name decorateRenderContainers
14726          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14727          * @description  decorates grid renderContainers with cellNav functions
14728          */
14729         decorateRenderContainers: function (grid) {
14730
14731           var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
14732           var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
14733
14734           if (leftContainer !== null) {
14735             grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
14736           }
14737           if (rightContainer !== null) {
14738             grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
14739           }
14740
14741           grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
14742         },
14743
14744         /**
14745          * @ngdoc service
14746          * @name getDirection
14747          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14748          * @description  determines which direction to for a given keyDown event
14749          * @returns {uiGridCellNavConstants.direction} direction
14750          */
14751         getDirection: function (evt) {
14752           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
14753             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
14754             return uiGridCellNavConstants.direction.LEFT;
14755           }
14756           if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
14757             evt.keyCode === uiGridConstants.keymap.TAB) {
14758             return uiGridCellNavConstants.direction.RIGHT;
14759           }
14760
14761           if (evt.keyCode === uiGridConstants.keymap.UP ||
14762             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
14763             return uiGridCellNavConstants.direction.UP;
14764           }
14765
14766           if (evt.keyCode === uiGridConstants.keymap.PG_UP){
14767             return uiGridCellNavConstants.direction.PG_UP;
14768           }
14769
14770           if (evt.keyCode === uiGridConstants.keymap.DOWN ||
14771             evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
14772             return uiGridCellNavConstants.direction.DOWN;
14773           }
14774
14775           if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
14776             return uiGridCellNavConstants.direction.PG_DOWN;
14777           }
14778
14779           return null;
14780         },
14781
14782         /**
14783          * @ngdoc service
14784          * @name cellNavColumnBuilder
14785          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14786          * @description columnBuilder function that adds cell navigation properties to grid column
14787          * @returns {promise} promise that will load any needed templates when resolved
14788          */
14789         cellNavColumnBuilder: function (colDef, col, gridOptions) {
14790           var promises = [];
14791
14792           /**
14793            *  @ngdoc object
14794            *  @name ui.grid.cellNav.api:ColumnDef
14795            *
14796            *  @description Column Definitions for cellNav feature, these are available to be
14797            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
14798            */
14799
14800           /**
14801            *  @ngdoc object
14802            *  @name allowCellFocus
14803            *  @propertyOf  ui.grid.cellNav.api:ColumnDef
14804            *  @description Enable focus on a cell within this column.
14805            *  <br/>Defaults to true
14806            */
14807           colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
14808
14809           return $q.all(promises);
14810         },
14811
14812         /**
14813          * @ngdoc method
14814          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14815          * @name scrollToFocus
14816          * @description Scroll the grid such that the specified
14817          * row and column is in view, and set focus to the cell in that row and column
14818          * @param {Grid} grid the grid you'd like to act upon, usually available
14819          * from gridApi.grid
14820          * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
14821          * @param {object} colDef to make visible and set focus to
14822          * @returns {promise} a promise that is resolved after any scrolling is finished
14823          */
14824         scrollToFocus: function (grid, rowEntity, colDef) {
14825           var gridRow = null, gridCol = null;
14826
14827           if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
14828             gridRow = grid.getRow(rowEntity);
14829           }
14830
14831           if (typeof(colDef) !== 'undefined' && colDef !== null) {
14832             gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
14833           }
14834           return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
14835             var rowCol = { row: gridRow, col: gridCol };
14836
14837             // Broadcast the navigation
14838             if (gridRow !== null && gridCol !== null) {
14839               grid.cellNav.broadcastCellNav(rowCol);
14840             }
14841           });
14842
14843
14844
14845         },
14846
14847
14848         /**
14849          * @ngdoc method
14850          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
14851          * @name getLeftWidth
14852          * @description Get the current drawn width of the columns in the
14853          * grid up to the numbered column, and add an apportionment for the
14854          * column that we're on.  So if we are on column 0, we want to scroll
14855          * 0% (i.e. exclude this column from calc).  If we're on the last column
14856          * we want to scroll to 100% (i.e. include this column in the calc). So
14857          * we include (thisColIndex / totalNumberCols) % of this column width
14858          * @param {Grid} grid the grid you'd like to act upon, usually available
14859          * from gridApi.grid
14860          * @param {gridCol} upToCol the column to total up to and including
14861          */
14862         getLeftWidth: function (grid, upToCol) {
14863           var width = 0;
14864
14865           if (!upToCol) {
14866             return width;
14867           }
14868
14869           var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
14870
14871           // total column widths up-to but not including the passed in column
14872           grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
14873             if ( index < lastIndex ){
14874               width += col.drawnWidth;
14875             }
14876           });
14877
14878           // pro-rata the final column based on % of total columns.
14879           var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
14880           width += upToCol.drawnWidth * percentage;
14881
14882           return width;
14883         }
14884       };
14885
14886       return service;
14887     }]);
14888
14889   /**
14890    *  @ngdoc directive
14891    *  @name ui.grid.cellNav.directive:uiCellNav
14892    *  @element div
14893    *  @restrict EA
14894    *
14895    *  @description Adds cell navigation features to the grid columns
14896    *
14897    *  @example
14898    <example module="app">
14899    <file name="app.js">
14900    var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
14901
14902    app.controller('MainCtrl', ['$scope', function ($scope) {
14903       $scope.data = [
14904         { name: 'Bob', title: 'CEO' },
14905             { name: 'Frank', title: 'Lowly Developer' }
14906       ];
14907
14908       $scope.columnDefs = [
14909         {name: 'name'},
14910         {name: 'title'}
14911       ];
14912     }]);
14913    </file>
14914    <file name="index.html">
14915    <div ng-controller="MainCtrl">
14916    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
14917    </div>
14918    </file>
14919    </example>
14920    */
14921   module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
14922     function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
14923       return {
14924         replace: true,
14925         priority: -150,
14926         require: '^uiGrid',
14927         scope: false,
14928         controller: function () {},
14929         compile: function () {
14930           return {
14931             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14932               var _scope = $scope;
14933
14934               var grid = uiGridCtrl.grid;
14935               uiGridCellNavService.initializeGrid(grid);
14936
14937               uiGridCtrl.cellNav = {};
14938
14939               //Ensure that the object has all of the methods we expect it to
14940               uiGridCtrl.cellNav.makeRowCol = function (obj) {
14941                 if (!(obj instanceof GridRowColumn)) {
14942                   obj = new GridRowColumn(obj.row, obj.col);
14943                 }
14944                 return obj;
14945               };
14946
14947               uiGridCtrl.cellNav.getActiveCell = function () {
14948                 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
14949                 if (elms.length > 0){
14950                   return elms[0];
14951                 }
14952
14953                 return undefined;
14954               };
14955
14956               uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
14957                 modifierDown = !(modifierDown === undefined || !modifierDown);
14958
14959                 newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
14960
14961                 uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
14962                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
14963               };
14964
14965               uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
14966                 grid.cellNav.focusedCells = [];
14967                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
14968               };
14969
14970               uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
14971                 modifierDown = !(modifierDown === undefined || !modifierDown);
14972
14973                 rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
14974
14975                 var row = rowCol.row,
14976                   col = rowCol.col;
14977
14978                 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
14979
14980                 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
14981                   var newRowCol = new GridRowColumn(row, col);
14982
14983                   if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col){
14984                     grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
14985                     grid.cellNav.lastRowCol = newRowCol;  
14986                   }
14987                   if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
14988                     grid.cellNav.focusedCells.push(rowCol);
14989                   } else {
14990                     grid.cellNav.focusedCells = [rowCol];
14991                   }
14992                 } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
14993                   rowColSelectIndex >= 0) {
14994
14995                   grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
14996                 }
14997               };
14998
14999               uiGridCtrl.cellNav.handleKeyDown = function (evt) {
15000                 var direction = uiGridCellNavService.getDirection(evt);
15001                 if (direction === null) {
15002                   return null;
15003                 }
15004
15005                 var containerId = 'body';
15006                 if (evt.uiGridTargetRenderContainerId) {
15007                   containerId = evt.uiGridTargetRenderContainerId;
15008                 }
15009
15010                 // Get the last-focused row+col combo
15011                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15012                 if (lastRowCol) {
15013                   // Figure out which new row+combo we're navigating to
15014                   var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
15015                   var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
15016                   var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
15017                   // Shift+tab on top-left cell should exit cellnav on render container
15018                   if (
15019                     // Navigating left
15020                     direction === uiGridCellNavConstants.direction.LEFT &&
15021                     // New col is last col (i.e. wrap around)
15022                     rowCol.col === focusableCols[focusableCols.length - 1] &&
15023                     // Staying on same row, which means we're at first row
15024                     rowCol.row === lastRowCol.row &&
15025                     evt.keyCode === uiGridConstants.keymap.TAB &&
15026                     evt.shiftKey
15027                   ) {
15028                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15029                     uiGridCtrl.cellNav.clearFocus();
15030                     return true;
15031                   }
15032                   // Tab on bottom-right cell should exit cellnav on render container
15033                   else if (
15034                     direction === uiGridCellNavConstants.direction.RIGHT &&
15035                     // New col is first col (i.e. wrap around)
15036                     rowCol.col === focusableCols[0] &&
15037                     // Staying on same row, which means we're at first row
15038                     rowCol.row === lastRowCol.row &&
15039                     evt.keyCode === uiGridConstants.keymap.TAB &&
15040                     !evt.shiftKey
15041                   ) {
15042                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15043                     uiGridCtrl.cellNav.clearFocus();
15044                     return true;
15045                   }
15046
15047                   // Scroll to the new cell, if it's not completely visible within the render container's viewport
15048                   grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
15049                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15050                   });
15051
15052
15053                   evt.stopPropagation();
15054                   evt.preventDefault();
15055
15056                   return false;
15057                 }
15058               };
15059             },
15060             post: function ($scope, $elm, $attrs, uiGridCtrl) {
15061               var _scope = $scope;
15062               var grid = uiGridCtrl.grid;
15063
15064               function addAriaLiveRegion(){
15065                 // Thanks to google docs for the inspiration behind how to do this
15066                 // XXX: Why is this entire mess nessasary?
15067                 // Because browsers take a lot of coercing to get them to read out live regions
15068                 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
15069                 var ariaNotifierDomElt = '<div ' +
15070                                            'id="' + grid.id +'-aria-speakable" ' +
15071                                            'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
15072                                            'aria-live="assertive" ' +
15073                                            'role="region" ' +
15074                                            'aria-atomic="true" ' +
15075                                            'aria-hidden="false" ' +
15076                                            'aria-relevant="additions" ' +
15077                                            '>' +
15078                                            '&nbsp;' +
15079                                          '</div>';
15080
15081                 var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
15082                 $elm.prepend(ariaNotifier);
15083                 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
15084                   /*
15085                    * If the cell nav event was because of a focus event then we don't want to
15086                    * change the notifier text.
15087                    * Reasoning: Voice Over fires a focus events when moving arround the grid.
15088                    * If the screen reader is handing the grid nav properly then we don't need to
15089                    * use the alert to notify the user of the movement.
15090                    * In all other cases we do want a notification event.
15091                    */
15092                   if (originEvt && originEvt.type === 'focus'){return;}
15093
15094                   function setNotifyText(text){
15095                     if (text === ariaNotifier.text()){return;}
15096                     ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
15097                     /*
15098                      * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
15099                      */
15100                     ariaNotifier[0].innerHTML = "";
15101                     ariaNotifier[0].style.visibility = 'hidden';
15102                     ariaNotifier[0].style.visibility = 'visible';
15103                     if (text !== ''){
15104                       ariaNotifier[0].style.clip = 'auto';
15105                       /*
15106                        * The space after the text is something that google docs does.
15107                        */
15108                       ariaNotifier[0].appendChild(document.createTextNode(text + " "));
15109                       ariaNotifier[0].style.visibility = 'hidden';
15110                       ariaNotifier[0].style.visibility = 'visible';
15111                     }
15112                   }
15113
15114                   var values = [];
15115                   var currentSelection = grid.api.cellNav.getCurrentSelection();
15116                   for (var i = 0; i < currentSelection.length; i++) {
15117                     values.push(currentSelection[i].getIntersectionValueFiltered());
15118                   }
15119                   var cellText = values.toString();
15120                   setNotifyText(cellText);
15121
15122                 });
15123               }
15124               addAriaLiveRegion();
15125             }
15126           };
15127         }
15128       };
15129     }]);
15130
15131   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
15132     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
15133       return {
15134         replace: true,
15135         priority: -99999, //this needs to run very last
15136         require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
15137         scope: false,
15138         compile: function () {
15139           return {
15140             post: function ($scope, $elm, $attrs, controllers) {
15141               var uiGridCtrl = controllers[0],
15142                  renderContainerCtrl = controllers[1],
15143                  uiGridCellnavCtrl = controllers[2];
15144
15145               // Skip attaching cell-nav specific logic if the directive is not attached above us
15146               if (!uiGridCtrl.grid.api.cellNav) { return; }
15147
15148               var containerId = renderContainerCtrl.containerId;
15149
15150               var grid = uiGridCtrl.grid;
15151
15152               //run each time a render container is created
15153               uiGridCellNavService.decorateRenderContainers(grid);
15154
15155               // focusser only created for body
15156               if (containerId !== 'body') {
15157                 return;
15158               }
15159
15160
15161
15162               if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
15163                 $elm.attr('aria-multiselectable', true);
15164               } else {
15165                 $elm.attr('aria-multiselectable', false);
15166               }
15167
15168               //add an element with no dimensions that can be used to set focus and capture keystrokes
15169               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);
15170               $elm.append(focuser);
15171
15172               focuser.on('focus', function (evt) {
15173                 evt.uiGridTargetRenderContainerId = containerId;
15174                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15175                 if (rowCol === null) {
15176                   rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
15177                   if (rowCol.row && rowCol.col) {
15178                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15179                   }
15180                 }
15181               });
15182
15183               uiGridCellnavCtrl.setAriaActivedescendant = function(id){
15184                 $elm.attr('aria-activedescendant', id);
15185               };
15186
15187               uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
15188                 if ($elm.attr('aria-activedescendant') === id){
15189                   $elm.attr('aria-activedescendant', '');
15190                 }
15191               };
15192
15193
15194               uiGridCtrl.focus = function () {
15195                 gridUtil.focus.byElement(focuser[0]);
15196                 //allow for first time grid focus
15197               };
15198
15199               var viewPortKeyDownWasRaisedForRowCol = null;
15200               // Bind to keydown events in the render container
15201               focuser.on('keydown', function (evt) {
15202                 evt.uiGridTargetRenderContainerId = containerId;
15203                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15204                 var result = uiGridCtrl.cellNav.handleKeyDown(evt);
15205                 if (result === null) {
15206                   uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
15207                   viewPortKeyDownWasRaisedForRowCol = rowCol;
15208                 }
15209               });
15210               //Bind to keypress events in the render container
15211               //keypress events are needed by edit function so the key press
15212               //that initiated an edit is not lost
15213               //must fire the event in a timeout so the editor can
15214               //initialize and subscribe to the event on another event loop
15215               focuser.on('keypress', function (evt) {
15216                 if (viewPortKeyDownWasRaisedForRowCol) {
15217                   $timeout(function () {
15218                     uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
15219                   },4);
15220
15221                   viewPortKeyDownWasRaisedForRowCol = null;
15222                 }
15223               });
15224
15225               $scope.$on('$destroy', function(){
15226                 //Remove all event handlers associated with this focuser.
15227                 focuser.off();
15228               });
15229
15230             }
15231           };
15232         }
15233       };
15234     }]);
15235
15236   module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
15237     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
15238       return {
15239         replace: true,
15240         priority: -99999, //this needs to run very last
15241         require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
15242         scope: false,
15243         compile: function () {
15244           return {
15245             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15246             },
15247             post: function ($scope, $elm, $attrs, controllers) {
15248               var uiGridCtrl = controllers[0],
15249                 renderContainerCtrl = controllers[1];
15250
15251               // Skip attaching cell-nav specific logic if the directive is not attached above us
15252               if (!uiGridCtrl.grid.api.cellNav) { return; }
15253
15254               var containerId = renderContainerCtrl.containerId;
15255               //no need to process for other containers
15256               if (containerId !== 'body') {
15257                 return;
15258               }
15259
15260               var grid = uiGridCtrl.grid;
15261
15262               grid.api.core.on.scrollBegin($scope, function (args) {
15263
15264                 // Skip if there's no currently-focused cell
15265                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15266                 if (lastRowCol === null) {
15267                   return;
15268                 }
15269
15270                 //if not in my container, move on
15271                 //todo: worry about horiz scroll
15272                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15273                   return;
15274                 }
15275
15276                 uiGridCtrl.cellNav.clearFocus();
15277
15278               });
15279
15280               grid.api.core.on.scrollEnd($scope, function (args) {
15281                 // Skip if there's no currently-focused cell
15282                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15283                 if (lastRowCol === null) {
15284                   return;
15285                 }
15286
15287                 //if not in my container, move on
15288                 //todo: worry about horiz scroll
15289                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15290                   return;
15291                 }
15292
15293                 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
15294
15295               });
15296
15297               grid.api.cellNav.on.navigate($scope, function () {
15298                 //focus again because it can be lost
15299                  uiGridCtrl.focus();
15300               });
15301
15302             }
15303           };
15304         }
15305       };
15306     }]);
15307
15308   /**
15309    *  @ngdoc directive
15310    *  @name ui.grid.cellNav.directive:uiGridCell
15311    *  @element div
15312    *  @restrict A
15313    *  @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
15314    */
15315   module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
15316     function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
15317       return {
15318         priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
15319         restrict: 'A',
15320         require: ['^uiGrid', '?^uiGridCellnav'],
15321         scope: false,
15322         link: function ($scope, $elm, $attrs, controllers) {
15323           var uiGridCtrl = controllers[0],
15324               uiGridCellnavCtrl = controllers[1];
15325           // Skip attaching cell-nav specific logic if the directive is not attached above us
15326           if (!uiGridCtrl.grid.api.cellNav) { return; }
15327
15328           if (!$scope.col.colDef.allowCellFocus) {
15329             return;
15330           }
15331
15332           //Convinience local variables
15333           var grid = uiGridCtrl.grid;
15334           $scope.focused = false;
15335
15336           // Make this cell focusable but only with javascript/a mouse click
15337           $elm.attr('tabindex', -1);
15338
15339           // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
15340           $elm.find('div').on('click', function (evt) {
15341             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
15342
15343             evt.stopPropagation();
15344             $scope.$apply();
15345           });
15346
15347
15348           /*
15349            * XXX Hack for screen readers.
15350            * This allows the grid to focus using only the screen reader cursor.
15351            * Since the focus event doesn't include key press information we can't use it
15352            * as our primary source of the event.
15353            */
15354           $elm.on('mousedown', preventMouseDown);
15355
15356           //turn on and off for edit events
15357           if (uiGridCtrl.grid.api.edit) {
15358             uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
15359               $elm.off('mousedown', preventMouseDown);
15360             });
15361
15362             uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
15363               $elm.on('mousedown', preventMouseDown);
15364             });
15365
15366             uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
15367               $elm.on('mousedown', preventMouseDown);
15368             });
15369           }
15370
15371           function preventMouseDown(evt) {
15372             //Prevents the foucus event from firing if the click event is already going to fire.
15373             //If both events fire it will cause bouncing behavior.
15374             evt.preventDefault();
15375           }
15376
15377           //You can only focus on elements with a tabindex value
15378           $elm.on('focus', function (evt) {
15379             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
15380             evt.stopPropagation();
15381             $scope.$apply();
15382           });
15383
15384           // This event is fired for all cells.  If the cell matches, then focus is set
15385           $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown) {
15386             var isFocused = grid.cellNav.focusedCells.some(function(focusedRowCol, index){
15387               return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
15388             });
15389             if (isFocused){
15390               setFocused();
15391             } else {
15392               clearFocus();
15393             }
15394           });
15395
15396           function setFocused() {
15397             if (!$scope.focused){
15398               var div = $elm.find('div');
15399               div.addClass('ui-grid-cell-focus');
15400               $elm.attr('aria-selected', true);
15401               uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
15402               $scope.focused = true;
15403             }
15404           }
15405
15406           function clearFocus() {
15407             if ($scope.focused){
15408               var div = $elm.find('div');
15409               div.removeClass('ui-grid-cell-focus');
15410               $elm.attr('aria-selected', false);
15411               uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
15412               $scope.focused = false;
15413             }
15414           }
15415
15416           $scope.$on('$destroy', function () {
15417             //.off withouth paramaters removes all handlers
15418             $elm.find('div').off();
15419             $elm.off();
15420           });
15421         }
15422       };
15423     }]);
15424
15425 })();
15426
15427 (function () {
15428   'use strict';
15429
15430   /**
15431    * @ngdoc overview
15432    * @name ui.grid.edit
15433    * @description
15434    *
15435    * # ui.grid.edit
15436    *
15437    * <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>
15438    *
15439    * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
15440    * a keyboard.
15441    * <br/>
15442    * <br/>
15443    * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
15444    * user to key data and then tab, arrow, or enter to the cells beside or below.
15445    *
15446    * <div doc-module-components="ui.grid.edit"></div>
15447    */
15448
15449   var module = angular.module('ui.grid.edit', ['ui.grid']);
15450
15451   /**
15452    *  @ngdoc object
15453    *  @name ui.grid.edit.constant:uiGridEditConstants
15454    *
15455    *  @description constants available in edit module
15456    */
15457   module.constant('uiGridEditConstants', {
15458     EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
15459     //must be lowercase because template bulder converts to lower
15460     EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
15461     events: {
15462       BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
15463       END_CELL_EDIT: 'uiGridEventEndCellEdit',
15464       CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
15465     }
15466   });
15467
15468   /**
15469    *  @ngdoc service
15470    *  @name ui.grid.edit.service:uiGridEditService
15471    *
15472    *  @description Services for editing features
15473    */
15474   module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
15475     function ($q, uiGridConstants, gridUtil) {
15476
15477       var service = {
15478
15479         initializeGrid: function (grid) {
15480
15481           service.defaultGridOptions(grid.options);
15482
15483           grid.registerColumnBuilder(service.editColumnBuilder);
15484           grid.edit = {};
15485
15486           /**
15487            *  @ngdoc object
15488            *  @name ui.grid.edit.api:PublicApi
15489            *
15490            *  @description Public Api for edit feature
15491            */
15492           var publicApi = {
15493             events: {
15494               edit: {
15495                 /**
15496                  * @ngdoc event
15497                  * @name afterCellEdit
15498                  * @eventOf  ui.grid.edit.api:PublicApi
15499                  * @description raised when cell editing is complete
15500                  * <pre>
15501                  *      gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
15502                  * </pre>
15503                  * @param {object} rowEntity the options.data element that was edited
15504                  * @param {object} colDef the column that was edited
15505                  * @param {object} newValue new value
15506                  * @param {object} oldValue old value
15507                  */
15508                 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
15509                 },
15510                 /**
15511                  * @ngdoc event
15512                  * @name beginCellEdit
15513                  * @eventOf  ui.grid.edit.api:PublicApi
15514                  * @description raised when cell editing starts on a cell
15515                  * <pre>
15516                  *      gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
15517                  * </pre>
15518                  * @param {object} rowEntity the options.data element that was edited
15519                  * @param {object} colDef the column that was edited
15520                  * @param {object} triggerEvent the event that triggered the edit.  Useful to prevent losing keystrokes on some
15521                  *                 complex editors
15522                  */
15523                 beginCellEdit: function (rowEntity, colDef, triggerEvent) {
15524                 },
15525                 /**
15526                  * @ngdoc event
15527                  * @name cancelCellEdit
15528                  * @eventOf  ui.grid.edit.api:PublicApi
15529                  * @description raised when cell editing is cancelled on a cell
15530                  * <pre>
15531                  *      gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
15532                  * </pre>
15533                  * @param {object} rowEntity the options.data element that was edited
15534                  * @param {object} colDef the column that was edited
15535                  */
15536                 cancelCellEdit: function (rowEntity, colDef) {
15537                 }
15538               }
15539             },
15540             methods: {
15541               edit: { }
15542             }
15543           };
15544
15545           grid.api.registerEventsFromObject(publicApi.events);
15546           //grid.api.registerMethodsFromObject(publicApi.methods);
15547
15548         },
15549
15550         defaultGridOptions: function (gridOptions) {
15551
15552           /**
15553            *  @ngdoc object
15554            *  @name ui.grid.edit.api:GridOptions
15555            *
15556            *  @description Options for configuring the edit feature, these are available to be
15557            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15558            */
15559
15560           /**
15561            *  @ngdoc object
15562            *  @name enableCellEdit
15563            *  @propertyOf  ui.grid.edit.api:GridOptions
15564            *  @description If defined, sets the default value for the editable flag on each individual colDefs
15565            *  if their individual enableCellEdit configuration is not defined. Defaults to undefined.
15566            */
15567
15568           /**
15569            *  @ngdoc object
15570            *  @name cellEditableCondition
15571            *  @propertyOf  ui.grid.edit.api:GridOptions
15572            *  @description If specified, either a value or function to be used by all columns before editing.
15573            *  If falsy, then editing of cell is not allowed.
15574            *  @example
15575            *  <pre>
15576            *  function($scope){
15577            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15578            *    return true;
15579            *  }
15580            *  </pre>
15581            */
15582           gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
15583
15584           /**
15585            *  @ngdoc object
15586            *  @name editableCellTemplate
15587            *  @propertyOf  ui.grid.edit.api:GridOptions
15588            *  @description If specified, cellTemplate to use as the editor for all columns.
15589            *  <br/> defaults to 'ui-grid/cellTextEditor'
15590            */
15591
15592           /**
15593            *  @ngdoc object
15594            *  @name enableCellEditOnFocus
15595            *  @propertyOf  ui.grid.edit.api:GridOptions
15596            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15597            *  <br/>_requires cellNav feature and the edit feature to be enabled_
15598            */
15599             //enableCellEditOnFocus can only be used if cellnav module is used
15600           gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
15601         },
15602
15603         /**
15604          * @ngdoc service
15605          * @name editColumnBuilder
15606          * @methodOf ui.grid.edit.service:uiGridEditService
15607          * @description columnBuilder function that adds edit properties to grid column
15608          * @returns {promise} promise that will load any needed templates when resolved
15609          */
15610         editColumnBuilder: function (colDef, col, gridOptions) {
15611
15612           var promises = [];
15613
15614           /**
15615            *  @ngdoc object
15616            *  @name ui.grid.edit.api:ColumnDef
15617            *
15618            *  @description Column Definition for edit feature, these are available to be
15619            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15620            */
15621
15622           /**
15623            *  @ngdoc object
15624            *  @name enableCellEdit
15625            *  @propertyOf  ui.grid.edit.api:ColumnDef
15626            *  @description enable editing on column
15627            */
15628           colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
15629             (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
15630
15631           /**
15632            *  @ngdoc object
15633            *  @name cellEditableCondition
15634            *  @propertyOf  ui.grid.edit.api:ColumnDef
15635            *  @description If specified, either a value or function evaluated before editing cell.  If falsy, then editing of cell is not allowed.
15636            *  @example
15637            *  <pre>
15638            *  function($scope){
15639            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
15640            *    return true;
15641            *  }
15642            *  </pre>
15643            */
15644           colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition :  colDef.cellEditableCondition;
15645
15646           /**
15647            *  @ngdoc object
15648            *  @name editableCellTemplate
15649            *  @propertyOf  ui.grid.edit.api:ColumnDef
15650            *  @description cell template to be used when editing this column. Can be Url or text template
15651            *  <br/>Defaults to gridOptions.editableCellTemplate
15652            */
15653           if (colDef.enableCellEdit) {
15654             colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
15655
15656             promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
15657               .then(
15658               function (template) {
15659                 col.editableCellTemplate = template;
15660               },
15661               function (res) {
15662                 // Todo handle response error here?
15663                 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
15664               }));
15665           }
15666
15667           /**
15668            *  @ngdoc object
15669            *  @name enableCellEditOnFocus
15670            *  @propertyOf  ui.grid.edit.api:ColumnDef
15671            *  @requires ui.grid.cellNav
15672            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
15673            *  <br>_requires both the cellNav feature and the edit feature to be enabled_
15674            */
15675             //enableCellEditOnFocus can only be used if cellnav module is used
15676           colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
15677
15678
15679           /**
15680            *  @ngdoc string
15681            *  @name editModelField
15682            *  @propertyOf  ui.grid.edit.api:ColumnDef
15683            *  @description a bindable string value that is used when binding to edit controls instead of colDef.field
15684            *  <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}.  The
15685            *  grid should display state.name in the cell and sort/filter based on the state.name property but the editor
15686            *  requires the full state object.
15687            *  <br/>colDef.field = 'state.name'
15688            *  <br/>colDef.editModelField = 'state'
15689            */
15690           //colDef.editModelField
15691
15692           return $q.all(promises);
15693         },
15694
15695         /**
15696          * @ngdoc service
15697          * @name isStartEditKey
15698          * @methodOf ui.grid.edit.service:uiGridEditService
15699          * @description  Determines if a keypress should start editing.  Decorate this service to override with your
15700          * own key events.  See service decorator in angular docs.
15701          * @param {Event} evt keydown event
15702          * @returns {boolean} true if an edit should start
15703          */
15704         isStartEditKey: function (evt) {
15705           if (evt.metaKey ||
15706               evt.keyCode === uiGridConstants.keymap.ESC ||
15707               evt.keyCode === uiGridConstants.keymap.SHIFT ||
15708               evt.keyCode === uiGridConstants.keymap.CTRL ||
15709               evt.keyCode === uiGridConstants.keymap.ALT ||
15710               evt.keyCode === uiGridConstants.keymap.WIN ||
15711               evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
15712
15713              evt.keyCode === uiGridConstants.keymap.LEFT ||
15714             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
15715
15716             evt.keyCode === uiGridConstants.keymap.RIGHT ||
15717             evt.keyCode === uiGridConstants.keymap.TAB ||
15718
15719             evt.keyCode === uiGridConstants.keymap.UP ||
15720             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
15721
15722             evt.keyCode === uiGridConstants.keymap.DOWN ||
15723             evt.keyCode === uiGridConstants.keymap.ENTER) {
15724             return false;
15725
15726           }
15727           return true;
15728         }
15729
15730
15731       };
15732
15733       return service;
15734
15735     }]);
15736
15737   /**
15738    *  @ngdoc directive
15739    *  @name ui.grid.edit.directive:uiGridEdit
15740    *  @element div
15741    *  @restrict A
15742    *
15743    *  @description Adds editing features to the ui-grid directive.
15744    *
15745    *  @example
15746    <example module="app">
15747    <file name="app.js">
15748    var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
15749
15750    app.controller('MainCtrl', ['$scope', function ($scope) {
15751       $scope.data = [
15752         { name: 'Bob', title: 'CEO' },
15753             { name: 'Frank', title: 'Lowly Developer' }
15754       ];
15755
15756       $scope.columnDefs = [
15757         {name: 'name', enableCellEdit: true},
15758         {name: 'title', enableCellEdit: true}
15759       ];
15760     }]);
15761    </file>
15762    <file name="index.html">
15763    <div ng-controller="MainCtrl">
15764    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
15765    </div>
15766    </file>
15767    </example>
15768    */
15769   module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
15770     return {
15771       replace: true,
15772       priority: 0,
15773       require: '^uiGrid',
15774       scope: false,
15775       compile: function () {
15776         return {
15777           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15778             uiGridEditService.initializeGrid(uiGridCtrl.grid);
15779           },
15780           post: function ($scope, $elm, $attrs, uiGridCtrl) {
15781           }
15782         };
15783       }
15784     };
15785   }]);
15786
15787   /**
15788    *  @ngdoc directive
15789    *  @name ui.grid.edit.directive:uiGridRenderContainer
15790    *  @element div
15791    *  @restrict A
15792    *
15793    *  @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
15794    *
15795    */
15796   module.directive('uiGridViewport', [ 'uiGridEditConstants',
15797     function ( uiGridEditConstants) {
15798       return {
15799         replace: true,
15800         priority: -99998, //run before cellNav
15801         require: ['^uiGrid', '^uiGridRenderContainer'],
15802         scope: false,
15803         compile: function () {
15804           return {
15805             post: function ($scope, $elm, $attrs, controllers) {
15806               var uiGridCtrl = controllers[0];
15807
15808               // Skip attaching if edit and cellNav is not enabled
15809               if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
15810
15811               var containerId =  controllers[1].containerId;
15812               //no need to process for other containers
15813               if (containerId !== 'body') {
15814                 return;
15815               }
15816
15817               //refocus on the grid
15818               $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
15819                 uiGridCtrl.focus();
15820               });
15821               $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
15822                 uiGridCtrl.focus();
15823               });
15824
15825             }
15826           };
15827         }
15828       };
15829     }]);
15830
15831   /**
15832    *  @ngdoc directive
15833    *  @name ui.grid.edit.directive:uiGridCell
15834    *  @element div
15835    *  @restrict A
15836    *
15837    *  @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
15838    *  Editing Actions.
15839    *
15840    *  Binds edit start events to the uiGridCell element.  When the events fire, the gridCell element is appended
15841    *  with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
15842    *
15843    *  The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
15844    *  and do the initial steps needed to edit the cell (setfocus on input element, etc).
15845    *
15846    *  When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
15847    *  it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
15848    *
15849    *  If editableCellTemplate recognizes that the editing has been cancelled (esc key)
15850    *  it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event.  The original value
15851    *  will be set back on the model by the uiGridCell directive.
15852    *
15853    *  Events that invoke editing:
15854    *    - dblclick
15855    *    - F2 keydown (when using cell selection)
15856    *
15857    *  Events that end editing:
15858    *    - Dependent on the specific editableCellTemplate
15859    *    - Standards should be blur and enter keydown
15860    *
15861    *  Events that cancel editing:
15862    *    - Dependent on the specific editableCellTemplate
15863    *    - Standards should be Esc keydown
15864    *
15865    *  Grid Events that end editing:
15866    *    - uiGridConstants.events.GRID_SCROLL
15867    *
15868    */
15869
15870   /**
15871    *  @ngdoc object
15872    *  @name ui.grid.edit.api:GridRow
15873    *
15874    *  @description GridRow options for edit feature, these are available to be
15875    *  set internally only, by other features
15876    */
15877
15878   /**
15879    *  @ngdoc object
15880    *  @name enableCellEdit
15881    *  @propertyOf  ui.grid.edit.api:GridRow
15882    *  @description enable editing on row, grouping for example might disable editing on group header rows
15883    */
15884
15885   module.directive('uiGridCell',
15886     ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
15887       function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
15888         var touchstartTimeout = 500;
15889         if ($injector.has('uiGridCellNavService')) {
15890           var uiGridCellNavService = $injector.get('uiGridCellNavService');
15891         }
15892
15893         return {
15894           priority: -100, // run after default uiGridCell directive
15895           restrict: 'A',
15896           scope: false,
15897           require: '?^uiGrid',
15898           link: function ($scope, $elm, $attrs, uiGridCtrl) {
15899             var html;
15900             var origCellValue;
15901             var inEdit = false;
15902             var cellModel;
15903             var cancelTouchstartTimeout;
15904
15905             var editCellScope;
15906
15907             if (!$scope.col.colDef.enableCellEdit) {
15908               return;
15909             }
15910
15911             var cellNavNavigateDereg = function() {};
15912             var viewPortKeyDownDereg = function() {};
15913
15914
15915             var setEditable = function() {
15916               if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
15917                 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
15918                   registerBeginEditEvents();
15919                 }
15920               } else {
15921                 if ($scope.beginEditEventsWired) {
15922                   cancelBeginEditEvents();
15923                 }
15924               }
15925             };
15926
15927             setEditable();
15928
15929             var rowWatchDereg = $scope.$watch('row', function (n, o) {
15930               if (n !== o) {
15931                 setEditable();
15932               }
15933             });
15934
15935
15936             $scope.$on( '$destroy', rowWatchDereg );
15937
15938             function registerBeginEditEvents() {
15939               $elm.on('dblclick', beginEdit);
15940
15941               // Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
15942               $elm.on('touchstart', touchStart);
15943
15944               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
15945
15946                 viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
15947                   if (rowCol === null) {
15948                     return;
15949                   }
15950
15951                   if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
15952                     //important to do this before scrollToIfNecessary
15953                     beginEditKeyDown(evt);
15954                   }
15955                 });
15956
15957                 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol) {
15958                   if ($scope.col.colDef.enableCellEditOnFocus) {
15959                     // Don't begin edit if the cell hasn't changed
15960                     if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
15961                       newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
15962                       $timeout(function () {
15963                         beginEdit();
15964                       });
15965                     }
15966                   }
15967                 });
15968               }
15969
15970               $scope.beginEditEventsWired = true;
15971
15972             }
15973
15974             function touchStart(event) {
15975               // jQuery masks events
15976               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
15977                 event = event.originalEvent;
15978               }
15979
15980               // Bind touchend handler
15981               $elm.on('touchend', touchEnd);
15982
15983               // Start a timeout
15984               cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
15985
15986               // Timeout's done! Start the edit
15987               cancelTouchstartTimeout.then(function () {
15988                 // Use setTimeout to start the edit because beginEdit expects to be outside of $digest
15989                 setTimeout(beginEdit, 0);
15990
15991                 // Undbind the touchend handler, we don't need it anymore
15992                 $elm.off('touchend', touchEnd);
15993               });
15994             }
15995
15996             // Cancel any touchstart timeout
15997             function touchEnd(event) {
15998               $timeout.cancel(cancelTouchstartTimeout);
15999               $elm.off('touchend', touchEnd);
16000             }
16001
16002             function cancelBeginEditEvents() {
16003               $elm.off('dblclick', beginEdit);
16004               $elm.off('keydown', beginEditKeyDown);
16005               $elm.off('touchstart', touchStart);
16006               cellNavNavigateDereg();
16007               viewPortKeyDownDereg();
16008               $scope.beginEditEventsWired = false;
16009             }
16010
16011             function beginEditKeyDown(evt) {
16012               if (uiGridEditService.isStartEditKey(evt)) {
16013                 beginEdit(evt);
16014               }
16015             }
16016
16017             function shouldEdit(col, row) {
16018               return !row.isSaving &&
16019                 ( angular.isFunction(col.colDef.cellEditableCondition) ?
16020                     col.colDef.cellEditableCondition($scope) :
16021                     col.colDef.cellEditableCondition );
16022             }
16023
16024
16025             function beginEdit(triggerEvent) {
16026               //we need to scroll the cell into focus before invoking the editor
16027               $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
16028                 .then(function () {
16029                   beginEditAfterScroll(triggerEvent);
16030                 });
16031             }
16032
16033             /**
16034              *  @ngdoc property
16035              *  @name editDropdownOptionsArray
16036              *  @propertyOf ui.grid.edit.api:ColumnDef
16037              *  @description an array of values in the format
16038              *  [ {id: xxx, value: xxx} ], which is populated
16039              *  into the edit dropdown
16040              *
16041              */
16042             /**
16043              *  @ngdoc property
16044              *  @name editDropdownIdLabel
16045              *  @propertyOf ui.grid.edit.api:ColumnDef
16046              *  @description the label for the "id" field
16047              *  in the editDropdownOptionsArray.  Defaults
16048              *  to 'id'
16049              *  @example
16050              *  <pre>
16051              *    $scope.gridOptions = {
16052              *      columnDefs: [
16053              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16054              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16055              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16056              *      ],
16057              *  </pre>
16058              *
16059              */
16060             /**
16061              *  @ngdoc property
16062              *  @name editDropdownRowEntityOptionsArrayPath
16063              *  @propertyOf ui.grid.edit.api:ColumnDef
16064              *  @description a path to a property on row.entity containing an
16065              *  array of values in the format
16066              *  [ {id: xxx, value: xxx} ], which will be used to populate
16067              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16068              *  the backing row entity.
16069              *  If this property is set then editDropdownOptionsArray will be ignored.
16070              *  @example
16071              *  <pre>
16072              *    $scope.gridOptions = {
16073              *      columnDefs: [
16074              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16075              *          editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
16076              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16077              *      ],
16078              *  </pre>
16079              *
16080              */
16081             /**
16082              *  @ngdoc service
16083              *  @name editDropdownOptionsFunction
16084              *  @methodOf ui.grid.edit.api:ColumnDef
16085              *  @description a function returning an array of values in the format
16086              *  [ {id: xxx, value: xxx} ], which will be used to populate
16087              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16088              *  the backing row entity with some kind of algorithm.
16089              *  If this property is set then both editDropdownOptionsArray and 
16090              *  editDropdownRowEntityOptionsArrayPath will be ignored.
16091              *  @param {object} rowEntity the options.data element that the returned array refers to
16092              *  @param {object} colDef the column that implements this dropdown
16093              *  @returns {object} an array of values in the format
16094              *  [ {id: xxx, value: xxx} ] used to populate the edit dropdown
16095              *  @example
16096              *  <pre>
16097              *    $scope.gridOptions = {
16098              *      columnDefs: [
16099              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16100              *          editDropdownOptionsFunction: function(rowEntity, colDef) {
16101              *            if (rowEntity.foo === 'bar') {
16102              *              return [{id: 'bar1', value: 'BAR 1'},
16103              *                      {id: 'bar2', value: 'BAR 2'},
16104              *                      {id: 'bar3', value: 'BAR 3'}];
16105              *            } else {
16106              *              return [{id: 'foo1', value: 'FOO 1'},
16107              *                      {id: 'foo2', value: 'FOO 2'}];
16108              *            }
16109              *          },
16110              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16111              *      ],
16112              *  </pre>
16113              *
16114              */
16115             /**
16116              *  @ngdoc property
16117              *  @name editDropdownValueLabel
16118              *  @propertyOf ui.grid.edit.api:ColumnDef
16119              *  @description the label for the "value" field
16120              *  in the editDropdownOptionsArray.  Defaults
16121              *  to 'value'
16122              *  @example
16123              *  <pre>
16124              *    $scope.gridOptions = {
16125              *      columnDefs: [
16126              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16127              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16128              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16129              *      ],
16130              *  </pre>
16131              *
16132              */
16133             /**
16134              *  @ngdoc property
16135              *  @name editDropdownFilter
16136              *  @propertyOf ui.grid.edit.api:ColumnDef
16137              *  @description A filter that you would like to apply to the values in the options list
16138              *  of the dropdown.  For example if you were using angular-translate you might set this
16139              *  to `'translate'`
16140              *  @example
16141              *  <pre>
16142              *    $scope.gridOptions = {
16143              *      columnDefs: [
16144              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16145              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16146              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
16147              *      ],
16148              *  </pre>
16149              *
16150              */
16151             function beginEditAfterScroll(triggerEvent) {
16152               // If we are already editing, then just skip this so we don't try editing twice...
16153               if (inEdit) {
16154                 return;
16155               }
16156
16157               if (!shouldEdit($scope.col, $scope.row)) {
16158                 return;
16159               }
16160
16161
16162               cellModel = $parse($scope.row.getQualifiedColField($scope.col));
16163               //get original value from the cell
16164               origCellValue = cellModel($scope);
16165
16166               html = $scope.col.editableCellTemplate;
16167
16168               if ($scope.col.colDef.editModelField) {
16169                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField));
16170               }
16171               else {
16172                 html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
16173               }
16174
16175               html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
16176
16177               var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
16178               html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
16179
16180               var inputType = 'text';
16181               switch ($scope.col.colDef.type){
16182                 case 'boolean':
16183                   inputType = 'checkbox';
16184                   break;
16185                 case 'number':
16186                   inputType = 'number';
16187                   break;
16188                 case 'date':
16189                   inputType = 'date';
16190                   break;
16191               }
16192               html = html.replace('INPUT_TYPE', inputType);
16193
16194               // In order to fill dropdown options we use:
16195               // - A function/promise or
16196               // - An array inside of row entity if no function exists or
16197               // - A single array for the whole column if none of the previous exists.
16198               var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
16199               if (editDropdownOptionsFunction) {
16200                 $q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
16201                         .then(function(result) {
16202                   $scope.editDropdownOptionsArray = result;
16203                 });
16204               } else {
16205                 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
16206                 if (editDropdownRowEntityOptionsArrayPath) {
16207                   $scope.editDropdownOptionsArray =  resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
16208                 }
16209                 else {
16210                   $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
16211                 }
16212               }
16213               $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
16214               $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
16215
16216               var cellElement;
16217               var createEditor = function(){
16218                 inEdit = true;
16219                 cancelBeginEditEvents();
16220                 var cellElement = angular.element(html);
16221                 $elm.append(cellElement);
16222                 editCellScope = $scope.$new();
16223                 $compile(cellElement)(editCellScope);
16224                 var gridCellContentsEl = angular.element($elm.children()[0]);
16225                 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
16226               };
16227               if (!$rootScope.$$phase) {
16228                 $scope.$apply(createEditor);
16229               } else {
16230                 createEditor();
16231               }
16232
16233               //stop editing when grid is scrolled
16234               var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
16235                 if ($scope.grid.disableScrolling) {
16236                   return;
16237                 }
16238                 endEdit();
16239                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16240                 deregOnGridScroll();
16241                 deregOnEndCellEdit();
16242                 deregOnCancelCellEdit();
16243               });
16244
16245               //end editing
16246               var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16247                 endEdit();
16248                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16249                 deregOnEndCellEdit();
16250                 deregOnGridScroll();
16251                 deregOnCancelCellEdit();
16252               });
16253
16254               //cancel editing
16255               var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16256                 cancelEdit();
16257                 deregOnCancelCellEdit();
16258                 deregOnGridScroll();
16259                 deregOnEndCellEdit();
16260               });
16261
16262               $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
16263               $timeout(function () {
16264                 //execute in a timeout to give any complex editor templates a cycle to completely render
16265                 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
16266               });
16267             }
16268
16269             function endEdit() {
16270               $scope.grid.disableScrolling = false;
16271               if (!inEdit) {
16272                 return;
16273               }
16274
16275               //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
16276               //back to grid here. The focus call needs to be before the $destroy and removal of the control,
16277               //otherwise ng-model-options of UpdateOn: 'blur' will not work.
16278               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16279                 uiGridCtrl.focus();
16280               }
16281
16282               var gridCellContentsEl = angular.element($elm.children()[0]);
16283               //remove edit element
16284               editCellScope.$destroy();
16285               angular.element($elm.children()[1]).remove();
16286               gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
16287               inEdit = false;
16288               registerBeginEditEvents();
16289               $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
16290             }
16291
16292             function cancelEdit() {
16293               $scope.grid.disableScrolling = false;
16294               if (!inEdit) {
16295                 return;
16296               }
16297               cellModel.assign($scope, origCellValue);
16298               $scope.$apply();
16299
16300               $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
16301               endEdit();
16302             }
16303
16304             // resolves a string path against the given object
16305             // shamelessly borrowed from
16306             // http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
16307             function resolveObjectFromPath(object, path) {
16308               path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
16309               path = path.replace(/^\./, '');           // strip a leading dot
16310               var a = path.split('.');
16311               while (a.length) {
16312                   var n = a.shift();
16313                   if (n in object) {
16314                       object = object[n];
16315                   } else {
16316                       return;
16317                   }
16318               }
16319               return object;
16320             }
16321
16322           }
16323         };
16324       }]);
16325
16326   /**
16327    *  @ngdoc directive
16328    *  @name ui.grid.edit.directive:uiGridEditor
16329    *  @element div
16330    *  @restrict A
16331    *
16332    *  @description input editor directive for editable fields.
16333    *  Provides EndEdit and CancelEdit events
16334    *
16335    *  Events that end editing:
16336    *     blur and enter keydown
16337    *
16338    *  Events that cancel editing:
16339    *    - Esc keydown
16340    *
16341    */
16342   module.directive('uiGridEditor',
16343     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
16344       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
16345         return {
16346           scope: true,
16347           require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
16348           compile: function () {
16349             return {
16350               pre: function ($scope, $elm, $attrs) {
16351
16352               },
16353               post: function ($scope, $elm, $attrs, controllers) {
16354                 var uiGridCtrl, renderContainerCtrl, ngModel;
16355                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16356                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16357                 if (controllers[2]) { ngModel = controllers[2]; }
16358
16359                 //set focus at start of edit
16360                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
16361                   $timeout(function () {
16362                     $elm[0].focus();
16363                     //only select text if it is not being replaced below in the cellNav viewPortKeyPress
16364                     if ($elm[0].select && $scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav)) {
16365                       $elm[0].select();
16366                     }
16367                     else {
16368                       //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
16369                       //fields should not allow setSelectionRange.  We ignore the error for those browsers
16370                       //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
16371                       try {
16372                         $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
16373                       }
16374                       catch (ex) {
16375                         //ignore
16376                       }
16377                     }
16378                   });
16379
16380                   //set the keystroke that started the edit event
16381                   //we must do this because the BeginEdit is done in a different event loop than the intitial
16382                   //keydown event
16383                   //fire this event for the keypress that is received
16384                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16385                     var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
16386                       if (uiGridEditService.isStartEditKey(evt)) {
16387                         ngModel.$setViewValue(String.fromCharCode( typeof evt.which === 'number' ? evt.which : evt.keyCode), evt);
16388                         ngModel.$render();
16389                       }
16390                       viewPortKeyDownUnregister();
16391                     });
16392                   }
16393
16394                   $elm.on('blur', function (evt) {
16395                     $scope.stopEdit(evt);
16396                   });
16397                 });
16398
16399
16400                 $scope.deepEdit = false;
16401
16402                 $scope.stopEdit = function (evt) {
16403                   if ($scope.inputForm && !$scope.inputForm.$valid) {
16404                     evt.stopPropagation();
16405                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16406                   }
16407                   else {
16408                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16409                   }
16410                   $scope.deepEdit = false;
16411                 };
16412
16413
16414                 $elm.on('click', function (evt) {
16415                   if ($elm[0].type !== 'checkbox') {
16416                     $scope.deepEdit = true;
16417                     $timeout(function () {
16418                       $scope.grid.disableScrolling = true;
16419                     });
16420                   }
16421                 });
16422
16423                 $elm.on('keydown', function (evt) {
16424                   switch (evt.keyCode) {
16425                     case uiGridConstants.keymap.ESC:
16426                       evt.stopPropagation();
16427                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16428                       break;
16429                   }
16430
16431                   if ($scope.deepEdit &&
16432                     (evt.keyCode === uiGridConstants.keymap.LEFT ||
16433                      evt.keyCode === uiGridConstants.keymap.RIGHT ||
16434                      evt.keyCode === uiGridConstants.keymap.UP ||
16435                      evt.keyCode === uiGridConstants.keymap.DOWN)) {
16436                     evt.stopPropagation();
16437                   }
16438                   // Pass the keydown event off to the cellNav service, if it exists
16439                   else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16440                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16441                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16442                       $scope.stopEdit(evt);
16443                     }
16444                   }
16445                   else {
16446                     //handle enter and tab for editing not using cellNav
16447                     switch (evt.keyCode) {
16448                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16449                       case uiGridConstants.keymap.TAB:
16450                         evt.stopPropagation();
16451                         evt.preventDefault();
16452                         $scope.stopEdit(evt);
16453                         break;
16454                     }
16455                   }
16456
16457                   return true;
16458                 });
16459               }
16460             };
16461           }
16462         };
16463       }]);
16464
16465   /**
16466    *  @ngdoc directive
16467    *  @name ui.grid.edit.directive:input
16468    *  @element input
16469    *  @restrict E
16470    *
16471    *  @description directive to provide binding between input[date] value and ng-model for angular 1.2
16472    *  It is similar to input[date] directive of angular 1.3
16473    *
16474    *  Supported date format for input is 'yyyy-MM-dd'
16475    *  The directive will set the $valid property of input element and the enclosing form to false if
16476    *  model is invalid date or value of input is entered wrong.
16477    *
16478    */
16479     module.directive('uiGridEditor', ['$filter', function ($filter) {
16480       function parseDateString(dateString) {
16481         if (typeof(dateString) === 'undefined' || dateString === '') {
16482           return null;
16483         }
16484         var parts = dateString.split('-');
16485         if (parts.length !== 3) {
16486           return null;
16487         }
16488         var year = parseInt(parts[0], 10);
16489         var month = parseInt(parts[1], 10);
16490         var day = parseInt(parts[2], 10);
16491
16492         if (month < 1 || year < 1 || day < 1) {
16493           return null;
16494         }
16495         return new Date(year, (month - 1), day);
16496       }
16497       return {
16498         priority: -100, // run after default uiGridEditor directive
16499         require: '?ngModel',
16500         link: function (scope, element, attrs, ngModel) {
16501
16502           if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
16503
16504             ngModel.$formatters.push(function (modelValue) {
16505               ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
16506               return $filter('date')(modelValue, 'yyyy-MM-dd');
16507             });
16508
16509             ngModel.$parsers.push(function (viewValue) {
16510               if (viewValue && viewValue.length > 0) {
16511                 var dateValue = parseDateString(viewValue);
16512                 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
16513                 return dateValue;
16514               }
16515               else {
16516                 ngModel.$setValidity(null, true);
16517                 return null;
16518               }
16519             });
16520           }
16521         }
16522       };
16523     }]);
16524
16525
16526   /**
16527    *  @ngdoc directive
16528    *  @name ui.grid.edit.directive:uiGridEditDropdown
16529    *  @element div
16530    *  @restrict A
16531    *
16532    *  @description dropdown editor for editable fields.
16533    *  Provides EndEdit and CancelEdit events
16534    *
16535    *  Events that end editing:
16536    *     blur and enter keydown, and any left/right nav
16537    *
16538    *  Events that cancel editing:
16539    *    - Esc keydown
16540    *
16541    */
16542   module.directive('uiGridEditDropdown',
16543     ['uiGridConstants', 'uiGridEditConstants',
16544       function (uiGridConstants, uiGridEditConstants) {
16545         return {
16546           require: ['?^uiGrid', '?^uiGridRenderContainer'],
16547           scope: true,
16548           compile: function () {
16549             return {
16550               pre: function ($scope, $elm, $attrs) {
16551
16552               },
16553               post: function ($scope, $elm, $attrs, controllers) {
16554                 var uiGridCtrl = controllers[0];
16555                 var renderContainerCtrl = controllers[1];
16556
16557                 //set focus at start of edit
16558                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16559                   $elm[0].focus();
16560                   $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
16561                   $elm.on('blur', function (evt) {
16562                     $scope.stopEdit(evt);
16563                   });
16564                 });
16565
16566
16567                 $scope.stopEdit = function (evt) {
16568                   // no need to validate a dropdown - invalid values shouldn't be
16569                   // available in the list
16570                   $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16571                 };
16572
16573                 $elm.on('keydown', function (evt) {
16574                   switch (evt.keyCode) {
16575                     case uiGridConstants.keymap.ESC:
16576                       evt.stopPropagation();
16577                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16578                       break;
16579                   }
16580                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16581                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16582                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16583                       $scope.stopEdit(evt);
16584                     }
16585                   }
16586                   else {
16587                     //handle enter and tab for editing not using cellNav
16588                     switch (evt.keyCode) {
16589                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16590                       case uiGridConstants.keymap.TAB:
16591                         evt.stopPropagation();
16592                         evt.preventDefault();
16593                         $scope.stopEdit(evt);
16594                         break;
16595                     }
16596                   }
16597                   return true;
16598                 });
16599               }
16600             };
16601           }
16602         };
16603       }]);
16604
16605   /**
16606    *  @ngdoc directive
16607    *  @name ui.grid.edit.directive:uiGridEditFileChooser
16608    *  @element div
16609    *  @restrict A
16610    *
16611    *  @description input editor directive for editable fields.
16612    *  Provides EndEdit and CancelEdit events
16613    *
16614    *  Events that end editing:
16615    *     blur and enter keydown
16616    *
16617    *  Events that cancel editing:
16618    *    - Esc keydown
16619    *
16620    */
16621   module.directive('uiGridEditFileChooser',
16622     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
16623       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
16624         return {
16625           scope: true,
16626           require: ['?^uiGrid', '?^uiGridRenderContainer'],
16627           compile: function () {
16628             return {
16629               pre: function ($scope, $elm, $attrs) {
16630
16631               },
16632               post: function ($scope, $elm, $attrs, controllers) {
16633                 var uiGridCtrl, renderContainerCtrl;
16634                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16635                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16636                 var grid = uiGridCtrl.grid;
16637
16638                 var handleFileSelect = function( event ){
16639                   var target = event.srcElement || event.target;
16640
16641                   if (target && target.files && target.files.length > 0) {
16642                     /**
16643                      *  @ngdoc property
16644                      *  @name editFileChooserCallback
16645                      *  @propertyOf  ui.grid.edit.api:ColumnDef
16646                      *  @description A function that should be called when any files have been chosen
16647                      *  by the user.  You should use this to process the files appropriately for your
16648                      *  application.
16649                      *
16650                      *  It passes the gridCol, the gridRow (from which you can get gridRow.entity),
16651                      *  and the files.  The files are in the format as returned from the file chooser,
16652                      *  an array of files, with each having useful information such as:
16653                      *  - `files[0].lastModifiedDate`
16654                      *  - `files[0].name`
16655                      *  - `files[0].size`  (appears to be in bytes)
16656                      *  - `files[0].type`  (MIME type by the looks)
16657                      *
16658                      *  Typically you would do something with these files - most commonly you would
16659                      *  use the filename or read the file itself in.  The example function does both.
16660                      *
16661                      *  @example
16662                      *  <pre>
16663                      *  editFileChooserCallBack: function(gridRow, gridCol, files ){
16664                      *    // ignore all but the first file, it can only choose one anyway
16665                      *    // set the filename into this column
16666                      *    gridRow.entity.filename = file[0].name;
16667                      *
16668                      *    // read the file and set it into a hidden column, which we may do stuff with later
16669                      *    var setFile = function(fileContent){
16670                      *      gridRow.entity.file = fileContent.currentTarget.result;
16671                      *    };
16672                      *    var reader = new FileReader();
16673                      *    reader.onload = setFile;
16674                      *    reader.readAsText( files[0] );
16675                      *  }
16676                      *  </pre>
16677                      */
16678                     if ( typeof($scope.col.colDef.editFileChooserCallback) === 'function' ) {
16679                       $scope.col.colDef.editFileChooserCallback($scope.row, $scope.col, target.files);
16680                     } else {
16681                       gridUtil.logError('You need to set colDef.editFileChooserCallback to use the file chooser');
16682                     }
16683
16684                     target.form.reset();
16685                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16686                   } else {
16687                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16688                   }
16689                 };
16690
16691                 $elm[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
16692
16693                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16694                   $elm[0].focus();
16695                   $elm[0].select();
16696
16697                   $elm.on('blur', function (evt) {
16698                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16699                   });
16700                 });
16701               }
16702             };
16703           }
16704         };
16705       }]);
16706
16707
16708 })();
16709
16710 (function () {
16711   'use strict';
16712
16713   /**
16714    * @ngdoc overview
16715    * @name ui.grid.expandable
16716    * @description
16717    *
16718    * # ui.grid.expandable
16719    *
16720    * <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>
16721    *
16722    * This module provides the ability to create subgrids with the ability to expand a row
16723    * to show the subgrid.
16724    *
16725    * <div doc-module-components="ui.grid.expandable"></div>
16726    */
16727   var module = angular.module('ui.grid.expandable', ['ui.grid']);
16728
16729   /**
16730    *  @ngdoc service
16731    *  @name ui.grid.expandable.service:uiGridExpandableService
16732    *
16733    *  @description Services for the expandable grid
16734    */
16735   module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
16736     var service = {
16737       initializeGrid: function (grid) {
16738
16739         grid.expandable = {};
16740         grid.expandable.expandedAll = false;
16741
16742         /**
16743          *  @ngdoc object
16744          *  @name enableExpandable
16745          *  @propertyOf  ui.grid.expandable.api:GridOptions
16746          *  @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
16747          *  within your application, or in specific modes on _this_ grid. Defaults to true.
16748          *  @example
16749          *  <pre>
16750          *    $scope.gridOptions = {
16751          *      enableExpandable: false
16752          *    }
16753          *  </pre>
16754          */
16755         grid.options.enableExpandable = grid.options.enableExpandable !== false;
16756
16757         /**
16758          *  @ngdoc object
16759          *  @name expandableRowHeight
16760          *  @propertyOf  ui.grid.expandable.api:GridOptions
16761          *  @description Height in pixels of the expanded subgrid.  Defaults to
16762          *  150
16763          *  @example
16764          *  <pre>
16765          *    $scope.gridOptions = {
16766          *      expandableRowHeight: 150
16767          *    }
16768          *  </pre>
16769          */
16770         grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
16771
16772         /**
16773          *  @ngdoc object
16774          *  @name
16775          *  @propertyOf  ui.grid.expandable.api:GridOptions
16776          *  @description Width in pixels of the expandable column. Defaults to 40
16777          *  @example
16778          *  <pre>
16779          *    $scope.gridOptions = {
16780          *      expandableRowHeaderWidth: 40
16781          *    }
16782          *  </pre>
16783          */
16784         grid.options.expandableRowHeaderWidth = grid.options.expandableRowHeaderWidth || 40;
16785
16786         /**
16787          *  @ngdoc object
16788          *  @name expandableRowTemplate
16789          *  @propertyOf  ui.grid.expandable.api:GridOptions
16790          *  @description Mandatory. The template for your expanded row
16791          *  @example
16792          *  <pre>
16793          *    $scope.gridOptions = {
16794          *      expandableRowTemplate: 'expandableRowTemplate.html'
16795          *    }
16796          *  </pre>
16797          */
16798         if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
16799           gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
16800           grid.options.enableExpandable = false;
16801         }
16802
16803         /**
16804          *  @ngdoc object
16805          *  @name ui.grid.expandable.api:PublicApi
16806          *
16807          *  @description Public Api for expandable feature
16808          */
16809         /**
16810          *  @ngdoc object
16811          *  @name ui.grid.expandable.api:GridRow
16812          *
16813          *  @description Additional properties added to GridRow when using the expandable module
16814          */
16815         /**
16816          *  @ngdoc object
16817          *  @name ui.grid.expandable.api:GridOptions
16818          *
16819          *  @description Options for configuring the expandable feature, these are available to be
16820          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16821          */
16822         var publicApi = {
16823           events: {
16824             expandable: {
16825               /**
16826                * @ngdoc event
16827                * @name rowExpandedStateChanged
16828                * @eventOf  ui.grid.expandable.api:PublicApi
16829                * @description raised when cell editing is complete
16830                * <pre>
16831                *      gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
16832                * </pre>
16833                * @param {GridRow} row the row that was expanded
16834                */
16835               rowExpandedBeforeStateChanged: function(scope,row){
16836               },
16837               rowExpandedStateChanged: function (scope, row) {
16838               }
16839             }
16840           },
16841
16842           methods: {
16843             expandable: {
16844               /**
16845                * @ngdoc method
16846                * @name toggleRowExpansion
16847                * @methodOf  ui.grid.expandable.api:PublicApi
16848                * @description Toggle a specific row
16849                * <pre>
16850                *      gridApi.expandable.toggleRowExpansion(rowEntity);
16851                * </pre>
16852                * @param {object} rowEntity the data entity for the row you want to expand
16853                */
16854               toggleRowExpansion: function (rowEntity) {
16855                 var row = grid.getRow(rowEntity);
16856                 if (row !== null) {
16857                   service.toggleRowExpansion(grid, row);
16858                 }
16859               },
16860
16861               /**
16862                * @ngdoc method
16863                * @name expandAllRows
16864                * @methodOf  ui.grid.expandable.api:PublicApi
16865                * @description Expand all subgrids.
16866                * <pre>
16867                *      gridApi.expandable.expandAllRows();
16868                * </pre>
16869                */
16870               expandAllRows: function() {
16871                 service.expandAllRows(grid);
16872               },
16873
16874               /**
16875                * @ngdoc method
16876                * @name collapseAllRows
16877                * @methodOf  ui.grid.expandable.api:PublicApi
16878                * @description Collapse all subgrids.
16879                * <pre>
16880                *      gridApi.expandable.collapseAllRows();
16881                * </pre>
16882                */
16883               collapseAllRows: function() {
16884                 service.collapseAllRows(grid);
16885               },
16886
16887               /**
16888                * @ngdoc method
16889                * @name toggleAllRows
16890                * @methodOf  ui.grid.expandable.api:PublicApi
16891                * @description Toggle all subgrids.
16892                * <pre>
16893                *      gridApi.expandable.toggleAllRows();
16894                * </pre>
16895                */
16896               toggleAllRows: function() {
16897                 service.toggleAllRows(grid);
16898               },
16899               /**
16900                * @ngdoc function
16901                * @name expandRow
16902                * @methodOf  ui.grid.expandable.api:PublicApi
16903                * @description Expand the data row
16904                * @param {object} rowEntity gridOptions.data[] array instance
16905                */
16906               expandRow: function (rowEntity) {
16907                 var row = grid.getRow(rowEntity);
16908                 if (row !== null && !row.isExpanded) {
16909                   service.toggleRowExpansion(grid, row);
16910                 }
16911               },
16912               /**
16913                * @ngdoc function
16914                * @name collapseRow
16915                * @methodOf  ui.grid.expandable.api:PublicApi
16916                * @description Collapse the data row
16917                * @param {object} rowEntity gridOptions.data[] array instance
16918                */
16919               collapseRow: function (rowEntity) {
16920                 var row = grid.getRow(rowEntity);
16921                 if (row !== null && row.isExpanded) {
16922                   service.toggleRowExpansion(grid, row);
16923                 }
16924               },
16925               /**
16926                * @ngdoc function
16927                * @name getExpandedRows
16928                * @methodOf  ui.grid.expandable.api:PublicApi
16929                * @description returns all expandedRow's entity references
16930                */
16931               getExpandedRows: function () {
16932                 return service.getExpandedRows(grid).map(function (gridRow) {
16933                   return gridRow.entity;
16934                 });
16935               }
16936             }
16937           }
16938         };
16939         grid.api.registerEventsFromObject(publicApi.events);
16940         grid.api.registerMethodsFromObject(publicApi.methods);
16941       },
16942
16943       toggleRowExpansion: function (grid, row) {
16944         // trigger the "before change" event. Can change row height dynamically this way.
16945         grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
16946         /**
16947          *  @ngdoc object
16948          *  @name isExpanded
16949          *  @propertyOf  ui.grid.expandable.api:GridRow
16950          *  @description Whether or not the row is currently expanded.
16951          *  @example
16952          *  <pre>
16953          *    $scope.api.expandable.on.rowExpandedStateChanged($scope, function (row) {
16954          *      if (row.isExpanded) {
16955          *        //...
16956          *      }
16957          *    });
16958          *  </pre>
16959          */
16960         row.isExpanded = !row.isExpanded;
16961         if (angular.isUndefined(row.expandedRowHeight)){
16962           row.expandedRowHeight = grid.options.expandableRowHeight;
16963         }
16964
16965         if (row.isExpanded) {
16966           row.height = row.grid.options.rowHeight + row.expandedRowHeight;
16967         }
16968         else {
16969           row.height = row.grid.options.rowHeight;
16970           grid.expandable.expandedAll = false;
16971         }
16972         grid.api.expandable.raise.rowExpandedStateChanged(row);
16973       },
16974
16975       expandAllRows: function(grid, $scope) {
16976         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16977           if (!row.isExpanded) {
16978             service.toggleRowExpansion(grid, row);
16979           }
16980         });
16981         grid.expandable.expandedAll = true;
16982         grid.queueGridRefresh();
16983       },
16984
16985       collapseAllRows: function(grid) {
16986         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16987           if (row.isExpanded) {
16988             service.toggleRowExpansion(grid, row);
16989           }
16990         });
16991         grid.expandable.expandedAll = false;
16992         grid.queueGridRefresh();
16993       },
16994
16995       toggleAllRows: function(grid) {
16996         if (grid.expandable.expandedAll) {
16997           service.collapseAllRows(grid);
16998         }
16999         else {
17000           service.expandAllRows(grid);
17001         }
17002       },
17003
17004       getExpandedRows: function (grid) {
17005         return grid.rows.filter(function (row) {
17006           return row.isExpanded;
17007         });
17008       }
17009     };
17010     return service;
17011   }]);
17012
17013   /**
17014    *  @ngdoc object
17015    *  @name enableExpandableRowHeader
17016    *  @propertyOf  ui.grid.expandable.api:GridOptions
17017    *  @description Show a rowHeader to provide the expandable buttons.  If set to false then implies
17018    *  you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
17019    *  @example
17020    *  <pre>
17021    *    $scope.gridOptions = {
17022    *      enableExpandableRowHeader: false
17023    *    }
17024    *  </pre>
17025    */
17026   module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
17027     function (uiGridExpandableService, $templateCache) {
17028       return {
17029         replace: true,
17030         priority: 0,
17031         require: '^uiGrid',
17032         scope: false,
17033         compile: function () {
17034           return {
17035             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17036               if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
17037                 var expandableRowHeaderColDef = {
17038                   name: 'expandableButtons',
17039                   displayName: '',
17040                   exporterSuppressExport: true,
17041                   enableColumnResizing: false,
17042                   enableColumnMenu: false,
17043                   width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
17044                 };
17045                 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
17046                 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
17047                 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
17048               }
17049               uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
17050             },
17051             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17052             }
17053           };
17054         }
17055       };
17056     }]);
17057
17058   /**
17059    *  @ngdoc directive
17060    *  @name ui.grid.expandable.directive:uiGrid
17061    *  @description stacks on the uiGrid directive to register child grid with parent row when child is created
17062    */
17063   module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
17064     function (uiGridExpandableService, $templateCache) {
17065       return {
17066         replace: true,
17067         priority: 599,
17068         require: '^uiGrid',
17069         scope: false,
17070         compile: function () {
17071           return {
17072             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17073
17074               uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
17075                 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
17076                 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
17077
17078                   /**
17079                    *  @ngdoc directive
17080                    *  @name ui.grid.expandable.class:Grid
17081                    *  @description Additional Grid properties added by expandable module
17082                    */
17083
17084                   /**
17085                    *  @ngdoc object
17086                    *  @name parentRow
17087                    *  @propertyOf ui.grid.expandable.class:Grid
17088                    *  @description reference to the expanded parent row that owns this grid
17089                    */
17090                   uiGridCtrl.grid.parentRow = $scope.row;
17091
17092                   //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
17093                  // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
17094                  //   uiGridCtrl.grid.parentRow = newHeight;
17095                  // });
17096                 }
17097
17098               });
17099             },
17100             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17101
17102             }
17103           };
17104         }
17105       };
17106     }]);
17107
17108   /**
17109    *  @ngdoc directive
17110    *  @name ui.grid.expandable.directive:uiGridExpandableRow
17111    *  @description directive to render the expandable row template
17112    */
17113   module.directive('uiGridExpandableRow',
17114   ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
17115     function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
17116
17117       return {
17118         replace: false,
17119         priority: 0,
17120         scope: false,
17121
17122         compile: function () {
17123           return {
17124             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17125               gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
17126                 function (template) {
17127                   if ($scope.grid.options.expandableRowScope) {
17128                     var expandableRowScope = $scope.grid.options.expandableRowScope;
17129                     for (var property in expandableRowScope) {
17130                       if (expandableRowScope.hasOwnProperty(property)) {
17131                         $scope[property] = expandableRowScope[property];
17132                       }
17133                     }
17134                   }
17135                   var expandedRowElement = $compile(template)($scope);
17136                   $elm.append(expandedRowElement);
17137                   $scope.row.expandedRendered = true;
17138               });
17139             },
17140
17141             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17142               $scope.$on('$destroy', function() {
17143                 $scope.row.expandedRendered = false;
17144               });
17145             }
17146           };
17147         }
17148       };
17149     }]);
17150
17151   /**
17152    *  @ngdoc directive
17153    *  @name ui.grid.expandable.directive:uiGridRow
17154    *  @description stacks on the uiGridRow directive to add support for expandable rows
17155    */
17156   module.directive('uiGridRow',
17157     ['$compile', 'gridUtil', '$templateCache',
17158       function ($compile, gridUtil, $templateCache) {
17159         return {
17160           priority: -200,
17161           scope: false,
17162           compile: function ($elm, $attrs) {
17163             return {
17164               pre: function ($scope, $elm, $attrs, controllers) {
17165
17166                 $scope.expandableRow = {};
17167
17168                 $scope.expandableRow.shouldRenderExpand = function () {
17169                   var ret = $scope.colContainer.name === 'body' &&  $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
17170                   return ret;
17171                 };
17172
17173                 $scope.expandableRow.shouldRenderFiller = function () {
17174                   var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
17175                   return ret;
17176                 };
17177
17178  /*
17179   * Commented out @PaulL1.  This has no purpose that I can see, and causes #2964.  If this code needs to be reinstated for some
17180   * reason it needs to use drawnWidth, not width, and needs to check column visibility.  It should really use render container
17181   * visible column cache also instead of checking column.renderContainer.
17182                   function updateRowContainerWidth() {
17183                       var grid = $scope.grid;
17184                       var colWidth = 0;
17185                       grid.columns.forEach( function (column) {
17186                           if (column.renderContainer === 'left') {
17187                             colWidth += column.width;
17188                           }
17189                       });
17190                       colWidth = Math.floor(colWidth);
17191                       return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
17192                           ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
17193                           ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
17194                   }
17195
17196                   if ($scope.colContainer.name === 'left') {
17197                       $scope.grid.registerStyleComputation({
17198                           priority: 15,
17199                           func: updateRowContainerWidth
17200                       });
17201                   }*/
17202
17203               },
17204               post: function ($scope, $elm, $attrs, controllers) {
17205               }
17206             };
17207           }
17208         };
17209       }]);
17210
17211   /**
17212    *  @ngdoc directive
17213    *  @name ui.grid.expandable.directive:uiGridViewport
17214    *  @description stacks on the uiGridViewport directive to append the expandable row html elements to the
17215    *  default gridRow template
17216    */
17217   module.directive('uiGridViewport',
17218     ['$compile', 'gridUtil', '$templateCache',
17219       function ($compile, gridUtil, $templateCache) {
17220         return {
17221           priority: -200,
17222           scope: false,
17223           compile: function ($elm, $attrs) {
17224             var rowRepeatDiv = angular.element($elm.children().children()[0]);
17225             var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
17226             var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
17227             rowRepeatDiv.append(expandedRowElement);
17228             rowRepeatDiv.append(expandedRowFillerElement);
17229             return {
17230               pre: function ($scope, $elm, $attrs, controllers) {
17231               },
17232               post: function ($scope, $elm, $attrs, controllers) {
17233               }
17234             };
17235           }
17236         };
17237       }]);
17238
17239 })();
17240
17241 /* global console */
17242
17243 (function () {
17244   'use strict';
17245
17246   /**
17247    * @ngdoc overview
17248    * @name ui.grid.exporter
17249    * @description
17250    *
17251    * # ui.grid.exporter
17252    *
17253    * <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>
17254    *
17255    * This module provides the ability to export data from the grid.
17256    *
17257    * Data can be exported in a range of formats, and all data, visible
17258    * data, or selected rows can be exported, with all columns or visible
17259    * columns.
17260    *
17261    * No UI is provided, the caller should provide their own UI/buttons
17262    * as appropriate, or enable the gridMenu
17263    *
17264    * <br/>
17265    * <br/>
17266    *
17267    * <div doc-module-components="ui.grid.exporter"></div>
17268    */
17269
17270   var module = angular.module('ui.grid.exporter', ['ui.grid']);
17271
17272   /**
17273    *  @ngdoc object
17274    *  @name ui.grid.exporter.constant:uiGridExporterConstants
17275    *
17276    *  @description constants available in exporter module
17277    */
17278   /**
17279    * @ngdoc property
17280    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17281    * @name ALL
17282    * @description export all data, including data not visible.  Can
17283    * be set for either rowTypes or colTypes
17284    */
17285   /**
17286    * @ngdoc property
17287    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17288    * @name VISIBLE
17289    * @description export only visible data, including data not visible.  Can
17290    * be set for either rowTypes or colTypes
17291    */
17292   /**
17293    * @ngdoc property
17294    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
17295    * @name SELECTED
17296    * @description export all data, including data not visible.  Can
17297    * be set only for rowTypes, selection of only some columns is
17298    * not supported
17299    */
17300   module.constant('uiGridExporterConstants', {
17301     featureName: 'exporter',
17302     ALL: 'all',
17303     VISIBLE: 'visible',
17304     SELECTED: 'selected',
17305     CSV_CONTENT: 'CSV_CONTENT',
17306     BUTTON_LABEL: 'BUTTON_LABEL',
17307     FILE_NAME: 'FILE_NAME'
17308   });
17309
17310   /**
17311    *  @ngdoc service
17312    *  @name ui.grid.exporter.service:uiGridExporterService
17313    *
17314    *  @description Services for exporter feature
17315    */
17316   module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
17317     function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
17318
17319       var service = {
17320
17321         delay: 100,
17322
17323         initializeGrid: function (grid) {
17324
17325           //add feature namespace and any properties to grid for needed state
17326           grid.exporter = {};
17327           this.defaultGridOptions(grid.options);
17328
17329           /**
17330            *  @ngdoc object
17331            *  @name ui.grid.exporter.api:PublicApi
17332            *
17333            *  @description Public Api for exporter feature
17334            */
17335           var publicApi = {
17336             events: {
17337               exporter: {
17338               }
17339             },
17340             methods: {
17341               exporter: {
17342                 /**
17343                  * @ngdoc function
17344                  * @name csvExport
17345                  * @methodOf  ui.grid.exporter.api:PublicApi
17346                  * @description Exports rows from the grid in csv format,
17347                  * the data exported is selected based on the provided options
17348                  * @param {string} rowTypes which rows to export, valid values are
17349                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17350                  * uiGridExporterConstants.SELECTED
17351                  * @param {string} colTypes which columns to export, valid values are
17352                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
17353                  */
17354                 csvExport: function (rowTypes, colTypes) {
17355                   service.csvExport(grid, rowTypes, colTypes);
17356                 },
17357                 /**
17358                  * @ngdoc function
17359                  * @name pdfExport
17360                  * @methodOf  ui.grid.exporter.api:PublicApi
17361                  * @description Exports rows from the grid in pdf format,
17362                  * the data exported is selected based on the provided options
17363                  * Note that this function has a dependency on pdfMake, all
17364                  * going well this has been installed for you.
17365                  * The resulting pdf opens in a new browser window.
17366                  * @param {string} rowTypes which rows to export, valid values are
17367                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17368                  * uiGridExporterConstants.SELECTED
17369                  * @param {string} colTypes which columns to export, valid values are
17370                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
17371                  */
17372                 pdfExport: function (rowTypes, colTypes) {
17373                   service.pdfExport(grid, rowTypes, colTypes);
17374                 }
17375               }
17376             }
17377           };
17378
17379           grid.api.registerEventsFromObject(publicApi.events);
17380
17381           grid.api.registerMethodsFromObject(publicApi.methods);
17382
17383           if (grid.api.core.addToGridMenu){
17384             service.addToMenu( grid );
17385           } else {
17386             // order of registration is not guaranteed, register in a little while
17387             $interval( function() {
17388               if (grid.api.core.addToGridMenu){
17389                 service.addToMenu( grid );
17390               }
17391             }, this.delay, 1);
17392           }
17393
17394         },
17395
17396         defaultGridOptions: function (gridOptions) {
17397           //default option to true unless it was explicitly set to false
17398           /**
17399            * @ngdoc object
17400            * @name ui.grid.exporter.api:GridOptions
17401            *
17402            * @description GridOptions for exporter feature, these are available to be
17403            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17404            */
17405           /**
17406            * @ngdoc object
17407            * @name ui.grid.exporter.api:ColumnDef
17408            * @description ColumnDef settings for exporter
17409            */
17410           /**
17411            * @ngdoc object
17412            * @name exporterSuppressMenu
17413            * @propertyOf  ui.grid.exporter.api:GridOptions
17414            * @description Don't show the export menu button, implying the user
17415            * will roll their own UI for calling the exporter
17416            * <br/>Defaults to false
17417            */
17418           gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
17419           /**
17420            * @ngdoc object
17421            * @name exporterMenuLabel
17422            * @propertyOf  ui.grid.exporter.api:GridOptions
17423            * @description The text to show on the exporter menu button
17424            * link
17425            * <br/>Defaults to 'Export'
17426            */
17427           gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
17428           /**
17429            * @ngdoc object
17430            * @name exporterSuppressColumns
17431            * @propertyOf  ui.grid.exporter.api:GridOptions
17432            * @description Columns that should not be exported.  The selectionRowHeader is already automatically
17433            * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
17434            * output then add it in this list.  You should provide an array of column names.
17435            * <br/>Defaults to: []
17436            * <pre>
17437            *   gridOptions.exporterSuppressColumns = [ 'buttons' ];
17438            * </pre>
17439            */
17440           gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
17441           /**
17442            * @ngdoc object
17443            * @name exporterCsvColumnSeparator
17444            * @propertyOf  ui.grid.exporter.api:GridOptions
17445            * @description The character to use as column separator
17446            * link
17447            * <br/>Defaults to ','
17448            */
17449           gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
17450           /**
17451            * @ngdoc object
17452            * @name exporterCsvFilename
17453            * @propertyOf  ui.grid.exporter.api:GridOptions
17454            * @description The default filename to use when saving the downloaded csv.
17455            * This will only work in some browsers.
17456            * <br/>Defaults to 'download.csv'
17457            */
17458           gridOptions.exporterCsvFilename = gridOptions.exporterCsvFilename ? gridOptions.exporterCsvFilename : 'download.csv';
17459           /**
17460            * @ngdoc object
17461            * @name exporterPdfFilename
17462            * @propertyOf  ui.grid.exporter.api:GridOptions
17463            * @description The default filename to use when saving the downloaded pdf, only used in IE (other browsers open pdfs in a new window)
17464            * <br/>Defaults to 'download.pdf'
17465            */
17466           gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
17467           /**
17468            * @ngdoc object
17469            * @name exporterOlderExcelCompatibility
17470            * @propertyOf  ui.grid.exporter.api:GridOptions
17471            * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
17472            * through as ï»¿ in the first column header.  Setting this option to false will suppress this, at the
17473            * expense of proper utf-16 handling in applications that do recognise the BOM
17474            * <br/>Defaults to false
17475            */
17476           gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
17477           /**
17478            * @ngdoc object
17479            * @name exporterPdfDefaultStyle
17480            * @propertyOf  ui.grid.exporter.api:GridOptions
17481            * @description The default style in pdfMake format
17482            * <br/>Defaults to:
17483            * <pre>
17484            *   {
17485            *     fontSize: 11
17486            *   }
17487            * </pre>
17488            */
17489           gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
17490           /**
17491            * @ngdoc object
17492            * @name exporterPdfTableStyle
17493            * @propertyOf  ui.grid.exporter.api:GridOptions
17494            * @description The table style in pdfMake format
17495            * <br/>Defaults to:
17496            * <pre>
17497            *   {
17498            *     margin: [0, 5, 0, 15]
17499            *   }
17500            * </pre>
17501            */
17502           gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
17503           /**
17504            * @ngdoc object
17505            * @name exporterPdfTableHeaderStyle
17506            * @propertyOf  ui.grid.exporter.api:GridOptions
17507            * @description The tableHeader style in pdfMake format
17508            * <br/>Defaults to:
17509            * <pre>
17510            *   {
17511            *     bold: true,
17512            *     fontSize: 12,
17513            *     color: 'black'
17514            *   }
17515            * </pre>
17516            */
17517           gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
17518           /**
17519            * @ngdoc object
17520            * @name exporterPdfHeader
17521            * @propertyOf  ui.grid.exporter.api:GridOptions
17522            * @description The header section for pdf exports.  Can be
17523            * simple text:
17524            * <pre>
17525            *   gridOptions.exporterPdfHeader = 'My Header';
17526            * </pre>
17527            * Can be a more complex object in pdfMake format:
17528            * <pre>
17529            *   gridOptions.exporterPdfHeader = {
17530            *     columns: [
17531            *       'Left part',
17532            *       { text: 'Right part', alignment: 'right' }
17533            *     ]
17534            *   };
17535            * </pre>
17536            * Or can be a function, allowing page numbers and the like
17537            * <pre>
17538            *   gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
17539            * </pre>
17540            */
17541           gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
17542           /**
17543            * @ngdoc object
17544            * @name exporterPdfFooter
17545            * @propertyOf  ui.grid.exporter.api:GridOptions
17546            * @description The header section for pdf exports.  Can be
17547            * simple text:
17548            * <pre>
17549            *   gridOptions.exporterPdfFooter = 'My Footer';
17550            * </pre>
17551            * Can be a more complex object in pdfMake format:
17552            * <pre>
17553            *   gridOptions.exporterPdfFooter = {
17554            *     columns: [
17555            *       'Left part',
17556            *       { text: 'Right part', alignment: 'right' }
17557            *     ]
17558            *   };
17559            * </pre>
17560            * Or can be a function, allowing page numbers and the like
17561            * <pre>
17562            *   gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
17563            * </pre>
17564            */
17565           gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
17566           /**
17567            * @ngdoc object
17568            * @name exporterPdfOrientation
17569            * @propertyOf  ui.grid.exporter.api:GridOptions
17570            * @description The orientation, should be a valid pdfMake value,
17571            * 'landscape' or 'portrait'
17572            * <br/>Defaults to landscape
17573            */
17574           gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
17575           /**
17576            * @ngdoc object
17577            * @name exporterPdfPageSize
17578            * @propertyOf  ui.grid.exporter.api:GridOptions
17579            * @description The orientation, should be a valid pdfMake
17580            * paper size, usually 'A4' or 'LETTER'
17581            * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
17582            * <br/>Defaults to A4
17583            */
17584           gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
17585           /**
17586            * @ngdoc object
17587            * @name exporterPdfMaxGridWidth
17588            * @propertyOf  ui.grid.exporter.api:GridOptions
17589            * @description The maxium grid width - the current grid width
17590            * will be scaled to match this, with any fixed width columns
17591            * being adjusted accordingly.
17592            * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
17593            */
17594           gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
17595           /**
17596            * @ngdoc object
17597            * @name exporterPdfTableLayout
17598            * @propertyOf  ui.grid.exporter.api:GridOptions
17599            * @description A tableLayout in pdfMake format,
17600            * controls gridlines and the like.  We use the default
17601            * layout usually.
17602            * <br/>Defaults to null, which means no layout
17603            */
17604
17605           /**
17606            * @ngdoc object
17607            * @name exporterMenuAllData
17608            * @porpertyOf  ui.grid.exporter.api:GridOptions
17609            * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17610            */
17611           gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
17612
17613           /**
17614            * @ngdoc object
17615            * @name exporterMenuVisibleData
17616            * @porpertyOf  ui.grid.exporter.api:GridOptions
17617            * @description Add export visible data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17618            */
17619           gridOptions.exporterMenuVisibleData = gridOptions.exporterMenuVisibleData !== undefined ? gridOptions.exporterMenuVisibleData : true;
17620
17621           /**
17622            * @ngdoc object
17623            * @name exporterMenuSelectedData
17624            * @porpertyOf  ui.grid.exporter.api:GridOptions
17625            * @description Add export selected data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17626            */
17627           gridOptions.exporterMenuSelectedData = gridOptions.exporterMenuSelectedData !== undefined ? gridOptions.exporterMenuSelectedData : true;
17628
17629           /**
17630            * @ngdoc object
17631            * @name exporterMenuCsv
17632            * @propertyOf  ui.grid.exporter.api:GridOptions
17633            * @description Add csv export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17634            */
17635           gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
17636
17637           /**
17638            * @ngdoc object
17639            * @name exporterMenuPdf
17640            * @propertyOf  ui.grid.exporter.api:GridOptions
17641            * @description Add pdf export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
17642            */
17643           gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
17644
17645           /**
17646            * @ngdoc object
17647            * @name exporterPdfCustomFormatter
17648            * @propertyOf  ui.grid.exporter.api:GridOptions
17649            * @description A custom callback routine that changes the pdf document, adding any
17650            * custom styling or content that is supported by pdfMake.  Takes in the complete docDefinition, and
17651            * must return an updated docDefinition ready for pdfMake.
17652            * @example
17653            * In this example we add a style to the style array, so that we can use it in our
17654            * footer definition.
17655            * <pre>
17656            *   gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
17657            *     docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
17658            *     return docDefinition;
17659            *   }
17660            *
17661            *   gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
17662            * </pre>
17663            */
17664           gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
17665
17666           /**
17667            * @ngdoc object
17668            * @name exporterHeaderFilterUseName
17669            * @propertyOf  ui.grid.exporter.api:GridOptions
17670            * @description Defaults to false, which leads to `displayName` being passed into the headerFilter.
17671            * If set to true, then will pass `name` instead.
17672            *
17673            *
17674            * @example
17675            * <pre>
17676            *   gridOptions.exporterHeaderFilterUseName = true;
17677            * </pre>
17678            */
17679           gridOptions.exporterHeaderFilterUseName = gridOptions.exporterHeaderFilterUseName === true;
17680
17681           /**
17682            * @ngdoc object
17683            * @name exporterHeaderFilter
17684            * @propertyOf  ui.grid.exporter.api:GridOptions
17685            * @description A function to apply to the header displayNames before exporting.  Useful for internationalisation,
17686            * for example if you were using angular-translate you'd set this to `$translate.instant`.  Note that this
17687            * call must be synchronous, it cannot be a call that returns a promise.
17688            *
17689            * Behaviour can be changed to pass in `name` instead of `displayName` through use of `exporterHeaderFilterUseName: true`.
17690            *
17691            * @example
17692            * <pre>
17693            *   gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
17694            * </pre>
17695            * OR
17696            * <pre>
17697            *   gridOptions.exporterHeaderFilter = $translate.instant;
17698            * </pre>
17699            */
17700
17701           /**
17702            * @ngdoc function
17703            * @name exporterFieldCallback
17704            * @propertyOf  ui.grid.exporter.api:GridOptions
17705            * @description A function to call for each field before exporting it.  Allows
17706            * massaging of raw data into a display format, for example if you have applied
17707            * filters to convert codes into decodes, or you require
17708            * a specific date format in the exported content.
17709            *
17710            * The method is called once for each field exported, and provides the grid, the
17711            * gridCol and the GridRow for you to use as context in massaging the data.
17712            *
17713            * @param {Grid} grid provides the grid in case you have need of it
17714            * @param {GridRow} row the row from which the data comes
17715            * @param {GridCol} col the column from which the data comes
17716            * @param {object} value the value for your massaging
17717            * @returns {object} you must return the massaged value ready for exporting
17718            *
17719            * @example
17720            * <pre>
17721            *   gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
17722            *     if ( col.name === 'status' ){
17723            *       value = decodeStatus( value );
17724            *     }
17725            *     return value;
17726            *   }
17727            * </pre>
17728            */
17729           gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
17730
17731           /**
17732            * @ngdoc function
17733            * @name exporterAllDataFn
17734            * @propertyOf  ui.grid.exporter.api:GridOptions
17735            * @description This promise is needed when exporting all rows,
17736            * and the data need to be provided by server side. Default is null.
17737            * @returns {Promise} a promise to load all data from server
17738            *
17739            * @example
17740            * <pre>
17741            *   gridOptions.exporterAllDataFn = function () {
17742            *     return $http.get('/data/100.json')
17743            *   }
17744            * </pre>
17745            */
17746           gridOptions.exporterAllDataFn = gridOptions.exporterAllDataFn ? gridOptions.exporterAllDataFn : null;
17747
17748           /**
17749            * @ngdoc function
17750            * @name exporterAllDataPromise
17751            * @propertyOf  ui.grid.exporter.api:GridOptions
17752            * @description DEPRECATED - exporterAllDataFn used to be
17753            * called this, but it wasn't a promise, it was a function that returned
17754            * a promise.  Deprecated, but supported for backward compatibility, use
17755            * exporterAllDataFn instead.
17756            * @returns {Promise} a promise to load all data from server
17757            *
17758            * @example
17759            * <pre>
17760            *   gridOptions.exporterAllDataFn = function () {
17761            *     return $http.get('/data/100.json')
17762            *   }
17763            * </pre>
17764            */
17765           if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
17766             gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
17767           }
17768         },
17769
17770
17771         /**
17772          * @ngdoc function
17773          * @name addToMenu
17774          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17775          * @description Adds export items to the grid menu,
17776          * allowing the user to select export options
17777          * @param {Grid} grid the grid from which data should be exported
17778          */
17779         addToMenu: function ( grid ) {
17780           grid.api.core.addToGridMenu( grid, [
17781             {
17782               title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
17783               action: function ($event) {
17784                 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17785               },
17786               shown: function() {
17787                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
17788               },
17789               order: 200
17790             },
17791             {
17792               title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
17793               action: function ($event) {
17794                 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17795               },
17796               shown: function() {
17797                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuVisibleData;
17798               },
17799               order: 201
17800             },
17801             {
17802               title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
17803               action: function ($event) {
17804                 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17805               },
17806               shown: function() {
17807                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuSelectedData &&
17808                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17809               },
17810               order: 202
17811             },
17812             {
17813               title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
17814               action: function ($event) {
17815                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
17816               },
17817               shown: function() {
17818                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
17819               },
17820               order: 203
17821             },
17822             {
17823               title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
17824               action: function ($event) {
17825                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
17826               },
17827               shown: function() {
17828                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuVisibleData;
17829               },
17830               order: 204
17831             },
17832             {
17833               title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
17834               action: function ($event) {
17835                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
17836               },
17837               shown: function() {
17838                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuSelectedData &&
17839                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
17840               },
17841               order: 205
17842             }
17843           ]);
17844         },
17845
17846
17847         /**
17848          * @ngdoc function
17849          * @name csvExport
17850          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17851          * @description Exports rows from the grid in csv format,
17852          * the data exported is selected based on the provided options
17853          * @param {Grid} grid the grid from which data should be exported
17854          * @param {string} rowTypes which rows to export, valid values are
17855          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17856          * uiGridExporterConstants.SELECTED
17857          * @param {string} colTypes which columns to export, valid values are
17858          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17859          * uiGridExporterConstants.SELECTED
17860          */
17861         csvExport: function (grid, rowTypes, colTypes) {
17862           var self = this;
17863           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
17864             var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
17865             var exportData = self.getData(grid, rowTypes, colTypes);
17866             var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
17867
17868             self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterOlderExcelCompatibility);
17869           });
17870         },
17871
17872         /**
17873          * @ngdoc function
17874          * @name loadAllDataIfNeeded
17875          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17876          * @description When using server side pagination, use exporterAllDataFn to
17877          * load all data before continuing processing.
17878          * When using client side pagination, return a resolved promise so processing
17879          * continues immediately
17880          * @param {Grid} grid the grid from which data should be exported
17881          * @param {string} rowTypes which rows to export, valid values are
17882          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17883          * uiGridExporterConstants.SELECTED
17884          * @param {string} colTypes which columns to export, valid values are
17885          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17886          * uiGridExporterConstants.SELECTED
17887          */
17888         loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
17889           if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
17890             return grid.options.exporterAllDataFn()
17891               .then(function() {
17892                 grid.modifyRows(grid.options.data);
17893               });
17894           } else {
17895             var deferred = $q.defer();
17896             deferred.resolve();
17897             return deferred.promise;
17898           }
17899         },
17900
17901         /**
17902          * @ngdoc property
17903          * @propertyOf ui.grid.exporter.api:ColumnDef
17904          * @name exporterSuppressExport
17905          * @description Suppresses export for this column.  Used by selection and expandable.
17906          */
17907
17908         /**
17909          * @ngdoc function
17910          * @name getColumnHeaders
17911          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17912          * @description Gets the column headers from the grid to use
17913          * as a title row for the exported file, all headers have
17914          * headerCellFilters applied as appropriate.
17915          *
17916          * Column headers are an array of objects, each object has
17917          * name, displayName, width and align attributes.  Only name is
17918          * used for csv, all attributes are used for pdf.
17919          *
17920          * @param {Grid} grid the grid from which data should be exported
17921          * @param {string} colTypes which columns to export, valid values are
17922          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17923          * uiGridExporterConstants.SELECTED
17924          */
17925         getColumnHeaders: function (grid, colTypes) {
17926           var headers = [];
17927           var columns;
17928
17929           if ( colTypes === uiGridExporterConstants.ALL ){
17930             columns = grid.columns;
17931           } else {
17932             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17933             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17934             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17935
17936             columns = leftColumns.concat(bodyColumns,rightColumns);
17937           }
17938
17939           columns.forEach( function( gridCol, index ) {
17940             if ( gridCol.colDef.exporterSuppressExport !== true &&
17941                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17942               headers.push({
17943                 name: gridCol.field,
17944                 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
17945                 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
17946                 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
17947               });
17948             }
17949           });
17950
17951           return headers;
17952         },
17953
17954
17955         /**
17956          * @ngdoc property
17957          * @propertyOf ui.grid.exporter.api:ColumnDef
17958          * @name exporterPdfAlign
17959          * @description the alignment you'd like for this specific column when
17960          * exported into a pdf.  Can be 'left', 'right', 'center' or any other
17961          * valid pdfMake alignment option.
17962          */
17963
17964
17965         /**
17966          * @ngdoc object
17967          * @name ui.grid.exporter.api:GridRow
17968          * @description GridRow settings for exporter
17969          */
17970         /**
17971          * @ngdoc object
17972          * @name exporterEnableExporting
17973          * @propertyOf  ui.grid.exporter.api:GridRow
17974          * @description If set to false, then don't export this row, notwithstanding visible or
17975          * other settings
17976          * <br/>Defaults to true
17977          */
17978
17979         /**
17980          * @ngdoc function
17981          * @name getData
17982          * @methodOf  ui.grid.exporter.service:uiGridExporterService
17983          * @description Gets data from the grid based on the provided options,
17984          * all cells have cellFilters applied as appropriate.  Any rows marked
17985          * `exporterEnableExporting: false` will not be exported
17986          * @param {Grid} grid the grid from which data should be exported
17987          * @param {string} rowTypes which rows to export, valid values are
17988          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17989          * uiGridExporterConstants.SELECTED
17990          * @param {string} colTypes which columns to export, valid values are
17991          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
17992          * uiGridExporterConstants.SELECTED
17993          * @param {boolean} applyCellFilters whether or not to get the display value or the raw value of the data
17994          */
17995         getData: function (grid, rowTypes, colTypes, applyCellFilters) {
17996           var data = [];
17997           var rows;
17998           var columns;
17999
18000           switch ( rowTypes ) {
18001             case uiGridExporterConstants.ALL:
18002               rows = grid.rows;
18003               break;
18004             case uiGridExporterConstants.VISIBLE:
18005               rows = grid.getVisibleRows();
18006               break;
18007             case uiGridExporterConstants.SELECTED:
18008               if ( grid.api.selection ){
18009                 rows = grid.api.selection.getSelectedGridRows();
18010               } else {
18011                 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
18012               }
18013               break;
18014           }
18015
18016           if ( colTypes === uiGridExporterConstants.ALL ){
18017             columns = grid.columns;
18018           } else {
18019             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18020             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18021             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18022
18023             columns = leftColumns.concat(bodyColumns,rightColumns);
18024           }
18025
18026           rows.forEach( function( row, index ) {
18027
18028             if (row.exporterEnableExporting !== false) {
18029               var extractedRow = [];
18030
18031
18032               columns.forEach( function( gridCol, index ) {
18033               if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
18034                    gridCol.colDef.exporterSuppressExport !== true &&
18035                    grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
18036                   var cellValue = applyCellFilters ? grid.getCellDisplayValue( row, gridCol ) : grid.getCellValue( row, gridCol );
18037                   var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, cellValue ) };
18038                   if ( gridCol.colDef.exporterPdfAlign ) {
18039                     extractedField.alignment = gridCol.colDef.exporterPdfAlign;
18040                   }
18041                   extractedRow.push(extractedField);
18042                 }
18043               });
18044
18045               data.push(extractedRow);
18046             }
18047           });
18048
18049           return data;
18050         },
18051
18052
18053         /**
18054          * @ngdoc function
18055          * @name formatAsCSV
18056          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18057          * @description Formats the column headers and data as a CSV,
18058          * and sends that data to the user
18059          * @param {array} exportColumnHeaders an array of column headers,
18060          * where each header is an object with name, width and maybe alignment
18061          * @param {array} exportData an array of rows, where each row is
18062          * an array of column data
18063          * @returns {string} csv the formatted csv as a string
18064          */
18065         formatAsCsv: function (exportColumnHeaders, exportData, separator) {
18066           var self = this;
18067
18068           var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
18069
18070           var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
18071
18072           csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
18073
18074           return csv;
18075         },
18076
18077         /**
18078          * @ngdoc function
18079          * @name formatRowAsCsv
18080          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18081          * @description Renders a single field as a csv field, including
18082          * quotes around the value
18083          * @param {exporterService} exporter pass in exporter
18084          * @param {array} row the row to be turned into a csv string
18085          * @returns {string} a csv-ified version of the row
18086          */
18087         formatRowAsCsv: function (exporter, separator) {
18088           return function (row) {
18089             return row.map(exporter.formatFieldAsCsv).join(separator);
18090           };
18091         },
18092
18093         /**
18094          * @ngdoc function
18095          * @name formatFieldAsCsv
18096          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18097          * @description Renders a single field as a csv field, including
18098          * quotes around the value
18099          * @param {field} field the field to be turned into a csv string,
18100          * may be of any type
18101          * @returns {string} a csv-ified version of the field
18102          */
18103         formatFieldAsCsv: function (field) {
18104           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18105             return '';
18106           }
18107           if (typeof(field.value) === 'number') {
18108             return field.value;
18109           }
18110           if (typeof(field.value) === 'boolean') {
18111             return (field.value ? 'TRUE' : 'FALSE') ;
18112           }
18113           if (typeof(field.value) === 'string') {
18114             return '"' + field.value.replace(/"/g,'""') + '"';
18115           }
18116
18117           return JSON.stringify(field.value);
18118         },
18119
18120
18121         /**
18122          * @ngdoc function
18123          * @name isIE
18124          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18125          * @description Checks whether current browser is IE and returns it's version if it is
18126         */
18127         isIE: function () {
18128           var match = navigator.userAgent.search(/(?:Edge|MSIE|Trident\/.*; rv:)/);
18129           var isIE = false;
18130
18131           if (match !== -1) {
18132             isIE = true;
18133           }
18134
18135           return isIE;
18136         },
18137
18138
18139         /**
18140          * @ngdoc function
18141          * @name downloadFile
18142          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18143          * @description Triggers download of a csv file.  Logic provided
18144          * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
18145          * @param {string} fileName the filename we'd like our file to be
18146          * given
18147          * @param {string} csvContent the csv content that we'd like to
18148          * download as a file
18149          * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
18150          */
18151         downloadFile: function (fileName, csvContent, exporterOlderExcelCompatibility) {
18152           var D = document;
18153           var a = D.createElement('a');
18154           var strMimeType = 'application/octet-stream;charset=utf-8';
18155           var rawFile;
18156           var ieVersion;
18157
18158           ieVersion = this.isIE();
18159           if (ieVersion && ieVersion < 10) {
18160             var frame = D.createElement('iframe');
18161             document.body.appendChild(frame);
18162
18163             frame.contentWindow.document.open("text/html", "replace");
18164             frame.contentWindow.document.write('sep=,\r\n' + csvContent);
18165             frame.contentWindow.document.close();
18166             frame.contentWindow.focus();
18167             frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18168
18169             document.body.removeChild(frame);
18170             return true;
18171           }
18172
18173           // IE10+
18174           if (navigator.msSaveBlob) {
18175             return navigator.msSaveOrOpenBlob(
18176               new Blob(
18177                 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18178                 { type: strMimeType } ),
18179               fileName
18180             );
18181           }
18182
18183           //html5 A[download]
18184           if ('download' in a) {
18185             var blob = new Blob(
18186               [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18187               { type: strMimeType }
18188             );
18189             rawFile = URL.createObjectURL(blob);
18190             a.setAttribute('download', fileName);
18191           } else {
18192             rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
18193             a.setAttribute('target', '_blank');
18194           }
18195
18196           a.href = rawFile;
18197           a.setAttribute('style', 'display:none;');
18198           D.body.appendChild(a);
18199           setTimeout(function() {
18200             if (a.click) {
18201               a.click();
18202               // Workaround for Safari 5
18203             } else if (document.createEvent) {
18204               var eventObj = document.createEvent('MouseEvents');
18205               eventObj.initEvent('click', true, true);
18206               a.dispatchEvent(eventObj);
18207             }
18208             D.body.removeChild(a);
18209
18210           }, this.delay);
18211         },
18212
18213         /**
18214          * @ngdoc function
18215          * @name pdfExport
18216          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18217          * @description Exports rows from the grid in pdf format,
18218          * the data exported is selected based on the provided options.
18219          * Note that this function has a dependency on pdfMake, which must
18220          * be installed.  The resulting pdf opens in a new
18221          * browser window.
18222          * @param {Grid} grid the grid from which data should be exported
18223          * @param {string} rowTypes which rows to export, valid values are
18224          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18225          * uiGridExporterConstants.SELECTED
18226          * @param {string} colTypes which columns to export, valid values are
18227          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18228          * uiGridExporterConstants.SELECTED
18229          */
18230         pdfExport: function (grid, rowTypes, colTypes) {
18231           var self = this;
18232           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
18233             var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
18234             var exportData = self.getData(grid, rowTypes, colTypes);
18235             var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
18236
18237             if (self.isIE() || navigator.appVersion.indexOf("Edge") !== -1) {
18238               self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
18239             } else {
18240               pdfMake.createPdf(docDefinition).open();
18241             }
18242           });
18243         },
18244
18245
18246         /**
18247          * @ngdoc function
18248          * @name downloadPdf
18249          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18250          * @description Generates and retrieves the pdf as a blob, then downloads
18251          * it as a file.  Only used in IE, in all other browsers we use the native
18252          * pdfMake.open function to just open the PDF
18253          * @param {string} fileName the filename to give to the pdf, can be set
18254          * through exporterPdfFilename
18255          * @param {object} docDefinition a pdf docDefinition that we can generate
18256          * and get a blob from
18257          */
18258         downloadPDF: function (fileName, docDefinition) {
18259           var D = document;
18260           var a = D.createElement('a');
18261           var strMimeType = 'application/octet-stream;charset=utf-8';
18262           var rawFile;
18263           var ieVersion;
18264
18265           ieVersion = this.isIE(); // This is now a boolean value
18266           var doc = pdfMake.createPdf(docDefinition);
18267           var blob;
18268
18269           doc.getBuffer( function (buffer) {
18270             blob = new Blob([buffer]);
18271
18272             // IE10+
18273             if (navigator.msSaveBlob) {
18274               return navigator.msSaveBlob(
18275                 blob, fileName
18276               );
18277             }
18278
18279             // Previously:  && ieVersion < 10
18280             // ieVersion now returns a boolean for the
18281             // sake of sanity. We just check `msSaveBlob` first.
18282             if (ieVersion) {
18283               var frame = D.createElement('iframe');
18284               document.body.appendChild(frame);
18285
18286               frame.contentWindow.document.open("text/html", "replace");
18287               frame.contentWindow.document.write(blob);
18288               frame.contentWindow.document.close();
18289               frame.contentWindow.focus();
18290               frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18291
18292               document.body.removeChild(frame);
18293               return true;
18294             }
18295           });
18296         },
18297
18298
18299         /**
18300          * @ngdoc function
18301          * @name renderAsPdf
18302          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18303          * @description Renders the data into a pdf, and opens that pdf.
18304          *
18305          * @param {Grid} grid the grid from which data should be exported
18306          * @param {array} exportColumnHeaders an array of column headers,
18307          * where each header is an object with name, width and maybe alignment
18308          * @param {array} exportData an array of rows, where each row is
18309          * an array of column data
18310          * @returns {object} a pdfMake format document definition, ready
18311          * for generation
18312          */
18313         prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
18314           var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
18315
18316           var headerColumns = exportColumnHeaders.map( function( header ) {
18317             return { text: header.displayName, style: 'tableHeader' };
18318           });
18319
18320           var stringData = exportData.map(this.formatRowAsPdf(this));
18321
18322           var allData = [headerColumns].concat(stringData);
18323
18324           var docDefinition = {
18325             pageOrientation: grid.options.exporterPdfOrientation,
18326             pageSize: grid.options.exporterPdfPageSize,
18327             content: [{
18328               style: 'tableStyle',
18329               table: {
18330                 headerRows: 1,
18331                 widths: headerWidths,
18332                 body: allData
18333               }
18334             }],
18335             styles: {
18336               tableStyle: grid.options.exporterPdfTableStyle,
18337               tableHeader: grid.options.exporterPdfTableHeaderStyle
18338             },
18339             defaultStyle: grid.options.exporterPdfDefaultStyle
18340           };
18341
18342           if ( grid.options.exporterPdfLayout ){
18343             docDefinition.layout = grid.options.exporterPdfLayout;
18344           }
18345
18346           if ( grid.options.exporterPdfHeader ){
18347             docDefinition.header = grid.options.exporterPdfHeader;
18348           }
18349
18350           if ( grid.options.exporterPdfFooter ){
18351             docDefinition.footer = grid.options.exporterPdfFooter;
18352           }
18353
18354           if ( grid.options.exporterPdfCustomFormatter ){
18355             docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
18356           }
18357           return docDefinition;
18358
18359         },
18360
18361
18362         /**
18363          * @ngdoc function
18364          * @name calculatePdfHeaderWidths
18365          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18366          * @description Determines the column widths base on the
18367          * widths we got from the grid.  If the column is drawn
18368          * then we have a drawnWidth.  If the column is not visible
18369          * then we have '*', 'x%' or a width.  When columns are
18370          * not visible they don't contribute to the overall gridWidth,
18371          * so we need to adjust to allow for extra columns
18372          *
18373          * Our basic heuristic is to take the current gridWidth, plus
18374          * numeric columns and call this the base gridwidth.
18375          *
18376          * To that we add 100 for any '*' column, and x% of the base gridWidth
18377          * for any column that is a %
18378          *
18379          * @param {Grid} grid the grid from which data should be exported
18380          * @param {array} exportHeaders array of header information
18381          * @returns {object} an array of header widths
18382          */
18383         calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
18384           var baseGridWidth = 0;
18385           exportHeaders.forEach( function(value){
18386             if (typeof(value.width) === 'number'){
18387               baseGridWidth += value.width;
18388             }
18389           });
18390
18391           var extraColumns = 0;
18392           exportHeaders.forEach( function(value){
18393             if (value.width === '*'){
18394               extraColumns += 100;
18395             }
18396             if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
18397               var percent = parseInt(value.width.match(/(\d)*%/)[0]);
18398
18399               value.width = baseGridWidth * percent / 100;
18400               extraColumns += value.width;
18401             }
18402           });
18403
18404           var gridWidth = baseGridWidth + extraColumns;
18405
18406           return exportHeaders.map(function( header ) {
18407             return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
18408           });
18409
18410         },
18411
18412         /**
18413          * @ngdoc function
18414          * @name formatRowAsPdf
18415          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18416          * @description Renders a row in a format consumable by PDF,
18417          * mainly meaning casting everything to a string
18418          * @param {exporterService} exporter pass in exporter
18419          * @param {array} row the row to be turned into a csv string
18420          * @returns {string} a csv-ified version of the row
18421          */
18422         formatRowAsPdf: function ( exporter ) {
18423           return function( row ) {
18424             return row.map(exporter.formatFieldAsPdfString);
18425           };
18426         },
18427
18428
18429         /**
18430          * @ngdoc function
18431          * @name formatFieldAsCsv
18432          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18433          * @description Renders a single field as a pdf-able field, which
18434          * is different from a csv field only in that strings don't have quotes
18435          * around them
18436          * @param {field} field the field to be turned into a pdf string,
18437          * may be of any type
18438          * @returns {string} a string-ified version of the field
18439          */
18440         formatFieldAsPdfString: function (field) {
18441           var returnVal;
18442           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18443             returnVal = '';
18444           } else if (typeof(field.value) === 'number') {
18445             returnVal = field.value.toString();
18446           } else if (typeof(field.value) === 'boolean') {
18447             returnVal = (field.value ? 'TRUE' : 'FALSE') ;
18448           } else if (typeof(field.value) === 'string') {
18449             returnVal = field.value.replace(/"/g,'""');
18450           } else {
18451             returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
18452           }
18453
18454           if (field.alignment && typeof(field.alignment) === 'string' ){
18455             returnVal = { text: returnVal, alignment: field.alignment };
18456           }
18457
18458           return returnVal;
18459         }
18460       };
18461
18462       return service;
18463
18464     }
18465   ]);
18466
18467   /**
18468    *  @ngdoc directive
18469    *  @name ui.grid.exporter.directive:uiGridExporter
18470    *  @element div
18471    *  @restrict A
18472    *
18473    *  @description Adds exporter features to grid
18474    *
18475    *  @example
18476    <example module="app">
18477    <file name="app.js">
18478    var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
18479
18480    app.controller('MainCtrl', ['$scope', function ($scope) {
18481       $scope.data = [
18482         { name: 'Bob', title: 'CEO' },
18483             { name: 'Frank', title: 'Lowly Developer' }
18484       ];
18485
18486       $scope.gridOptions = {
18487         enableGridMenu: true,
18488         exporterMenuCsv: false,
18489         columnDefs: [
18490           {name: 'name', enableCellEdit: true},
18491           {name: 'title', enableCellEdit: true}
18492         ],
18493         data: $scope.data
18494       };
18495     }]);
18496    </file>
18497    <file name="index.html">
18498    <div ng-controller="MainCtrl">
18499    <div ui-grid="gridOptions" ui-grid-exporter></div>
18500    </div>
18501    </file>
18502    </example>
18503    */
18504   module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
18505     function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
18506       return {
18507         replace: true,
18508         priority: 0,
18509         require: '^uiGrid',
18510         scope: false,
18511         link: function ($scope, $elm, $attrs, uiGridCtrl) {
18512           uiGridExporterService.initializeGrid(uiGridCtrl.grid);
18513           uiGridCtrl.grid.exporter.$scope = $scope;
18514         }
18515       };
18516     }
18517   ]);
18518 })();
18519
18520 (function () {
18521   'use strict';
18522
18523   /**
18524    * @ngdoc overview
18525    * @name ui.grid.grouping
18526    * @description
18527    *
18528    * # ui.grid.grouping
18529    *
18530    * <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>
18531    *
18532    * This module provides grouping of rows based on the data in them, similar
18533    * in concept to excel grouping.  You can group multiple columns, resulting in
18534    * nested grouping.
18535    *
18536    * In concept this feature is similar to sorting + grid footer/aggregation, it
18537    * sorts the data based on the grouped columns, then creates group rows that
18538    * reflect a break in the data.  Each of those group rows can have aggregations for
18539    * the data within that group.
18540    *
18541    * This feature leverages treeBase to provide the tree functionality itself,
18542    * the key thing this feature does therefore is to set treeLevels on the rows
18543    * and insert the group headers.
18544    *
18545    * Design information:
18546    * -------------------
18547    *
18548    * Each column will get new menu items - group by, and aggregate by.  Group by
18549    * will cause this column to be sorted (if not already), and will move this column
18550    * to the front of the sorted columns (i.e. grouped columns take precedence over
18551    * sorted columns).  It will respect the sort order already set if there is one,
18552    * and it will allow the sorting logic to change that sort order, it just forces
18553    * the column to the front of the sorting.  You can group by multiple columns, the
18554    * logic will add this column to the sorting after any already grouped columns.
18555    *
18556    * Once a grouping is defined, grouping logic is added to the rowsProcessors.  This
18557    * will process the rows, identifying a break in the data value, and inserting a grouping row.
18558    * Grouping rows have specific attributes on them:
18559    *
18560    *  - internalRow = true: tells us that this isn't a real row, so we can ignore it
18561    *    from any processing that it looking at core data rows.  This is used by the core
18562    *    logic (or will be one day), as it's not grouping specific
18563    *  - groupHeader = true: tells us this is a groupHeader.  This is used by the grouping logic
18564    *    to know if this is a groupHeader row or not
18565    *
18566    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
18567    * row order or filtering or anything like that is changed.  In order to avoid the row instantiation
18568    * time, and to preserve state across invocations, we hold a cache of the rows that we created
18569    * last time, and we use them again this time if we can.
18570    *
18571    * By default rows are collapsed, which means all data rows have their visible property
18572    * set to false, and only level 0 group rows are set to visible.
18573    *
18574    * <br/>
18575    * <br/>
18576    *
18577    * <div doc-module-components="ui.grid.grouping"></div>
18578    */
18579
18580   var module = angular.module('ui.grid.grouping', ['ui.grid', 'ui.grid.treeBase']);
18581
18582   /**
18583    *  @ngdoc object
18584    *  @name ui.grid.grouping.constant:uiGridGroupingConstants
18585    *
18586    *  @description constants available in grouping module, this includes
18587    *  all the constants declared in the treeBase module (these are manually copied
18588    *  as there isn't an easy way to include constants in another constants file, and
18589    *  we don't want to make users include treeBase)
18590    *
18591    */
18592   module.constant('uiGridGroupingConstants', {
18593     featureName: "grouping",
18594     rowHeaderColName: 'treeBaseRowHeaderCol',
18595     EXPANDED: 'expanded',
18596     COLLAPSED: 'collapsed',
18597     aggregation: {
18598       COUNT: 'count',
18599       SUM: 'sum',
18600       MAX: 'max',
18601       MIN: 'min',
18602       AVG: 'avg'
18603     }
18604   });
18605
18606   /**
18607    *  @ngdoc service
18608    *  @name ui.grid.grouping.service:uiGridGroupingService
18609    *
18610    *  @description Services for grouping features
18611    */
18612   module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
18613   function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
18614
18615     var service = {
18616
18617       initializeGrid: function (grid, $scope) {
18618         uiGridTreeBaseService.initializeGrid( grid, $scope );
18619
18620         //add feature namespace and any properties to grid for needed
18621         /**
18622          *  @ngdoc object
18623          *  @name ui.grid.grouping.grid:grouping
18624          *
18625          *  @description Grid properties and functions added for grouping
18626          */
18627         grid.grouping = {};
18628
18629         /**
18630          *  @ngdoc property
18631          *  @propertyOf ui.grid.grouping.grid:grouping
18632          *  @name groupHeaderCache
18633          *
18634          *  @description Cache that holds the group header rows we created last time, we'll
18635          *  reuse these next time, not least because they hold our expanded states.
18636          *
18637          *  We need to take care with these that they don't become a memory leak, we
18638          *  create a new cache each time using the values from the old cache.  This works
18639          *  so long as we're creating group rows for invisible rows as well.
18640          *
18641          *  The cache is a nested hash, indexed on the value we grouped by.  So if we
18642          *  grouped by gender then age, we'd maybe have something like:
18643          *  ```
18644          *    {
18645          *      male: {
18646          *        row: <pointer to the old row>,
18647          *        children: {
18648          *          22: { row: <pointer to the old row> },
18649          *          31: { row: <pointer to the old row> }
18650          *      },
18651          *      female: {
18652          *        row: <pointer to the old row>,
18653          *        children: {
18654          *          28: { row: <pointer to the old row> },
18655          *          55: { row: <pointer to the old row> }
18656          *      }
18657          *    }
18658          *  ```
18659          *
18660          *  We create new rows for any missing rows, this means that they come in as collapsed.
18661          *
18662          */
18663         grid.grouping.groupHeaderCache = {};
18664
18665         service.defaultGridOptions(grid.options);
18666
18667         grid.registerRowsProcessor(service.groupRows, 400);
18668
18669         grid.registerColumnBuilder( service.groupingColumnBuilder);
18670
18671         grid.registerColumnsProcessor(service.groupingColumnProcessor, 400);
18672
18673         /**
18674          *  @ngdoc object
18675          *  @name ui.grid.grouping.api:PublicApi
18676          *
18677          *  @description Public Api for grouping feature
18678          */
18679         var publicApi = {
18680           events: {
18681             grouping: {
18682               /**
18683                * @ngdoc event
18684                * @eventOf ui.grid.grouping.api:PublicApi
18685                * @name aggregationChanged
18686                * @description raised whenever aggregation is changed, added or removed from a column
18687                *
18688                * <pre>
18689                *      gridApi.grouping.on.aggregationChanged(scope,function(col){})
18690                * </pre>
18691                * @param {gridCol} col the column which on which aggregation changed. The aggregation
18692                * type is available as `col.treeAggregation.type`
18693                */
18694               aggregationChanged: {},
18695               /**
18696                * @ngdoc event
18697                * @eventOf ui.grid.grouping.api:PublicApi
18698                * @name groupingChanged
18699                * @description raised whenever the grouped columns changes
18700                *
18701                * <pre>
18702                *      gridApi.grouping.on.groupingChanged(scope,function(col){})
18703                * </pre>
18704                * @param {gridCol} col the column which on which grouping changed. The new grouping is
18705                * available as `col.grouping`
18706                */
18707               groupingChanged: {}
18708             }
18709           },
18710           methods: {
18711             grouping: {
18712               /**
18713                * @ngdoc function
18714                * @name getGrouping
18715                * @methodOf  ui.grid.grouping.api:PublicApi
18716                * @description Get the grouping configuration for this grid,
18717                * used by the saveState feature.  Adds expandedState to the information
18718                * provided by the internal getGrouping, and removes any aggregations that have a source
18719                * of grouping (i.e. will be automatically reapplied when we regroup the column)
18720                * Returned grouping is an object
18721                *   `{ grouping: groupArray, treeAggregations: aggregateArray, expandedState: hash }`
18722                * where grouping contains an array of objects:
18723                *   `{ field: column.field, colName: column.name, groupPriority: column.grouping.groupPriority }`
18724                * and aggregations contains an array of objects:
18725                *   `{ field: column.field, colName: column.name, aggregation: column.grouping.aggregation }`
18726                * and expandedState is a hash of the currently expanded nodes
18727                *
18728                * The groupArray will be sorted by groupPriority.
18729                *
18730                * @param {boolean} getExpanded whether or not to return the expanded state
18731                * @returns {object} grouping configuration
18732                */
18733               getGrouping: function ( getExpanded ) {
18734                 var grouping = service.getGrouping(grid);
18735
18736                 grouping.grouping.forEach( function( group ) {
18737                   group.colName = group.col.name;
18738                   delete group.col;
18739                 });
18740
18741                 grouping.aggregations.forEach( function( aggregation ) {
18742                   aggregation.colName = aggregation.col.name;
18743                   delete aggregation.col;
18744                 });
18745
18746                 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
18747                   return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
18748                 });
18749
18750                 if ( getExpanded ){
18751                   grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
18752                 }
18753
18754                 return grouping;
18755               },
18756
18757               /**
18758                * @ngdoc function
18759                * @name setGrouping
18760                * @methodOf  ui.grid.grouping.api:PublicApi
18761                * @description Set the grouping configuration for this grid,
18762                * used by the saveState feature, but can also be used by any
18763                * user to specify a combined grouping and aggregation configuration
18764                * @param {object} config the config you want to apply, in the format
18765                * provided out by getGrouping
18766                */
18767               setGrouping: function ( config ) {
18768                 service.setGrouping(grid, config);
18769               },
18770
18771               /**
18772                * @ngdoc function
18773                * @name groupColumn
18774                * @methodOf  ui.grid.grouping.api:PublicApi
18775                * @description Adds this column to the existing grouping, at the end of the priority order.
18776                * If the column doesn't have a sort, adds one, by default ASC
18777                *
18778                * This column will move to the left of any non-group columns, the
18779                * move is handled in a columnProcessor, so gets called as part of refresh
18780                *
18781                * @param {string} columnName the name of the column we want to group
18782                */
18783               groupColumn: function( columnName ) {
18784                 var column = grid.getColumn(columnName);
18785                 service.groupColumn(grid, column);
18786               },
18787
18788               /**
18789                * @ngdoc function
18790                * @name ungroupColumn
18791                * @methodOf  ui.grid.grouping.api:PublicApi
18792                * @description Removes the groupPriority from this column.  If the
18793                * column was previously aggregated the aggregation will come back.
18794                * The sort will remain.
18795                *
18796                * This column will move to the right of any other group columns, the
18797                * move is handled in a columnProcessor, so gets called as part of refresh
18798                *
18799                * @param {string} columnName the name of the column we want to ungroup
18800                */
18801               ungroupColumn: function( columnName ) {
18802                 var column = grid.getColumn(columnName);
18803                 service.ungroupColumn(grid, column);
18804               },
18805
18806               /**
18807                * @ngdoc function
18808                * @name clearGrouping
18809                * @methodOf  ui.grid.grouping.api:PublicApi
18810                * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
18811                * as we don't know whether that sorting was added by grouping or was there beforehand
18812                *
18813                */
18814               clearGrouping: function() {
18815                 service.clearGrouping(grid);
18816               },
18817
18818               /**
18819                * @ngdoc function
18820                * @name aggregateColumn
18821                * @methodOf  ui.grid.grouping.api:PublicApi
18822                * @description Sets the aggregation type on a column, if the
18823                * column is currently grouped then it removes the grouping first.
18824                * If the aggregationDef is null then will result in the aggregation
18825                * being removed
18826                *
18827                * @param {string} columnName the column we want to aggregate
18828                * @param {string} or {function} aggregationDef one of the recognised types
18829                * from uiGridGroupingConstants or a custom aggregation function.
18830                * @param {string} aggregationLabel (optional) The label to use for this aggregation.
18831                */
18832               aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
18833                 var column = grid.getColumn(columnName);
18834                 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
18835               }
18836
18837             }
18838           }
18839         };
18840
18841         grid.api.registerEventsFromObject(publicApi.events);
18842
18843         grid.api.registerMethodsFromObject(publicApi.methods);
18844
18845         grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
18846
18847       },
18848
18849       defaultGridOptions: function (gridOptions) {
18850         //default option to true unless it was explicitly set to false
18851         /**
18852          *  @ngdoc object
18853          *  @name ui.grid.grouping.api:GridOptions
18854          *
18855          *  @description GridOptions for grouping feature, these are available to be
18856          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18857          */
18858
18859         /**
18860          *  @ngdoc object
18861          *  @name enableGrouping
18862          *  @propertyOf  ui.grid.grouping.api:GridOptions
18863          *  @description Enable row grouping for entire grid.
18864          *  <br/>Defaults to true
18865          */
18866         gridOptions.enableGrouping = gridOptions.enableGrouping !== false;
18867
18868         /**
18869          *  @ngdoc object
18870          *  @name groupingShowCounts
18871          *  @propertyOf  ui.grid.grouping.api:GridOptions
18872          *  @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
18873          *  sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
18874          *  to break, since the group header rows will always be a string with groupingShowCounts enabled.
18875          *  <br/>Defaults to true except on columns of type 'date'
18876          */
18877         gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
18878
18879         /**
18880          *  @ngdoc object
18881          *  @name groupingNullLabel
18882          *  @propertyOf  ui.grid.grouping.api:GridOptions
18883          *  @description The string to use for the grouping header row label on rows which contain a null or undefined value in the grouped column.
18884          *  <br/>Defaults to "Null"
18885          */
18886         gridOptions.groupingNullLabel = typeof(gridOptions.groupingNullLabel) === 'undefined' ? 'Null' : gridOptions.groupingNullLabel;
18887
18888         /**
18889          *  @ngdoc object
18890          *  @name enableGroupHeaderSelection
18891          *  @propertyOf  ui.grid.grouping.api:GridOptions
18892          *  @description Allows group header rows to be selected.
18893          *  <br/>Defaults to false
18894          */
18895         gridOptions.enableGroupHeaderSelection = gridOptions.enableGroupHeaderSelection === true;
18896       },
18897
18898
18899       /**
18900        * @ngdoc function
18901        * @name groupingColumnBuilder
18902        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
18903        * @description Sets the grouping defaults based on the columnDefs
18904        *
18905        * @param {object} colDef columnDef we're basing on
18906        * @param {GridCol} col the column we're to update
18907        * @param {object} gridOptions the options we should use
18908        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
18909        */
18910       groupingColumnBuilder: function (colDef, col, gridOptions) {
18911         /**
18912          *  @ngdoc object
18913          *  @name ui.grid.grouping.api:ColumnDef
18914          *
18915          *  @description ColumnDef for grouping feature, these are available to be
18916          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
18917          */
18918
18919         /**
18920          *  @ngdoc object
18921          *  @name enableGrouping
18922          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18923          *  @description Enable grouping on this column
18924          *  <br/>Defaults to true.
18925          */
18926         if (colDef.enableGrouping === false){
18927           return;
18928         }
18929
18930         /**
18931          *  @ngdoc object
18932          *  @name grouping
18933          *  @propertyOf  ui.grid.grouping.api:ColumnDef
18934          *  @description Set the grouping for a column.  Format is:
18935          *  ```
18936          *    {
18937          *      groupPriority: <number, starts at 0, if less than 0 or undefined then we're aggregating in this column>
18938          *    }
18939          *  ```
18940          *
18941          *  **Note that aggregation used to be included in grouping, but is now separately set on the column via treeAggregation
18942          *  setting in treeBase**
18943          *
18944          *  We group in the priority order given, this will also put these columns to the high order of the sort irrespective
18945          *  of the sort priority given them.  If there is no sort defined then we sort ascending, if there is a sort defined then
18946          *  we use that sort.
18947          *
18948          *  If the groupPriority is undefined or less than 0, then we expect to be aggregating, and we look at the
18949          *  aggregation types to determine what sort of aggregation we can do.  Values are in the constants file, but
18950          *  include SUM, COUNT, MAX, MIN
18951          *
18952          *  groupPriorities should generally be sequential, if they're not then the next time getGrouping is called
18953          *  we'll renumber them to be sequential.
18954          *  <br/>Defaults to undefined.
18955          */
18956
18957         if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
18958           col.grouping = angular.copy(colDef.grouping);
18959           if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
18960             col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
18961             col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
18962           }
18963         } else if (typeof(col.grouping) === 'undefined'){
18964           col.grouping = {};
18965         }
18966
18967         if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
18968           col.suppressRemoveSort = true;
18969         }
18970
18971         var groupColumn = {
18972           name: 'ui.grid.grouping.group',
18973           title: i18nService.get().grouping.group,
18974           icon: 'ui-grid-icon-indent-right',
18975           shown: function () {
18976             return typeof(this.context.col.grouping) === 'undefined' ||
18977                    typeof(this.context.col.grouping.groupPriority) === 'undefined' ||
18978                    this.context.col.grouping.groupPriority < 0;
18979           },
18980           action: function () {
18981             service.groupColumn( this.context.col.grid, this.context.col );
18982           }
18983         };
18984
18985         var ungroupColumn = {
18986           name: 'ui.grid.grouping.ungroup',
18987           title: i18nService.get().grouping.ungroup,
18988           icon: 'ui-grid-icon-indent-left',
18989           shown: function () {
18990             return typeof(this.context.col.grouping) !== 'undefined' &&
18991                    typeof(this.context.col.grouping.groupPriority) !== 'undefined' &&
18992                    this.context.col.grouping.groupPriority >= 0;
18993           },
18994           action: function () {
18995             service.ungroupColumn( this.context.col.grid, this.context.col );
18996           }
18997         };
18998
18999         var aggregateRemove = {
19000           name: 'ui.grid.grouping.aggregateRemove',
19001           title: i18nService.get().grouping.aggregate_remove,
19002           shown: function () {
19003             return typeof(this.context.col.treeAggregationFn) !== 'undefined';
19004           },
19005           action: function () {
19006             service.aggregateColumn( this.context.col.grid, this.context.col, null);
19007           }
19008         };
19009
19010         // generic adder for the aggregation menus, which follow a pattern
19011         var addAggregationMenu = function(type, title){
19012           title = title || i18nService.get().grouping['aggregate_' + type] || type;
19013           var menuItem = {
19014             name: 'ui.grid.grouping.aggregate' + type,
19015             title: title,
19016             shown: function () {
19017               return typeof(this.context.col.treeAggregation) === 'undefined' ||
19018                      typeof(this.context.col.treeAggregation.type) === 'undefined' ||
19019                      this.context.col.treeAggregation.type !== type;
19020             },
19021             action: function () {
19022               service.aggregateColumn( this.context.col.grid, this.context.col, type);
19023             }
19024           };
19025
19026           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregate' + type)) {
19027             col.menuItems.push(menuItem);
19028           }
19029         };
19030
19031         /**
19032          *  @ngdoc object
19033          *  @name groupingShowGroupingMenu
19034          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19035          *  @description Show the grouping (group and ungroup items) menu on this column
19036          *  <br/>Defaults to true.
19037          */
19038         if ( col.colDef.groupingShowGroupingMenu !== false ){
19039           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
19040             col.menuItems.push(groupColumn);
19041           }
19042
19043           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.ungroup')) {
19044             col.menuItems.push(ungroupColumn);
19045           }
19046         }
19047
19048
19049         /**
19050          *  @ngdoc object
19051          *  @name groupingShowAggregationMenu
19052          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19053          *  @description Show the aggregation menu on this column
19054          *  <br/>Defaults to true.
19055          */
19056         if ( col.colDef.groupingShowAggregationMenu !== false ){
19057           angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
19058             addAggregationMenu(name);
19059           });
19060           angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
19061             addAggregationMenu(name, aggregationDef.menuTitle);
19062           });
19063
19064           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregateRemove')) {
19065             col.menuItems.push(aggregateRemove);
19066           }
19067         }
19068       },
19069
19070
19071
19072
19073       /**
19074        * @ngdoc function
19075        * @name groupingColumnProcessor
19076        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19077        * @description Moves the columns around based on which are grouped
19078        *
19079        * @param {array} columns the columns to consider rendering
19080        * @param {array} rows the grid rows, which we don't use but are passed to us
19081        * @returns {array} updated columns array
19082        */
19083       groupingColumnProcessor: function( columns, rows ) {
19084         var grid = this;
19085
19086         columns = service.moveGroupColumns(this, columns, rows);
19087         return columns;
19088       },
19089
19090       /**
19091        * @ngdoc function
19092        * @name groupedFinalizerFn
19093        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19094        * @description Used on group columns to display the rendered value and optionally
19095        * display the count of rows.
19096        *
19097        * @param {aggregation} the aggregation entity for a grouped column
19098        */
19099       groupedFinalizerFn: function( aggregation ){
19100         var col = this;
19101
19102         if ( typeof(aggregation.groupVal) !== 'undefined') {
19103           aggregation.rendered = aggregation.groupVal;
19104           if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' ){
19105             aggregation.rendered += (' (' + aggregation.value + ')');
19106           }
19107         } else {
19108           aggregation.rendered = null;
19109         }
19110       },
19111
19112       /**
19113        * @ngdoc function
19114        * @name moveGroupColumns
19115        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19116        * @description Moves the column order so that the grouped columns are lined up
19117        * to the left (well, unless you're RTL, then it's the right).  By doing this in
19118        * the columnsProcessor, we make it transient - when the column is ungrouped it'll
19119        * go back to where it was.
19120        *
19121        * Does nothing if the option `moveGroupColumns` is set to false.
19122        *
19123        * @param {Grid} grid grid object
19124        * @param {array} columns the columns that we should process/move
19125        * @param {array} rows the grid rows
19126        * @returns {array} updated columns
19127        */
19128       moveGroupColumns: function( grid, columns, rows ){
19129         if ( grid.options.moveGroupColumns === false){
19130           return columns;
19131         }
19132
19133         columns.forEach( function(column, index){
19134           // position used to make stable sort in moveGroupColumns
19135           column.groupingPosition = index;
19136         });
19137
19138         columns.sort(function(a, b){
19139           var a_group, b_group;
19140           if (a.isRowHeader){
19141             a_group = -1000;
19142           }
19143           else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
19144             a_group = null;
19145           } else {
19146             a_group = a.grouping.groupPriority;
19147           }
19148
19149           if (b.isRowHeader){
19150             b_group = -1000;
19151           }
19152           else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
19153             b_group = null;
19154           } else {
19155             b_group = b.grouping.groupPriority;
19156           }
19157
19158           // groups get sorted to the top
19159           if ( a_group !== null && b_group === null) { return -1; }
19160           if ( b_group !== null && a_group === null) { return 1; }
19161           if ( a_group !== null && b_group !== null) {return a_group - b_group; }
19162
19163           return a.groupingPosition - b.groupingPosition;
19164         });
19165
19166         columns.forEach( function(column, index) {
19167           delete column.groupingPosition;
19168         });
19169
19170         return columns;
19171       },
19172
19173
19174       /**
19175        * @ngdoc function
19176        * @name groupColumn
19177        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19178        * @description Adds this column to the existing grouping, at the end of the priority order.
19179        * If the column doesn't have a sort, adds one, by default ASC
19180        *
19181        * This column will move to the left of any non-group columns, the
19182        * move is handled in a columnProcessor, so gets called as part of refresh
19183        *
19184        * @param {Grid} grid grid object
19185        * @param {GridCol} column the column we want to group
19186        */
19187       groupColumn: function( grid, column){
19188         if ( typeof(column.grouping) === 'undefined' ){
19189           column.grouping = {};
19190         }
19191
19192         // set the group priority to the next number in the hierarchy
19193         var existingGrouping = service.getGrouping( grid );
19194         column.grouping.groupPriority = existingGrouping.grouping.length;
19195
19196         // add sort if not present
19197         if ( !column.sort ){
19198           column.sort = { direction: uiGridConstants.ASC };
19199         } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
19200           column.sort.direction = uiGridConstants.ASC;
19201         }
19202
19203         column.treeAggregation = { type: uiGridGroupingConstants.aggregation.COUNT, source: 'grouping' };
19204         column.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19205         column.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19206
19207         grid.api.grouping.raise.groupingChanged(column);
19208         // This indirectly calls service.tidyPriorities( grid );
19209         grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
19210
19211         grid.queueGridRefresh();
19212       },
19213
19214
19215        /**
19216        * @ngdoc function
19217        * @name ungroupColumn
19218        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19219        * @description Removes the groupPriority from this column.  If the
19220        * column was previously aggregated the aggregation will come back.
19221        * The sort will remain.
19222        *
19223        * This column will move to the right of any other group columns, the
19224        * move is handled in a columnProcessor, so gets called as part of refresh
19225        *
19226        * @param {Grid} grid grid object
19227        * @param {GridCol} column the column we want to ungroup
19228        */
19229       ungroupColumn: function( grid, column){
19230         if ( typeof(column.grouping) === 'undefined' ){
19231           return;
19232         }
19233
19234         delete column.grouping.groupPriority;
19235         delete column.treeAggregation;
19236         delete column.customTreeAggregationFinalizer;
19237
19238         service.tidyPriorities( grid );
19239
19240         grid.api.grouping.raise.groupingChanged(column);
19241
19242         grid.queueGridRefresh();
19243       },
19244
19245       /**
19246        * @ngdoc function
19247        * @name aggregateColumn
19248        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19249        * @description Sets the aggregation type on a column, if the
19250        * column is currently grouped then it removes the grouping first.
19251        *
19252        * @param {Grid} grid grid object
19253        * @param {GridCol} column the column we want to aggregate
19254        * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
19255        */
19256       aggregateColumn: function( grid, column, aggregationType){
19257
19258         if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19259           service.ungroupColumn( grid, column );
19260         }
19261
19262         var aggregationDef = {};
19263         if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
19264           aggregationDef = grid.options.treeCustomAggregations[aggregationType];
19265         } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
19266           aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
19267         }
19268
19269         column.treeAggregation = { type: aggregationType, label:  i18nService.get().aggregation[aggregationDef.label] || aggregationDef.label };
19270         column.treeAggregationFn = aggregationDef.aggregationFn;
19271         column.treeAggregationFinalizerFn = aggregationDef.finalizerFn;
19272
19273         grid.api.grouping.raise.aggregationChanged(column);
19274
19275         grid.queueGridRefresh();
19276       },
19277
19278
19279       /**
19280        * @ngdoc function
19281        * @name setGrouping
19282        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19283        * @description Set the grouping based on a config object, used by the save state feature
19284        * (more specifically, by the restore function in that feature )
19285        *
19286        * @param {Grid} grid grid object
19287        * @param {object} config the config we want to set, same format as that returned by getGrouping
19288        */
19289       setGrouping: function ( grid, config ){
19290         if ( typeof(config) === 'undefined' ){
19291           return;
19292         }
19293
19294         // first remove any existing grouping
19295         service.clearGrouping(grid);
19296
19297         if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
19298           config.grouping.forEach( function( group ) {
19299             var col = grid.getColumn(group.colName);
19300
19301             if ( col ) {
19302               service.groupColumn( grid, col );
19303             }
19304           });
19305         }
19306
19307         if ( config.aggregations && config.aggregations.length ){
19308           config.aggregations.forEach( function( aggregation ) {
19309             var col = grid.getColumn(aggregation.colName);
19310
19311             if ( col ) {
19312               service.aggregateColumn( grid, col, aggregation.aggregation.type );
19313             }
19314           });
19315         }
19316
19317         if ( config.rowExpandedStates ){
19318           service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
19319         }
19320       },
19321
19322
19323       /**
19324        * @ngdoc function
19325        * @name clearGrouping
19326        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19327        * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
19328        * as we don't know whether that sorting was added by grouping or was there beforehand
19329        *
19330        * @param {Grid} grid grid object
19331        */
19332       clearGrouping: function( grid ) {
19333         var currentGrouping = service.getGrouping(grid);
19334
19335         if ( currentGrouping.grouping.length > 0 ){
19336           currentGrouping.grouping.forEach( function( group ) {
19337             if (!group.col){
19338               // should have a group.colName if there's no col
19339               group.col = grid.getColumn(group.colName);
19340             }
19341             service.ungroupColumn(grid, group.col);
19342           });
19343         }
19344
19345         if ( currentGrouping.aggregations.length > 0 ){
19346           currentGrouping.aggregations.forEach( function( aggregation ){
19347             if (!aggregation.col){
19348               // should have a group.colName if there's no col
19349               aggregation.col = grid.getColumn(aggregation.colName);
19350             }
19351             service.aggregateColumn(grid, aggregation.col, null);
19352           });
19353         }
19354       },
19355
19356
19357       /**
19358        * @ngdoc function
19359        * @name tidyPriorities
19360        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19361        * @description Renumbers groupPriority and sortPriority such that
19362        * groupPriority is contiguous, and sortPriority either matches
19363        * groupPriority (for group columns), and otherwise is contiguous and
19364        * higher than groupPriority.
19365        *
19366        * @param {Grid} grid grid object
19367        */
19368       tidyPriorities: function( grid ){
19369         // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
19370         if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
19371           grid = this.grid;
19372         }
19373
19374         var groupArray = [];
19375         var sortArray = [];
19376
19377         grid.columns.forEach( function(column, index){
19378           if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19379             groupArray.push(column);
19380           } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
19381             sortArray.push(column);
19382           }
19383         });
19384
19385         groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
19386         groupArray.forEach( function(column, index){
19387           column.grouping.groupPriority = index;
19388           column.suppressRemoveSort = true;
19389           if ( typeof(column.sort) === 'undefined'){
19390             column.sort = {};
19391           }
19392           column.sort.priority = index;
19393         });
19394
19395         var i = groupArray.length;
19396         sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
19397         sortArray.forEach( function(column, index){
19398           column.sort.priority = i;
19399           column.suppressRemoveSort = column.colDef.suppressRemoveSort;
19400           i++;
19401         });
19402       },
19403
19404
19405       /**
19406        * @ngdoc function
19407        * @name groupRows
19408        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19409        * @description The rowProcessor that creates the groupHeaders (i.e. does
19410        * the actual grouping).
19411        *
19412        * Assumes it is always called after the sorting processor, guaranteed by the priority setting
19413        *
19414        * Processes all the rows in order, inserting a groupHeader row whenever there is a change
19415        * in value of a grouped row, based on the sortAlgorithm used for the column.  The group header row
19416        * is looked up in the groupHeaderCache, and used from there if there is one. The entity is reset
19417        * to {} if one is found.
19418        *
19419        * As it processes it maintains a `processingState` array. This records, for each level of grouping we're
19420        * working with, the following information:
19421        * ```
19422        *   {
19423        *     fieldName: name,
19424        *     col: col,
19425        *     initialised: boolean,
19426        *     currentValue: value,
19427        *     currentRow: gridRow,
19428        *   }
19429        * ```
19430        * We look for changes in the currentValue at any of the levels.  Where we find a change we:
19431        *
19432        * - create a new groupHeader row in the array
19433        *
19434        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
19435        * @returns {array} the updated rows, including our new group rows
19436        */
19437       groupRows: function( renderableRows ) {
19438         if (renderableRows.length === 0){
19439           return renderableRows;
19440         }
19441
19442         var grid = this;
19443         grid.grouping.oldGroupingHeaderCache = grid.grouping.groupingHeaderCache || {};
19444         grid.grouping.groupingHeaderCache = {};
19445
19446         var processingState = service.initialiseProcessingState( grid );
19447
19448         // processes each of the fields we are grouping by, checks if the value has changed and inserts a groupHeader
19449         // Broken out as shouldn't create functions in a loop.
19450         var updateProcessingState = function( groupFieldState, stateIndex ) {
19451           var fieldValue = grid.getCellValue(row, groupFieldState.col);
19452
19453           // look for change of value - and insert a header
19454           if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
19455             service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
19456             i++;
19457           }
19458         };
19459
19460         // use a for loop because it's tolerant of the array length changing whilst we go - we can
19461         // manipulate the iterator when we insert groupHeader rows
19462         for (var i = 0; i < renderableRows.length; i++ ){
19463           var row = renderableRows[i];
19464
19465           if ( row.visible ){
19466             processingState.forEach( updateProcessingState );
19467           }
19468         }
19469
19470         delete grid.grouping.oldGroupingHeaderCache;
19471         return renderableRows;
19472       },
19473
19474
19475       /**
19476        * @ngdoc function
19477        * @name initialiseProcessingState
19478        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19479        * @description Creates the processing state array that is used
19480        * for groupRows.
19481        *
19482        * @param {Grid} grid grid object
19483        * @returns {array} an array in the format described in the groupRows method,
19484        * initialised with blank values
19485        */
19486       initialiseProcessingState: function( grid ){
19487         var processingState = [];
19488         var columnSettings = service.getGrouping( grid );
19489
19490         columnSettings.grouping.forEach( function( groupItem, index){
19491           processingState.push({
19492             fieldName: groupItem.field,
19493             col: groupItem.col,
19494             initialised: false,
19495             currentValue: null,
19496             currentRow: null
19497           });
19498         });
19499
19500         return processingState;
19501       },
19502
19503
19504       /**
19505        * @ngdoc function
19506        * @name getGrouping
19507        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19508        * @description Get the grouping settings from the columns.  As a side effect
19509        * this always renumbers the grouping starting at 0
19510        * @param {Grid} grid grid object
19511        * @returns {array} an array of the group fields, in order of priority
19512        */
19513       getGrouping: function( grid ){
19514         var groupArray = [];
19515         var aggregateArray = [];
19516
19517         // get all the grouping
19518         grid.columns.forEach( function(column, columnIndex){
19519           if ( column.grouping ){
19520             if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
19521               groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
19522             }
19523           }
19524           if ( column.treeAggregation && column.treeAggregation.type ){
19525             aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
19526           }
19527         });
19528
19529         // sort grouping into priority order
19530         groupArray.sort( function(a, b){
19531           return a.groupPriority - b.groupPriority;
19532         });
19533
19534         // renumber the priority in case it was somewhat messed up, then remove the grouping reference
19535         groupArray.forEach( function( group, index) {
19536           group.grouping.groupPriority = index;
19537           group.groupPriority = index;
19538           delete group.grouping;
19539         });
19540
19541         return { grouping: groupArray, aggregations: aggregateArray };
19542       },
19543
19544
19545       /**
19546        * @ngdoc function
19547        * @name insertGroupHeader
19548        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19549        * @description Create a group header row, and link it to the various configuration
19550        * items that we use.
19551        *
19552        * Look for the row in the oldGroupingHeaderCache, write the row into the new groupingHeaderCache.
19553        *
19554        * @param {Grid} grid grid object
19555        * @param {array} renderableRows the rows that we are processing
19556        * @param {number} rowIndex the row we were up to processing
19557        * @param {array} processingState the current processing state
19558        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header -
19559        * i.e. the column that we want to create a header for
19560        */
19561       insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
19562         // set the value that caused the end of a group into the header row and the processing state
19563         var fieldName = processingState[stateIndex].fieldName;
19564         var col = processingState[stateIndex].col;
19565
19566         var newValue = grid.getCellValue(renderableRows[rowIndex], col);
19567         var newDisplayValue = newValue;
19568         if ( typeof(newValue) === 'undefined' || newValue === null ) {
19569           newDisplayValue = grid.options.groupingNullLabel;
19570         }
19571
19572         var getKeyAsValueForCacheMap = function(key) {
19573           if (angular.isObject(key)) {
19574               return JSON.stringify(key);
19575           } else {
19576               return key;
19577           }
19578         };
19579
19580         var cacheItem = grid.grouping.oldGroupingHeaderCache;
19581         for ( var i = 0; i < stateIndex; i++ ){
19582           if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)] ){
19583             cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
19584           }
19585         }
19586
19587         var headerRow;
19588         if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(newValue)]){
19589           headerRow = cacheItem[getKeyAsValueForCacheMap(newValue)].row;
19590           headerRow.entity = {};
19591         } else {
19592           headerRow = new GridRow( {}, null, grid );
19593           gridClassFactory.rowTemplateAssigner.call(grid, headerRow);
19594         }
19595
19596         headerRow.entity['$$' + processingState[stateIndex].col.uid] = { groupVal: newDisplayValue };
19597         headerRow.treeLevel = stateIndex;
19598         headerRow.groupHeader = true;
19599         headerRow.internalRow = true;
19600         headerRow.enableCellEdit = false;
19601         headerRow.enableSelection = grid.options.enableGroupHeaderSelection;
19602         processingState[stateIndex].initialised = true;
19603         processingState[stateIndex].currentValue = newValue;
19604         processingState[stateIndex].currentRow = headerRow;
19605
19606         // set all processing states below this one to not be initialised - change of this state
19607         // means all those need to start again
19608         service.finaliseProcessingState( processingState, stateIndex + 1);
19609
19610         // insert our new header row
19611         renderableRows.splice(rowIndex, 0, headerRow);
19612
19613         // add our new header row to the cache
19614         cacheItem = grid.grouping.groupingHeaderCache;
19615         for ( i = 0; i < stateIndex; i++ ){
19616           cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
19617         }
19618         cacheItem[getKeyAsValueForCacheMap(newValue)] = { row: headerRow, children: {} };
19619       },
19620
19621
19622       /**
19623        * @ngdoc function
19624        * @name finaliseProcessingState
19625        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19626        * @description Set all processing states lower than the one that had a break in value to
19627        * no longer be initialised.  Render the counts into the entity ready for display.
19628        *
19629        * @param {Grid} grid grid object
19630        * @param {array} processingState the current processing state
19631        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
19632        * processing states after this need to be finalised
19633        */
19634       finaliseProcessingState: function( processingState, stateIndex ){
19635         for ( var i = stateIndex; i < processingState.length; i++){
19636           processingState[i].initialised = false;
19637           processingState[i].currentRow = null;
19638           processingState[i].currentValue = null;
19639         }
19640       },
19641
19642
19643       /**
19644        * @ngdoc function
19645        * @name getRowExpandedStates
19646        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19647        * @description Extract the groupHeaderCache hash, pulling out only the states.
19648        *
19649        * The example below shows a grid that is grouped by gender then age
19650        *
19651        * <pre>
19652        *   {
19653        *     male: {
19654        *       state: 'expanded',
19655        *       children: {
19656        *         22: { state: 'expanded' },
19657        *         30: { state: 'collapsed' }
19658        *       }
19659        *     },
19660        *     female: {
19661        *       state: 'expanded',
19662        *       children: {
19663        *         28: { state: 'expanded' },
19664        *         55: { state: 'collapsed' }
19665        *       }
19666        *     }
19667        *   }
19668        * </pre>
19669        *
19670        * @param {Grid} grid grid object
19671        * @returns {hash} the expanded states as a hash
19672        */
19673       getRowExpandedStates: function(treeChildren){
19674         if ( typeof(treeChildren) === 'undefined' ){
19675           return {};
19676         }
19677
19678         var newChildren = {};
19679
19680         angular.forEach( treeChildren, function( value, key ){
19681           newChildren[key] = { state: value.row.treeNode.state };
19682           if ( value.children ){
19683             newChildren[key].children = service.getRowExpandedStates( value.children );
19684           } else {
19685             newChildren[key].children = {};
19686           }
19687         });
19688
19689         return newChildren;
19690       },
19691
19692
19693       /**
19694        * @ngdoc function
19695        * @name applyRowExpandedStates
19696        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19697        * @description Take a hash in the format as created by getRowExpandedStates,
19698        * and apply it to the grid.grouping.groupHeaderCache.
19699        *
19700        * Takes a treeSubset, and applies to a treeSubset - so can be called
19701        * recursively.
19702        *
19703        * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
19704        * the children of that hash
19705        * @returns {hash} expandedStates can be the full expanded states, or children
19706        * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
19707        */
19708       applyRowExpandedStates: function( currentNode, expandedStates ){
19709         if ( typeof(expandedStates) === 'undefined' ){
19710           return;
19711         }
19712
19713         angular.forEach(expandedStates, function( value, key ) {
19714           if ( currentNode[key] ){
19715             currentNode[key].row.treeNode.state = value.state;
19716
19717             if (value.children && currentNode[key].children){
19718               service.applyRowExpandedStates( currentNode[key].children, value.children );
19719             }
19720           }
19721         });
19722       }
19723
19724
19725     };
19726
19727     return service;
19728
19729   }]);
19730
19731
19732   /**
19733    *  @ngdoc directive
19734    *  @name ui.grid.grouping.directive:uiGridGrouping
19735    *  @element div
19736    *  @restrict A
19737    *
19738    *  @description Adds grouping features to grid
19739    *
19740    *  @example
19741    <example module="app">
19742    <file name="app.js">
19743    var app = angular.module('app', ['ui.grid', 'ui.grid.grouping']);
19744
19745    app.controller('MainCtrl', ['$scope', function ($scope) {
19746       $scope.data = [
19747         { name: 'Bob', title: 'CEO' },
19748             { name: 'Frank', title: 'Lowly Developer' }
19749       ];
19750
19751       $scope.columnDefs = [
19752         {name: 'name', enableCellEdit: true},
19753         {name: 'title', enableCellEdit: true}
19754       ];
19755
19756       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
19757     }]);
19758    </file>
19759    <file name="index.html">
19760    <div ng-controller="MainCtrl">
19761    <div ui-grid="gridOptions" ui-grid-grouping></div>
19762    </div>
19763    </file>
19764    </example>
19765    */
19766   module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
19767   function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
19768     return {
19769       replace: true,
19770       priority: 0,
19771       require: '^uiGrid',
19772       scope: false,
19773       compile: function () {
19774         return {
19775           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
19776             if (uiGridCtrl.grid.options.enableGrouping !== false){
19777               uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
19778             }
19779           },
19780           post: function ($scope, $elm, $attrs, uiGridCtrl) {
19781           }
19782         };
19783       }
19784     };
19785   }]);
19786
19787 })();
19788
19789 (function () {
19790   'use strict';
19791
19792   /**
19793    * @ngdoc overview
19794    * @name ui.grid.importer
19795    * @description
19796    *
19797    * # ui.grid.importer
19798    *
19799    * <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>
19800    *
19801    * This module provides the ability to import data into the grid. It
19802    * uses the column defs to work out which data belongs in which column,
19803    * and creates entities from a configured class (typically a $resource).
19804    *
19805    * If the rowEdit feature is enabled, it also calls save on those newly
19806    * created objects, and then displays any errors in the imported data.
19807    *
19808    * Currently the importer imports only CSV and json files, although provision has been
19809    * made to process other file formats, and these can be added over time.
19810    *
19811    * For json files, the properties within each object in the json must match the column names
19812    * (to put it another way, the importer doesn't process the json, it just copies the objects
19813    * within the json into a new instance of the specified object type)
19814    *
19815    * For CSV import, the default column identification relies on each column in the
19816    * header row matching a column.name or column.displayName. Optionally, a column identification
19817    * callback can be used.  This allows matching using other attributes, which is particularly
19818    * useful if your application has internationalised column headings (i.e. the headings that
19819    * the user sees don't match the column names).
19820    *
19821    * The importer makes use of the grid menu as the UI for requesting an
19822    * import.
19823    *
19824    * <div ui-grid-importer></div>
19825    */
19826
19827   var module = angular.module('ui.grid.importer', ['ui.grid']);
19828
19829   /**
19830    *  @ngdoc object
19831    *  @name ui.grid.importer.constant:uiGridImporterConstants
19832    *
19833    *  @description constants available in importer module
19834    */
19835
19836   module.constant('uiGridImporterConstants', {
19837     featureName: 'importer'
19838   });
19839
19840   /**
19841    *  @ngdoc service
19842    *  @name ui.grid.importer.service:uiGridImporterService
19843    *
19844    *  @description Services for importer feature
19845    */
19846   module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
19847     function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
19848
19849       var service = {
19850
19851         initializeGrid: function ($scope, grid) {
19852
19853           //add feature namespace and any properties to grid for needed state
19854           grid.importer = {
19855             $scope: $scope
19856           };
19857
19858           this.defaultGridOptions(grid.options);
19859
19860           /**
19861            *  @ngdoc object
19862            *  @name ui.grid.importer.api:PublicApi
19863            *
19864            *  @description Public Api for importer feature
19865            */
19866           var publicApi = {
19867             events: {
19868               importer: {
19869               }
19870             },
19871             methods: {
19872               importer: {
19873                 /**
19874                  * @ngdoc function
19875                  * @name importFile
19876                  * @methodOf  ui.grid.importer.api:PublicApi
19877                  * @description Imports a file into the grid using the file object
19878                  * provided.  Bypasses the grid menu
19879                  * @param {File} fileObject the file we want to import, as a javascript
19880                  * File object
19881                  */
19882                 importFile: function ( fileObject ) {
19883                   service.importThisFile( grid, fileObject );
19884                 }
19885               }
19886             }
19887           };
19888
19889           grid.api.registerEventsFromObject(publicApi.events);
19890
19891           grid.api.registerMethodsFromObject(publicApi.methods);
19892
19893           if ( grid.options.enableImporter && grid.options.importerShowMenu ){
19894             if ( grid.api.core.addToGridMenu ){
19895               service.addToMenu( grid );
19896             } else {
19897               // order of registration is not guaranteed, register in a little while
19898               $interval( function() {
19899                 if (grid.api.core.addToGridMenu){
19900                   service.addToMenu( grid );
19901                 }
19902               }, 100, 1);
19903             }
19904           }
19905         },
19906
19907
19908         defaultGridOptions: function (gridOptions) {
19909           //default option to true unless it was explicitly set to false
19910           /**
19911            * @ngdoc object
19912            * @name ui.grid.importer.api:GridOptions
19913            *
19914            * @description GridOptions for importer feature, these are available to be
19915            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
19916            */
19917
19918           /**
19919            * @ngdoc property
19920            * @propertyOf ui.grid.importer.api:GridOptions
19921            * @name enableImporter
19922            * @description Whether or not importer is enabled.  Automatically set
19923            * to false if the user's browser does not support the required fileApi.
19924            * Otherwise defaults to true.
19925            *
19926            */
19927           if (gridOptions.enableImporter  || gridOptions.enableImporter === undefined) {
19928             if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
19929               gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
19930               gridOptions.enableImporter = false;
19931             } else {
19932               gridOptions.enableImporter = true;
19933             }
19934           } else {
19935             gridOptions.enableImporter = false;
19936           }
19937
19938           /**
19939            * @ngdoc method
19940            * @name importerProcessHeaders
19941            * @methodOf ui.grid.importer.api:GridOptions
19942            * @description A callback function that will process headers using custom
19943            * logic.  Set this callback function if the headers that your user will provide in their
19944            * import file don't necessarily match the grid header or field names.  This might commonly
19945            * occur where your application is internationalised, and therefore the field names
19946            * that the user recognises are in a different language than the field names that
19947            * ui-grid knows about.
19948            *
19949            * Defaults to the internal `processHeaders` method, which seeks to match using both
19950            * displayName and column.name.  Any non-matching columns are discarded.
19951            *
19952            * Your callback routine should respond by processing the header array, and returning an array
19953            * of matching column names.  A null value in any given position means "don't import this column"
19954            *
19955            * <pre>
19956            *      gridOptions.importerProcessHeaders: function( headerArray ) {
19957            *        var myHeaderColumns = [];
19958            *        var thisCol;
19959            *        headerArray.forEach( function( value, index ) {
19960            *          thisCol = mySpecialLookupFunction( value );
19961            *          myHeaderColumns.push( thisCol.name );
19962            *        });
19963            *
19964            *        return myHeaderCols;
19965            *      })
19966            * </pre>
19967            * @param {Grid} grid the grid we're importing into
19968            * @param {array} headerArray an array of the text from the first row of the csv file,
19969            * which you need to match to column.names
19970            * @returns {array} array of matching column names, in the same order as the headerArray
19971            *
19972            */
19973           gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
19974
19975           /**
19976            * @ngdoc method
19977            * @name importerHeaderFilter
19978            * @methodOf ui.grid.importer.api:GridOptions
19979            * @description A callback function that will filter (usually translate) a single
19980            * header.  Used when you want to match the passed in column names to the column
19981            * displayName after the header filter.
19982            *
19983            * Your callback routine needs to return the filtered header value.
19984            * <pre>
19985            *      gridOptions.importerHeaderFilter: function( displayName ) {
19986            *        return $translate.instant( displayName );
19987            *      })
19988            * </pre>
19989            *
19990            * or:
19991            * <pre>
19992            *      gridOptions.importerHeaderFilter: $translate.instant
19993            * </pre>
19994            * @param {string} displayName the displayName that we'd like to translate
19995            * @returns {string} the translated name
19996            *
19997            */
19998           gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
19999
20000           /**
20001            * @ngdoc method
20002            * @name importerErrorCallback
20003            * @methodOf ui.grid.importer.api:GridOptions
20004            * @description A callback function that provides custom error handling, rather
20005            * than the standard grid behaviour of an alert box and a console message.  You
20006            * might use this to internationalise the console log messages, or to write to a
20007            * custom logging routine that returned errors to the server.
20008            *
20009            * <pre>
20010            *      gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
20011            *        myUserDisplayRoutine( errorKey );
20012            *        myLoggingRoutine( consoleMessage, context );
20013            *      })
20014            * </pre>
20015            * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
20016            * in some way
20017            * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
20018            * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
20019            * @param {string} consoleMessage the English console message that importer would have written
20020            * @param {object} context the context data that importer would have appended to that console message,
20021            * often the file content itself or the element that is in error
20022            *
20023            */
20024           if ( !gridOptions.importerErrorCallback ||  typeof(gridOptions.importerErrorCallback) !== 'function' ){
20025             delete gridOptions.importerErrorCallback;
20026           }
20027
20028           /**
20029            * @ngdoc method
20030            * @name importerDataAddCallback
20031            * @methodOf ui.grid.importer.api:GridOptions
20032            * @description A mandatory callback function that adds data to the source data array.  The grid
20033            * generally doesn't add rows to the source data array, it is tidier to handle this through a user
20034            * callback.
20035            *
20036            * <pre>
20037            *      gridOptions.importerDataAddCallback: function( grid, newObjects ) {
20038            *        $scope.myData = $scope.myData.concat( newObjects );
20039            *      })
20040            * </pre>
20041            * @param {Grid} grid the grid we're importing into, may be useful in some way
20042            * @param {array} newObjects an array of new objects that you should add to your data
20043            *
20044            */
20045           if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
20046             gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
20047             gridOptions.enableImporter = false;
20048           }
20049
20050           /**
20051            * @ngdoc object
20052            * @name importerNewObject
20053            * @propertyOf  ui.grid.importer.api:GridOptions
20054            * @description An object on which we call `new` to create each new row before inserting it into
20055            * the data array.  Typically this would be a $resource entity, which means that if you're using
20056            * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
20057            *
20058            * Defaults to a vanilla javascript object
20059            *
20060            * @example
20061            * <pre>
20062            *   gridOptions.importerNewObject = MyRes;
20063            * </pre>
20064            *
20065            */
20066
20067           /**
20068            * @ngdoc property
20069            * @propertyOf ui.grid.importer.api:GridOptions
20070            * @name importerShowMenu
20071            * @description Whether or not to show an item in the grid menu.  Defaults to true.
20072            *
20073            */
20074           gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
20075
20076           /**
20077            * @ngdoc method
20078            * @methodOf ui.grid.importer.api:GridOptions
20079            * @name importerObjectCallback
20080            * @description A callback that massages the data for each object.  For example,
20081            * you might have data stored as a code value, but display the decode.  This callback
20082            * can be used to change the decoded value back into a code.  Defaults to doing nothing.
20083            * @param {Grid} grid in case you need it
20084            * @param {object} newObject the new object as importer has created it, modify it
20085            * then return the modified version
20086            * @returns {object} the modified object
20087            * @example
20088            * <pre>
20089            *   gridOptions.importerObjectCallback = function ( grid, newObject ) {
20090            *     switch newObject.status {
20091            *       case 'Active':
20092            *         newObject.status = 1;
20093            *         break;
20094            *       case 'Inactive':
20095            *         newObject.status = 2;
20096            *         break;
20097            *     }
20098            *     return newObject;
20099            *   };
20100            * </pre>
20101            */
20102           gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
20103         },
20104
20105
20106         /**
20107          * @ngdoc function
20108          * @name addToMenu
20109          * @methodOf  ui.grid.importer.service:uiGridImporterService
20110          * @description Adds import menu item to the grid menu,
20111          * allowing the user to request import of a file
20112          * @param {Grid} grid the grid into which data should be imported
20113          */
20114         addToMenu: function ( grid ) {
20115           grid.api.core.addToGridMenu( grid, [
20116             {
20117               title: i18nService.getSafeText('gridMenu.importerTitle'),
20118               order: 150
20119             },
20120             {
20121               templateUrl: 'ui-grid/importerMenuItemContainer',
20122               action: function ($event) {
20123                 this.grid.api.importer.importAFile( grid );
20124               },
20125               order: 151
20126             }
20127           ]);
20128         },
20129
20130
20131         /**
20132          * @ngdoc function
20133          * @name importThisFile
20134          * @methodOf ui.grid.importer.service:uiGridImporterService
20135          * @description Imports the provided file into the grid using the file object
20136          * provided.  Bypasses the grid menu
20137          * @param {Grid} grid the grid we're importing into
20138          * @param {File} fileObject the file we want to import, as returned from the File
20139          * javascript object
20140          */
20141         importThisFile: function ( grid, fileObject ) {
20142           if (!fileObject){
20143             gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
20144             return;
20145           }
20146
20147           var reader = new FileReader();
20148
20149           switch ( fileObject.type ){
20150             case 'application/json':
20151               reader.onload = service.importJsonClosure( grid );
20152               break;
20153             default:
20154               reader.onload = service.importCsvClosure( grid );
20155               break;
20156           }
20157
20158           reader.readAsText( fileObject );
20159         },
20160
20161
20162         /**
20163          * @ngdoc function
20164          * @name importJson
20165          * @methodOf ui.grid.importer.service:uiGridImporterService
20166          * @description Creates a function that imports a json file into the grid.
20167          * The json data is imported into new objects of type `gridOptions.importerNewObject`,
20168          * and if the rowEdit feature is enabled the rows are marked as dirty
20169          * @param {Grid} grid the grid we want to import into
20170          * @param {FileObject} importFile the file that we want to import, as
20171          * a FileObject
20172          */
20173         importJsonClosure: function( grid ) {
20174           return function( importFile ){
20175             var newObjects = [];
20176             var newObject;
20177
20178             var importArray = service.parseJson( grid, importFile );
20179             if (importArray === null){
20180               return;
20181             }
20182             importArray.forEach(  function( value, index ) {
20183               newObject = service.newObject( grid );
20184               angular.extend( newObject, value );
20185               newObject = grid.options.importerObjectCallback( grid, newObject );
20186               newObjects.push( newObject );
20187             });
20188
20189             service.addObjects( grid, newObjects );
20190
20191           };
20192         },
20193
20194
20195         /**
20196          * @ngdoc function
20197          * @name parseJson
20198          * @methodOf ui.grid.importer.service:uiGridImporterService
20199          * @description Parses a json file, returns the parsed data.
20200          * Displays an error if file doesn't parse
20201          * @param {Grid} grid the grid that we want to import into
20202          * @param {FileObject} importFile the file that we want to import, as
20203          * a FileObject
20204          * @returns {array} array of objects from the imported json
20205          */
20206         parseJson: function( grid, importFile ){
20207           var loadedObjects;
20208           try {
20209             loadedObjects = JSON.parse( importFile.target.result );
20210           } catch (e) {
20211             service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
20212             return;
20213           }
20214
20215           if ( !Array.isArray( loadedObjects ) ){
20216             service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
20217             return [];
20218           } else {
20219             return loadedObjects;
20220           }
20221         },
20222
20223
20224
20225         /**
20226          * @ngdoc function
20227          * @name importCsvClosure
20228          * @methodOf ui.grid.importer.service:uiGridImporterService
20229          * @description Creates a function that imports a csv file into the grid
20230          * (allowing it to be used in the reader.onload event)
20231          * @param {Grid} grid the grid that we want to import into
20232          * @param {FileObject} importFile the file that we want to import, as
20233          * a file object
20234          */
20235         importCsvClosure: function( grid ) {
20236           return function( importFile ){
20237             var importArray = service.parseCsv( importFile );
20238             if ( !importArray || importArray.length < 1 ){
20239               service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
20240               return;
20241             }
20242
20243             var newObjects = service.createCsvObjects( grid, importArray );
20244             if ( !newObjects || newObjects.length === 0 ){
20245               service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
20246               return;
20247             }
20248
20249             service.addObjects( grid, newObjects );
20250           };
20251         },
20252
20253
20254         /**
20255          * @ngdoc function
20256          * @name parseCsv
20257          * @methodOf ui.grid.importer.service:uiGridImporterService
20258          * @description Parses a csv file into an array of arrays, with the first
20259          * array being the headers, and the remaining arrays being the data.
20260          * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
20261          * which is noted as being under the MIT license.  The code is modified to pass the jscs yoda condition
20262          * checker
20263          * @param {FileObject} importFile the file that we want to import, as a
20264          * file object
20265          */
20266         parseCsv: function( importFile ) {
20267           var csv = importFile.target.result;
20268
20269           // use the CSV-JS library to parse
20270           return CSV.parse(csv);
20271         },
20272
20273
20274         /**
20275          * @ngdoc function
20276          * @name createCsvObjects
20277          * @methodOf ui.grid.importer.service:uiGridImporterService
20278          * @description Converts an array of arrays (representing the csv file)
20279          * into a set of objects.  Uses the provided `gridOptions.importerNewObject`
20280          * to create the objects, and maps the header row into the individual columns
20281          * using either `gridOptions.importerProcessHeaders`, or by using a native method
20282          * of matching to either the displayName, column name or column field of
20283          * the columns in the column defs.  The resulting objects will have attributes
20284          * that are named based on the column.field or column.name, in that order.
20285          * @param {Grid} grid the grid that we want to import into
20286          * @param {Array} importArray the data that we want to import, as an array
20287          */
20288         createCsvObjects: function( grid, importArray ){
20289           // pull off header row and turn into headers
20290           var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
20291           if ( !headerMapping || headerMapping.length === 0 ){
20292             service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
20293             return [];
20294           }
20295
20296           var newObjects = [];
20297           var newObject;
20298           importArray.forEach( function( row, index ) {
20299             newObject = service.newObject( grid );
20300             if ( row !== null ){
20301               row.forEach( function( field, index ){
20302                 if ( headerMapping[index] !== null ){
20303                   newObject[ headerMapping[index] ] = field;
20304                 }
20305               });
20306             }
20307             newObject = grid.options.importerObjectCallback( grid, newObject );
20308             newObjects.push( newObject );
20309           });
20310
20311           return newObjects;
20312         },
20313
20314
20315         /**
20316          * @ngdoc function
20317          * @name processHeaders
20318          * @methodOf ui.grid.importer.service:uiGridImporterService
20319          * @description Determines the columns that the header row from
20320          * a csv (or other) file represents.
20321          * @param {Grid} grid the grid we're importing into
20322          * @param {array} headerRow the header row that we wish to match against
20323          * the column definitions
20324          * @returns {array} an array of the attribute names that should be used
20325          * for that column, based on matching the headers or creating the headers
20326          *
20327          */
20328         processHeaders: function( grid, headerRow ) {
20329           var headers = [];
20330           if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
20331             // we are going to create new columnDefs for all these columns, so just remove
20332             // spaces from the names to create fields
20333             headerRow.forEach( function( value, index ) {
20334               headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
20335             });
20336             return headers;
20337           } else {
20338             var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
20339             headerRow.forEach(  function( value, index ) {
20340               if ( lookupHash[value] ) {
20341                 headers.push( lookupHash[value] );
20342               } else if ( lookupHash[ value.toLowerCase() ] ) {
20343                 headers.push( lookupHash[ value.toLowerCase() ] );
20344               } else {
20345                 headers.push( null );
20346               }
20347             });
20348             return headers;
20349           }
20350         },
20351
20352
20353         /**
20354          * @name flattenColumnDefs
20355          * @methodOf ui.grid.importer.service:uiGridImporterService
20356          * @description Runs through the column defs and creates a hash of
20357          * the displayName, name and field, and of each of those values forced to lower case,
20358          * with each pointing to the field or name
20359          * (whichever is present).  Used to lookup column headers and decide what
20360          * attribute name to give to the resulting field.
20361          * @param {Grid} grid the grid we're importing into
20362          * @param {array} columnDefs the columnDefs that we should flatten
20363          * @returns {hash} the flattened version of the column def information, allowing
20364          * us to look up a value by `flattenedHash[ headerValue ]`
20365          */
20366         flattenColumnDefs: function( grid, columnDefs ){
20367           var flattenedHash = {};
20368           columnDefs.forEach(  function( columnDef, index) {
20369             if ( columnDef.name ){
20370               flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
20371               flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
20372             }
20373
20374             if ( columnDef.field ){
20375               flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
20376               flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
20377             }
20378
20379             if ( columnDef.displayName ){
20380               flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
20381               flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
20382             }
20383
20384             if ( columnDef.displayName && grid.options.importerHeaderFilter ){
20385               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
20386               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
20387             }
20388           });
20389
20390           return flattenedHash;
20391         },
20392
20393
20394         /**
20395          * @ngdoc function
20396          * @name addObjects
20397          * @methodOf ui.grid.importer.service:uiGridImporterService
20398          * @description Inserts our new objects into the grid data, and
20399          * sets the rows to dirty if the rowEdit feature is being used
20400          *
20401          * Does this by registering a watch on dataChanges, which essentially
20402          * is waiting on the result of the grid data watch, and downstream processing.
20403          *
20404          * When the callback is called, it deregisters itself - we don't want to run
20405          * again next time data is added.
20406          *
20407          * If we never get called, we deregister on destroy.
20408          *
20409          * @param {Grid} grid the grid we're importing into
20410          * @param {array} newObjects the objects we want to insert into the grid data
20411          * @returns {object} the new object
20412          */
20413         addObjects: function( grid, newObjects, $scope ){
20414           if ( grid.api.rowEdit ){
20415             var dataChangeDereg = grid.registerDataChangeCallback( function() {
20416               grid.api.rowEdit.setRowsDirty( newObjects );
20417               dataChangeDereg();
20418             }, [uiGridConstants.dataChange.ROW] );
20419
20420             grid.importer.$scope.$on( '$destroy', dataChangeDereg );
20421           }
20422
20423           grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
20424
20425         },
20426
20427
20428         /**
20429          * @ngdoc function
20430          * @name newObject
20431          * @methodOf ui.grid.importer.service:uiGridImporterService
20432          * @description Makes a new object based on `gridOptions.importerNewObject`,
20433          * or based on an empty object if not present
20434          * @param {Grid} grid the grid we're importing into
20435          * @returns {object} the new object
20436          */
20437         newObject: function( grid ){
20438           if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
20439             return new grid.options.importerNewObject();
20440           } else {
20441             return {};
20442           }
20443         },
20444
20445
20446         /**
20447          * @ngdoc function
20448          * @name alertError
20449          * @methodOf ui.grid.importer.service:uiGridImporterService
20450          * @description Provides an internationalised user alert for the failure,
20451          * and logs a console message including diagnostic content.
20452          * Optionally, if the the `gridOptions.importerErrorCallback` routine
20453          * is defined, then calls that instead, allowing user specified error routines
20454          * @param {Grid} grid the grid we're importing into
20455          * @param {array} headerRow the header row that we wish to match against
20456          * the column definitions
20457          */
20458         alertError: function( grid, alertI18nToken, consoleMessage, context ){
20459           if ( grid.options.importerErrorCallback ){
20460             grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
20461           } else {
20462             $window.alert(i18nService.getSafeText( alertI18nToken ));
20463             gridUtil.logError(consoleMessage + context );
20464           }
20465         }
20466       };
20467
20468       return service;
20469
20470     }
20471   ]);
20472
20473   /**
20474    *  @ngdoc directive
20475    *  @name ui.grid.importer.directive:uiGridImporter
20476    *  @element div
20477    *  @restrict A
20478    *
20479    *  @description Adds importer features to grid
20480    *
20481    */
20482   module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
20483     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
20484       return {
20485         replace: true,
20486         priority: 0,
20487         require: '^uiGrid',
20488         scope: false,
20489         link: function ($scope, $elm, $attrs, uiGridCtrl) {
20490           uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
20491         }
20492       };
20493     }
20494   ]);
20495
20496   /**
20497    *  @ngdoc directive
20498    *  @name ui.grid.importer.directive:uiGridImporterMenuItem
20499    *  @element div
20500    *  @restrict A
20501    *
20502    *  @description Handles the processing from the importer menu item - once a file is
20503    *  selected
20504    *
20505    */
20506   module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
20507     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
20508       return {
20509         replace: true,
20510         priority: 0,
20511         require: '^uiGrid',
20512         scope: false,
20513         templateUrl: 'ui-grid/importerMenuItem',
20514         link: function ($scope, $elm, $attrs, uiGridCtrl) {
20515           var handleFileSelect = function( event ){
20516             var target = event.srcElement || event.target;
20517
20518             if (target && target.files && target.files.length === 1) {
20519               var fileObject = target.files[0];
20520               uiGridImporterService.importThisFile( grid, fileObject );
20521               target.form.reset();
20522             }
20523           };
20524
20525           var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
20526           var grid = uiGridCtrl.grid;
20527
20528           if ( fileChooser.length !== 1 ){
20529             gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
20530           } else {
20531             fileChooser[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
20532           }
20533         }
20534       };
20535     }
20536   ]);
20537 })();
20538
20539 (function() {
20540   'use strict';
20541   /**
20542    *  @ngdoc overview
20543    *  @name ui.grid.infiniteScroll
20544    *
20545    *  @description
20546    *
20547    * #ui.grid.infiniteScroll
20548    *
20549    * <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>
20550    *
20551    * This module provides infinite scroll functionality to ui-grid
20552    *
20553    */
20554   var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
20555   /**
20556    *  @ngdoc service
20557    *  @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20558    *
20559    *  @description Service for infinite scroll features
20560    */
20561   module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
20562
20563     var service = {
20564
20565       /**
20566        * @ngdoc function
20567        * @name initializeGrid
20568        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20569        * @description This method register events and methods into grid public API
20570        */
20571
20572       initializeGrid: function(grid, $scope) {
20573         service.defaultGridOptions(grid.options);
20574
20575         if (!grid.options.enableInfiniteScroll){
20576           return;
20577         }
20578
20579         grid.infiniteScroll = { dataLoading: false };
20580         service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
20581           grid.api.core.on.scrollEnd($scope, service.handleScroll);
20582
20583         /**
20584          *  @ngdoc object
20585          *  @name ui.grid.infiniteScroll.api:PublicAPI
20586          *
20587          *  @description Public API for infinite scroll feature
20588          */
20589         var publicApi = {
20590           events: {
20591             infiniteScroll: {
20592
20593               /**
20594                * @ngdoc event
20595                * @name needLoadMoreData
20596                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
20597                * @description This event fires when scroll reaches bottom percentage of grid
20598                * and needs to load data
20599                */
20600
20601               needLoadMoreData: function ($scope, fn) {
20602               },
20603
20604               /**
20605                * @ngdoc event
20606                * @name needLoadMoreDataTop
20607                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
20608                * @description This event fires when scroll reaches top percentage of grid
20609                * and needs to load data
20610                */
20611
20612               needLoadMoreDataTop: function ($scope, fn) {
20613               }
20614             }
20615           },
20616           methods: {
20617             infiniteScroll: {
20618
20619               /**
20620                * @ngdoc function
20621                * @name dataLoaded
20622                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20623                * @description Call this function when you have loaded the additional data
20624                * requested.  You should set scrollUp and scrollDown to indicate
20625                * whether there are still more pages in each direction.
20626                *
20627                * If you call dataLoaded without first calling `saveScrollPercentage` then we will
20628                * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
20629                * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
20630                * on variable speed internet connections.  Using `saveScrollPercentage` as demonstrated in the tutorial
20631                * should give a smoother scrolling experience for users.
20632                *
20633                * See infinite_scroll tutorial for example of usage
20634                * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
20635                * any more infinite scroll events upward
20636                * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
20637                * fire any more infinite scroll events downward
20638                * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted.  If you're
20639                * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
20640                */
20641               dataLoaded: function( scrollUp, scrollDown ) {
20642                 service.setScrollDirections(grid, scrollUp, scrollDown);
20643
20644                 var promise = service.adjustScroll(grid).then(function() {
20645                   grid.infiniteScroll.dataLoading = false;
20646                 });
20647
20648                 return promise;
20649               },
20650
20651               /**
20652                * @ngdoc function
20653                * @name resetScroll
20654                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20655                * @description Call this function when you have taken some action that makes the current
20656                * scroll position invalid.  For example, if you're using external sorting and you've resorted
20657                * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
20658                * you've reused an existing grid for a new data set
20659                *
20660                * You must tell us whether there is data upwards or downwards after the reset
20661                *
20662                * @param {boolean} scrollUp flag that there are pages upwards, fire
20663                * infinite scroll events upward
20664                * @param {boolean} scrollDown flag that there are pages downwards, so
20665                * fire infinite scroll events downward
20666                * @returns {promise} promise that is resolved when the scroll reset is complete
20667                */
20668               resetScroll: function( scrollUp, scrollDown ) {
20669                 service.setScrollDirections( grid, scrollUp, scrollDown);
20670
20671                 return service.adjustInfiniteScrollPosition(grid, 0);
20672               },
20673
20674
20675               /**
20676                * @ngdoc function
20677                * @name saveScrollPercentage
20678                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20679                * @description Saves the scroll percentage and number of visible rows before you adjust the data,
20680                * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
20681                */
20682               saveScrollPercentage: function() {
20683                 grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
20684                 grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
20685               },
20686
20687
20688               /**
20689                * @ngdoc function
20690                * @name dataRemovedTop
20691                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20692                * @description Adjusts the scroll position after you've removed data at the top
20693                * @param {boolean} scrollUp flag that there are pages upwards, fire
20694                * infinite scroll events upward
20695                * @param {boolean} scrollDown flag that there are pages downwards, so
20696                * fire infinite scroll events downward
20697                */
20698               dataRemovedTop: function( scrollUp, scrollDown ) {
20699                 service.dataRemovedTop( grid, scrollUp, scrollDown );
20700               },
20701
20702               /**
20703                * @ngdoc function
20704                * @name dataRemovedBottom
20705                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20706                * @description Adjusts the scroll position after you've removed data at the bottom
20707                * @param {boolean} scrollUp flag that there are pages upwards, fire
20708                * infinite scroll events upward
20709                * @param {boolean} scrollDown flag that there are pages downwards, so
20710                * fire infinite scroll events downward
20711                */
20712               dataRemovedBottom: function( scrollUp, scrollDown ) {
20713                 service.dataRemovedBottom( grid, scrollUp, scrollDown );
20714               },
20715
20716               /**
20717                * @ngdoc function
20718                * @name setScrollDirections
20719                * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20720                * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20721                * and also sets the grid.suppressParentScroll
20722                * @param {boolean} scrollUp whether there are pages available up - defaults to false
20723                * @param {boolean} scrollDown whether there are pages available down - defaults to true
20724                */
20725               setScrollDirections:  function ( scrollUp, scrollDown ) {
20726                 service.setScrollDirections( grid, scrollUp, scrollDown );
20727               }
20728
20729             }
20730           }
20731         };
20732         grid.api.registerEventsFromObject(publicApi.events);
20733         grid.api.registerMethodsFromObject(publicApi.methods);
20734       },
20735
20736
20737       defaultGridOptions: function (gridOptions) {
20738         //default option to true unless it was explicitly set to false
20739         /**
20740          *  @ngdoc object
20741          *  @name ui.grid.infiniteScroll.api:GridOptions
20742          *
20743          *  @description GridOptions for infinite scroll feature, these are available to be
20744          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20745          */
20746
20747         /**
20748          *  @ngdoc object
20749          *  @name enableInfiniteScroll
20750          *  @propertyOf  ui.grid.infiniteScroll.api:GridOptions
20751          *  @description Enable infinite scrolling for this grid
20752          *  <br/>Defaults to true
20753          */
20754         gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
20755
20756         /**
20757          * @ngdoc property
20758          * @name infiniteScrollRowsFromEnd
20759          * @propertyOf ui.grid.class:GridOptions
20760          * @description This setting controls how close to the end of the dataset a user gets before
20761          * more data is requested by the infinite scroll, whether scrolling up or down.  This allows you to
20762          * 'prefetch' rows before the user actually runs out of scrolling.
20763          *
20764          * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
20765          * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
20766          * preserve that scroll position
20767          *
20768          * <br> Defaults to 20
20769          */
20770         gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
20771
20772         /**
20773          * @ngdoc property
20774          * @name infiniteScrollUp
20775          * @propertyOf ui.grid.class:GridOptions
20776          * @description Whether you allow infinite scroll up, implying that the first page of data
20777          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20778          * needMoreDataTop event when the user hits the top of the scrollbar.
20779          * <br> Defaults to false
20780          */
20781         gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
20782
20783         /**
20784          * @ngdoc property
20785          * @name infiniteScrollDown
20786          * @propertyOf ui.grid.class:GridOptions
20787          * @description Whether you allow infinite scroll down, implying that the first page of data
20788          * you have displayed is in the middle of your data set.  If set to true then we trigger the
20789          * needMoreData event when the user hits the bottom of the scrollbar.
20790          * <br> Defaults to true
20791          */
20792         gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
20793       },
20794
20795
20796       /**
20797        * @ngdoc function
20798        * @name setScrollDirections
20799        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20800        * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
20801        * and also sets the grid.suppressParentScroll
20802        * @param {grid} grid the grid we're operating on
20803        * @param {boolean} scrollUp whether there are pages available up - defaults to false
20804        * @param {boolean} scrollDown whether there are pages available down - defaults to true
20805        */
20806       setScrollDirections:  function ( grid, scrollUp, scrollDown ) {
20807         grid.infiniteScroll.scrollUp = ( scrollUp === true );
20808         grid.suppressParentScrollUp = ( scrollUp === true );
20809
20810         grid.infiniteScroll.scrollDown = ( scrollDown !== false);
20811         grid.suppressParentScrollDown = ( scrollDown !== false);
20812       },
20813
20814
20815       /**
20816        * @ngdoc function
20817        * @name handleScroll
20818        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20819        * @description Called whenever the grid scrolls, determines whether the scroll should
20820        * trigger an infinite scroll request for more data
20821        * @param {object} args the args from the event
20822        */
20823       handleScroll:  function (args) {
20824         // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
20825         if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
20826           return;
20827         }
20828
20829         if (args.y) {
20830           var percentage;
20831           var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
20832           if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
20833             percentage = args.y.percentage;
20834             if (percentage <= targetPercentage){
20835               service.loadData(args.grid);
20836             }
20837           } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
20838             percentage = 1 - args.y.percentage;
20839             if (percentage <= targetPercentage){
20840               service.loadData(args.grid);
20841             }
20842           }
20843         }
20844       },
20845
20846
20847       /**
20848        * @ngdoc function
20849        * @name loadData
20850        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20851        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20852        * and whether there are more pages upwards or downwards.  It also stores the number of rows that we had previously,
20853        * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
20854        * @param {Grid} grid the grid we're working on
20855        */
20856       loadData: function (grid) {
20857         // save number of currently visible rows to calculate new scroll position later - we know that we want
20858         // to be at approximately the row we're currently at
20859         grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20860         grid.infiniteScroll.direction = grid.scrollDirection;
20861         delete grid.infiniteScroll.prevScrollTop;
20862
20863         if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
20864           grid.infiniteScroll.dataLoading = true;
20865           grid.api.infiniteScroll.raise.needLoadMoreDataTop();
20866         } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
20867           grid.infiniteScroll.dataLoading = true;
20868           grid.api.infiniteScroll.raise.needLoadMoreData();
20869         }
20870       },
20871
20872
20873       /**
20874        * @ngdoc function
20875        * @name adjustScroll
20876        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20877        * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
20878        * addition and to make things look clean.
20879        *
20880        * If we're scrolling up we scroll to the first row of the old data set -
20881        * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
20882        * 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
20883        * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
20884        * the data comes back.
20885        *
20886        * Neither of these are good assumptions, but making this a smoother experience really requires
20887        * 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
20888        * 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
20889        * somewhere else in the mean-time, in which case they'll get a jump back to the new data.  Anyway, this will do for
20890        * now, until someone wants to do better.
20891        * @param {Grid} grid the grid we're working on
20892        * @returns {promise} a promise that is resolved when scrolling has finished
20893        */
20894       adjustScroll: function(grid){
20895         var promise = $q.defer();
20896         $timeout(function () {
20897           var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
20898
20899           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
20900           rowHeight = grid.options.rowHeight;
20901
20902           if ( grid.infiniteScroll.direction === undefined ){
20903             // called from initialize, tweak our scroll up a little
20904             service.adjustInfiniteScrollPosition(grid, 0);
20905           }
20906
20907           newVisibleRows = grid.getVisibleRowCount();
20908
20909           // in case not enough data is loaded to enable scroller - load more data
20910           var canvasHeight = rowHeight * newVisibleRows;
20911           if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
20912             grid.api.infiniteScroll.raise.needLoadMoreData();
20913           }
20914
20915           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
20916             oldTop = grid.infiniteScroll.prevScrollTop || 0;
20917             newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
20918             service.adjustInfiniteScrollPosition(grid, newTop);
20919             $timeout( function() {
20920               promise.resolve();
20921             });
20922           }
20923
20924           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
20925             newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
20926             service.adjustInfiniteScrollPosition(grid, newTop);
20927             $timeout( function() {
20928               promise.resolve();
20929             });
20930           }
20931         }, 0);
20932
20933         return promise.promise;
20934       },
20935
20936
20937       /**
20938        * @ngdoc function
20939        * @name adjustInfiniteScrollPosition
20940        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
20941        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
20942        * @param {Grid} grid the grid we're working on
20943        * @param {number} scrollTop the position through the grid that we want to scroll to
20944        * @returns {promise} a promise that is resolved when the scrolling finishes
20945        */
20946       adjustInfiniteScrollPosition: function (grid, scrollTop) {
20947         var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
20948           visibleRows = grid.getVisibleRowCount(),
20949           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
20950           rowHeight = grid.options.rowHeight,
20951           scrollHeight = visibleRows*rowHeight-viewportHeight;
20952
20953         //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
20954         if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
20955           // using pixels results in a relative scroll, hence we have to use percentage
20956           scrollEvent.y = {percentage: 1/scrollHeight};
20957         }
20958         else {
20959           scrollEvent.y = {percentage: scrollTop/scrollHeight};
20960         }
20961         grid.scrollContainers('', scrollEvent);
20962       },
20963
20964
20965       /**
20966        * @ngdoc function
20967        * @name dataRemovedTop
20968        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20969        * @description Adjusts the scroll position after you've removed data at the top. You should
20970        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
20971        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
20972        * before you start removing data
20973        * @param {Grid} grid the grid we're working on
20974        * @param {boolean} scrollUp flag that there are pages upwards, fire
20975        * infinite scroll events upward
20976        * @param {boolean} scrollDown flag that there are pages downwards, so
20977        * fire infinite scroll events downward
20978        * @returns {promise} a promise that is resolved when the scrolling finishes
20979        */
20980       dataRemovedTop: function( grid, scrollUp, scrollDown ) {
20981         var newVisibleRows, oldTop, newTop, rowHeight;
20982         service.setScrollDirections( grid, scrollUp, scrollDown );
20983
20984         newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
20985         oldTop = grid.infiniteScroll.prevScrollTop;
20986         rowHeight = grid.options.rowHeight;
20987
20988         // since we removed from the top, our new scroll row will be the old scroll row less the number
20989         // of rows removed
20990         newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
20991
20992         return service.adjustInfiniteScrollPosition( grid, newTop );
20993       },
20994
20995       /**
20996        * @ngdoc function
20997        * @name dataRemovedBottom
20998        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
20999        * @description Adjusts the scroll position after you've removed data at the bottom.  You should
21000        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
21001        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
21002        * before you start removing data
21003        * @param {Grid} grid the grid we're working on
21004        * @param {boolean} scrollUp flag that there are pages upwards, fire
21005        * infinite scroll events upward
21006        * @param {boolean} scrollDown flag that there are pages downwards, so
21007        * fire infinite scroll events downward
21008        */
21009       dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
21010         var newTop;
21011         service.setScrollDirections( grid, scrollUp, scrollDown );
21012
21013         newTop = grid.infiniteScroll.prevScrollTop;
21014
21015         return service.adjustInfiniteScrollPosition( grid, newTop );
21016       }
21017     };
21018     return service;
21019   }]);
21020   /**
21021    *  @ngdoc directive
21022    *  @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
21023    *  @element div
21024    *  @restrict A
21025    *
21026    *  @description Adds infinite scroll features to grid
21027    *
21028    *  @example
21029    <example module="app">
21030    <file name="app.js">
21031    var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
21032
21033    app.controller('MainCtrl', ['$scope', function ($scope) {
21034       $scope.data = [
21035         { name: 'Alex', car: 'Toyota' },
21036             { name: 'Sam', car: 'Lexus' }
21037       ];
21038
21039       $scope.columnDefs = [
21040         {name: 'name'},
21041         {name: 'car'}
21042       ];
21043     }]);
21044    </file>
21045    <file name="index.html">
21046    <div ng-controller="MainCtrl">
21047    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
21048    </div>
21049    </file>
21050    </example>
21051    */
21052
21053   module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
21054     function (uiGridInfiniteScrollService) {
21055       return {
21056         priority: -200,
21057         scope: false,
21058         require: '^uiGrid',
21059         compile: function($scope, $elm, $attr){
21060           return {
21061             pre: function($scope, $elm, $attr, uiGridCtrl) {
21062               uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
21063             },
21064             post: function($scope, $elm, $attr) {
21065             }
21066           };
21067         }
21068       };
21069     }]);
21070
21071 })();
21072
21073 (function () {
21074   'use strict';
21075
21076   /**
21077    * @ngdoc overview
21078    * @name ui.grid.moveColumns
21079    * @description
21080    *
21081    * # ui.grid.moveColumns
21082    *
21083    * <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>
21084    *
21085    * This module provides column moving capability to ui.grid. It enables to change the position of columns.
21086    * <div doc-module-components="ui.grid.moveColumns"></div>
21087    */
21088   var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
21089
21090   /**
21091    *  @ngdoc service
21092    *  @name ui.grid.moveColumns.service:uiGridMoveColumnService
21093    *  @description Service for column moving feature.
21094    */
21095   module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
21096
21097     var service = {
21098       initializeGrid: function (grid) {
21099         var self = this;
21100         this.registerPublicApi(grid);
21101         this.defaultGridOptions(grid.options);
21102         grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
21103         grid.registerColumnBuilder(self.movableColumnBuilder);
21104         grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
21105       },
21106       registerPublicApi: function (grid) {
21107         var self = this;
21108         /**
21109          *  @ngdoc object
21110          *  @name ui.grid.moveColumns.api:PublicApi
21111          *  @description Public Api for column moving feature.
21112          */
21113         var publicApi = {
21114           events: {
21115             /**
21116              * @ngdoc event
21117              * @name columnPositionChanged
21118              * @eventOf  ui.grid.moveColumns.api:PublicApi
21119              * @description raised when column is moved
21120              * <pre>
21121              *      gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
21122              * </pre>
21123              * @param {object} colDef the column that was moved
21124              * @param {integer} originalPosition of the column
21125              * @param {integer} finalPosition of the column
21126              */
21127             colMovable: {
21128               columnPositionChanged: function (colDef, originalPosition, newPosition) {
21129               }
21130             }
21131           },
21132           methods: {
21133             /**
21134              * @ngdoc method
21135              * @name moveColumn
21136              * @methodOf  ui.grid.moveColumns.api:PublicApi
21137              * @description Method can be used to change column position.
21138              * <pre>
21139              *      gridApi.colMovable.moveColumn(oldPosition, newPosition)
21140              * </pre>
21141              * @param {integer} originalPosition of the column
21142              * @param {integer} finalPosition of the column
21143              */
21144             colMovable: {
21145               moveColumn: function (originalPosition, finalPosition) {
21146                 var columns = grid.columns;
21147                 if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
21148                   gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
21149                   return;
21150                 }
21151                 var nonMovableColumns = 0;
21152                 for (var i = 0; i < columns.length; i++) {
21153                   if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
21154                     nonMovableColumns++;
21155                   }
21156                 }
21157                 if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
21158                   gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
21159                   return;
21160                 }
21161                 var findPositionForRenderIndex = function (index) {
21162                   var position = index;
21163                   for (var i = 0; i <= position; i++) {
21164                     if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
21165                       position++;
21166                     }
21167                   }
21168                   return position;
21169                 };
21170                 self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
21171               }
21172             }
21173           }
21174         };
21175         grid.api.registerEventsFromObject(publicApi.events);
21176         grid.api.registerMethodsFromObject(publicApi.methods);
21177       },
21178       defaultGridOptions: function (gridOptions) {
21179         /**
21180          *  @ngdoc object
21181          *  @name ui.grid.moveColumns.api:GridOptions
21182          *
21183          *  @description Options for configuring the move column feature, these are available to be
21184          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21185          */
21186         /**
21187          *  @ngdoc object
21188          *  @name enableColumnMoving
21189          *  @propertyOf  ui.grid.moveColumns.api:GridOptions
21190          *  @description If defined, sets the default value for the colMovable flag on each individual colDefs
21191          *  if their individual enableColumnMoving configuration is not defined. Defaults to true.
21192          */
21193         gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
21194       },
21195       movableColumnBuilder: function (colDef, col, gridOptions) {
21196         var promises = [];
21197         /**
21198          *  @ngdoc object
21199          *  @name ui.grid.moveColumns.api:ColumnDef
21200          *
21201          *  @description Column Definition for move column feature, these are available to be
21202          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
21203          */
21204         /**
21205          *  @ngdoc object
21206          *  @name enableColumnMoving
21207          *  @propertyOf  ui.grid.moveColumns.api:ColumnDef
21208          *  @description Enable column moving for the column.
21209          */
21210         colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
21211           : colDef.enableColumnMoving;
21212         return $q.all(promises);
21213       },
21214       /**
21215        * @ngdoc method
21216        * @name updateColumnCache
21217        * @methodOf  ui.grid.moveColumns
21218        * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
21219        */
21220       updateColumnCache: function(grid){
21221         grid.moveColumns.orderCache = grid.getOnlyDataColumns();
21222       },
21223       /**
21224        * @ngdoc method
21225        * @name verifyColumnOrder
21226        * @methodOf  ui.grid.moveColumns
21227        * @description dataChangeCallback which uses the cached column order to restore the column order
21228        * when it is reset by altering the columnDefs array.
21229        */
21230       verifyColumnOrder: function(grid){
21231         var headerRowOffset = grid.rowHeaderColumns.length;
21232         var newIndex;
21233
21234         angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
21235           newIndex = grid.columns.indexOf(cacheCol);
21236           if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
21237             var column = grid.columns.splice(newIndex, 1)[0];
21238             grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
21239           }
21240         });
21241       },
21242       redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
21243         if (originalPosition === newPosition) {
21244           return;
21245         }
21246
21247         var columns = grid.columns;
21248
21249         var originalColumn = columns[originalPosition];
21250         if (originalColumn.colDef.enableColumnMoving) {
21251           if (originalPosition > newPosition) {
21252             for (var i1 = originalPosition; i1 > newPosition; i1--) {
21253               columns[i1] = columns[i1 - 1];
21254             }
21255           }
21256           else if (newPosition > originalPosition) {
21257             for (var i2 = originalPosition; i2 < newPosition; i2++) {
21258               columns[i2] = columns[i2 + 1];
21259             }
21260           }
21261           columns[newPosition] = originalColumn;
21262           service.updateColumnCache(grid);
21263           grid.queueGridRefresh();
21264           $timeout(function () {
21265             grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
21266             grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
21267           });
21268         }
21269       }
21270     };
21271     return service;
21272   }]);
21273
21274   /**
21275    *  @ngdoc directive
21276    *  @name ui.grid.moveColumns.directive:uiGridMoveColumns
21277    *  @element div
21278    *  @restrict A
21279    *  @description Adds column moving features to the ui-grid directive.
21280    *  @example
21281    <example module="app">
21282    <file name="app.js">
21283    var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
21284    app.controller('MainCtrl', ['$scope', function ($scope) {
21285         $scope.data = [
21286           { name: 'Bob', title: 'CEO', age: 45 },
21287           { name: 'Frank', title: 'Lowly Developer', age: 25 },
21288           { name: 'Jenny', title: 'Highly Developer', age: 35 }
21289         ];
21290         $scope.columnDefs = [
21291           {name: 'name'},
21292           {name: 'title'},
21293           {name: 'age'}
21294         ];
21295       }]);
21296    </file>
21297    <file name="main.css">
21298    .grid {
21299       width: 100%;
21300       height: 150px;
21301     }
21302    </file>
21303    <file name="index.html">
21304    <div ng-controller="MainCtrl">
21305    <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
21306    </div>
21307    </file>
21308    </example>
21309    */
21310   module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
21311     return {
21312       replace: true,
21313       priority: 0,
21314       require: '^uiGrid',
21315       scope: false,
21316       compile: function () {
21317         return {
21318           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
21319             uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
21320           },
21321           post: function ($scope, $elm, $attrs, uiGridCtrl) {
21322           }
21323         };
21324       }
21325     };
21326   }]);
21327
21328   /**
21329    *  @ngdoc directive
21330    *  @name ui.grid.moveColumns.directive:uiGridHeaderCell
21331    *  @element div
21332    *  @restrict A
21333    *
21334    *  @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
21335    *
21336    *  On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
21337    *  In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
21338    *  On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
21339    *
21340    *  Events that invoke cloning of header cell:
21341    *    - mousedown
21342    *
21343    *  Events that invoke movement of cloned header cell:
21344    *    - mousemove
21345    *
21346    *  Events that invoke repositioning of column:
21347    *    - mouseup
21348    */
21349   module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
21350     function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
21351       return {
21352         priority: -10,
21353         require: '^uiGrid',
21354         compile: function () {
21355           return {
21356             post: function ($scope, $elm, $attrs, uiGridCtrl) {
21357
21358               if ($scope.col.colDef.enableColumnMoving) {
21359
21360                 /*
21361                  * Our general approach to column move is that we listen to a touchstart or mousedown
21362                  * event over the column header.  When we hear one, then we wait for a move of the same type
21363                  * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
21364                  * a mousemove (i.e. a drag) before we decide that there's a move underway.  If there's never a move,
21365                  * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
21366                  *
21367                  */
21368                 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
21369
21370                 var gridLeft;
21371                 var previousMouseX;
21372                 var totalMouseMovement;
21373                 var rightMoveLimit;
21374                 var elmCloned = false;
21375                 var movingElm;
21376                 var reducedWidth;
21377                 var moveOccurred = false;
21378
21379                 var downFn = function( event ){
21380                   //Setting some variables required for calculations.
21381                   gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
21382                   if ( $scope.grid.hasLeftContainer() ){
21383                     gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
21384                   }
21385
21386                   previousMouseX = event.pageX;
21387                   totalMouseMovement = 0;
21388                   rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
21389
21390                   if ( event.type === 'mousedown' ){
21391                     $document.on('mousemove', moveFn);
21392                     $document.on('mouseup', upFn);
21393                   } else if ( event.type === 'touchstart' ){
21394                     $document.on('touchmove', moveFn);
21395                     $document.on('touchend', upFn);
21396                   }
21397                 };
21398
21399                 var moveFn = function( event ) {
21400                   var changeValue = event.pageX - previousMouseX;
21401                   if ( changeValue === 0 ){ return; }
21402                   //Disable text selection in Chrome during column move
21403                   document.onselectstart = function() { return false; };
21404
21405                   moveOccurred = true;
21406
21407                   if (!elmCloned) {
21408                     cloneElement();
21409                   }
21410                   else if (elmCloned) {
21411                     moveElement(changeValue);
21412                     previousMouseX = event.pageX;
21413                   }
21414                 };
21415
21416                 var upFn = function( event ){
21417                   //Re-enable text selection after column move
21418                   document.onselectstart = null;
21419
21420                   //Remove the cloned element on mouse up.
21421                   if (movingElm) {
21422                     movingElm.remove();
21423                     elmCloned = false;
21424                   }
21425
21426                   offAllEvents();
21427                   onDownEvents();
21428
21429                   if (!moveOccurred){
21430                     return;
21431                   }
21432
21433                   var columns = $scope.grid.columns;
21434                   var columnIndex = 0;
21435                   for (var i = 0; i < columns.length; i++) {
21436                     if (columns[i].colDef.name !== $scope.col.colDef.name) {
21437                       columnIndex++;
21438                     }
21439                     else {
21440                       break;
21441                     }
21442                   }
21443
21444                   var targetIndex;
21445
21446                   //Case where column should be moved to a position on its left
21447                   if (totalMouseMovement < 0) {
21448                     var totalColumnsLeftWidth = 0;
21449                     var il;
21450                     if ( $scope.grid.isRTL() ){
21451                       for (il = columnIndex + 1; il < columns.length; il++) {
21452                         if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
21453                           totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
21454                           if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
21455                             uiGridMoveColumnService.redrawColumnAtPosition
21456                             ($scope.grid, columnIndex, il - 1);
21457                             break;
21458                           }
21459                         }
21460                       }
21461                     }
21462                     else {
21463                       for (il = columnIndex - 1; il >= 0; il--) {
21464                         if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
21465                           totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
21466                           if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
21467                             uiGridMoveColumnService.redrawColumnAtPosition
21468                             ($scope.grid, columnIndex, il + 1);
21469                             break;
21470                           }
21471                         }
21472                       }
21473                     }
21474
21475                     //Case where column should be moved to beginning (or end in RTL) of the grid.
21476                     if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
21477                       targetIndex = 0;
21478                       if ( $scope.grid.isRTL() ){
21479                         targetIndex = columns.length - 1;
21480                       }
21481                       uiGridMoveColumnService.redrawColumnAtPosition
21482                       ($scope.grid, columnIndex, targetIndex);
21483                     }
21484                   }
21485
21486                   //Case where column should be moved to a position on its right
21487                   else if (totalMouseMovement > 0) {
21488                     var totalColumnsRightWidth = 0;
21489                     var ir;
21490                     if ( $scope.grid.isRTL() ){
21491                       for (ir = columnIndex - 1; ir > 0; ir--) {
21492                         if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
21493                           totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
21494                           if (totalColumnsRightWidth > totalMouseMovement) {
21495                             uiGridMoveColumnService.redrawColumnAtPosition
21496                             ($scope.grid, columnIndex, ir);
21497                             break;
21498                           }
21499                         }
21500                       }
21501                     }
21502                     else {
21503                       for (ir = columnIndex + 1; ir < columns.length; ir++) {
21504                         if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
21505                           totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
21506                           if (totalColumnsRightWidth > totalMouseMovement) {
21507                             uiGridMoveColumnService.redrawColumnAtPosition
21508                             ($scope.grid, columnIndex, ir - 1);
21509                             break;
21510                           }
21511                         }
21512                       }
21513                     }
21514
21515
21516                     //Case where column should be moved to end (or beginning in RTL) of the grid.
21517                     if (totalColumnsRightWidth < totalMouseMovement) {
21518                       targetIndex = columns.length - 1;
21519                       if ( $scope.grid.isRTL() ){
21520                         targetIndex = 0;
21521                       }
21522                       uiGridMoveColumnService.redrawColumnAtPosition
21523                       ($scope.grid, columnIndex, targetIndex);
21524                     }
21525                   }
21526
21527
21528
21529                 };
21530
21531                 var onDownEvents = function(){
21532                   $contentsElm.on('touchstart', downFn);
21533                   $contentsElm.on('mousedown', downFn);
21534                 };
21535
21536                 var offAllEvents = function() {
21537                   $contentsElm.off('touchstart', downFn);
21538                   $contentsElm.off('mousedown', downFn);
21539
21540                   $document.off('mousemove', moveFn);
21541                   $document.off('touchmove', moveFn);
21542
21543                   $document.off('mouseup', upFn);
21544                   $document.off('touchend', upFn);
21545                 };
21546
21547                 onDownEvents();
21548
21549
21550                 var cloneElement = function () {
21551                   elmCloned = true;
21552
21553                   //Cloning header cell and appending to current header cell.
21554                   movingElm = $elm.clone();
21555                   $elm.parent().append(movingElm);
21556
21557                   //Left of cloned element should be aligned to original header cell.
21558                   movingElm.addClass('movingColumn');
21559                   var movingElementStyles = {};
21560                   movingElementStyles.left = $elm[0].offsetLeft + 'px';
21561                   var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
21562                   var elmRight = $elm[0].getBoundingClientRect().right;
21563                   if (elmRight > gridRight) {
21564                     reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
21565                     movingElementStyles.width = reducedWidth + 'px';
21566                   }
21567                   movingElm.css(movingElementStyles);
21568                 };
21569
21570                 var moveElement = function (changeValue) {
21571                   //Calculate total column width
21572                   var columns = $scope.grid.columns;
21573                   var totalColumnWidth = 0;
21574                   for (var i = 0; i < columns.length; i++) {
21575                     if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
21576                       totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
21577                     }
21578                   }
21579
21580                   //Calculate new position of left of column
21581                   var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
21582                   var currentElmRight = movingElm[0].getBoundingClientRect().right;
21583                   var newElementLeft;
21584
21585                   newElementLeft = currentElmLeft - gridLeft + changeValue;
21586                   newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
21587
21588                   //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
21589                   if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
21590                     movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft +
21591                     (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
21592                   }
21593                   else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
21594                     changeValue *= 8;
21595                     var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
21596                     scrollEvent.x = {pixels: changeValue};
21597                     scrollEvent.grid.scrollContainers('',scrollEvent);
21598                   }
21599
21600                   //Calculate total width of columns on the left of the moving column and the mouse movement
21601                   var totalColumnsLeftWidth = 0;
21602                   for (var il = 0; il < columns.length; il++) {
21603                     if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
21604                       if (columns[il].colDef.name !== $scope.col.colDef.name) {
21605                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
21606                       }
21607                       else {
21608                         break;
21609                       }
21610                     }
21611                   }
21612                   if ($scope.newScrollLeft === undefined) {
21613                     totalMouseMovement += changeValue;
21614                   }
21615                   else {
21616                     totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
21617                   }
21618
21619                   //Increase width of moving column, in case the rightmost column was moved and its width was
21620                   //decreased because of overflow
21621                   if (reducedWidth < $scope.col.drawnWidth) {
21622                     reducedWidth += Math.abs(changeValue);
21623                     movingElm.css({'width': reducedWidth + 'px'});
21624                   }
21625                 };
21626               }
21627             }
21628           };
21629         }
21630       };
21631     }]);
21632 })();
21633
21634 (function() {
21635   'use strict';
21636
21637   /**
21638    * @ngdoc overview
21639    * @name ui.grid.pagination
21640    *
21641    * @description
21642    *
21643    * # ui.grid.pagination
21644    *
21645    * <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>
21646    *
21647    * This module provides pagination support to ui-grid
21648    */
21649   var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);
21650
21651   /**
21652    * @ngdoc service
21653    * @name ui.grid.pagination.service:uiGridPaginationService
21654    *
21655    * @description Service for the pagination feature
21656    */
21657   module.service('uiGridPaginationService', ['gridUtil',
21658     function (gridUtil) {
21659       var service = {
21660         /**
21661          * @ngdoc method
21662          * @name initializeGrid
21663          * @methodOf ui.grid.pagination.service:uiGridPaginationService
21664          * @description Attaches the service to a certain grid
21665          * @param {Grid} grid The grid we want to work with
21666          */
21667         initializeGrid: function (grid) {
21668           service.defaultGridOptions(grid.options);
21669
21670           /**
21671           * @ngdoc object
21672           * @name ui.grid.pagination.api:PublicAPI
21673           *
21674           * @description Public API for the pagination feature
21675           */
21676           var publicApi = {
21677             events: {
21678               pagination: {
21679               /**
21680                * @ngdoc event
21681                * @name paginationChanged
21682                * @eventOf ui.grid.pagination.api:PublicAPI
21683                * @description This event fires when the pageSize or currentPage changes
21684                * @param {int} currentPage requested page number
21685                * @param {int} pageSize requested page size
21686                */
21687                 paginationChanged: function (currentPage, pageSize) { }
21688               }
21689             },
21690             methods: {
21691               pagination: {
21692                 /**
21693                  * @ngdoc method
21694                  * @name getPage
21695                  * @methodOf ui.grid.pagination.api:PublicAPI
21696                  * @description Returns the number of the current page
21697                  */
21698                 getPage: function () {
21699                   return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
21700                 },
21701                 /**
21702                  * @ngdoc method
21703                  * @name getTotalPages
21704                  * @methodOf ui.grid.pagination.api:PublicAPI
21705                  * @description Returns the total number of pages
21706                  */
21707                 getTotalPages: function () {
21708                   if (!grid.options.enablePagination) {
21709                     return null;
21710                   }
21711
21712                   return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
21713                 },
21714                 /**
21715                  * @ngdoc method
21716                  * @name nextPage
21717                  * @methodOf ui.grid.pagination.api:PublicAPI
21718                  * @description Moves to the next page, if possible
21719                  */
21720                 nextPage: function () {
21721                   if (!grid.options.enablePagination) {
21722                     return;
21723                   }
21724
21725                   if (grid.options.totalItems > 0) {
21726                     grid.options.paginationCurrentPage = Math.min(
21727                       grid.options.paginationCurrentPage + 1,
21728                       publicApi.methods.pagination.getTotalPages()
21729                     );
21730                   } else {
21731                     grid.options.paginationCurrentPage++;
21732                   }
21733                 },
21734                 /**
21735                  * @ngdoc method
21736                  * @name previousPage
21737                  * @methodOf ui.grid.pagination.api:PublicAPI
21738                  * @description Moves to the previous page, if we're not on the first page
21739                  */
21740                 previousPage: function () {
21741                   if (!grid.options.enablePagination) {
21742                     return;
21743                   }
21744
21745                   grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
21746                 },
21747                 /**
21748                  * @ngdoc method
21749                  * @name seek
21750                  * @methodOf ui.grid.pagination.api:PublicAPI
21751                  * @description Moves to the requested page
21752                  * @param {int} page The number of the page that should be displayed
21753                  */
21754                 seek: function (page) {
21755                   if (!grid.options.enablePagination) {
21756                     return;
21757                   }
21758                   if (!angular.isNumber(page) || page < 1) {
21759                     throw 'Invalid page number: ' + page;
21760                   }
21761
21762                   grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
21763                 }
21764               }
21765             }
21766           };
21767
21768           grid.api.registerEventsFromObject(publicApi.events);
21769           grid.api.registerMethodsFromObject(publicApi.methods);
21770
21771           var processPagination = function( renderableRows ){
21772             if (grid.options.useExternalPagination || !grid.options.enablePagination) {
21773               return renderableRows;
21774             }
21775             //client side pagination
21776             var pageSize = parseInt(grid.options.paginationPageSize, 10);
21777             var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
21778
21779             var visibleRows = renderableRows.filter(function (row) { return row.visible; });
21780             grid.options.totalItems = visibleRows.length;
21781
21782             var firstRow = (currentPage - 1) * pageSize;
21783             if (firstRow > visibleRows.length) {
21784               currentPage = grid.options.paginationCurrentPage = 1;
21785               firstRow = (currentPage - 1) * pageSize;
21786             }
21787             return visibleRows.slice(firstRow, firstRow + pageSize);
21788           };
21789
21790           grid.registerRowsProcessor(processPagination, 900 );
21791
21792         },
21793         defaultGridOptions: function (gridOptions) {
21794           /**
21795            * @ngdoc object
21796            * @name ui.grid.pagination.api:GridOptions
21797            *
21798            * @description GridOptions for the pagination feature, these are available to be
21799            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21800            */
21801
21802           /**
21803            * @ngdoc property
21804            * @name enablePagination
21805            * @propertyOf ui.grid.pagination.api:GridOptions
21806            * @description Enables pagination.  Defaults to true.
21807            */
21808           gridOptions.enablePagination = gridOptions.enablePagination !== false;
21809           /**
21810            * @ngdoc property
21811            * @name enablePaginationControls
21812            * @propertyOf ui.grid.pagination.api:GridOptions
21813            * @description Enables the paginator at the bottom of the grid. Turn this off if you want to implement your
21814            *              own controls outside the grid.
21815            */
21816           gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
21817           /**
21818            * @ngdoc property
21819            * @name useExternalPagination
21820            * @propertyOf ui.grid.pagination.api:GridOptions
21821            * @description Disables client side pagination. When true, handle the paginationChanged event and set data
21822            *              and totalItems.  Defaults to `false`
21823            */
21824           gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
21825           /**
21826            * @ngdoc property
21827            * @name totalItems
21828            * @propertyOf ui.grid.pagination.api:GridOptions
21829            * @description Total number of items, set automatically when using client side pagination, but needs set by user
21830            *              for server side pagination
21831            */
21832           if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
21833             gridOptions.totalItems = 0;
21834           }
21835           /**
21836            * @ngdoc property
21837            * @name paginationPageSizes
21838            * @propertyOf ui.grid.pagination.api:GridOptions
21839            * @description Array of page sizes, defaults to `[250, 500, 1000]`
21840            */
21841           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
21842             gridOptions.paginationPageSizes = [250, 500, 1000];
21843           }
21844           /**
21845            * @ngdoc property
21846            * @name paginationPageSize
21847            * @propertyOf ui.grid.pagination.api:GridOptions
21848            * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
21849            */
21850           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
21851             if (gridOptions.paginationPageSizes.length > 0) {
21852               gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
21853             } else {
21854               gridOptions.paginationPageSize = 0;
21855             }
21856           }
21857           /**
21858            * @ngdoc property
21859            * @name paginationCurrentPage
21860            * @propertyOf ui.grid.pagination.api:GridOptions
21861            * @description Current page number, defaults to 1
21862            */
21863           if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
21864             gridOptions.paginationCurrentPage = 1;
21865           }
21866
21867           /**
21868            * @ngdoc property
21869            * @name paginationTemplate
21870            * @propertyOf ui.grid.pagination.api:GridOptions
21871            * @description A custom template for the pager, defaults to `ui-grid/pagination`
21872            */
21873           if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
21874             gridOptions.paginationTemplate = 'ui-grid/pagination';
21875           }
21876         },
21877         /**
21878          * @ngdoc method
21879          * @methodOf ui.grid.pagination.service:uiGridPaginationService
21880          * @name uiGridPaginationService
21881          * @description  Raises paginationChanged and calls refresh for client side pagination
21882          * @param {Grid} grid the grid for which the pagination changed
21883          * @param {int} currentPage requested page number
21884          * @param {int} pageSize requested page size
21885          */
21886         onPaginationChanged: function (grid, currentPage, pageSize) {
21887             grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
21888             if (!grid.options.useExternalPagination) {
21889               grid.queueGridRefresh(); //client side pagination
21890             }
21891         }
21892       };
21893
21894       return service;
21895     }
21896   ]);
21897   /**
21898    *  @ngdoc directive
21899    *  @name ui.grid.pagination.directive:uiGridPagination
21900    *  @element div
21901    *  @restrict A
21902    *
21903    *  @description Adds pagination features to grid
21904    *  @example
21905    <example module="app">
21906    <file name="app.js">
21907    var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);
21908
21909    app.controller('MainCtrl', ['$scope', function ($scope) {
21910       $scope.data = [
21911         { name: 'Alex', car: 'Toyota' },
21912         { name: 'Sam', car: 'Lexus' },
21913         { name: 'Joe', car: 'Dodge' },
21914         { name: 'Bob', car: 'Buick' },
21915         { name: 'Cindy', car: 'Ford' },
21916         { name: 'Brian', car: 'Audi' },
21917         { name: 'Malcom', car: 'Mercedes Benz' },
21918         { name: 'Dave', car: 'Ford' },
21919         { name: 'Stacey', car: 'Audi' },
21920         { name: 'Amy', car: 'Acura' },
21921         { name: 'Scott', car: 'Toyota' },
21922         { name: 'Ryan', car: 'BMW' },
21923       ];
21924
21925       $scope.gridOptions = {
21926         data: 'data',
21927         paginationPageSizes: [5, 10, 25],
21928         paginationPageSize: 5,
21929         columnDefs: [
21930           {name: 'name'},
21931           {name: 'car'}
21932         ]
21933        }
21934     }]);
21935    </file>
21936    <file name="index.html">
21937    <div ng-controller="MainCtrl">
21938    <div ui-grid="gridOptions" ui-grid-pagination></div>
21939    </div>
21940    </file>
21941    </example>
21942    */
21943   module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
21944     function (gridUtil, uiGridPaginationService) {
21945       return {
21946         priority: -200,
21947         scope: false,
21948         require: 'uiGrid',
21949         link: {
21950           pre: function ($scope, $elm, $attr, uiGridCtrl) {
21951             uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
21952
21953             gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
21954               .then(function (contents) {
21955                 var template = angular.element(contents);
21956                 $elm.append(template);
21957                 uiGridCtrl.innerCompile(template);
21958               });
21959           }
21960         }
21961       };
21962     }
21963   ]);
21964
21965   /**
21966    *  @ngdoc directive
21967    *  @name ui.grid.pagination.directive:uiGridPager
21968    *  @element div
21969    *
21970    *  @description Panel for handling pagination
21971    */
21972   module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
21973     function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
21974       return {
21975         priority: -200,
21976         scope: true,
21977         require: '^uiGrid',
21978         link: function ($scope, $elm, $attr, uiGridCtrl) {
21979           var defaultFocusElementSelector = '.ui-grid-pager-control-input';
21980           $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
21981
21982           $scope.paginationApi = uiGridCtrl.grid.api.pagination;
21983           $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
21984           $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
21985           $scope.paginationOf = i18nService.getSafeText('pagination.of');
21986           $scope.paginationThrough = i18nService.getSafeText('pagination.through');
21987
21988           var options = uiGridCtrl.grid.options;
21989
21990           uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
21991             adjustment.height = adjustment.height - gridUtil.elementHeight($elm, "padding");
21992             return adjustment;
21993           });
21994
21995           var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
21996             if (!grid.options.useExternalPagination) {
21997               grid.options.totalItems = grid.rows.length;
21998             }
21999           }, [uiGridConstants.dataChange.ROW]);
22000
22001           $scope.$on('$destroy', dataChangeDereg);
22002
22003           var setShowing = function () {
22004             $scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
22005             $scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
22006           };
22007
22008           var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);
22009
22010           var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
22011               if (newValues === oldValues || oldValues === undefined) {
22012                 return;
22013               }
22014
22015               if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
22016                 options.paginationCurrentPage = 1;
22017                 return;
22018               }
22019
22020               if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
22021                 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
22022                 return;
22023               }
22024
22025               setShowing();
22026               uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
22027             }
22028           );
22029
22030           $scope.$on('$destroy', function() {
22031             deregT();
22032             deregP();
22033           });
22034
22035           $scope.cantPageForward = function () {
22036             if (options.totalItems > 0) {
22037               return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
22038             } else {
22039               return options.data.length < 1;
22040             }
22041           };
22042
22043           $scope.cantPageToLast = function () {
22044             if (options.totalItems > 0) {
22045               return $scope.cantPageForward();
22046             } else {
22047               return true;
22048             }
22049           };
22050
22051           $scope.cantPageBackward = function () {
22052             return options.paginationCurrentPage <= 1;
22053           };
22054
22055           var focusToInputIf = function(condition){
22056             if (condition){
22057               gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
22058             }
22059           };
22060
22061           //Takes care of setting focus to the middle element when focus is lost
22062           $scope.pageFirstPageClick = function () {
22063             $scope.paginationApi.seek(1);
22064             focusToInputIf($scope.cantPageBackward());
22065           };
22066
22067           $scope.pagePreviousPageClick = function () {
22068             $scope.paginationApi.previousPage();
22069             focusToInputIf($scope.cantPageBackward());
22070           };
22071
22072           $scope.pageNextPageClick = function () {
22073             $scope.paginationApi.nextPage();
22074             focusToInputIf($scope.cantPageForward());
22075           };
22076
22077           $scope.pageLastPageClick = function () {
22078             $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
22079             focusToInputIf($scope.cantPageToLast());
22080           };
22081
22082         }
22083       };
22084     }
22085   ]);
22086 })();
22087
22088 (function () {
22089   'use strict';
22090
22091   /**
22092    * @ngdoc overview
22093    * @name ui.grid.pinning
22094    * @description
22095    *
22096    * # ui.grid.pinning
22097    *
22098    * <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>
22099    *
22100    * This module provides column pinning to the end user via menu options in the column header
22101    *
22102    * <div doc-module-components="ui.grid.pinning"></div>
22103    */
22104
22105   var module = angular.module('ui.grid.pinning', ['ui.grid']);
22106
22107   module.constant('uiGridPinningConstants', {
22108     container: {
22109       LEFT: 'left',
22110       RIGHT: 'right',
22111       NONE: ''
22112     }
22113   });
22114
22115   module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) {
22116     var service = {
22117
22118       initializeGrid: function (grid) {
22119         service.defaultGridOptions(grid.options);
22120
22121         // Register a column builder to add new menu items for pinning left and right
22122         grid.registerColumnBuilder(service.pinningColumnBuilder);
22123
22124         /**
22125          *  @ngdoc object
22126          *  @name ui.grid.pinning.api:PublicApi
22127          *
22128          *  @description Public Api for pinning feature
22129          */
22130         var publicApi = {
22131           events: {
22132             pinning: {
22133               /**
22134                * @ngdoc event
22135                * @name columnPin
22136                * @eventOf ui.grid.pinning.api:PublicApi
22137                * @description raised when column pin state has changed
22138                * <pre>
22139                *   gridApi.pinning.on.columnPinned(scope, function(colDef){})
22140                * </pre>
22141                * @param {object} colDef the column that was changed
22142                * @param {string} container the render container the column is in ('left', 'right', '')
22143                */
22144               columnPinned: function(colDef, container) {
22145               }
22146             }
22147           },
22148           methods: {
22149             pinning: {
22150               /**
22151                * @ngdoc function
22152                * @name pinColumn
22153                * @methodOf ui.grid.pinning.api:PublicApi
22154                * @description pin column left, right, or none
22155                * <pre>
22156                *   gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
22157                * </pre>
22158                * @param {gridColumn} col the column being pinned
22159                * @param {string} container one of the recognised types
22160                * from uiGridPinningConstants
22161                */
22162               pinColumn: function(col, container) {
22163                 service.pinColumn(grid, col, container);
22164               }
22165             }
22166           }
22167         };
22168
22169         grid.api.registerEventsFromObject(publicApi.events);
22170         grid.api.registerMethodsFromObject(publicApi.methods);
22171       },
22172
22173       defaultGridOptions: function (gridOptions) {
22174         //default option to true unless it was explicitly set to false
22175         /**
22176          *  @ngdoc object
22177          *  @name ui.grid.pinning.api:GridOptions
22178          *
22179          *  @description GridOptions for pinning feature, these are available to be
22180            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22181          */
22182
22183         /**
22184          *  @ngdoc object
22185          *  @name enablePinning
22186          *  @propertyOf  ui.grid.pinning.api:GridOptions
22187          *  @description Enable pinning for the entire grid.
22188          *  <br/>Defaults to true
22189          */
22190         gridOptions.enablePinning = gridOptions.enablePinning !== false;
22191
22192       },
22193
22194       pinningColumnBuilder: function (colDef, col, gridOptions) {
22195         //default to true unless gridOptions or colDef is explicitly false
22196
22197         /**
22198          *  @ngdoc object
22199          *  @name ui.grid.pinning.api:ColumnDef
22200          *
22201          *  @description ColumnDef for pinning feature, these are available to be
22202          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
22203          */
22204
22205         /**
22206          *  @ngdoc object
22207          *  @name enablePinning
22208          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22209          *  @description Enable pinning for the individual column.
22210          *  <br/>Defaults to true
22211          */
22212         colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
22213
22214
22215         /**
22216          *  @ngdoc object
22217          *  @name pinnedLeft
22218          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22219          *  @description Column is pinned left when grid is rendered
22220          *  <br/>Defaults to false
22221          */
22222
22223         /**
22224          *  @ngdoc object
22225          *  @name pinnedRight
22226          *  @propertyOf  ui.grid.pinning.api:ColumnDef
22227          *  @description Column is pinned right when grid is rendered
22228          *  <br/>Defaults to false
22229          */
22230         if (colDef.pinnedLeft) {
22231           col.renderContainer = 'left';
22232           col.grid.createLeftContainer();
22233         }
22234         else if (colDef.pinnedRight) {
22235           col.renderContainer = 'right';
22236           col.grid.createRightContainer();
22237         }
22238
22239         if (!colDef.enablePinning) {
22240           return;
22241         }
22242
22243         var pinColumnLeftAction = {
22244           name: 'ui.grid.pinning.pinLeft',
22245           title: i18nService.get().pinning.pinLeft,
22246           icon: 'ui-grid-icon-left-open',
22247           shown: function () {
22248             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
22249           },
22250           action: function () {
22251             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT);
22252           }
22253         };
22254
22255         var pinColumnRightAction = {
22256           name: 'ui.grid.pinning.pinRight',
22257           title: i18nService.get().pinning.pinRight,
22258           icon: 'ui-grid-icon-right-open',
22259           shown: function () {
22260             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
22261           },
22262           action: function () {
22263             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT);
22264           }
22265         };
22266
22267         var removePinAction = {
22268           name: 'ui.grid.pinning.unpin',
22269           title: i18nService.get().pinning.unpin,
22270           icon: 'ui-grid-icon-cancel',
22271           shown: function () {
22272             return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
22273           },
22274           action: function () {
22275             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.NONE);
22276           }
22277         };
22278
22279         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
22280           col.menuItems.push(pinColumnLeftAction);
22281         }
22282         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
22283           col.menuItems.push(pinColumnRightAction);
22284         }
22285         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
22286           col.menuItems.push(removePinAction);
22287         }
22288       },
22289
22290       pinColumn: function(grid, col, container) {
22291         if (container === uiGridPinningConstants.container.NONE) {
22292           col.renderContainer = null;
22293           col.colDef.pinnedLeft = col.colDef.pinnedRight = false;
22294         }
22295         else {
22296           col.renderContainer = container;
22297           if (container === uiGridPinningConstants.container.LEFT) {
22298             grid.createLeftContainer();
22299           }
22300           else if (container === uiGridPinningConstants.container.RIGHT) {
22301             grid.createRightContainer();
22302           }
22303         }
22304
22305         grid.refresh()
22306         .then(function() {
22307           grid.api.pinning.raise.columnPinned( col.colDef, container );
22308         });
22309       }
22310     };
22311
22312     return service;
22313   }]);
22314
22315   module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
22316     function (gridUtil, uiGridPinningService) {
22317       return {
22318         require: 'uiGrid',
22319         scope: false,
22320         compile: function () {
22321           return {
22322             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22323               uiGridPinningService.initializeGrid(uiGridCtrl.grid);
22324             },
22325             post: function ($scope, $elm, $attrs, uiGridCtrl) {
22326             }
22327           };
22328         }
22329       };
22330     }]);
22331
22332
22333 })();
22334
22335 (function(){
22336   'use strict';
22337
22338   /**
22339    * @ngdoc overview
22340    * @name ui.grid.resizeColumns
22341    * @description
22342    *
22343    * # ui.grid.resizeColumns
22344    *
22345    * <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>
22346    *
22347    * This module allows columns to be resized.
22348    */
22349   var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
22350
22351   module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
22352     function (gridUtil, $q, $timeout) {
22353
22354       var service = {
22355         defaultGridOptions: function(gridOptions){
22356           //default option to true unless it was explicitly set to false
22357           /**
22358            *  @ngdoc object
22359            *  @name ui.grid.resizeColumns.api:GridOptions
22360            *
22361            *  @description GridOptions for resizeColumns feature, these are available to be
22362            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22363            */
22364
22365           /**
22366            *  @ngdoc object
22367            *  @name enableColumnResizing
22368            *  @propertyOf  ui.grid.resizeColumns.api:GridOptions
22369            *  @description Enable column resizing on the entire grid
22370            *  <br/>Defaults to true
22371            */
22372           gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
22373
22374           //legacy support
22375           //use old name if it is explicitly false
22376           if (gridOptions.enableColumnResize === false){
22377             gridOptions.enableColumnResizing = false;
22378           }
22379         },
22380
22381         colResizerColumnBuilder: function (colDef, col, gridOptions) {
22382
22383           var promises = [];
22384           /**
22385            *  @ngdoc object
22386            *  @name ui.grid.resizeColumns.api:ColumnDef
22387            *
22388            *  @description ColumnDef for resizeColumns feature, these are available to be
22389            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
22390            */
22391
22392           /**
22393            *  @ngdoc object
22394            *  @name enableColumnResizing
22395            *  @propertyOf  ui.grid.resizeColumns.api:ColumnDef
22396            *  @description Enable column resizing on an individual column
22397            *  <br/>Defaults to GridOptions.enableColumnResizing
22398            */
22399           //default to true unless gridOptions or colDef is explicitly false
22400           colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
22401
22402
22403           //legacy support of old option name
22404           if (colDef.enableColumnResize === false){
22405             colDef.enableColumnResizing = false;
22406           }
22407
22408           return $q.all(promises);
22409         },
22410
22411         registerPublicApi: function (grid) {
22412             /**
22413              *  @ngdoc object
22414              *  @name ui.grid.resizeColumns.api:PublicApi
22415              *  @description Public Api for column resize feature.
22416              */
22417             var publicApi = {
22418               events: {
22419                 /**
22420                  * @ngdoc event
22421                  * @name columnSizeChanged
22422                  * @eventOf  ui.grid.resizeColumns.api:PublicApi
22423                  * @description raised when column is resized
22424                  * <pre>
22425                  *      gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
22426                  * </pre>
22427                  * @param {object} colDef the column that was resized
22428                  * @param {integer} delta of the column size change
22429                  */
22430                 colResizable: {
22431                   columnSizeChanged: function (colDef, deltaChange) {
22432                   }
22433                 }
22434               }
22435             };
22436             grid.api.registerEventsFromObject(publicApi.events);
22437         },
22438
22439         fireColumnSizeChanged: function (grid, colDef, deltaChange) {
22440           $timeout(function () {
22441             if ( grid.api.colResizable ){
22442               grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
22443             } else {
22444               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.");
22445             }
22446           });
22447         },
22448
22449         // get either this column, or the column next to this column, to resize,
22450         // returns the column we're going to resize
22451         findTargetCol: function(col, position, rtlMultiplier){
22452           var renderContainer = col.getRenderContainer();
22453
22454           if (position === 'left') {
22455             // Get the column to the left of this one
22456             var colIndex = renderContainer.visibleColumnCache.indexOf(col);
22457             return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
22458           } else {
22459             return col;
22460           }
22461         }
22462
22463       };
22464
22465       return service;
22466
22467     }]);
22468
22469
22470   /**
22471    * @ngdoc directive
22472    * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
22473    * @element div
22474    * @restrict A
22475    * @description
22476    * 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
22477    * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
22478    *
22479    * @example
22480    <doc:example module="app">
22481    <doc:source>
22482    <script>
22483    var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
22484
22485    app.controller('MainCtrl', ['$scope', function ($scope) {
22486           $scope.gridOpts = {
22487             data: [
22488               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
22489               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
22490               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
22491               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
22492             ]
22493           };
22494         }]);
22495    </script>
22496
22497    <div ng-controller="MainCtrl">
22498    <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
22499    </div>
22500    </doc:source>
22501    <doc:scenario>
22502
22503    </doc:scenario>
22504    </doc:example>
22505    */
22506   module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
22507     return {
22508       replace: true,
22509       priority: 0,
22510       require: '^uiGrid',
22511       scope: false,
22512       compile: function () {
22513         return {
22514           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22515             uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
22516             uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
22517             uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
22518           },
22519           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22520           }
22521         };
22522       }
22523     };
22524   }]);
22525
22526   // Extend the uiGridHeaderCell directive
22527   module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
22528     return {
22529       // Run after the original uiGridHeaderCell
22530       priority: -10,
22531       require: '^uiGrid',
22532       // scope: false,
22533       compile: function() {
22534         return {
22535           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22536             var grid = uiGridCtrl.grid;
22537
22538             if (grid.options.enableColumnResizing) {
22539               var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
22540
22541               var rtlMultiplier = 1;
22542               //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
22543               if (grid.isRTL()) {
22544                 $scope.position = 'left';
22545                 rtlMultiplier = -1;
22546               }
22547
22548               var displayResizers = function(){
22549
22550                 // remove any existing resizers.
22551                 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
22552                 for ( var i = 0; i < resizers.length; i++ ){
22553                   angular.element(resizers[i]).remove();
22554                 }
22555
22556                 // get the target column for the left resizer
22557                 var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
22558                 var renderContainer = $scope.col.getRenderContainer();
22559
22560                 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
22561                 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
22562                   var resizerLeft = angular.element(columnResizerElm).clone();
22563                   resizerLeft.attr('position', 'left');
22564
22565                   $elm.prepend(resizerLeft);
22566                   $compile(resizerLeft)($scope);
22567                 }
22568
22569                 // Don't append the right resizer if this column has resizing disabled
22570                 if ($scope.col.colDef.enableColumnResizing !== false) {
22571                   var resizerRight = angular.element(columnResizerElm).clone();
22572                   resizerRight.attr('position', 'right');
22573
22574                   $elm.append(resizerRight);
22575                   $compile(resizerRight)($scope);
22576                 }
22577               };
22578
22579               displayResizers();
22580
22581               var waitDisplay = function(){
22582                 $timeout(displayResizers);
22583               };
22584
22585               var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
22586
22587               $scope.$on( '$destroy', dataChangeDereg );
22588             }
22589           }
22590         };
22591       }
22592     };
22593   }]);
22594
22595
22596
22597   /**
22598    * @ngdoc directive
22599    * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
22600    * @element div
22601    * @restrict A
22602    *
22603    * @description
22604    * Draggable handle that controls column resizing.
22605    *
22606    * @example
22607    <doc:example module="app">
22608      <doc:source>
22609        <script>
22610         var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
22611
22612         app.controller('MainCtrl', ['$scope', function ($scope) {
22613           $scope.gridOpts = {
22614             enableColumnResizing: true,
22615             data: [
22616               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
22617               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
22618               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
22619               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
22620             ]
22621           };
22622         }]);
22623        </script>
22624
22625        <div ng-controller="MainCtrl">
22626         <div class="testGrid" ui-grid="gridOpts"></div>
22627        </div>
22628      </doc:source>
22629      <doc:scenario>
22630       // TODO: e2e specs?
22631
22632       // TODO: post-resize a horizontal scroll event should be fired
22633      </doc:scenario>
22634    </doc:example>
22635    */
22636   module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
22637     var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
22638
22639     var resizer = {
22640       priority: 0,
22641       scope: {
22642         col: '=',
22643         position: '@',
22644         renderIndex: '='
22645       },
22646       require: '?^uiGrid',
22647       link: function ($scope, $elm, $attrs, uiGridCtrl) {
22648         var startX = 0,
22649             x = 0,
22650             gridLeft = 0,
22651             rtlMultiplier = 1;
22652
22653         //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
22654         if (uiGridCtrl.grid.isRTL()) {
22655           $scope.position = 'left';
22656           rtlMultiplier = -1;
22657         }
22658
22659         if ($scope.position === 'left') {
22660           $elm.addClass('left');
22661         }
22662         else if ($scope.position === 'right') {
22663           $elm.addClass('right');
22664         }
22665
22666         // Refresh the grid canvas
22667         //   takes an argument representing the diff along the X-axis that the resize had
22668         function refreshCanvas(xDiff) {
22669           // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
22670           uiGridCtrl.grid.refreshCanvas(true).then( function() {
22671             uiGridCtrl.grid.queueGridRefresh();
22672           });
22673         }
22674
22675         // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
22676         // Returns the new recommended with, after constraints applied
22677         function constrainWidth(col, width){
22678           var newWidth = width;
22679
22680           // If the new width would be less than the column's allowably minimum width, don't allow it
22681           if (col.minWidth && newWidth < col.minWidth) {
22682             newWidth = col.minWidth;
22683           }
22684           else if (col.maxWidth && newWidth > col.maxWidth) {
22685             newWidth = col.maxWidth;
22686           }
22687
22688           return newWidth;
22689         }
22690
22691
22692         /*
22693          * Our approach to event handling aims to deal with both touch devices and mouse devices
22694          * We register down handlers on both touch and mouse.  When a touchstart or mousedown event
22695          * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
22696          *
22697          * This way we can listen for both without worrying about the fact many touch devices also emulate
22698          * mouse events - basically whichever one we hear first is what we'll go with.
22699          */
22700         function moveFunction(event, args) {
22701           if (event.originalEvent) { event = event.originalEvent; }
22702           event.preventDefault();
22703
22704           x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22705
22706           if (x < 0) { x = 0; }
22707           else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
22708
22709           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22710
22711           // Don't resize if it's disabled on this column
22712           if (col.colDef.enableColumnResizing === false) {
22713             return;
22714           }
22715
22716           if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
22717             uiGridCtrl.grid.element.addClass('column-resizing');
22718           }
22719
22720           // Get the diff along the X axis
22721           var xDiff = x - startX;
22722
22723           // Get the width that this mouse would give the column
22724           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22725
22726           // check we're not outside the allowable bounds for this column
22727           x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
22728
22729           resizeOverlay.css({ left: x + 'px' });
22730
22731           uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
22732         }
22733
22734
22735         function upFunction(event, args) {
22736           if (event.originalEvent) { event = event.originalEvent; }
22737           event.preventDefault();
22738
22739           uiGridCtrl.grid.element.removeClass('column-resizing');
22740
22741           resizeOverlay.remove();
22742
22743           // Resize the column
22744           x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
22745           var xDiff = x - startX;
22746
22747           if (xDiff === 0) {
22748             // no movement, so just reset event handlers, including turning back on both
22749             // down events - we turned one off when this event started
22750             offAllEvents();
22751             onDownEvents();
22752             return;
22753           }
22754
22755           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22756
22757           // Don't resize if it's disabled on this column
22758           if (col.colDef.enableColumnResizing === false) {
22759             return;
22760           }
22761
22762           // Get the new width
22763           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
22764
22765           // check we're not outside the allowable bounds for this column
22766           col.width = constrainWidth(col, newWidth);
22767           col.hasCustomWidth = true;
22768
22769           refreshCanvas(xDiff);
22770
22771           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
22772
22773           // stop listening of up and move events - wait for next down
22774           // reset the down events - we will have turned one off when this event started
22775           offAllEvents();
22776           onDownEvents();
22777         }
22778
22779
22780         var downFunction = function(event, args) {
22781           if (event.originalEvent) { event = event.originalEvent; }
22782           event.stopPropagation();
22783
22784           // Get the left offset of the grid
22785           // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
22786           gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
22787
22788           // Get the starting X position, which is the X coordinate of the click minus the grid's offset
22789           startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
22790
22791           // Append the resizer overlay
22792           uiGridCtrl.grid.element.append(resizeOverlay);
22793
22794           // Place the resizer overlay at the start position
22795           resizeOverlay.css({ left: startX });
22796
22797           // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
22798           // we were touchdown then we listen for touchmove and touchup.  Also remove the handler for the equivalent
22799           // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
22800           // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
22801           if ( event.type === 'touchstart' ){
22802             $document.on('touchend', upFunction);
22803             $document.on('touchmove', moveFunction);
22804             $elm.off('mousedown', downFunction);
22805           } else {
22806             $document.on('mouseup', upFunction);
22807             $document.on('mousemove', moveFunction);
22808             $elm.off('touchstart', downFunction);
22809           }
22810         };
22811
22812         var onDownEvents = function() {
22813           $elm.on('mousedown', downFunction);
22814           $elm.on('touchstart', downFunction);
22815         };
22816
22817         var offAllEvents = function() {
22818           $document.off('mouseup', upFunction);
22819           $document.off('touchend', upFunction);
22820           $document.off('mousemove', moveFunction);
22821           $document.off('touchmove', moveFunction);
22822           $elm.off('mousedown', downFunction);
22823           $elm.off('touchstart', downFunction);
22824         };
22825
22826         onDownEvents();
22827
22828
22829         // On doubleclick, resize to fit all rendered cells
22830         var dblClickFn = function(event, args){
22831           event.stopPropagation();
22832
22833           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
22834
22835           // Don't resize if it's disabled on this column
22836           if (col.colDef.enableColumnResizing === false) {
22837             return;
22838           }
22839
22840           // Go through the rendered rows and find out the max size for the data in this column
22841           var maxWidth = 0;
22842           var xDiff = 0;
22843
22844           // Get the parent render container element
22845           var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
22846
22847           // 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
22848           var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
22849           Array.prototype.forEach.call(cells, function (cell) {
22850               // Get the cell width
22851               // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
22852
22853               // Account for the menu button if it exists
22854               var menuButton;
22855               if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
22856                 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
22857               }
22858
22859               gridUtil.fakeElement(cell, {}, function(newElm) {
22860                 // Make the element float since it's a div and can expand to fill its container
22861                 var e = angular.element(newElm);
22862                 e.attr('style', 'float: left');
22863
22864                 var width = gridUtil.elementWidth(e);
22865
22866                 if (menuButton) {
22867                   var menuButtonWidth = gridUtil.elementWidth(menuButton);
22868                   width = width + menuButtonWidth;
22869                 }
22870
22871                 if (width > maxWidth) {
22872                   maxWidth = width;
22873                   xDiff = maxWidth - width;
22874                 }
22875               });
22876             });
22877
22878           // check we're not outside the allowable bounds for this column
22879           col.width = constrainWidth(col, maxWidth);
22880           col.hasCustomWidth = true;
22881
22882           refreshCanvas(xDiff);
22883
22884           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);        };
22885         $elm.on('dblclick', dblClickFn);
22886
22887         $elm.on('$destroy', function() {
22888           $elm.off('dblclick', dblClickFn);
22889           offAllEvents();
22890         });
22891       }
22892     };
22893
22894     return resizer;
22895   }]);
22896
22897 })();
22898
22899 (function () {
22900   'use strict';
22901
22902   /**
22903    * @ngdoc overview
22904    * @name ui.grid.rowEdit
22905    * @description
22906    *
22907    * # ui.grid.rowEdit
22908    *
22909    * <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>
22910    *
22911    * This module extends the edit feature to provide tracking and saving of rows
22912    * of data.  The tutorial provides more information on how this feature is best
22913    * used {@link tutorial/205_row_editable here}.
22914    * <br/>
22915    * This feature depends on usage of the ui-grid-edit feature, and also benefits
22916    * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
22917    * experience
22918    *
22919    */
22920
22921   var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
22922
22923   /**
22924    *  @ngdoc object
22925    *  @name ui.grid.rowEdit.constant:uiGridRowEditConstants
22926    *
22927    *  @description constants available in row edit module
22928    */
22929   module.constant('uiGridRowEditConstants', {
22930   });
22931
22932   /**
22933    *  @ngdoc service
22934    *  @name ui.grid.rowEdit.service:uiGridRowEditService
22935    *
22936    *  @description Services for row editing features
22937    */
22938   module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
22939     function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
22940
22941       var service = {
22942
22943         initializeGrid: function (scope, grid) {
22944           /**
22945            *  @ngdoc object
22946            *  @name ui.grid.rowEdit.api:PublicApi
22947            *
22948            *  @description Public Api for rowEdit feature
22949            */
22950
22951           grid.rowEdit = {};
22952
22953           var publicApi = {
22954             events: {
22955               rowEdit: {
22956                 /**
22957                  * @ngdoc event
22958                  * @eventOf ui.grid.rowEdit.api:PublicApi
22959                  * @name saveRow
22960                  * @description raised when a row is ready for saving.  Once your
22961                  * row has saved you may need to use angular.extend to update the
22962                  * data entity with any changed data from your save (for example,
22963                  * lock version information if you're using optimistic locking,
22964                  * or last update time/user information).
22965                  *
22966                  * Your method should call setSavePromise somewhere in the body before
22967                  * returning control.  The feature will then wait, with the gridRow greyed out
22968                  * whilst this promise is being resolved.
22969                  *
22970                  * <pre>
22971                  *      gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
22972                  * </pre>
22973                  * and somewhere within the event handler:
22974                  * <pre>
22975                  *      gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
22976                  * </pre>
22977                  * @param {object} rowEntity the options.data element that was edited
22978                  * @returns {promise} Your saveRow method should return a promise, the
22979                  * promise should either be resolved (implying successful save), or
22980                  * rejected (implying an error).
22981                  */
22982                 saveRow: function (rowEntity) {
22983                 }
22984               }
22985             },
22986             methods: {
22987               rowEdit: {
22988                 /**
22989                  * @ngdoc method
22990                  * @methodOf ui.grid.rowEdit.api:PublicApi
22991                  * @name setSavePromise
22992                  * @description Sets the promise associated with the row save, mandatory that
22993                  * the saveRow event handler calls this method somewhere before returning.
22994                  * <pre>
22995                  *      gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
22996                  * </pre>
22997                  * @param {object} rowEntity a data row from the grid for which a save has
22998                  * been initiated
22999                  * @param {promise} savePromise the promise that will be resolved when the
23000                  * save is successful, or rejected if the save fails
23001                  *
23002                  */
23003                 setSavePromise: function ( rowEntity, savePromise) {
23004                   service.setSavePromise(grid, rowEntity, savePromise);
23005                 },
23006                 /**
23007                  * @ngdoc method
23008                  * @methodOf ui.grid.rowEdit.api:PublicApi
23009                  * @name getDirtyRows
23010                  * @description Returns all currently dirty rows
23011                  * <pre>
23012                  *      gridApi.rowEdit.getDirtyRows(grid)
23013                  * </pre>
23014                  * @returns {array} An array of gridRows that are currently dirty
23015                  *
23016                  */
23017                 getDirtyRows: function () {
23018                   return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
23019                 },
23020                 /**
23021                  * @ngdoc method
23022                  * @methodOf ui.grid.rowEdit.api:PublicApi
23023                  * @name getErrorRows
23024                  * @description Returns all currently errored rows
23025                  * <pre>
23026                  *      gridApi.rowEdit.getErrorRows(grid)
23027                  * </pre>
23028                  * @returns {array} An array of gridRows that are currently in error
23029                  *
23030                  */
23031                 getErrorRows: function () {
23032                   return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
23033                 },
23034                 /**
23035                  * @ngdoc method
23036                  * @methodOf ui.grid.rowEdit.api:PublicApi
23037                  * @name flushDirtyRows
23038                  * @description Triggers a save event for all currently dirty rows, could
23039                  * be used where user presses a save button or navigates away from the page
23040                  * <pre>
23041                  *      gridApi.rowEdit.flushDirtyRows(grid)
23042                  * </pre>
23043                  * @returns {promise} a promise that represents the aggregate of all
23044                  * of the individual save promises - i.e. it will be resolved when all
23045                  * the individual save promises have been resolved.
23046                  *
23047                  */
23048                 flushDirtyRows: function () {
23049                   return service.flushDirtyRows(grid);
23050                 },
23051
23052                 /**
23053                  * @ngdoc method
23054                  * @methodOf ui.grid.rowEdit.api:PublicApi
23055                  * @name setRowsDirty
23056                  * @description Sets each of the rows passed in dataRows
23057                  * to be dirty.  note that if you have only just inserted the
23058                  * rows into your data you will need to wait for a $digest cycle
23059                  * before the gridRows are present - so often you would wrap this
23060                  * call in a $interval or $timeout
23061                  * <pre>
23062                  *      $interval( function() {
23063                  *        gridApi.rowEdit.setRowsDirty(myDataRows);
23064                  *      }, 0, 1);
23065                  * </pre>
23066                  * @param {array} dataRows the data entities for which the gridRows
23067                  * should be set dirty.
23068                  *
23069                  */
23070                 setRowsDirty: function ( dataRows) {
23071                   service.setRowsDirty(grid, dataRows);
23072                 },
23073
23074                 /**
23075                  * @ngdoc method
23076                  * @methodOf ui.grid.rowEdit.api:PublicApi
23077                  * @name setRowsClean
23078                  * @description Sets each of the rows passed in dataRows
23079                  * to be clean, removing them from the dirty cache and the error cache,
23080                  * and clearing the error flag and the dirty flag
23081                  * <pre>
23082                  *      var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
23083                  *      var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
23084                  *      $scope.gridApi.rowEdit.setRowsClean( dataRows );
23085                  * </pre>
23086                  * @param {array} dataRows the data entities for which the gridRows
23087                  * should be set clean.
23088                  *
23089                  */
23090                 setRowsClean: function ( dataRows) {
23091                   service.setRowsClean(grid, dataRows);
23092                 }
23093               }
23094             }
23095           };
23096
23097           grid.api.registerEventsFromObject(publicApi.events);
23098           grid.api.registerMethodsFromObject(publicApi.methods);
23099
23100           grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
23101             grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
23102             grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
23103             grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
23104
23105             if ( grid.api.cellNav ) {
23106               grid.api.cellNav.on.navigate( scope, service.navigate );
23107             }
23108           });
23109
23110         },
23111
23112         defaultGridOptions: function (gridOptions) {
23113
23114           /**
23115            *  @ngdoc object
23116            *  @name ui.grid.rowEdit.api:GridOptions
23117            *
23118            *  @description Options for configuring the rowEdit feature, these are available to be
23119            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23120            */
23121
23122         },
23123
23124
23125         /**
23126          * @ngdoc method
23127          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23128          * @name saveRow
23129          * @description  Returns a function that saves the specified row from the grid,
23130          * and returns a promise
23131          * @param {object} grid the grid for which dirty rows should be flushed
23132          * @param {GridRow} gridRow the row that should be saved
23133          * @returns {function} the saveRow function returns a function.  That function
23134          * in turn, when called, returns a promise relating to the save callback
23135          */
23136         saveRow: function ( grid, gridRow ) {
23137           var self = this;
23138
23139           return function() {
23140             gridRow.isSaving = true;
23141
23142             if ( gridRow.rowEditSavePromise ){
23143               // don't save the row again if it's already saving - that causes stale object exceptions
23144               return gridRow.rowEditSavePromise;
23145             }
23146
23147             var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
23148
23149             if ( gridRow.rowEditSavePromise ){
23150               gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
23151             } else {
23152               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' );
23153             }
23154             return promise;
23155           };
23156         },
23157
23158
23159         /**
23160          * @ngdoc method
23161          * @methodOf  ui.grid.rowEdit.service:uiGridRowEditService
23162          * @name setSavePromise
23163          * @description Sets the promise associated with the row save, mandatory that
23164          * the saveRow event handler calls this method somewhere before returning.
23165          * <pre>
23166          *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
23167          * </pre>
23168          * @param {object} grid the grid for which dirty rows should be returned
23169          * @param {object} rowEntity a data row from the grid for which a save has
23170          * been initiated
23171          * @param {promise} savePromise the promise that will be resolved when the
23172          * save is successful, or rejected if the save fails
23173          *
23174          */
23175         setSavePromise: function (grid, rowEntity, savePromise) {
23176           var gridRow = grid.getRow( rowEntity );
23177           gridRow.rowEditSavePromise = savePromise;
23178         },
23179
23180
23181         /**
23182          * @ngdoc method
23183          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23184          * @name processSuccessPromise
23185          * @description  Returns a function that processes the successful
23186          * resolution of a save promise
23187          * @param {object} grid the grid for which the promise should be processed
23188          * @param {GridRow} gridRow the row that has been saved
23189          * @returns {function} the success handling function
23190          */
23191         processSuccessPromise: function ( grid, gridRow ) {
23192           var self = this;
23193
23194           return function() {
23195             delete gridRow.isSaving;
23196             delete gridRow.isDirty;
23197             delete gridRow.isError;
23198             delete gridRow.rowEditSaveTimer;
23199             delete gridRow.rowEditSavePromise;
23200             self.removeRow( grid.rowEdit.errorRows, gridRow );
23201             self.removeRow( grid.rowEdit.dirtyRows, gridRow );
23202           };
23203         },
23204
23205
23206         /**
23207          * @ngdoc method
23208          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23209          * @name processErrorPromise
23210          * @description  Returns a function that processes the failed
23211          * resolution of a save promise
23212          * @param {object} grid the grid for which the promise should be processed
23213          * @param {GridRow} gridRow the row that is now in error
23214          * @returns {function} the error handling function
23215          */
23216         processErrorPromise: function ( grid, gridRow ) {
23217           return function() {
23218             delete gridRow.isSaving;
23219             delete gridRow.rowEditSaveTimer;
23220             delete gridRow.rowEditSavePromise;
23221
23222             gridRow.isError = true;
23223
23224             if (!grid.rowEdit.errorRows){
23225               grid.rowEdit.errorRows = [];
23226             }
23227             if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
23228               grid.rowEdit.errorRows.push( gridRow );
23229             }
23230           };
23231         },
23232
23233
23234         /**
23235          * @ngdoc method
23236          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23237          * @name removeRow
23238          * @description  Removes a row from a cache of rows - either
23239          * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows.  If the row
23240          * is not present silently does nothing.
23241          * @param {array} rowArray the array from which to remove the row
23242          * @param {GridRow} gridRow the row that should be removed
23243          */
23244         removeRow: function( rowArray, removeGridRow ){
23245           if (typeof(rowArray) === 'undefined' || rowArray === null){
23246             return;
23247           }
23248
23249           rowArray.forEach( function( gridRow, index ){
23250             if ( gridRow.uid === removeGridRow.uid ){
23251               rowArray.splice( index, 1);
23252             }
23253           });
23254         },
23255
23256
23257         /**
23258          * @ngdoc method
23259          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23260          * @name isRowPresent
23261          * @description  Checks whether a row is already present
23262          * in the given array
23263          * @param {array} rowArray the array in which to look for the row
23264          * @param {GridRow} gridRow the row that should be looked for
23265          */
23266         isRowPresent: function( rowArray, removeGridRow ){
23267           var present = false;
23268           rowArray.forEach( function( gridRow, index ){
23269             if ( gridRow.uid === removeGridRow.uid ){
23270               present = true;
23271             }
23272           });
23273           return present;
23274         },
23275
23276
23277         /**
23278          * @ngdoc method
23279          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23280          * @name flushDirtyRows
23281          * @description Triggers a save event for all currently dirty rows, could
23282          * be used where user presses a save button or navigates away from the page
23283          * <pre>
23284          *      gridApi.rowEdit.flushDirtyRows(grid)
23285          * </pre>
23286          * @param {object} grid the grid for which dirty rows should be flushed
23287          * @returns {promise} a promise that represents the aggregate of all
23288          * of the individual save promises - i.e. it will be resolved when all
23289          * the individual save promises have been resolved.
23290          *
23291          */
23292         flushDirtyRows: function(grid){
23293           var promises = [];
23294           grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
23295             service.saveRow( grid, gridRow )();
23296             promises.push( gridRow.rowEditSavePromise );
23297           });
23298
23299           return $q.all( promises );
23300         },
23301
23302
23303         /**
23304          * @ngdoc method
23305          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23306          * @name endEditCell
23307          * @description Receives an afterCellEdit event from the edit function,
23308          * and sets flags as appropriate.  Only the rowEntity parameter
23309          * is processed, although other params are available.  Grid
23310          * is automatically provided by the gridApi.
23311          * @param {object} rowEntity the data entity for which the cell
23312          * was edited
23313          */
23314         endEditCell: function( rowEntity, colDef, newValue, previousValue ){
23315           var grid = this.grid;
23316           var gridRow = grid.getRow( rowEntity );
23317           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
23318
23319           if ( newValue !== previousValue || gridRow.isDirty ){
23320             if ( !grid.rowEdit.dirtyRows ){
23321               grid.rowEdit.dirtyRows = [];
23322             }
23323
23324             if ( !gridRow.isDirty ){
23325               gridRow.isDirty = true;
23326               grid.rowEdit.dirtyRows.push( gridRow );
23327             }
23328
23329             delete gridRow.isError;
23330
23331             service.considerSetTimer( grid, gridRow );
23332           }
23333         },
23334
23335
23336         /**
23337          * @ngdoc method
23338          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23339          * @name beginEditCell
23340          * @description Receives a beginCellEdit event from the edit function,
23341          * and cancels any rowEditSaveTimers if present, as the user is still editing
23342          * this row.  Only the rowEntity parameter
23343          * is processed, although other params are available.  Grid
23344          * is automatically provided by the gridApi.
23345          * @param {object} rowEntity the data entity for which the cell
23346          * editing has commenced
23347          */
23348         beginEditCell: function( rowEntity, colDef ){
23349           var grid = this.grid;
23350           var gridRow = grid.getRow( rowEntity );
23351           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
23352
23353           service.cancelTimer( grid, gridRow );
23354         },
23355
23356
23357         /**
23358          * @ngdoc method
23359          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23360          * @name cancelEditCell
23361          * @description Receives a cancelCellEdit event from the edit function,
23362          * and if the row was already dirty, restarts the save timer.  If the row
23363          * was not already dirty, then it's not dirty now either and does nothing.
23364          *
23365          * Only the rowEntity parameter
23366          * is processed, although other params are available.  Grid
23367          * is automatically provided by the gridApi.
23368          *
23369          * @param {object} rowEntity the data entity for which the cell
23370          * editing was cancelled
23371          */
23372         cancelEditCell: function( rowEntity, colDef ){
23373           var grid = this.grid;
23374           var gridRow = grid.getRow( rowEntity );
23375           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
23376
23377           service.considerSetTimer( grid, gridRow );
23378         },
23379
23380
23381         /**
23382          * @ngdoc method
23383          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23384          * @name navigate
23385          * @description cellNav tells us that the selected cell has changed.  If
23386          * the new row had a timer running, then stop it similar to in a beginCellEdit
23387          * call.  If the old row is dirty and not the same as the new row, then
23388          * start a timer on it.
23389          * @param {object} newRowCol the row and column that were selected
23390          * @param {object} oldRowCol the row and column that was left
23391          *
23392          */
23393         navigate: function( newRowCol, oldRowCol ){
23394           var grid = this.grid;
23395           if ( newRowCol.row.rowEditSaveTimer ){
23396             service.cancelTimer( grid, newRowCol.row );
23397           }
23398
23399           if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
23400             service.considerSetTimer( grid, oldRowCol.row );
23401           }
23402         },
23403
23404
23405         /**
23406          * @ngdoc property
23407          * @propertyOf ui.grid.rowEdit.api:GridOptions
23408          * @name rowEditWaitInterval
23409          * @description How long the grid should wait for another change on this row
23410          * before triggering a save (in milliseconds).  If set to -1, then saves are
23411          * never triggered by timer (implying that the user will call flushDirtyRows()
23412          * manually)
23413          *
23414          * @example
23415          * Setting the wait interval to 4 seconds
23416          * <pre>
23417          *   $scope.gridOptions = { rowEditWaitInterval: 4000 }
23418          * </pre>
23419          *
23420          */
23421         /**
23422          * @ngdoc method
23423          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23424          * @name considerSetTimer
23425          * @description Consider setting a timer on this row (if it is dirty).  if there is a timer running
23426          * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
23427          * dirty and not currently saving then set a new timer
23428          * @param {object} grid the grid for which we are processing
23429          * @param {GridRow} gridRow the row for which the timer should be adjusted
23430          *
23431          */
23432         considerSetTimer: function( grid, gridRow ){
23433           service.cancelTimer( grid, gridRow );
23434
23435           if ( gridRow.isDirty && !gridRow.isSaving ){
23436             if ( grid.options.rowEditWaitInterval !== -1 ){
23437               var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
23438               gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
23439             }
23440           }
23441         },
23442
23443
23444         /**
23445          * @ngdoc method
23446          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23447          * @name cancelTimer
23448          * @description cancel the $interval for any timer running on this row
23449          * then delete the timer itself
23450          * @param {object} grid the grid for which we are processing
23451          * @param {GridRow} gridRow the row for which the timer should be adjusted
23452          *
23453          */
23454         cancelTimer: function( grid, gridRow ){
23455           if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
23456             $interval.cancel(gridRow.rowEditSaveTimer);
23457             delete gridRow.rowEditSaveTimer;
23458           }
23459         },
23460
23461
23462         /**
23463          * @ngdoc method
23464          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23465          * @name setRowsDirty
23466          * @description Sets each of the rows passed in dataRows
23467          * to be dirty.  note that if you have only just inserted the
23468          * rows into your data you will need to wait for a $digest cycle
23469          * before the gridRows are present - so often you would wrap this
23470          * call in a $interval or $timeout
23471          * <pre>
23472          *      $interval( function() {
23473          *        gridApi.rowEdit.setRowsDirty( myDataRows);
23474          *      }, 0, 1);
23475          * </pre>
23476          * @param {object} grid the grid for which rows should be set dirty
23477          * @param {array} dataRows the data entities for which the gridRows
23478          * should be set dirty.
23479          *
23480          */
23481         setRowsDirty: function( grid, myDataRows ) {
23482           var gridRow;
23483           myDataRows.forEach( function( value, index ){
23484             gridRow = grid.getRow( value );
23485             if ( gridRow ){
23486               if ( !grid.rowEdit.dirtyRows ){
23487                 grid.rowEdit.dirtyRows = [];
23488               }
23489
23490               if ( !gridRow.isDirty ){
23491                 gridRow.isDirty = true;
23492                 grid.rowEdit.dirtyRows.push( gridRow );
23493               }
23494
23495               delete gridRow.isError;
23496
23497               service.considerSetTimer( grid, gridRow );
23498             } else {
23499               gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
23500             }
23501           });
23502         },
23503
23504
23505         /**
23506          * @ngdoc method
23507          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23508          * @name setRowsClean
23509          * @description Sets each of the rows passed in dataRows
23510          * to be clean, clearing the dirty flag and the error flag, and removing
23511          * the rows from the dirty and error caches.
23512          * @param {object} grid the grid for which rows should be set clean
23513          * @param {array} dataRows the data entities for which the gridRows
23514          * should be set clean.
23515          *
23516          */
23517         setRowsClean: function( grid, myDataRows ) {
23518           var gridRow;
23519
23520           myDataRows.forEach( function( value, index ){
23521             gridRow = grid.getRow( value );
23522             if ( gridRow ){
23523               delete gridRow.isDirty;
23524               service.removeRow( grid.rowEdit.dirtyRows, gridRow );
23525               service.cancelTimer( grid, gridRow );
23526
23527               delete gridRow.isError;
23528               service.removeRow( grid.rowEdit.errorRows, gridRow );
23529             } else {
23530               gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
23531             }
23532           });
23533         }
23534
23535       };
23536
23537       return service;
23538
23539     }]);
23540
23541   /**
23542    *  @ngdoc directive
23543    *  @name ui.grid.rowEdit.directive:uiGridEdit
23544    *  @element div
23545    *  @restrict A
23546    *
23547    *  @description Adds row editing features to the ui-grid-edit directive.
23548    *
23549    */
23550   module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
23551   function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
23552     return {
23553       replace: true,
23554       priority: 0,
23555       require: '^uiGrid',
23556       scope: false,
23557       compile: function () {
23558         return {
23559           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23560             uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
23561           },
23562           post: function ($scope, $elm, $attrs, uiGridCtrl) {
23563           }
23564         };
23565       }
23566     };
23567   }]);
23568
23569
23570   /**
23571    *  @ngdoc directive
23572    *  @name ui.grid.rowEdit.directive:uiGridViewport
23573    *  @element div
23574    *
23575    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
23576    *  for the grid row to allow coloring of saving and error rows
23577    */
23578   module.directive('uiGridViewport',
23579     ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
23580       function ($compile, uiGridConstants, gridUtil, $parse) {
23581         return {
23582           priority: -200, // run after default  directive
23583           scope: false,
23584           compile: function ($elm, $attrs) {
23585             var rowRepeatDiv = angular.element($elm.children().children()[0]);
23586
23587             var existingNgClass = rowRepeatDiv.attr("ng-class");
23588             var newNgClass = '';
23589             if ( existingNgClass ) {
23590               newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
23591             } else {
23592               newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
23593             }
23594             rowRepeatDiv.attr("ng-class", newNgClass);
23595
23596             return {
23597               pre: function ($scope, $elm, $attrs, controllers) {
23598
23599               },
23600               post: function ($scope, $elm, $attrs, controllers) {
23601               }
23602             };
23603           }
23604         };
23605       }]);
23606
23607 })();
23608
23609 (function () {
23610   'use strict';
23611
23612   /**
23613    * @ngdoc overview
23614    * @name ui.grid.saveState
23615    * @description
23616    *
23617    * # ui.grid.saveState
23618    *
23619    * <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>
23620    *
23621    * This module provides the ability to save the grid state, and restore
23622    * it when the user returns to the page.
23623    *
23624    * No UI is provided, the caller should provide their own UI/buttons
23625    * as appropriate. Usually the navigate events would be used to save
23626    * the grid state and restore it.
23627    *
23628    * <br/>
23629    * <br/>
23630    *
23631    * <div doc-module-components="ui.grid.save-state"></div>
23632    */
23633
23634   var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
23635
23636   /**
23637    *  @ngdoc object
23638    *  @name ui.grid.saveState.constant:uiGridSaveStateConstants
23639    *
23640    *  @description constants available in save state module
23641    */
23642
23643   module.constant('uiGridSaveStateConstants', {
23644     featureName: 'saveState'
23645   });
23646
23647   /**
23648    *  @ngdoc service
23649    *  @name ui.grid.saveState.service:uiGridSaveStateService
23650    *
23651    *  @description Services for saveState feature
23652    */
23653   module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
23654     function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
23655
23656       var service = {
23657
23658         initializeGrid: function (grid) {
23659
23660           //add feature namespace and any properties to grid for needed state
23661           grid.saveState = {};
23662           this.defaultGridOptions(grid.options);
23663
23664           /**
23665            *  @ngdoc object
23666            *  @name ui.grid.saveState.api:PublicApi
23667            *
23668            *  @description Public Api for saveState feature
23669            */
23670           var publicApi = {
23671             events: {
23672               saveState: {
23673               }
23674             },
23675             methods: {
23676               saveState: {
23677                 /**
23678                  * @ngdoc function
23679                  * @name save
23680                  * @methodOf  ui.grid.saveState.api:PublicApi
23681                  * @description Packages the current state of the grid into
23682                  * an object, and provides it to the user for saving
23683                  * @returns {object} the state as a javascript object that can be saved
23684                  */
23685                 save: function () {
23686                   return service.save(grid);
23687                 },
23688                 /**
23689                  * @ngdoc function
23690                  * @name restore
23691                  * @methodOf  ui.grid.saveState.api:PublicApi
23692                  * @description Restores the provided state into the grid
23693                  * @param {scope} $scope a scope that we can broadcast on
23694                  * @param {object} state the state that should be restored into the grid
23695                  */
23696                 restore: function ( $scope, state) {
23697                   service.restore(grid, $scope, state);
23698                 }
23699               }
23700             }
23701           };
23702
23703           grid.api.registerEventsFromObject(publicApi.events);
23704
23705           grid.api.registerMethodsFromObject(publicApi.methods);
23706
23707         },
23708
23709         defaultGridOptions: function (gridOptions) {
23710           //default option to true unless it was explicitly set to false
23711           /**
23712            * @ngdoc object
23713            * @name ui.grid.saveState.api:GridOptions
23714            *
23715            * @description GridOptions for saveState feature, these are available to be
23716            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23717            */
23718           /**
23719            * @ngdoc object
23720            * @name saveWidths
23721            * @propertyOf  ui.grid.saveState.api:GridOptions
23722            * @description Save the current column widths.  Note that unless
23723            * you've provided the user with some way to resize their columns (say
23724            * the resize columns feature), then this makes little sense.
23725            * <br/>Defaults to true
23726            */
23727           gridOptions.saveWidths = gridOptions.saveWidths !== false;
23728           /**
23729            * @ngdoc object
23730            * @name saveOrder
23731            * @propertyOf  ui.grid.saveState.api:GridOptions
23732            * @description Restore the current column order.  Note that unless
23733            * you've provided the user with some way to reorder their columns (for
23734            * example the move columns feature), this makes little sense.
23735            * <br/>Defaults to true
23736            */
23737           gridOptions.saveOrder = gridOptions.saveOrder !== false;
23738           /**
23739            * @ngdoc object
23740            * @name saveScroll
23741            * @propertyOf  ui.grid.saveState.api:GridOptions
23742            * @description Save the current scroll position.  Note that this
23743            * is saved as the percentage of the grid scrolled - so if your
23744            * user returns to a grid with a significantly different number of
23745            * rows (perhaps some data has been deleted) then the scroll won't
23746            * actually show the same rows as before.  If you want to scroll to
23747            * a specific row then you should instead use the saveFocus option, which
23748            * is the default.
23749            *
23750            * Note that this element will only be saved if the cellNav feature is
23751            * enabled
23752            * <br/>Defaults to false
23753            */
23754           gridOptions.saveScroll = gridOptions.saveScroll === true;
23755           /**
23756            * @ngdoc object
23757            * @name saveFocus
23758            * @propertyOf  ui.grid.saveState.api:GridOptions
23759            * @description Save the current focused cell.  On returning
23760            * to this focused cell we'll also scroll.  This option is
23761            * preferred to the saveScroll option, so is set to true by
23762            * default.  If saveScroll is set to true then this option will
23763            * be disabled.
23764            *
23765            * By default this option saves the current row number and column
23766            * number, and returns to that row and column.  However, if you define
23767            * a saveRowIdentity function, then it will return you to the currently
23768            * selected column within that row (in a business sense - so if some
23769            * rows have been deleted, it will still find the same data, presuming it
23770            * still exists in the list.  If it isn't in the list then it will instead
23771            * return to the same row number - i.e. scroll percentage)
23772            *
23773            * Note that this option will do nothing if the cellNav
23774            * feature is not enabled.
23775            *
23776            * <br/>Defaults to true (unless saveScroll is true)
23777            */
23778           gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
23779           /**
23780            * @ngdoc object
23781            * @name saveRowIdentity
23782            * @propertyOf  ui.grid.saveState.api:GridOptions
23783            * @description A function that can be called, passing in a rowEntity,
23784            * and that will return a unique id for that row.  This might simply
23785            * return the `id` field from that row (if you have one), or it might
23786            * concatenate some fields within the row to make a unique value.
23787            *
23788            * This value will be used to find the same row again and set the focus
23789            * to it, if it exists when we return.
23790            *
23791            * <br/>Defaults to undefined
23792            */
23793           /**
23794            * @ngdoc object
23795            * @name saveVisible
23796            * @propertyOf  ui.grid.saveState.api:GridOptions
23797            * @description Save whether or not columns are visible.
23798            *
23799            * <br/>Defaults to true
23800            */
23801           gridOptions.saveVisible = gridOptions.saveVisible !== false;
23802           /**
23803            * @ngdoc object
23804            * @name saveSort
23805            * @propertyOf  ui.grid.saveState.api:GridOptions
23806            * @description Save the current sort state for each column
23807            *
23808            * <br/>Defaults to true
23809            */
23810           gridOptions.saveSort = gridOptions.saveSort !== false;
23811           /**
23812            * @ngdoc object
23813            * @name saveFilter
23814            * @propertyOf  ui.grid.saveState.api:GridOptions
23815            * @description Save the current filter state for each column
23816            *
23817            * <br/>Defaults to true
23818            */
23819           gridOptions.saveFilter = gridOptions.saveFilter !== false;
23820           /**
23821            * @ngdoc object
23822            * @name saveSelection
23823            * @propertyOf  ui.grid.saveState.api:GridOptions
23824            * @description Save the currently selected rows.  If the `saveRowIdentity` callback
23825            * is defined, then it will save the id of the row and select that.  If not, then
23826            * it will attempt to select the rows by row number, which will give the wrong results
23827            * if the data set has changed in the mean-time.
23828            *
23829            * Note that this option only does anything
23830            * if the selection feature is enabled.
23831            *
23832            * <br/>Defaults to true
23833            */
23834           gridOptions.saveSelection = gridOptions.saveSelection !== false;
23835           /**
23836            * @ngdoc object
23837            * @name saveGrouping
23838            * @propertyOf  ui.grid.saveState.api:GridOptions
23839            * @description Save the grouping configuration.  If set to true and the
23840            * grouping feature is not enabled then does nothing.
23841            *
23842            * <br/>Defaults to true
23843            */
23844           gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
23845           /**
23846            * @ngdoc object
23847            * @name saveGroupingExpandedStates
23848            * @propertyOf  ui.grid.saveState.api:GridOptions
23849            * @description Save the grouping row expanded states.  If set to true and the
23850            * grouping feature is not enabled then does nothing.
23851            *
23852            * This can be quite a bit of data, in many cases you wouldn't want to save this
23853            * information.
23854            *
23855            * <br/>Defaults to false
23856            */
23857           gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true;
23858           /**
23859            * @ngdoc object
23860            * @name savePinning
23861            * @propertyOf ui.grid.saveState.api:GridOptions
23862            * @description Save pinning state for columns.
23863            *
23864            * <br/>Defaults to true
23865            */
23866           gridOptions.savePinning = gridOptions.savePinning !== false;
23867           /**
23868            * @ngdoc object
23869            * @name saveTreeView
23870            * @propertyOf  ui.grid.saveState.api:GridOptions
23871            * @description Save the treeView configuration.  If set to true and the
23872            * treeView feature is not enabled then does nothing.
23873            *
23874            * <br/>Defaults to true
23875            */
23876           gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
23877         },
23878
23879
23880
23881         /**
23882          * @ngdoc function
23883          * @name save
23884          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23885          * @description Saves the current grid state into an object, and
23886          * passes that object back to the caller
23887          * @param {Grid} grid the grid whose state we'd like to save
23888          * @returns {object} the state ready to be saved
23889          */
23890         save: function (grid) {
23891           var savedState = {};
23892
23893           savedState.columns = service.saveColumns( grid );
23894           savedState.scrollFocus = service.saveScrollFocus( grid );
23895           savedState.selection = service.saveSelection( grid );
23896           savedState.grouping = service.saveGrouping( grid );
23897           savedState.treeView = service.saveTreeView( grid );
23898           savedState.pagination = service.savePagination( grid );
23899
23900           return savedState;
23901         },
23902
23903
23904         /**
23905          * @ngdoc function
23906          * @name restore
23907          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23908          * @description Applies the provided state to the grid
23909          *
23910          * @param {Grid} grid the grid whose state we'd like to restore
23911          * @param {scope} $scope a scope that we can broadcast on
23912          * @param {object} state the state we'd like to restore
23913          */
23914         restore: function( grid, $scope, state ){
23915           if ( state.columns ) {
23916             service.restoreColumns( grid, state.columns );
23917           }
23918
23919           if ( state.scrollFocus ){
23920             service.restoreScrollFocus( grid, $scope, state.scrollFocus );
23921           }
23922
23923           if ( state.selection ){
23924             service.restoreSelection( grid, state.selection );
23925           }
23926
23927           if ( state.grouping ){
23928             service.restoreGrouping( grid, state.grouping );
23929           }
23930
23931           if ( state.treeView ){
23932             service.restoreTreeView( grid, state.treeView );
23933           }
23934
23935           if ( state.pagination ){
23936             service.restorePagination( grid, state.pagination );
23937           }
23938
23939           grid.refresh();
23940         },
23941
23942
23943         /**
23944          * @ngdoc function
23945          * @name saveColumns
23946          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
23947          * @description Saves the column setup, including sort, filters, ordering,
23948          * pinning and column widths.
23949          *
23950          * Works through the current columns, storing them in order.  Stores the
23951          * column name, then the visible flag, width, sort and filters for each column.
23952          *
23953          * @param {Grid} grid the grid whose state we'd like to save
23954          * @returns {array} the columns state ready to be saved
23955          */
23956         saveColumns: function( grid ) {
23957           var columns = [];
23958           grid.getOnlyDataColumns().forEach( function( column ) {
23959             var savedColumn = {};
23960             savedColumn.name = column.name;
23961
23962             if ( grid.options.saveVisible ){
23963               savedColumn.visible = column.visible;
23964             }
23965
23966             if ( grid.options.saveWidths ){
23967               savedColumn.width = column.width;
23968             }
23969
23970             // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
23971             if ( grid.options.saveSort ){
23972               savedColumn.sort = angular.copy( column.sort );
23973             }
23974
23975             if ( grid.options.saveFilter ){
23976               savedColumn.filters = [];
23977               column.filters.forEach( function( filter ){
23978                 var copiedFilter = {};
23979                 angular.forEach( filter, function( value, key) {
23980                   if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
23981                     copiedFilter[key] = value;
23982                   }
23983                 });
23984                 savedColumn.filters.push(copiedFilter);
23985               });
23986             }
23987
23988             if ( !!grid.api.pinning && grid.options.savePinning ){
23989               savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
23990             }
23991
23992             columns.push( savedColumn );
23993           });
23994
23995           return columns;
23996         },
23997
23998
23999         /**
24000          * @ngdoc function
24001          * @name saveScrollFocus
24002          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24003          * @description Saves the currently scroll or focus.
24004          *
24005          * If cellNav isn't present then does nothing - we can't return
24006          * to the scroll position without cellNav anyway.
24007          *
24008          * If the cellNav module is present, and saveFocus is true, then
24009          * it saves the currently focused cell.  If rowIdentity is present
24010          * then saves using rowIdentity, otherwise saves visibleRowNum.
24011          *
24012          * If the cellNav module is not present, and saveScroll is true, then
24013          * it approximates the current scroll row and column, and saves that.
24014          *
24015          * @param {Grid} grid the grid whose state we'd like to save
24016          * @returns {object} the selection state ready to be saved
24017          */
24018         saveScrollFocus: function( grid ){
24019           if ( !grid.api.cellNav ){
24020             return {};
24021           }
24022
24023           var scrollFocus = {};
24024           if ( grid.options.saveFocus ){
24025             scrollFocus.focus = true;
24026             var rowCol = grid.api.cellNav.getFocusedCell();
24027             if ( rowCol !== null ) {
24028               if ( rowCol.col !== null ){
24029                 scrollFocus.colName = rowCol.col.colDef.name;
24030               }
24031               if ( rowCol.row !== null ){
24032                 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
24033               }
24034             }
24035           }
24036
24037           if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
24038             scrollFocus.focus = false;
24039             if ( grid.renderContainers.body.prevRowScrollIndex ){
24040               scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
24041             }
24042
24043             if ( grid.renderContainers.body.prevColScrollIndex ){
24044               scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
24045             }
24046           }
24047
24048           return scrollFocus;
24049         },
24050
24051
24052         /**
24053          * @ngdoc function
24054          * @name saveSelection
24055          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24056          * @description Saves the currently selected rows, if the selection feature is enabled
24057          * @param {Grid} grid the grid whose state we'd like to save
24058          * @returns {array} the selection state ready to be saved
24059          */
24060         saveSelection: function( grid ){
24061           if ( !grid.api.selection || !grid.options.saveSelection ){
24062             return [];
24063           }
24064
24065           var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
24066             return service.getRowVal( grid, gridRow );
24067           });
24068
24069           return selection;
24070         },
24071
24072
24073         /**
24074          * @ngdoc function
24075          * @name saveGrouping
24076          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24077          * @description Saves the grouping state, if the grouping feature is enabled
24078          * @param {Grid} grid the grid whose state we'd like to save
24079          * @returns {object} the grouping state ready to be saved
24080          */
24081         saveGrouping: function( grid ){
24082           if ( !grid.api.grouping || !grid.options.saveGrouping ){
24083             return {};
24084           }
24085
24086           return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
24087         },
24088
24089
24090         /**
24091          * @ngdoc function
24092          * @name savePagination
24093          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24094          * @description Saves the pagination state, if the pagination feature is enabled
24095          * @param {Grid} grid the grid whose state we'd like to save
24096          * @returns {object} the pagination state ready to be saved
24097          */
24098         savePagination: function( grid ) {
24099           if ( !grid.api.pagination || !grid.options.paginationPageSize ){
24100             return {};
24101           }
24102
24103           return {
24104             paginationCurrentPage: grid.options.paginationCurrentPage,
24105             paginationPageSize: grid.options.paginationPageSize
24106           };
24107         },
24108
24109
24110         /**
24111          * @ngdoc function
24112          * @name saveTreeView
24113          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24114          * @description Saves the tree view state, if the tree feature is enabled
24115          * @param {Grid} grid the grid whose state we'd like to save
24116          * @returns {object} the tree view state ready to be saved
24117          */
24118         saveTreeView: function( grid ){
24119           if ( !grid.api.treeView || !grid.options.saveTreeView ){
24120             return {};
24121           }
24122
24123           return grid.api.treeView.getTreeView();
24124         },
24125
24126
24127         /**
24128          * @ngdoc function
24129          * @name getRowVal
24130          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24131          * @description Helper function that gets either the rowNum or
24132          * the saveRowIdentity, given a gridRow
24133          * @param {Grid} grid the grid the row is in
24134          * @param {GridRow} gridRow the row we want the rowNum for
24135          * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
24136          *
24137          */
24138         getRowVal: function( grid, gridRow ){
24139           if ( !gridRow ) {
24140             return null;
24141           }
24142
24143           var rowVal = {};
24144           if ( grid.options.saveRowIdentity ){
24145             rowVal.identity = true;
24146             rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
24147           } else {
24148             rowVal.identity = false;
24149             rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
24150           }
24151           return rowVal;
24152         },
24153
24154
24155         /**
24156          * @ngdoc function
24157          * @name restoreColumns
24158          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24159          * @description Restores the columns, including order, visible, width,
24160          * pinning, sort and filters.
24161          *
24162          * @param {Grid} grid the grid whose state we'd like to restore
24163          * @param {object} columnsState the list of columns we had before, with their state
24164          */
24165         restoreColumns: function( grid, columnsState ){
24166           var isSortChanged = false;
24167
24168           columnsState.forEach( function( columnState, index ) {
24169             var currentCol = grid.getColumn( columnState.name );
24170
24171             if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
24172               if ( grid.options.saveVisible &&
24173                    ( currentCol.visible !== columnState.visible ||
24174                      currentCol.colDef.visible !== columnState.visible ) ){
24175                 currentCol.visible = columnState.visible;
24176                 currentCol.colDef.visible = columnState.visible;
24177                 grid.api.core.raise.columnVisibilityChanged(currentCol);
24178               }
24179
24180               if ( grid.options.saveWidths && currentCol.width !== columnState.width){
24181                 currentCol.width = columnState.width;
24182                 currentCol.hasCustomWidth = true;
24183               }
24184
24185               if ( grid.options.saveSort &&
24186                    !angular.equals(currentCol.sort, columnState.sort) &&
24187                    !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
24188                 currentCol.sort = angular.copy( columnState.sort );
24189                 isSortChanged = true;
24190               }
24191
24192               if ( grid.options.saveFilter &&
24193                    !angular.equals(currentCol.filters, columnState.filters ) ){
24194                 columnState.filters.forEach( function( filter, index ){
24195                   angular.extend( currentCol.filters[index], filter );
24196                   if ( typeof(filter.term) === 'undefined' || filter.term === null ){
24197                     delete currentCol.filters[index].term;
24198                   }
24199                 });
24200                 grid.api.core.raise.filterChanged();
24201               }
24202
24203               if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
24204                 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
24205               }
24206
24207               var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
24208               if (currentIndex !== -1) {
24209                 if (grid.options.saveOrder && currentIndex !== index) {
24210                   var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
24211                   grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
24212                 }
24213               }
24214             }
24215           });
24216
24217           if ( isSortChanged ) {
24218             grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
24219           }
24220         },
24221
24222
24223         /**
24224          * @ngdoc function
24225          * @name restoreScrollFocus
24226          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24227          * @description Scrolls to the position that was saved.  If focus is true, then
24228          * sets focus to the specified row/col.  If focus is false, then scrolls to the
24229          * specified row/col.
24230          *
24231          * @param {Grid} grid the grid whose state we'd like to restore
24232          * @param {scope} $scope a scope that we can broadcast on
24233          * @param {object} scrollFocusState the scroll/focus state ready to be restored
24234          */
24235         restoreScrollFocus: function( grid, $scope, scrollFocusState ){
24236           if ( !grid.api.cellNav ){
24237             return;
24238           }
24239
24240           var colDef, row;
24241           if ( scrollFocusState.colName ){
24242             var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
24243             if ( colDefs.length > 0 ){
24244               colDef = colDefs[0];
24245             }
24246           }
24247
24248           if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
24249             if ( scrollFocusState.rowVal.identity ){
24250               row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
24251             } else {
24252               row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
24253             }
24254           }
24255
24256           var entity = row && row.entity ? row.entity : null ;
24257
24258           if ( colDef || entity ) {
24259             if (scrollFocusState.focus ){
24260               grid.api.cellNav.scrollToFocus( entity, colDef );
24261             } else {
24262               grid.scrollTo( entity, colDef );
24263             }
24264           }
24265         },
24266
24267
24268         /**
24269          * @ngdoc function
24270          * @name restoreSelection
24271          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24272          * @description Selects the rows that are provided in the selection
24273          * state.  If you are using `saveRowIdentity` and more than one row matches the identity
24274          * function then only the first is selected.
24275          * @param {Grid} grid the grid whose state we'd like to restore
24276          * @param {object} selectionState the selection state ready to be restored
24277          */
24278         restoreSelection: function( grid, selectionState ){
24279           if ( !grid.api.selection ){
24280             return;
24281           }
24282
24283           grid.api.selection.clearSelectedRows();
24284
24285           selectionState.forEach(  function( rowVal ) {
24286             if ( rowVal.identity ){
24287               var foundRow = service.findRowByIdentity( grid, rowVal );
24288
24289               if ( foundRow ){
24290                 grid.api.selection.selectRow( foundRow.entity );
24291               }
24292
24293             } else {
24294               grid.api.selection.selectRowByVisibleIndex( rowVal.row );
24295             }
24296           });
24297         },
24298
24299
24300         /**
24301          * @ngdoc function
24302          * @name restoreGrouping
24303          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24304          * @description Restores the grouping configuration, if the grouping feature
24305          * is enabled.
24306          * @param {Grid} grid the grid whose state we'd like to restore
24307          * @param {object} groupingState the grouping state ready to be restored
24308          */
24309         restoreGrouping: function( grid, groupingState ){
24310           if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
24311             return;
24312           }
24313
24314           grid.api.grouping.setGrouping( groupingState );
24315         },
24316
24317         /**
24318          * @ngdoc function
24319          * @name restoreTreeView
24320          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24321          * @description Restores the tree view configuration, if the tree view feature
24322          * is enabled.
24323          * @param {Grid} grid the grid whose state we'd like to restore
24324          * @param {object} treeViewState the tree view state ready to be restored
24325          */
24326         restoreTreeView: function( grid, treeViewState ){
24327           if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
24328             return;
24329           }
24330
24331           grid.api.treeView.setTreeView( treeViewState );
24332         },
24333
24334         /**
24335          * @ngdoc function
24336          * @name restorePagination
24337          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24338          * @description Restores the pagination information, if pagination is enabled.
24339          * @param {Grid} grid the grid whose state we'd like to restore
24340          * @param {object} pagination the pagination object to be restored
24341          * @param {number} pagination.paginationCurrentPage the page number to restore
24342          * @param {number} pagination.paginationPageSize the number of items displayed per page
24343          */
24344         restorePagination: function( grid, pagination ){
24345           if ( !grid.api.pagination || !grid.options.paginationPageSize ){
24346             return;
24347           }
24348
24349           grid.options.paginationCurrentPage = pagination.paginationCurrentPage;
24350           grid.options.paginationPageSize = pagination.paginationPageSize;
24351         },
24352
24353         /**
24354          * @ngdoc function
24355          * @name findRowByIdentity
24356          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24357          * @description Finds a row given it's identity value, returns the first found row
24358          * if any are found, otherwise returns null if no rows are found.
24359          * @param {Grid} grid the grid whose state we'd like to restore
24360          * @param {object} rowVal the row we'd like to find
24361          * @returns {gridRow} the found row, or null if none found
24362          */
24363         findRowByIdentity: function( grid, rowVal ){
24364           if ( !grid.options.saveRowIdentity ){
24365             return null;
24366           }
24367
24368           var filteredRows = grid.rows.filter( function( gridRow ) {
24369             if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
24370               return true;
24371             } else {
24372               return false;
24373             }
24374           });
24375
24376           if ( filteredRows.length > 0 ){
24377             return filteredRows[0];
24378           } else {
24379             return null;
24380           }
24381         }
24382       };
24383
24384       return service;
24385
24386     }
24387   ]);
24388
24389   /**
24390    *  @ngdoc directive
24391    *  @name ui.grid.saveState.directive:uiGridSaveState
24392    *  @element div
24393    *  @restrict A
24394    *
24395    *  @description Adds saveState features to grid
24396    *
24397    *  @example
24398    <example module="app">
24399    <file name="app.js">
24400    var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
24401
24402    app.controller('MainCtrl', ['$scope', function ($scope) {
24403       $scope.data = [
24404         { name: 'Bob', title: 'CEO' },
24405         { name: 'Frank', title: 'Lowly Developer' }
24406       ];
24407
24408       $scope.gridOptions = {
24409         columnDefs: [
24410           {name: 'name'},
24411           {name: 'title', enableCellEdit: true}
24412         ],
24413         data: $scope.data
24414       };
24415     }]);
24416    </file>
24417    <file name="index.html">
24418    <div ng-controller="MainCtrl">
24419    <div ui-grid="gridOptions" ui-grid-save-state></div>
24420    </div>
24421    </file>
24422    </example>
24423    */
24424   module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
24425     function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
24426       return {
24427         replace: true,
24428         priority: 0,
24429         require: '^uiGrid',
24430         scope: false,
24431         link: function ($scope, $elm, $attrs, uiGridCtrl) {
24432           uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
24433         }
24434       };
24435     }
24436   ]);
24437 })();
24438
24439 (function () {
24440   'use strict';
24441
24442   /**
24443    * @ngdoc overview
24444    * @name ui.grid.selection
24445    * @description
24446    *
24447    * # ui.grid.selection
24448    * This module provides row selection
24449    *
24450    * <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>
24451    *
24452    * <div doc-module-components="ui.grid.selection"></div>
24453    */
24454
24455   var module = angular.module('ui.grid.selection', ['ui.grid']);
24456
24457   /**
24458    *  @ngdoc object
24459    *  @name ui.grid.selection.constant:uiGridSelectionConstants
24460    *
24461    *  @description constants available in selection module
24462    */
24463   module.constant('uiGridSelectionConstants', {
24464     featureName: "selection",
24465     selectionRowHeaderColName: 'selectionRowHeaderCol'
24466   });
24467
24468   //add methods to GridRow
24469   angular.module('ui.grid').config(['$provide', function($provide) {
24470     $provide.decorator('GridRow', ['$delegate', function($delegate) {
24471
24472       /**
24473        *  @ngdoc object
24474        *  @name ui.grid.selection.api:GridRow
24475        *
24476        *  @description GridRow prototype functions added for selection
24477        */
24478
24479       /**
24480        *  @ngdoc object
24481        *  @name enableSelection
24482        *  @propertyOf  ui.grid.selection.api:GridRow
24483        *  @description Enable row selection for this row, only settable by internal code.
24484        *
24485        *  The grouping feature, for example, might set group header rows to not be selectable.
24486        *  <br/>Defaults to true
24487        */
24488
24489       /**
24490        *  @ngdoc object
24491        *  @name isSelected
24492        *  @propertyOf  ui.grid.selection.api:GridRow
24493        *  @description Selected state of row.  Should be readonly. Make any changes to selected state using setSelected().
24494        *  <br/>Defaults to false
24495        */
24496
24497
24498         /**
24499          * @ngdoc function
24500          * @name setSelected
24501          * @methodOf ui.grid.selection.api:GridRow
24502          * @description Sets the isSelected property and updates the selectedCount
24503          * Changes to isSelected state should only be made via this function
24504          * @param {bool} selected value to set
24505          */
24506         $delegate.prototype.setSelected = function(selected) {
24507           this.isSelected = selected;
24508           if (selected) {
24509             this.grid.selection.selectedCount++;
24510           }
24511           else {
24512             this.grid.selection.selectedCount--;
24513           }
24514         };
24515
24516       return $delegate;
24517     }]);
24518   }]);
24519
24520   /**
24521    *  @ngdoc service
24522    *  @name ui.grid.selection.service:uiGridSelectionService
24523    *
24524    *  @description Services for selection features
24525    */
24526   module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
24527     function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
24528
24529       var service = {
24530
24531         initializeGrid: function (grid) {
24532
24533           //add feature namespace and any properties to grid for needed
24534           /**
24535            *  @ngdoc object
24536            *  @name ui.grid.selection.grid:selection
24537            *
24538            *  @description Grid properties and functions added for selection
24539            */
24540           grid.selection = {};
24541           grid.selection.lastSelectedRow = null;
24542           grid.selection.selectAll = false;
24543
24544
24545           /**
24546            *  @ngdoc object
24547            *  @name selectedCount
24548            *  @propertyOf  ui.grid.selection.grid:selection
24549            *  @description Current count of selected rows
24550            *  @example
24551            *  var count = grid.selection.selectedCount
24552            */
24553           grid.selection.selectedCount = 0;
24554
24555           service.defaultGridOptions(grid.options);
24556
24557           /**
24558            *  @ngdoc object
24559            *  @name ui.grid.selection.api:PublicApi
24560            *
24561            *  @description Public Api for selection feature
24562            */
24563           var publicApi = {
24564             events: {
24565               selection: {
24566                 /**
24567                  * @ngdoc event
24568                  * @name rowSelectionChanged
24569                  * @eventOf  ui.grid.selection.api:PublicApi
24570                  * @description  is raised after the row.isSelected state is changed
24571                  * @param {GridRow} row the row that was selected/deselected
24572                  * @param {Event} event object if raised from an event
24573                  */
24574                 rowSelectionChanged: function (scope, row, evt) {
24575                 },
24576                 /**
24577                  * @ngdoc event
24578                  * @name rowSelectionChangedBatch
24579                  * @eventOf  ui.grid.selection.api:PublicApi
24580                  * @description  is raised after the row.isSelected state is changed
24581                  * in bulk, if the `enableSelectionBatchEvent` option is set to true
24582                  * (which it is by default).  This allows more efficient processing
24583                  * of bulk events.
24584                  * @param {array} rows the rows that were selected/deselected
24585                  * @param {Event} event object if raised from an event
24586                  */
24587                 rowSelectionChangedBatch: function (scope, rows, evt) {
24588                 }
24589               }
24590             },
24591             methods: {
24592               selection: {
24593                 /**
24594                  * @ngdoc function
24595                  * @name toggleRowSelection
24596                  * @methodOf  ui.grid.selection.api:PublicApi
24597                  * @description Toggles data row as selected or unselected
24598                  * @param {object} rowEntity gridOptions.data[] array instance
24599                  * @param {Event} event object if raised from an event
24600                  */
24601                 toggleRowSelection: function (rowEntity, evt) {
24602                   var row = grid.getRow(rowEntity);
24603                   if (row !== null) {
24604                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24605                   }
24606                 },
24607                 /**
24608                  * @ngdoc function
24609                  * @name selectRow
24610                  * @methodOf  ui.grid.selection.api:PublicApi
24611                  * @description Select the data row
24612                  * @param {object} rowEntity gridOptions.data[] array instance
24613                  * @param {Event} event object if raised from an event
24614                  */
24615                 selectRow: function (rowEntity, evt) {
24616                   var row = grid.getRow(rowEntity);
24617                   if (row !== null && !row.isSelected) {
24618                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24619                   }
24620                 },
24621                 /**
24622                  * @ngdoc function
24623                  * @name selectRowByVisibleIndex
24624                  * @methodOf  ui.grid.selection.api:PublicApi
24625                  * @description Select the specified row by visible index (i.e. if you
24626                  * specify row 0 you'll get the first visible row selected).  In this context
24627                  * visible means of those rows that are theoretically visible (i.e. not filtered),
24628                  * rather than rows currently rendered on the screen.
24629                  * @param {number} index index within the rowsVisible array
24630                  * @param {Event} event object if raised from an event
24631                  */
24632                 selectRowByVisibleIndex: function ( rowNum, evt ) {
24633                   var row = grid.renderContainers.body.visibleRowCache[rowNum];
24634                   if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
24635                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24636                   }
24637                 },
24638                 /**
24639                  * @ngdoc function
24640                  * @name unSelectRow
24641                  * @methodOf  ui.grid.selection.api:PublicApi
24642                  * @description UnSelect the data row
24643                  * @param {object} rowEntity gridOptions.data[] array instance
24644                  * @param {Event} event object if raised from an event
24645                  */
24646                 unSelectRow: function (rowEntity, evt) {
24647                   var row = grid.getRow(rowEntity);
24648                   if (row !== null && row.isSelected) {
24649                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
24650                   }
24651                 },
24652                 /**
24653                  * @ngdoc function
24654                  * @name selectAllRows
24655                  * @methodOf  ui.grid.selection.api:PublicApi
24656                  * @description Selects all rows.  Does nothing if multiSelect = false
24657                  * @param {Event} event object if raised from an event
24658                  */
24659                 selectAllRows: function (evt) {
24660                   if (grid.options.multiSelect === false) {
24661                     return;
24662                   }
24663
24664                   var changedRows = [];
24665                   grid.rows.forEach(function (row) {
24666                     if ( !row.isSelected && row.enableSelection !== false ){
24667                       row.setSelected(true);
24668                       service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24669                     }
24670                   });
24671                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24672                   grid.selection.selectAll = true;
24673                 },
24674                 /**
24675                  * @ngdoc function
24676                  * @name selectAllVisibleRows
24677                  * @methodOf  ui.grid.selection.api:PublicApi
24678                  * @description Selects all visible rows.  Does nothing if multiSelect = false
24679                  * @param {Event} event object if raised from an event
24680                  */
24681                 selectAllVisibleRows: function (evt) {
24682                   if (grid.options.multiSelect === false) {
24683                     return;
24684                   }
24685
24686                   var changedRows = [];
24687                   grid.rows.forEach(function (row) {
24688                     if (row.visible) {
24689                       if (!row.isSelected && row.enableSelection !== false){
24690                         row.setSelected(true);
24691                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24692                       }
24693                     } else {
24694                       if (row.isSelected){
24695                         row.setSelected(false);
24696                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
24697                       }
24698                     }
24699                   });
24700                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24701                   grid.selection.selectAll = true;
24702                 },
24703                 /**
24704                  * @ngdoc function
24705                  * @name clearSelectedRows
24706                  * @methodOf  ui.grid.selection.api:PublicApi
24707                  * @description Unselects all rows
24708                  * @param {Event} event object if raised from an event
24709                  */
24710                 clearSelectedRows: function (evt) {
24711                   service.clearSelectedRows(grid, evt);
24712                 },
24713                 /**
24714                  * @ngdoc function
24715                  * @name getSelectedRows
24716                  * @methodOf  ui.grid.selection.api:PublicApi
24717                  * @description returns all selectedRow's entity references
24718                  */
24719                 getSelectedRows: function () {
24720                   return service.getSelectedRows(grid).map(function (gridRow) {
24721                     return gridRow.entity;
24722                   });
24723                 },
24724                 /**
24725                  * @ngdoc function
24726                  * @name getSelectedGridRows
24727                  * @methodOf  ui.grid.selection.api:PublicApi
24728                  * @description returns all selectedRow's as gridRows
24729                  */
24730                 getSelectedGridRows: function () {
24731                   return service.getSelectedRows(grid);
24732                 },
24733                 /**
24734                  * @ngdoc function
24735                  * @name getSelectedCount
24736                  * @methodOf  ui.grid.selection.api:PublicApi
24737                  * @description returns the number of rows selected
24738                  */
24739                 getSelectedCount: function () {
24740                   return grid.selection.selectedCount;
24741                 },
24742                 /**
24743                  * @ngdoc function
24744                  * @name setMultiSelect
24745                  * @methodOf  ui.grid.selection.api:PublicApi
24746                  * @description Sets the current gridOption.multiSelect to true or false
24747                  * @param {bool} multiSelect true to allow multiple rows
24748                  */
24749                 setMultiSelect: function (multiSelect) {
24750                   grid.options.multiSelect = multiSelect;
24751                 },
24752                 /**
24753                  * @ngdoc function
24754                  * @name setModifierKeysToMultiSelect
24755                  * @methodOf  ui.grid.selection.api:PublicApi
24756                  * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
24757                  * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
24758                  */
24759                 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
24760                   grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
24761                 },
24762                 /**
24763                  * @ngdoc function
24764                  * @name getSelectAllState
24765                  * @methodOf  ui.grid.selection.api:PublicApi
24766                  * @description Returns whether or not the selectAll checkbox is currently ticked.  The
24767                  * grid doesn't automatically select rows when you add extra data - so when you add data
24768                  * you need to explicitly check whether the selectAll is set, and then call setVisible rows
24769                  * if it is
24770                  */
24771                 getSelectAllState: function () {
24772                   return grid.selection.selectAll;
24773                 }
24774
24775               }
24776             }
24777           };
24778
24779           grid.api.registerEventsFromObject(publicApi.events);
24780
24781           grid.api.registerMethodsFromObject(publicApi.methods);
24782
24783         },
24784
24785         defaultGridOptions: function (gridOptions) {
24786           //default option to true unless it was explicitly set to false
24787           /**
24788            *  @ngdoc object
24789            *  @name ui.grid.selection.api:GridOptions
24790            *
24791            *  @description GridOptions for selection feature, these are available to be
24792            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
24793            */
24794
24795           /**
24796            *  @ngdoc object
24797            *  @name enableRowSelection
24798            *  @propertyOf  ui.grid.selection.api:GridOptions
24799            *  @description Enable row selection for entire grid.
24800            *  <br/>Defaults to true
24801            */
24802           gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
24803           /**
24804            *  @ngdoc object
24805            *  @name multiSelect
24806            *  @propertyOf  ui.grid.selection.api:GridOptions
24807            *  @description Enable multiple row selection for entire grid
24808            *  <br/>Defaults to true
24809            */
24810           gridOptions.multiSelect = gridOptions.multiSelect !== false;
24811           /**
24812            *  @ngdoc object
24813            *  @name noUnselect
24814            *  @propertyOf  ui.grid.selection.api:GridOptions
24815            *  @description Prevent a row from being unselected.  Works in conjunction
24816            *  with `multiselect = false` and `gridApi.selection.selectRow()` to allow
24817            *  you to create a single selection only grid - a row is always selected, you
24818            *  can only select different rows, you can't unselect the row.
24819            *  <br/>Defaults to false
24820            */
24821           gridOptions.noUnselect = gridOptions.noUnselect === true;
24822           /**
24823            *  @ngdoc object
24824            *  @name modifierKeysToMultiSelect
24825            *  @propertyOf  ui.grid.selection.api:GridOptions
24826            *  @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
24827            *  <br/>Defaults to false
24828            */
24829           gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
24830           /**
24831            *  @ngdoc object
24832            *  @name enableRowHeaderSelection
24833            *  @propertyOf  ui.grid.selection.api:GridOptions
24834            *  @description Enable a row header to be used for selection
24835            *  <br/>Defaults to true
24836            */
24837           gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
24838           /**
24839            *  @ngdoc object
24840            *  @name enableFullRowSelection
24841            *  @propertyOf  ui.grid.selection.api:GridOptions
24842            *  @description Enable selection by clicking anywhere on the row.  Defaults to
24843            *  false if `enableRowHeaderSelection` is true, otherwise defaults to false.
24844            */
24845           if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
24846             gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
24847           }
24848           /**
24849            *  @ngdoc object
24850            *  @name enableSelectAll
24851            *  @propertyOf  ui.grid.selection.api:GridOptions
24852            *  @description Enable the select all checkbox at the top of the selectionRowHeader
24853            *  <br/>Defaults to true
24854            */
24855           gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
24856           /**
24857            *  @ngdoc object
24858            *  @name enableSelectionBatchEvent
24859            *  @propertyOf  ui.grid.selection.api:GridOptions
24860            *  @description If selected rows are changed in bulk, either via the API or
24861            *  via the selectAll checkbox, then a separate event is fired.  Setting this
24862            *  option to false will cause the rowSelectionChanged event to be called multiple times
24863            *  instead
24864            *  <br/>Defaults to true
24865            */
24866           gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
24867           /**
24868            *  @ngdoc object
24869            *  @name selectionRowHeaderWidth
24870            *  @propertyOf  ui.grid.selection.api:GridOptions
24871            *  @description can be used to set a custom width for the row header selection column
24872            *  <br/>Defaults to 30px
24873            */
24874           gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
24875
24876           /**
24877            *  @ngdoc object
24878            *  @name enableFooterTotalSelected
24879            *  @propertyOf  ui.grid.selection.api:GridOptions
24880            *  @description Shows the total number of selected items in footer if true.
24881            *  <br/>Defaults to true.
24882            *  <br/>GridOptions.showGridFooter must also be set to true.
24883            */
24884           gridOptions.enableFooterTotalSelected = gridOptions.enableFooterTotalSelected !== false;
24885
24886           /**
24887            *  @ngdoc object
24888            *  @name isRowSelectable
24889            *  @propertyOf  ui.grid.selection.api:GridOptions
24890            *  @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
24891            */
24892
24893           gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
24894         },
24895
24896         /**
24897          * @ngdoc function
24898          * @name toggleRowSelection
24899          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24900          * @description Toggles row as selected or unselected
24901          * @param {Grid} grid grid object
24902          * @param {GridRow} row row to select or deselect
24903          * @param {Event} event object if resulting from event
24904          * @param {bool} multiSelect if false, only one row at time can be selected
24905          * @param {bool} noUnselect if true then rows cannot be unselected
24906          */
24907         toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
24908           var selected = row.isSelected;
24909
24910           if ( row.enableSelection === false && !selected ){
24911             return;
24912           }
24913
24914           var selectedRows;
24915           if (!multiSelect && !selected) {
24916             service.clearSelectedRows(grid, evt);
24917           } else if (!multiSelect && selected) {
24918             selectedRows = service.getSelectedRows(grid);
24919             if (selectedRows.length > 1) {
24920               selected = false; // Enable reselect of the row
24921               service.clearSelectedRows(grid, evt);
24922             }
24923           }
24924
24925           if (selected && noUnselect){
24926             // don't deselect the row
24927           } else {
24928             row.setSelected(!selected);
24929             if (row.isSelected === true) {
24930               grid.selection.lastSelectedRow = row;
24931             }
24932
24933             selectedRows = service.getSelectedRows(grid);
24934             grid.selection.selectAll = grid.rows.length === selectedRows.length;
24935
24936             grid.api.selection.raise.rowSelectionChanged(row, evt);
24937           }
24938         },
24939         /**
24940          * @ngdoc function
24941          * @name shiftSelect
24942          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24943          * @description selects a group of rows from the last selected row using the shift key
24944          * @param {Grid} grid grid object
24945          * @param {GridRow} clicked row
24946          * @param {Event} event object if raised from an event
24947          * @param {bool} multiSelect if false, does nothing this is for multiSelect only
24948          */
24949         shiftSelect: function (grid, row, evt, multiSelect) {
24950           if (!multiSelect) {
24951             return;
24952           }
24953           var selectedRows = service.getSelectedRows(grid);
24954           var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
24955           var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
24956           //reverse select direction
24957           if (fromRow > toRow) {
24958             var tmp = fromRow;
24959             fromRow = toRow;
24960             toRow = tmp;
24961           }
24962
24963           var changedRows = [];
24964           for (var i = fromRow; i <= toRow; i++) {
24965             var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
24966             if (rowToSelect) {
24967               if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
24968                 rowToSelect.setSelected(true);
24969                 grid.selection.lastSelectedRow = rowToSelect;
24970                 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
24971               }
24972             }
24973           }
24974           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
24975         },
24976         /**
24977          * @ngdoc function
24978          * @name getSelectedRows
24979          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24980          * @description Returns all the selected rows
24981          * @param {Grid} grid grid object
24982          */
24983         getSelectedRows: function (grid) {
24984           return grid.rows.filter(function (row) {
24985             return row.isSelected;
24986           });
24987         },
24988
24989         /**
24990          * @ngdoc function
24991          * @name clearSelectedRows
24992          * @methodOf  ui.grid.selection.service:uiGridSelectionService
24993          * @description Clears all selected rows
24994          * @param {Grid} grid grid object
24995          * @param {Event} event object if raised from an event
24996          */
24997         clearSelectedRows: function (grid, evt) {
24998           var changedRows = [];
24999           service.getSelectedRows(grid).forEach(function (row) {
25000             if ( row.isSelected ){
25001               row.setSelected(false);
25002               service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25003             }
25004           });
25005           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25006           grid.selection.selectAll = false;
25007           grid.selection.selectedCount = 0;
25008         },
25009
25010         /**
25011          * @ngdoc function
25012          * @name decideRaiseSelectionEvent
25013          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25014          * @description Decides whether to raise a single event or a batch event
25015          * @param {Grid} grid grid object
25016          * @param {GridRow} row row that has changed
25017          * @param {array} changedRows an array to which we can append the changed
25018          * @param {Event} event object if raised from an event
25019          * row if we're doing batch events
25020          */
25021         decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
25022           if ( !grid.options.enableSelectionBatchEvent ){
25023             grid.api.selection.raise.rowSelectionChanged(row, evt);
25024           } else {
25025             changedRows.push(row);
25026           }
25027         },
25028
25029         /**
25030          * @ngdoc function
25031          * @name raiseSelectionEvent
25032          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25033          * @description Decides whether we need to raise a batch event, and
25034          * raises it if we do.
25035          * @param {Grid} grid grid object
25036          * @param {array} changedRows an array of changed rows, only populated
25037          * @param {Event} event object if raised from an event
25038          * if we're doing batch events
25039          */
25040         decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
25041           if ( changedRows.length > 0 ){
25042             grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
25043           }
25044         }
25045       };
25046
25047       return service;
25048
25049     }]);
25050
25051   /**
25052    *  @ngdoc directive
25053    *  @name ui.grid.selection.directive:uiGridSelection
25054    *  @element div
25055    *  @restrict A
25056    *
25057    *  @description Adds selection features to grid
25058    *
25059    *  @example
25060    <example module="app">
25061    <file name="app.js">
25062    var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
25063
25064    app.controller('MainCtrl', ['$scope', function ($scope) {
25065       $scope.data = [
25066         { name: 'Bob', title: 'CEO' },
25067             { name: 'Frank', title: 'Lowly Developer' }
25068       ];
25069
25070       $scope.columnDefs = [
25071         {name: 'name', enableCellEdit: true},
25072         {name: 'title', enableCellEdit: true}
25073       ];
25074     }]);
25075    </file>
25076    <file name="index.html">
25077    <div ng-controller="MainCtrl">
25078    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
25079    </div>
25080    </file>
25081    </example>
25082    */
25083   module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
25084     function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
25085       return {
25086         replace: true,
25087         priority: 0,
25088         require: '^uiGrid',
25089         scope: false,
25090         compile: function () {
25091           return {
25092             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
25093               uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
25094               if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
25095                 var selectionRowHeaderDef = {
25096                   name: uiGridSelectionConstants.selectionRowHeaderColName,
25097                   displayName: '',
25098                   width:  uiGridCtrl.grid.options.selectionRowHeaderWidth,
25099                   minWidth: 10,
25100                   cellTemplate: 'ui-grid/selectionRowHeader',
25101                   headerCellTemplate: 'ui-grid/selectionHeaderCell',
25102                   enableColumnResizing: false,
25103                   enableColumnMenu: false,
25104                   exporterSuppressExport: true,
25105                   allowCellFocus: true
25106                 };
25107
25108                 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
25109               }
25110
25111               var processorSet = false;
25112
25113               var processSelectableRows = function( rows ){
25114                 rows.forEach(function(row){
25115                   row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
25116                 });
25117                 return rows;
25118               };
25119
25120               var updateOptions = function(){
25121                 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
25122                   uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
25123                   processorSet = true;
25124                 }
25125               };
25126
25127               updateOptions();
25128
25129               var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
25130
25131               $scope.$on( '$destroy', dataChangeDereg);
25132             },
25133             post: function ($scope, $elm, $attrs, uiGridCtrl) {
25134
25135             }
25136           };
25137         }
25138       };
25139     }]);
25140
25141   module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService', 'gridUtil',
25142     function ($templateCache, uiGridSelectionService, gridUtil) {
25143       return {
25144         replace: true,
25145         restrict: 'E',
25146         template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
25147         scope: true,
25148         require: '^uiGrid',
25149         link: function($scope, $elm, $attrs, uiGridCtrl) {
25150           var self = uiGridCtrl.grid;
25151           $scope.selectButtonClick = selectButtonClick;
25152
25153           // On IE, prevent mousedowns on the select button from starting a selection.
25154           //   If this is not done and you shift+click on another row, the browser will select a big chunk of text
25155           if (gridUtil.detectBrowser() === 'ie') {
25156             $elm.on('mousedown', selectButtonMouseDown);
25157           }
25158
25159
25160           function selectButtonClick(row, evt) {
25161             evt.stopPropagation();
25162
25163             if (evt.shiftKey) {
25164               uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
25165             }
25166             else if (evt.ctrlKey || evt.metaKey) {
25167               uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
25168             }
25169             else {
25170               uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
25171             }
25172           }
25173
25174           function selectButtonMouseDown(evt) {
25175             if (evt.ctrlKey || evt.shiftKey) {
25176               evt.target.onselectstart = function () { return false; };
25177               window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
25178             }
25179           }
25180         }
25181       };
25182     }]);
25183
25184   module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
25185     function ($templateCache, uiGridSelectionService) {
25186       return {
25187         replace: true,
25188         restrict: 'E',
25189         template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
25190         scope: false,
25191         link: function($scope, $elm, $attrs, uiGridCtrl) {
25192           var self = $scope.col.grid;
25193
25194           $scope.headerButtonClick = function(row, evt) {
25195             if ( self.selection.selectAll ){
25196               uiGridSelectionService.clearSelectedRows(self, evt);
25197               if ( self.options.noUnselect ){
25198                 self.api.selection.selectRowByVisibleIndex(0, evt);
25199               }
25200               self.selection.selectAll = false;
25201             } else {
25202               if ( self.options.multiSelect ){
25203                 self.api.selection.selectAllVisibleRows(evt);
25204                 self.selection.selectAll = true;
25205               }
25206             }
25207           };
25208         }
25209       };
25210     }]);
25211
25212   /**
25213    *  @ngdoc directive
25214    *  @name ui.grid.selection.directive:uiGridViewport
25215    *  @element div
25216    *
25217    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
25218    *  for the grid row
25219    */
25220   module.directive('uiGridViewport',
25221     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
25222       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
25223         return {
25224           priority: -200, // run after default  directive
25225           scope: false,
25226           compile: function ($elm, $attrs) {
25227             var rowRepeatDiv = angular.element($elm.children().children()[0]);
25228
25229             var existingNgClass = rowRepeatDiv.attr("ng-class");
25230             var newNgClass = '';
25231             if ( existingNgClass ) {
25232               newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
25233             } else {
25234               newNgClass = "{'ui-grid-row-selected': row.isSelected}";
25235             }
25236             rowRepeatDiv.attr("ng-class", newNgClass);
25237
25238             return {
25239               pre: function ($scope, $elm, $attrs, controllers) {
25240
25241               },
25242               post: function ($scope, $elm, $attrs, controllers) {
25243               }
25244             };
25245           }
25246         };
25247       }]);
25248
25249   /**
25250    *  @ngdoc directive
25251    *  @name ui.grid.selection.directive:uiGridCell
25252    *  @element div
25253    *  @restrict A
25254    *
25255    *  @description Stacks on top of ui.grid.uiGridCell to provide selection feature
25256    */
25257   module.directive('uiGridCell',
25258     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
25259       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
25260         return {
25261           priority: -200, // run after default uiGridCell directive
25262           restrict: 'A',
25263           require: '?^uiGrid',
25264           scope: false,
25265           link: function ($scope, $elm, $attrs, uiGridCtrl) {
25266
25267             var touchStartTime = 0;
25268             var touchTimeout = 300;
25269
25270             // Bind to keydown events in the render container
25271             if (uiGridCtrl.grid.api.cellNav) {
25272
25273               uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
25274                 if (rowCol === null ||
25275                   rowCol.row !== $scope.row ||
25276                   rowCol.col !== $scope.col) {
25277                   return;
25278                 }
25279
25280                 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
25281                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25282                   $scope.$apply();
25283                 }
25284
25285               //  uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
25286               });
25287             }
25288
25289             //$elm.bind('keydown', function (evt) {
25290             //  if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
25291             //    uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25292             //    $scope.$apply();
25293             //  }
25294             //});
25295
25296             var selectCells = function(evt){
25297               // if we get a click, then stop listening for touchend
25298               $elm.off('touchend', touchEnd);
25299
25300               if (evt.shiftKey) {
25301                 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
25302               }
25303               else if (evt.ctrlKey || evt.metaKey) {
25304                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
25305               }
25306               else {
25307                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
25308               }
25309               $scope.$apply();
25310
25311               // don't re-enable the touchend handler for a little while - some devices generate both, and it will
25312               // take a little while to move your hand from the mouse to the screen if you have both modes of input
25313               $timeout(function() {
25314                 $elm.on('touchend', touchEnd);
25315               }, touchTimeout);
25316             };
25317
25318             var touchStart = function(evt){
25319               touchStartTime = (new Date()).getTime();
25320
25321               // if we get a touch event, then stop listening for click
25322               $elm.off('click', selectCells);
25323             };
25324
25325             var touchEnd = function(evt) {
25326               var touchEndTime = (new Date()).getTime();
25327               var touchTime = touchEndTime - touchStartTime;
25328
25329               if (touchTime < touchTimeout ) {
25330                 // short touch
25331                 selectCells(evt);
25332               }
25333
25334               // don't re-enable the click handler for a little while - some devices generate both, and it will
25335               // take a little while to move your hand from the screen to the mouse if you have both modes of input
25336               $timeout(function() {
25337                 $elm.on('click', selectCells);
25338               }, touchTimeout);
25339             };
25340
25341             function registerRowSelectionEvents() {
25342               if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
25343                 $elm.addClass('ui-grid-disable-selection');
25344                 $elm.on('touchstart', touchStart);
25345                 $elm.on('touchend', touchEnd);
25346                 $elm.on('click', selectCells);
25347
25348                 $scope.registered = true;
25349               }
25350             }
25351
25352             function deregisterRowSelectionEvents() {
25353               if ($scope.registered){
25354                 $elm.removeClass('ui-grid-disable-selection');
25355
25356                 $elm.off('touchstart', touchStart);
25357                 $elm.off('touchend', touchEnd);
25358                 $elm.off('click', selectCells);
25359
25360                 $scope.registered = false;
25361               }
25362             }
25363
25364             registerRowSelectionEvents();
25365             // register a dataChange callback so that we can change the selection configuration dynamically
25366             // if the user changes the options
25367             var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
25368               if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
25369                 !$scope.registered ){
25370                 registerRowSelectionEvents();
25371               } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
25372                 $scope.registered ){
25373                 deregisterRowSelectionEvents();
25374               }
25375             }, [uiGridConstants.dataChange.OPTIONS] );
25376
25377             $elm.on( '$destroy', dataChangeDereg);
25378           }
25379         };
25380       }]);
25381
25382   module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
25383     return {
25384       restrict: 'EA',
25385       replace: true,
25386       priority: -1000,
25387       require: '^uiGrid',
25388       scope: true,
25389       compile: function ($elm, $attrs) {
25390         return {
25391           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
25392
25393             if (!uiGridCtrl.grid.options.showGridFooter) {
25394               return;
25395             }
25396
25397
25398             gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
25399               .then(function (contents) {
25400                 var template = angular.element(contents);
25401
25402                 var newElm = $compile(template)($scope);
25403
25404                 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
25405               });
25406           },
25407
25408           post: function ($scope, $elm, $attrs, controllers) {
25409
25410           }
25411         };
25412       }
25413     };
25414   }]);
25415
25416 })();
25417
25418 (function () {
25419   'use strict';
25420
25421   /**
25422    * @ngdoc overview
25423    * @name ui.grid.treeBase
25424    * @description
25425    *
25426    * # ui.grid.treeBase
25427    *
25428    * <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>
25429    *
25430    * This module provides base tree handling functions that are shared by other features, notably grouping
25431    * and treeView.  It provides a tree view of the data, with nodes in that
25432    * tree and leaves.
25433    *
25434    * Design information:
25435    * -------------------
25436    *
25437    * The raw data that is provided must come with a $$treeLevel on any non-leaf node.  Grouping will create
25438    * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
25439    * TreeBase will run a rowsProcessor that:
25440    *  - builds `treeBase.tree` out of the provided rows
25441    *  - permits a recursive sort of the tree
25442    *  - maintains the expand/collapse state of each node
25443    *  - provides the expand/collapse all button and the expand/collapse buttons
25444    *  - maintains the count of children for each node
25445    *
25446    * Each row is updated with a link to the tree node that represents it.  Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
25447    * for information.
25448    *
25449    *  TreeBase adds information to the rows
25450    *  - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
25451    *  - treeNode: pointer to the node in the grid.treeBase.tree that refers
25452    *    to this row, allowing us to manipulate the state
25453    *
25454    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
25455    * row order or filtering or anything like that is changed.  We recall the expanded state
25456    * across invocations of the rowsProcessors by the reference to the treeNode on the individual
25457    * rows.  We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
25458    * get the state, but we overwrite the other data in that treeNode.
25459    *
25460    * By default rows are collapsed, which means all data rows have their visible property
25461    * set to false, and only level 0 group rows are set to visible.
25462    *
25463    * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
25464    * grid.treeBase.tree, then call refresh.  This is because we can't easily change the visible
25465    * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
25466    * well use it all the time.
25467    *
25468    * Tree base provides sorting (on non-grouped columns).
25469    *
25470    * Sorting works in two passes.  The standard sorting is performed for any columns that are important to building
25471    * the tree (for example, any grouped columns).  Then after the tree is built, a recursive tree sort is performed
25472    * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
25473    * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
25474    *
25475    * To achieve this we make use of the `ignoreSort` property on the sort configuration.  The parent feature (treeView or grouping)
25476    * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
25477    * the `ignoreSort`on any sort that it wants to run on the tree.  TreeBase will clear the ignoreSort on all sorts - so it
25478    * will turn on any sorts that haven't run.  It will then call a recursive sort on the tree.
25479    *
25480    * Tree base provides treeAggregation.  It checks the treeAggregation configuration on each column, and aggregates based on
25481    * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
25482    * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
25483    * treeAggregations in the column footer.  Aggregation information will be collected in the format:
25484    *
25485    * ```
25486    *   {
25487    *     type: 'count',
25488    *     value: 4,
25489    *     label: 'count: ',
25490    *     rendered: 'count: 4'
25491    *   }
25492    * ```
25493    *
25494    * A callback is provided to format the value once it is finalised (aka a valueFilter).
25495    *
25496    * <br/>
25497    * <br/>
25498    *
25499    * <div doc-module-components="ui.grid.treeBase"></div>
25500    */
25501
25502   var module = angular.module('ui.grid.treeBase', ['ui.grid']);
25503
25504   /**
25505    *  @ngdoc object
25506    *  @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
25507    *
25508    *  @description constants available in treeBase module.
25509    *
25510    *  These constants are manually copied into grouping and treeView,
25511    *  as I haven't found a way to simply include them, and it's not worth
25512    *  investing time in for something that changes very infrequently.
25513    *
25514    */
25515   module.constant('uiGridTreeBaseConstants', {
25516     featureName: "treeBase",
25517     rowHeaderColName: 'treeBaseRowHeaderCol',
25518     EXPANDED: 'expanded',
25519     COLLAPSED: 'collapsed',
25520     aggregation: {
25521       COUNT: 'count',
25522       SUM: 'sum',
25523       MAX: 'max',
25524       MIN: 'min',
25525       AVG: 'avg'
25526     }
25527   });
25528
25529   /**
25530    *  @ngdoc service
25531    *  @name ui.grid.treeBase.service:uiGridTreeBaseService
25532    *
25533    *  @description Services for treeBase feature
25534    */
25535   /**
25536    *  @ngdoc object
25537    *  @name ui.grid.treeBase.api:ColumnDef
25538    *
25539    *  @description ColumnDef for tree feature, these are available to be
25540    *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
25541    */
25542
25543   module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
25544   function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
25545
25546     var service = {
25547
25548       initializeGrid: function (grid, $scope) {
25549
25550         //add feature namespace and any properties to grid for needed
25551         /**
25552          *  @ngdoc object
25553          *  @name ui.grid.treeBase.grid:treeBase
25554          *
25555          *  @description Grid properties and functions added for treeBase
25556          */
25557         grid.treeBase = {};
25558
25559         /**
25560          *  @ngdoc property
25561          *  @propertyOf ui.grid.treeBase.grid:treeBase
25562          *  @name numberLevels
25563          *
25564          *  @description Total number of tree levels currently used, calculated by the rowsProcessor by
25565          *  retaining the highest tree level it sees
25566          */
25567         grid.treeBase.numberLevels = 0;
25568
25569         /**
25570          *  @ngdoc property
25571          *  @propertyOf ui.grid.treeBase.grid:treeBase
25572          *  @name expandAll
25573          *
25574          *  @description Whether or not the expandAll box is selected
25575          */
25576         grid.treeBase.expandAll = false;
25577
25578         /**
25579          *  @ngdoc property
25580          *  @propertyOf ui.grid.treeBase.grid:treeBase
25581          *  @name tree
25582          *
25583          *  @description Tree represented as a nested array that holds the state of each node, along with a
25584          *  pointer to the row.  The array order is material - we will display the children in the order
25585          *  they are stored in the array
25586          *
25587          *  Each node stores:
25588          *
25589          *    - the state of this node
25590          *    - an array of children of this node
25591          *    - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
25592          *    - the number of children of this node
25593          *    - aggregation information calculated from the nodes
25594          *
25595          *  ```
25596          *    [{
25597          *      state: 'expanded',
25598          *      row: <reference to row>,
25599          *      parentRow: null,
25600          *      aggregations: [{
25601          *        type: 'count',
25602          *        col: <gridCol>,
25603          *        value: 2,
25604          *        label: 'count: ',
25605          *        rendered: 'count: 2'
25606          *      }],
25607          *      children: [
25608          *        {
25609          *          state: 'expanded',
25610          *          row: <reference to row>,
25611          *          parentRow: <reference to row>,
25612          *          aggregations: [{
25613          *            type: 'count',
25614          *            col: '<gridCol>,
25615          *            value: 4,
25616          *            label: 'count: ',
25617          *            rendered: 'count: 4'
25618          *          }],
25619          *          children: [
25620          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25621          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
25622          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25623          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
25624          *          ]
25625          *        },
25626          *        {
25627          *          state: 'collapsed',
25628          *          row: <reference to row>,
25629          *          parentRow: <reference to row>,
25630          *          aggregations: [{
25631          *            type: 'count',
25632          *            col: <gridCol>,
25633          *            value: 3,
25634          *            label: 'count: ',
25635          *            rendered: 'count: 3'
25636          *          }],
25637          *          children: [
25638          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
25639          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
25640          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
25641          *          ]
25642          *        }
25643          *      ]
25644          *    }, {<another level 0 node maybe>} ]
25645          *  ```
25646          *  Missing state values are false - meaning they aren't expanded.
25647          *
25648          *  This is used because the rowProcessors run every time the grid is refreshed, so
25649          *  we'd lose the expanded state every time the grid was refreshed.  This instead gives
25650          *  us a reliable lookup that persists across rowProcessors.
25651          *
25652          *  This tree is rebuilt every time we run the rowsProcessors.  Since each row holds a pointer
25653          *  to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
25654          *  all transient information on the tree (children, childCount) and recalculate it
25655          *
25656          */
25657         grid.treeBase.tree = [];
25658
25659         service.defaultGridOptions(grid.options);
25660
25661         grid.registerRowsProcessor(service.treeRows, 410);
25662
25663         grid.registerColumnBuilder( service.treeBaseColumnBuilder );
25664
25665         service.createRowHeader( grid );
25666
25667         /**
25668          *  @ngdoc object
25669          *  @name ui.grid.treeBase.api:PublicApi
25670          *
25671          *  @description Public Api for treeBase feature
25672          */
25673         var publicApi = {
25674           events: {
25675             treeBase: {
25676               /**
25677                * @ngdoc event
25678                * @eventOf ui.grid.treeBase.api:PublicApi
25679                * @name rowExpanded
25680                * @description raised whenever a row is expanded.  If you are dynamically
25681                * rendering your tree you can listen to this event, and then retrieve
25682                * the children of this row and load them into the grid data.
25683                *
25684                * When the data is loaded the grid will automatically refresh to show these new rows
25685                *
25686                * <pre>
25687                *      gridApi.treeBase.on.rowExpanded(scope,function(row){})
25688                * </pre>
25689                * @param {gridRow} row the row that was expanded.  You can also
25690                * retrieve the grid from this row with row.grid
25691                */
25692               rowExpanded: {},
25693
25694               /**
25695                * @ngdoc event
25696                * @eventOf ui.grid.treeBase.api:PublicApi
25697                * @name rowCollapsed
25698                * @description raised whenever a row is collapsed.  Doesn't really have
25699                * a purpose at the moment, included for symmetry
25700                *
25701                * <pre>
25702                *      gridApi.treeBase.on.rowCollapsed(scope,function(row){})
25703                * </pre>
25704                * @param {gridRow} row the row that was collapsed.  You can also
25705                * retrieve the grid from this row with row.grid
25706                */
25707               rowCollapsed: {}
25708             }
25709           },
25710
25711           methods: {
25712             treeBase: {
25713               /**
25714                * @ngdoc function
25715                * @name expandAllRows
25716                * @methodOf  ui.grid.treeBase.api:PublicApi
25717                * @description Expands all tree rows
25718                */
25719               expandAllRows: function () {
25720                 service.expandAllRows(grid);
25721               },
25722
25723               /**
25724                * @ngdoc function
25725                * @name collapseAllRows
25726                * @methodOf  ui.grid.treeBase.api:PublicApi
25727                * @description collapse all tree rows
25728                */
25729               collapseAllRows: function () {
25730                 service.collapseAllRows(grid);
25731               },
25732
25733               /**
25734                * @ngdoc function
25735                * @name toggleRowTreeState
25736                * @methodOf  ui.grid.treeBase.api:PublicApi
25737                * @description  call expand if the row is collapsed, collapse if it is expanded
25738                * @param {gridRow} row the row you wish to toggle
25739                */
25740               toggleRowTreeState: function (row) {
25741                 service.toggleRowTreeState(grid, row);
25742               },
25743
25744               /**
25745                * @ngdoc function
25746                * @name expandRow
25747                * @methodOf  ui.grid.treeBase.api:PublicApi
25748                * @description expand the immediate children of the specified row
25749                * @param {gridRow} row the row you wish to expand
25750                */
25751               expandRow: function (row) {
25752                 service.expandRow(grid, row);
25753               },
25754
25755               /**
25756                * @ngdoc function
25757                * @name expandRowChildren
25758                * @methodOf  ui.grid.treeBase.api:PublicApi
25759                * @description expand all children of the specified row
25760                * @param {gridRow} row the row you wish to expand
25761                */
25762               expandRowChildren: function (row) {
25763                 service.expandRowChildren(grid, row);
25764               },
25765
25766               /**
25767                * @ngdoc function
25768                * @name collapseRow
25769                * @methodOf  ui.grid.treeBase.api:PublicApi
25770                * @description collapse  the specified row.  When
25771                * you expand the row again, all grandchildren will retain their state
25772                * @param {gridRow} row the row you wish to collapse
25773                */
25774               collapseRow: function ( row ) {
25775                 service.collapseRow(grid, row);
25776               },
25777
25778               /**
25779                * @ngdoc function
25780                * @name collapseRowChildren
25781                * @methodOf  ui.grid.treeBase.api:PublicApi
25782                * @description collapse all children of the specified row.  When
25783                * you expand the row again, all grandchildren will be collapsed
25784                * @param {gridRow} row the row you wish to collapse children for
25785                */
25786               collapseRowChildren: function ( row ) {
25787                 service.collapseRowChildren(grid, row);
25788               },
25789
25790               /**
25791                * @ngdoc function
25792                * @name getTreeState
25793                * @methodOf  ui.grid.treeBase.api:PublicApi
25794                * @description Get the tree state for this grid,
25795                * used by the saveState feature
25796                * Returned treeState as an object
25797                *   `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
25798                * where expandedState is a hash of row uid and the current expanded state
25799                *
25800                * @returns {object} tree state
25801                *
25802                * TODO - this needs work - we need an identifier that persists across instantiations,
25803                * not uid.  This really means we need a row identity defined, but that won't work for
25804                * grouping.  Perhaps this needs to be moved up to treeView and grouping, rather than
25805                * being in base.
25806                */
25807               getTreeExpandedState: function () {
25808                 return { expandedState: service.getTreeState(grid) };
25809               },
25810
25811               /**
25812                * @ngdoc function
25813                * @name setTreeState
25814                * @methodOf  ui.grid.treeBase.api:PublicApi
25815                * @description Set the expanded states of the tree
25816                * @param {object} config the config you want to apply, in the format
25817                * provided by getTreeState
25818                */
25819               setTreeState: function ( config ) {
25820                 service.setTreeState( grid, config );
25821               },
25822
25823               /**
25824                * @ngdoc function
25825                * @name getRowChildren
25826                * @methodOf  ui.grid.treeBase.api:PublicApi
25827                * @description Get the children of the specified row
25828                * @param {GridRow} row the row you want the children of
25829                * @returns {Array} array of children of this row, the children
25830                * are all gridRows
25831                */
25832               getRowChildren: function ( row ){
25833                 return row.treeNode.children.map( function( childNode ){
25834                   return childNode.row;
25835                 });
25836               }
25837             }
25838           }
25839         };
25840
25841         grid.api.registerEventsFromObject(publicApi.events);
25842
25843         grid.api.registerMethodsFromObject(publicApi.methods);
25844       },
25845
25846
25847       defaultGridOptions: function (gridOptions) {
25848         //default option to true unless it was explicitly set to false
25849         /**
25850          *  @ngdoc object
25851          *  @name ui.grid.treeBase.api:GridOptions
25852          *
25853          *  @description GridOptions for treeBase feature, these are available to be
25854          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
25855          */
25856
25857         /**
25858          *  @ngdoc object
25859          *  @name treeRowHeaderBaseWidth
25860          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25861          *  @description Base width of the tree header, provides for a single level of tree.  This
25862          *  is incremented by `treeIndent` for each extra level
25863          *  <br/>Defaults to 30
25864          */
25865         gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
25866
25867         /**
25868          *  @ngdoc object
25869          *  @name treeIndent
25870          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25871          *  @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
25872          *  but will make the tree row header wider
25873          *  <br/>Defaults to 10
25874          */
25875         gridOptions.treeIndent = gridOptions.treeIndent || 10;
25876
25877         /**
25878          *  @ngdoc object
25879          *  @name showTreeRowHeader
25880          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25881          *  @description If set to false, don't create the row header.  Youll need to programatically control the expand
25882          *  states
25883          *  <br/>Defaults to true
25884          */
25885         gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
25886
25887         /**
25888          *  @ngdoc object
25889          *  @name showTreeExpandNoChildren
25890          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25891          *  @description If set to true, show the expand/collapse button even if there are no
25892          *  children of a node.  You'd use this if you're planning to dynamically load the children
25893          *
25894          *  <br/>Defaults to true, grouping overrides to false
25895          */
25896         gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
25897
25898         /**
25899          *  @ngdoc object
25900          *  @name treeRowHeaderAlwaysVisible
25901          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25902          *  @description If set to true, row header even if there are no tree nodes
25903          *
25904          *  <br/>Defaults to true
25905          */
25906         gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
25907
25908         /**
25909          *  @ngdoc object
25910          *  @name treeCustomAggregations
25911          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25912          *  @description Define custom aggregation functions. The properties of this object will be
25913          *  aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
25914          *  If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
25915          *  The object format is:
25916          *
25917          *  <pre>
25918          *    {
25919          *      aggregationName: {
25920          *        label: (optional) string,
25921          *        aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
25922          *        finalizerFn: (optional) function( aggregation ){...}
25923        *        },
25924          *      mean: {
25925          *        label: 'mean',
25926          *        aggregationFn: function( aggregation, fieldValue, numValue ){
25927        *            aggregation.count = (aggregation.count || 1) + 1;
25928          *          aggregation.sum = (aggregation.sum || 0) + numValue;
25929          *        },
25930          *        finalizerFn: function( aggregation ){
25931          *          aggregation.value = aggregation.sum / aggregation.count
25932          *        }
25933          *      }
25934          *    }
25935          *  </pre>
25936          *
25937          *  <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
25938          *  apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
25939          *  rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
25940          *  the label and the value.
25941          *
25942          *  <br/>Defaults to {}
25943          */
25944         gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
25945
25946         /**
25947          *  @ngdoc object
25948          *  @name enableExpandAll
25949          *  @propertyOf  ui.grid.treeBase.api:GridOptions
25950          *  @description Enable the expand all button at the top of the row header
25951          *
25952          *  <br/>Defaults to true
25953          */
25954         gridOptions.enableExpandAll = gridOptions.enableExpandAll !== false;
25955       },
25956
25957
25958       /**
25959        * @ngdoc function
25960        * @name treeBaseColumnBuilder
25961        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
25962        * @description Sets the tree defaults based on the columnDefs
25963        *
25964        * @param {object} colDef columnDef we're basing on
25965        * @param {GridCol} col the column we're to update
25966        * @param {object} gridOptions the options we should use
25967        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
25968        */
25969       treeBaseColumnBuilder: function (colDef, col, gridOptions) {
25970
25971
25972         /**
25973          *  @ngdoc object
25974          *  @name customTreeAggregationFn
25975          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
25976          *  @description A custom function that aggregates rows into some form of
25977          *  total.  Aggregations run row-by-row, the function needs to be capable of
25978          *  creating a running total.
25979          *
25980          *  The function will be provided the aggregation item (in which you can store running
25981          *  totals), the row value that is to be aggregated, and that same row value converted to
25982          *  a number (most aggregations work on numbers)
25983          *  @example
25984          *  <pre>
25985          *    customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
25986          *      // calculates the average of the squares of the values
25987          *      if ( typeof(aggregation.count) === 'undefined' ){
25988          *        aggregation.count = 0;
25989          *      }
25990          *      aggregation.count++;
25991          *
25992          *      if ( !isNaN(numValue) ){
25993          *        if ( typeof(aggregation.total) === 'undefined' ){
25994          *          aggregation.total = 0;
25995          *        }
25996          *        aggregation.total = aggregation.total + numValue * numValue;
25997          *      }
25998          *
25999          *      aggregation.value = aggregation.total / aggregation.count;
26000          *    }
26001          *  </pre>
26002          *  <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
26003          */
26004         if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
26005           col.treeAggregationFn = colDef.customTreeAggregationFn;
26006         }
26007
26008         /**
26009          *  @ngdoc object
26010          *  @name treeAggregationType
26011          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26012          *  @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
26013          *  Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
26014          *  name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
26015          *
26016          *  <pre>
26017          *      treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
26018          *    }
26019          *  </pre>
26020          *
26021          *  If you are using aggregations you should either:
26022          *
26023          *   - also use grouping, in which case the aggregations are displayed in the group header, OR
26024          *   - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
26025          *     treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
26026          *     in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
26027          *
26028          *  <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
26029          *  <br/>Defaults to undefined.
26030          */
26031         if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
26032           col.treeAggregation = { type: colDef.treeAggregationType };
26033           if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
26034             col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
26035             col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
26036             col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
26037           } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
26038             col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
26039             col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
26040           }
26041         }
26042
26043          /**
26044          *  @ngdoc object
26045          *  @name treeAggregationLabel
26046          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26047          *  @description A custom label to use for this aggregation. If provided we don't use native i18n.
26048          */
26049         if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
26050           if (typeof(col.treeAggregation) === 'undefined' ){
26051             col.treeAggregation = {};
26052           }
26053           col.treeAggregation.label = colDef.treeAggregationLabel;
26054         }
26055
26056         /**
26057          *  @ngdoc object
26058          *  @name treeAggregationUpdateEntity
26059          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26060          *  @description Store calculated aggregations into the entity, allowing them
26061          *  to be displayed in the grid using a standard cellTemplate.  This defaults to true,
26062          *  if you are using grouping then you shouldn't set it to false, as then the aggregations won't
26063          *  display.
26064          *
26065          *  If you are using treeView in most cases you'll want to set this to true.  This will result in
26066          *  getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
26067          *  the entity.  If you want to render the underlying entity value (and do something else with the aggregation)
26068          *  then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
26069          *
26070          *  <br/>Defaults to true
26071          *
26072          *  @example
26073          *  <pre>
26074          *    gridOptions.columns = [{
26075          *      name: 'myCol',
26076          *      treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
26077          *      treeAggregationUpdateEntity: true
26078          *      cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
26079          *    }];
26080          * </pre>
26081          */
26082         col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
26083
26084         /**
26085          *  @ngdoc object
26086          *  @name customTreeAggregationFinalizerFn
26087          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26088          *  @description A custom function that populates aggregation.rendered, this is called when
26089          *  a particular aggregation has been fully calculated, and we want to render the value.
26090          *
26091          *  With the native aggregation options we just concatenate `aggregation.label` and
26092          *  `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
26093          *  or the value, you can do so with this function. This function will be called after the
26094          *  the default `finalizerFn`.
26095          *
26096          *  @example
26097          *  <pre>
26098          *    customTreeAggregationFinalizerFn = function ( aggregation ){
26099          *      aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
26100          *    }
26101          *  </pre>
26102          *  <br/>Defaults to undefined.
26103          */
26104         if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
26105           col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
26106         }
26107
26108       },
26109
26110
26111       /**
26112        * @ngdoc function
26113        * @name createRowHeader
26114        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26115        * @description Create the rowHeader.  If treeRowHeaderAlwaysVisible then
26116        * set it to visible, otherwise set it to invisible
26117        *
26118        * @param {Grid} grid grid object
26119        */
26120       createRowHeader: function( grid ){
26121         var rowHeaderColumnDef = {
26122           name: uiGridTreeBaseConstants.rowHeaderColName,
26123           displayName: '',
26124           width:  grid.options.treeRowHeaderBaseWidth,
26125           minWidth: 10,
26126           cellTemplate: 'ui-grid/treeBaseRowHeader',
26127           headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
26128           enableColumnResizing: false,
26129           enableColumnMenu: false,
26130           exporterSuppressExport: true,
26131           allowCellFocus: true
26132         };
26133
26134         rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
26135         grid.addRowHeaderColumn( rowHeaderColumnDef );
26136       },
26137
26138
26139       /**
26140        * @ngdoc function
26141        * @name expandAllRows
26142        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26143        * @description Expands all nodes in the tree
26144        *
26145        * @param {Grid} grid grid object
26146        */
26147       expandAllRows: function (grid) {
26148         grid.treeBase.tree.forEach( function( node ) {
26149           service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
26150         });
26151         grid.treeBase.expandAll = true;
26152         grid.queueGridRefresh();
26153       },
26154
26155
26156       /**
26157        * @ngdoc function
26158        * @name collapseAllRows
26159        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26160        * @description Collapses all nodes in the tree
26161        *
26162        * @param {Grid} grid grid object
26163        */
26164       collapseAllRows: function (grid) {
26165         grid.treeBase.tree.forEach( function( node ) {
26166           service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
26167         });
26168         grid.treeBase.expandAll = false;
26169         grid.queueGridRefresh();
26170       },
26171
26172
26173       /**
26174        * @ngdoc function
26175        * @name setAllNodes
26176        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26177        * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
26178        * all child nodes (and their descendents) of the provided node to the given state.
26179        *
26180        * Calls itself recursively on all nodes so as to achieve this.
26181        *
26182        * @param {Grid} grid the grid we're operating on (so we can raise events)
26183        * @param {object} treeNode a node in the tree that we want to update
26184        * @param {string} targetState the state we want to set it to
26185        */
26186       setAllNodes: function (grid, treeNode, targetState) {
26187         if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
26188           treeNode.state = targetState;
26189
26190           if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
26191             grid.api.treeBase.raise.rowExpanded(treeNode.row);
26192           } else {
26193             grid.api.treeBase.raise.rowCollapsed(treeNode.row);
26194           }
26195         }
26196
26197         // set all child nodes
26198         if ( treeNode.children ){
26199           treeNode.children.forEach(function( childNode ){
26200             service.setAllNodes(grid, childNode, targetState);
26201           });
26202         }
26203       },
26204
26205
26206       /**
26207        * @ngdoc function
26208        * @name toggleRowTreeState
26209        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26210        * @description Toggles the expand or collapse state of this grouped row, if
26211        * it's a parent row
26212        *
26213        * @param {Grid} grid grid object
26214        * @param {GridRow} row the row we want to toggle
26215        */
26216       toggleRowTreeState: function ( grid, row ){
26217         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26218           return;
26219         }
26220
26221         if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
26222           service.collapseRow(grid, row);
26223         } else {
26224           service.expandRow(grid, row);
26225         }
26226
26227         grid.queueGridRefresh();
26228       },
26229
26230
26231       /**
26232        * @ngdoc function
26233        * @name expandRow
26234        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26235        * @description Expands this specific row, showing only immediate children.
26236        *
26237        * @param {Grid} grid grid object
26238        * @param {GridRow} row the row we want to expand
26239        */
26240       expandRow: function ( grid, row ){
26241         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26242           return;
26243         }
26244
26245         if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
26246           row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
26247           grid.api.treeBase.raise.rowExpanded(row);
26248           grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
26249           grid.queueGridRefresh();
26250         }
26251       },
26252
26253
26254       /**
26255        * @ngdoc function
26256        * @name expandRowChildren
26257        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26258        * @description Expands this specific row, showing all children.
26259        *
26260        * @param {Grid} grid grid object
26261        * @param {GridRow} row the row we want to expand
26262        */
26263       expandRowChildren: function ( grid, row ){
26264         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26265           return;
26266         }
26267
26268         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
26269         grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
26270         grid.queueGridRefresh();
26271       },
26272
26273
26274       /**
26275        * @ngdoc function
26276        * @name collapseRow
26277        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26278        * @description Collapses this specific row
26279        *
26280        * @param {Grid} grid grid object
26281        * @param {GridRow} row the row we want to collapse
26282        */
26283       collapseRow: function( grid, row ){
26284         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26285           return;
26286         }
26287
26288         if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
26289           row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
26290           grid.treeBase.expandAll = false;
26291           grid.api.treeBase.raise.rowCollapsed(row);
26292           grid.queueGridRefresh();
26293         }
26294       },
26295
26296
26297       /**
26298        * @ngdoc function
26299        * @name collapseRowChildren
26300        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26301        * @description Collapses this specific row and all children
26302        *
26303        * @param {Grid} grid grid object
26304        * @param {GridRow} row the row we want to collapse
26305        */
26306       collapseRowChildren: function( grid, row ){
26307         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
26308           return;
26309         }
26310
26311         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
26312         grid.treeBase.expandAll = false;
26313         grid.queueGridRefresh();
26314       },
26315
26316
26317       /**
26318        * @ngdoc function
26319        * @name allExpanded
26320        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26321        * @description Returns true if all rows are expanded, false
26322        * if they're not.  Walks the tree to determine this.  Used
26323        * to set the expandAll state.
26324        *
26325        * If the node has no children, then return true (it's immaterial
26326        * whether it is expanded).  If the node has children, then return
26327        * false if this node is collapsed, or if any child node is not all expanded
26328        *
26329        * @param {object} tree the grid to check
26330        * @returns {boolean} whether or not the tree is all expanded
26331        */
26332       allExpanded: function( tree ){
26333         var allExpanded = true;
26334         tree.forEach( function( node ){
26335           if ( !service.allExpandedInternal( node ) ){
26336             allExpanded = false;
26337           }
26338         });
26339         return allExpanded;
26340       },
26341
26342       allExpandedInternal: function( treeNode ){
26343         if ( treeNode.children && treeNode.children.length > 0 ){
26344           if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
26345             return false;
26346           }
26347           var allExpanded = true;
26348           treeNode.children.forEach( function( node ){
26349             if ( !service.allExpandedInternal( node ) ){
26350               allExpanded = false;
26351             }
26352           });
26353           return allExpanded;
26354         } else {
26355           return true;
26356         }
26357       },
26358
26359
26360       /**
26361        * @ngdoc function
26362        * @name treeRows
26363        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26364        * @description The rowProcessor that adds the nodes to the tree, and sets the visible
26365        * state of each row based on it's parent state
26366        *
26367        * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
26368        * Performs any tree sorts itself after having built the tree
26369        *
26370        * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
26371        * entity, and setting the visible state based on the parent's state.
26372        *
26373        * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
26374        * sized.
26375        *
26376        * Aggregates if necessary along the way.
26377        *
26378        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
26379        * @returns {array} the updated rows
26380        */
26381       treeRows: function( renderableRows ) {
26382         if (renderableRows.length === 0){
26383           return renderableRows;
26384         }
26385
26386         var grid = this;
26387         var currentLevel = 0;
26388         var currentState = uiGridTreeBaseConstants.EXPANDED;
26389         var parents = [];
26390
26391         grid.treeBase.tree = service.createTree( grid, renderableRows );
26392         service.updateRowHeaderWidth( grid );
26393
26394         service.sortTree( grid );
26395         service.fixFilter( grid );
26396
26397         return service.renderTree( grid.treeBase.tree );
26398       },
26399
26400
26401       /**
26402        * @ngdoc function
26403        * @name createOrUpdateRowHeaderWidth
26404        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26405        * @description Calculates the rowHeader width.
26406        *
26407        * If rowHeader is always present, updates the width.
26408        *
26409        * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
26410        * should be one, then creates or removes it as appropriate, with the created rowHeader having the
26411        * right width.
26412        *
26413        * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
26414        *
26415        * @param {Grid} grid the grid we want to set the row header on
26416        */
26417       updateRowHeaderWidth: function( grid ){
26418         var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
26419
26420         var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
26421         if ( rowHeader && newWidth !== rowHeader.width ){
26422           rowHeader.width = newWidth;
26423           grid.queueRefresh();
26424         }
26425
26426         var newVisibility = true;
26427         if ( grid.options.showTreeRowHeader === false ){
26428           newVisibility = false;
26429         }
26430         if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
26431           newVisibility = false;
26432         }
26433         if ( rowHeader.visible !== newVisibility ) {
26434           rowHeader.visible = newVisibility;
26435           rowHeader.colDef.visible = newVisibility;
26436           grid.queueGridRefresh();
26437         }
26438       },
26439
26440
26441       /**
26442        * @ngdoc function
26443        * @name renderTree
26444        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26445        * @description Creates an array of rows based on the tree, exporting only
26446        * the visible nodes and leaves
26447        *
26448        * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
26449        * we're calling recursively
26450        * @returns {array} renderable rows
26451        */
26452       renderTree: function( nodeList ){
26453         var renderableRows = [];
26454
26455         nodeList.forEach( function ( node ){
26456           if ( node.row.visible ){
26457             renderableRows.push( node.row );
26458           }
26459           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
26460             renderableRows = renderableRows.concat( service.renderTree( node.children ) );
26461           }
26462         });
26463         return renderableRows;
26464       },
26465
26466
26467       /**
26468        * @ngdoc function
26469        * @name createTree
26470        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26471        * @description Creates a tree from the renderableRows
26472        *
26473        * @param {Grid} grid the grid
26474        * @param {array} renderableRows the rows we want to create a tree from
26475        * @returns {object} the tree we've build
26476        */
26477       createTree: function( grid, renderableRows ) {
26478         var currentLevel = -1;
26479         var parents = [];
26480         var currentState;
26481         grid.treeBase.tree = [];
26482         grid.treeBase.numberLevels = 0;
26483         var aggregations = service.getAggregations( grid );
26484
26485         var createNode = function( row ){
26486           if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
26487             row.treeLevel = row.entity.$$treeLevel;
26488           }
26489
26490           if ( row.treeLevel <= currentLevel ){
26491             // pop any levels that aren't parents of this level, formatting the aggregation at the same time
26492             while ( row.treeLevel <= currentLevel ){
26493               var lastParent = parents.pop();
26494               service.finaliseAggregations( lastParent );
26495               currentLevel--;
26496             }
26497
26498             // reset our current state based on the new parent, set to expanded if this is a level 0 node
26499             if ( parents.length > 0 ){
26500               currentState = service.setCurrentState(parents);
26501             } else {
26502               currentState = uiGridTreeBaseConstants.EXPANDED;
26503             }
26504           }
26505
26506           // aggregate if this is a leaf node
26507           if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible  ){
26508             service.aggregate( grid, row, parents );
26509           }
26510
26511           // add this node to the tree
26512           service.addOrUseNode(grid, row, parents, aggregations);
26513
26514           if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
26515             parents.push(row);
26516             currentLevel++;
26517             currentState = service.setCurrentState(parents);
26518           }
26519
26520           // update the tree number of levels, so we can set header width if we need to
26521           if ( grid.treeBase.numberLevels < row.treeLevel + 1){
26522             grid.treeBase.numberLevels = row.treeLevel + 1;
26523           }
26524         };
26525
26526         renderableRows.forEach( createNode );
26527
26528         // finalise remaining aggregations
26529         while ( parents.length > 0 ){
26530           var lastParent = parents.pop();
26531           service.finaliseAggregations( lastParent );
26532         }
26533
26534         return grid.treeBase.tree;
26535       },
26536
26537
26538       /**
26539        * @ngdoc function
26540        * @name addOrUseNode
26541        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26542        * @description Creates a tree node for this row.  If this row already has a treeNode
26543        * recorded against it, preserves the state, but otherwise overwrites the data.
26544        *
26545        * @param {grid} grid the grid we're operating on
26546        * @param {gridRow} row the row we want to set
26547        * @param {array} parents an array of the parents this row should have
26548        * @param {array} aggregationBase empty aggregation information
26549        * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
26550        * grid.treeBase.tree
26551        */
26552       addOrUseNode: function( grid, row, parents, aggregationBase ){
26553         var newAggregations = [];
26554         aggregationBase.forEach( function(aggregation){
26555           newAggregations.push(service.buildAggregationObject(aggregation.col));
26556         });
26557
26558         var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
26559         if ( row.treeNode ){
26560           newNode.state = row.treeNode.state;
26561         }
26562         if ( parents.length > 0 ){
26563           newNode.parentRow = parents[parents.length - 1];
26564         }
26565         row.treeNode = newNode;
26566
26567         if ( parents.length === 0 ){
26568           grid.treeBase.tree.push( newNode );
26569         } else {
26570           parents[parents.length - 1].treeNode.children.push( newNode );
26571         }
26572       },
26573
26574
26575       /**
26576        * @ngdoc function
26577        * @name setCurrentState
26578        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26579        * @description Looks at the parents array to determine our current state.
26580        * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
26581        * expanded.
26582        *
26583        * @param {array} parents an array of the parents this row should have
26584        * @returns {string} the state we should be setting to any nodes we see
26585        */
26586       setCurrentState: function( parents ){
26587         var currentState = uiGridTreeBaseConstants.EXPANDED;
26588         parents.forEach( function(parent){
26589           if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
26590             currentState = uiGridTreeBaseConstants.COLLAPSED;
26591           }
26592         });
26593         return currentState;
26594       },
26595
26596
26597       /**
26598        * @ngdoc function
26599        * @name sortTree
26600        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26601        * @description Performs a recursive sort on the tree nodes, sorting the
26602        * children of each node and putting them back into the children array.
26603        *
26604        * Before doing this it turns back on all the sortIgnore - things that were previously
26605        * ignored we process now.  Since we're sorting within the nodes, presumably anything
26606        * that was already sorted is how we derived the nodes, we can keep those sorts too.
26607        *
26608        * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
26609        * nodes
26610        *
26611        * @param {Grid} grid the grid to get the aggregation information from
26612        * @returns {array} the aggregation information
26613        */
26614       sortTree: function( grid ){
26615         grid.columns.forEach( function( column ) {
26616           if ( column.sort && column.sort.ignoreSort ){
26617             delete column.sort.ignoreSort;
26618           }
26619         });
26620
26621         grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
26622       },
26623
26624       sortInternal: function( grid, treeList ){
26625         var rows = treeList.map( function( node ){
26626           return node.row;
26627         });
26628
26629         rows = rowSorter.sort( grid, rows, grid.columns );
26630
26631         var treeNodes = rows.map( function( row ){
26632           return row.treeNode;
26633         });
26634
26635         treeNodes.forEach( function( node ){
26636           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
26637             node.children = service.sortInternal( grid, node.children );
26638           }
26639         });
26640
26641         return treeNodes;
26642       },
26643
26644       /**
26645        * @ngdoc function
26646        * @name fixFilter
26647        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26648        * @description After filtering has run, we need to go back through the tree
26649        * and make sure the parent rows are always visible if any of the child rows
26650        * are visible (filtering may make a child visible, but the parent may not
26651        * match the filter criteria)
26652        *
26653        * This has a risk of being computationally expensive, we do it by walking
26654        * the tree and remembering whether there are any invisible nodes on the
26655        * way down.
26656        *
26657        * @param {Grid} grid the grid to fix filters on
26658        */
26659       fixFilter: function( grid ){
26660         var parentsVisible;
26661
26662         grid.treeBase.tree.forEach( function( node ){
26663           if ( node.children && node.children.length > 0 ){
26664             parentsVisible = node.row.visible;
26665             service.fixFilterInternal( node.children, parentsVisible );
26666           }
26667         });
26668       },
26669
26670       fixFilterInternal: function( nodes, parentsVisible) {
26671         nodes.forEach( function( node ){
26672           if ( node.row.visible && !parentsVisible ){
26673             service.setParentsVisible( node );
26674             parentsVisible = true;
26675           }
26676
26677           if ( node.children && node.children.length > 0 ){
26678             if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
26679               parentsVisible = true;
26680             }
26681           }
26682         });
26683
26684         return parentsVisible;
26685       },
26686
26687       setParentsVisible: function( node ){
26688         while ( node.parentRow ){
26689           node.parentRow.visible = true;
26690           node = node.parentRow.treeNode;
26691         }
26692       },
26693
26694       /**
26695        * @ngdoc function
26696        * @name buildAggregationObject
26697        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26698        * @description Build the object which is stored on the column for holding meta-data about the aggregation.
26699        * This method should only be called with columns which have an aggregation.
26700        *
26701        * @param {Column} the column which this object relates to
26702        * @returns {object} {col: Column object, label: string, type: string (optional)}
26703        */
26704       buildAggregationObject: function( column ){
26705         var newAggregation = { col: column };
26706
26707         if ( column.treeAggregation && column.treeAggregation.type ){
26708           newAggregation.type = column.treeAggregation.type;
26709         }
26710
26711         if ( column.treeAggregation && column.treeAggregation.label ){
26712           newAggregation.label = column.treeAggregation.label;
26713         }
26714
26715         return newAggregation;
26716       },
26717
26718       /**
26719        * @ngdoc function
26720        * @name getAggregations
26721        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26722        * @description Looks through the grid columns to find those with aggregations,
26723        * and collates the aggregation information into an array, returns that array
26724        *
26725        * @param {Grid} grid the grid to get the aggregation information from
26726        * @returns {array} the aggregation information
26727        */
26728       getAggregations: function( grid ){
26729         var aggregateArray = [];
26730
26731         grid.columns.forEach( function(column){
26732           if ( typeof(column.treeAggregationFn) !== 'undefined' ){
26733             aggregateArray.push( service.buildAggregationObject(column) );
26734
26735             if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
26736               // Add aggregation object for footer
26737               column.treeFooterAggregation = service.buildAggregationObject(column);
26738               column.aggregationType = service.treeFooterAggregationType;
26739             }
26740           }
26741         });
26742         return aggregateArray;
26743       },
26744
26745
26746       /**
26747        * @ngdoc function
26748        * @name aggregate
26749        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26750        * @description Accumulate the data from this row onto the aggregations for each parent
26751        *
26752        * Iterate over the parents, then iterate over the aggregations for each of those parents,
26753        * and perform the aggregation for each individual aggregation
26754        *
26755        * @param {Grid} grid grid object
26756        * @param {GridRow} row the row we want to set grouping visibility on
26757        * @param {array} parents the parents that we would want to aggregate onto
26758        */
26759       aggregate: function( grid, row, parents ){
26760         if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
26761           row.treeNode.aggregations.forEach(function(aggregation){
26762             // Calculate aggregations for footer even if there are no grouped rows
26763             if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
26764               var fieldValue = grid.getCellValue(row, aggregation.col);
26765               var numValue = Number(fieldValue);
26766               aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26767             }
26768           });
26769         }
26770
26771         parents.forEach( function( parent, index ){
26772           if ( parent.treeNode.aggregations ){
26773             parent.treeNode.aggregations.forEach( function( aggregation ){
26774               var fieldValue = grid.getCellValue(row, aggregation.col);
26775               var numValue = Number(fieldValue);
26776               aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
26777
26778               if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
26779                 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
26780               }
26781             });
26782           }
26783         });
26784       },
26785
26786
26787       // Aggregation routines - no doco needed as self evident
26788       nativeAggregations: function() {
26789         var nativeAggregations = {
26790           count: {
26791             label: i18nService.get().aggregation.count,
26792             menuTitle: i18nService.get().grouping.aggregate_count,
26793             aggregationFn: function (aggregation, fieldValue, numValue) {
26794               if (typeof(aggregation.value) === 'undefined') {
26795                 aggregation.value = 1;
26796               } else {
26797                 aggregation.value++;
26798               }
26799             }
26800           },
26801
26802           sum: {
26803             label: i18nService.get().aggregation.sum,
26804             menuTitle: i18nService.get().grouping.aggregate_sum,
26805             aggregationFn: function( aggregation, fieldValue, numValue ) {
26806               if (!isNaN(numValue)) {
26807                 if (typeof(aggregation.value) === 'undefined') {
26808                   aggregation.value = numValue;
26809                 } else {
26810                   aggregation.value += numValue;
26811                 }
26812               }
26813             }
26814           },
26815
26816           min: {
26817             label: i18nService.get().aggregation.min,
26818             menuTitle: i18nService.get().grouping.aggregate_min,
26819             aggregationFn: function( aggregation, fieldValue, numValue ) {
26820               if (typeof(aggregation.value) === 'undefined') {
26821                 aggregation.value = fieldValue;
26822               } else {
26823                 if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
26824                   aggregation.value = fieldValue;
26825                 }
26826               }
26827             }
26828           },
26829
26830           max: {
26831             label: i18nService.get().aggregation.max,
26832             menuTitle: i18nService.get().grouping.aggregate_max,
26833             aggregationFn: function( aggregation, fieldValue, numValue ){
26834               if ( typeof(aggregation.value) === 'undefined' ){
26835                 aggregation.value = fieldValue;
26836               } else {
26837                 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
26838                   aggregation.value = fieldValue;
26839                 }
26840               }
26841             }
26842           },
26843
26844           avg: {
26845             label: i18nService.get().aggregation.avg,
26846             menuTitle: i18nService.get().grouping.aggregate_avg,
26847             aggregationFn: function( aggregation, fieldValue, numValue ){
26848               if ( typeof(aggregation.count) === 'undefined' ){
26849                 aggregation.count = 1;
26850               } else {
26851                 aggregation.count++;
26852               }
26853
26854               if ( isNaN(numValue) ){
26855                 return;
26856               }
26857
26858               if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
26859                 aggregation.value = numValue;
26860                 aggregation.sum = numValue;
26861               } else {
26862                 aggregation.sum += numValue;
26863                 aggregation.value = aggregation.sum / aggregation.count;
26864               }
26865             }
26866           }
26867         };
26868         return nativeAggregations;
26869       },
26870
26871       /**
26872        * @ngdoc function
26873        * @name finaliseAggregation
26874        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26875        * @description Helper function used to finalize aggregation nodes and footer cells
26876        *
26877        * @param {gridRow} row the parent we're finalising
26878        * @param {aggregation} the aggregation object manipulated by the aggregationFn
26879        */
26880       finaliseAggregation: function(row, aggregation){
26881         if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
26882           angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
26883         }
26884
26885         if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
26886           aggregation.col.treeAggregationFinalizerFn( aggregation );
26887         }
26888         if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
26889           aggregation.col.customTreeAggregationFinalizerFn( aggregation );
26890         }
26891         if ( typeof(aggregation.rendered) === 'undefined' ){
26892           aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
26893         }
26894       },
26895
26896       /**
26897        * @ngdoc function
26898        * @name finaliseAggregations
26899        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26900        * @description Format the data from the aggregation into the rendered text
26901        * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
26902        *
26903        * As part of this we call any formatting callback routines we've been provided.
26904        *
26905        * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
26906        * set on the column - we don't overwrite any information that's already there, we append
26907        * to it so that grouping can have set the groupVal beforehand without us overwriting it.
26908        *
26909        * We need to copy the data from the row.entity first before we finalise the aggregation,
26910        * we need that information for the finaliserFn
26911        *
26912        * @param {gridRow} row the parent we're finalising
26913        */
26914       finaliseAggregations: function( row ){
26915         if ( typeof(row.treeNode.aggregations) === 'undefined' ){
26916           return;
26917         }
26918
26919         row.treeNode.aggregations.forEach( function( aggregation ) {
26920           service.finaliseAggregation(row, aggregation);
26921
26922           if ( aggregation.col.treeAggregationUpdateEntity ){
26923             var aggregationCopy = {};
26924             angular.forEach( aggregation, function( value, key ){
26925               if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
26926                 aggregationCopy[key] = value;
26927               }
26928             });
26929
26930             row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
26931           }
26932         });
26933       },
26934
26935       /**
26936        * @ngdoc function
26937        * @name treeFooterAggregationType
26938        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26939        * @description Uses the tree aggregation functions and finalizers to set the
26940        * column footer aggregations.
26941        *
26942        * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
26943        * @param {gridColumn} the column we are finalizing
26944        */
26945       treeFooterAggregationType: function( rows, column ) {
26946         service.finaliseAggregation(undefined, column.treeFooterAggregation);
26947         if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
26948           // The was apparently no aggregation performed (perhaps this is a grouped column
26949           return '';
26950         }
26951         return column.treeFooterAggregation.rendered;
26952       }
26953     };
26954
26955     return service;
26956
26957   }]);
26958
26959
26960   /**
26961    *  @ngdoc directive
26962    *  @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
26963    *  @element div
26964    *
26965    *  @description Provides the expand/collapse button on rows
26966    */
26967   module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
26968   function ($templateCache, uiGridTreeBaseService) {
26969     return {
26970       replace: true,
26971       restrict: 'E',
26972       template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
26973       scope: true,
26974       require: '^uiGrid',
26975       link: function($scope, $elm, $attrs, uiGridCtrl) {
26976         var self = uiGridCtrl.grid;
26977         $scope.treeButtonClick = function(row, evt) {
26978           uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
26979         };
26980       }
26981     };
26982   }]);
26983
26984
26985   /**
26986    *  @ngdoc directive
26987    *  @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
26988    *  @element div
26989    *
26990    *  @description Provides the expand/collapse all button
26991    */
26992   module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
26993   function ($templateCache, uiGridTreeBaseService) {
26994     return {
26995       replace: true,
26996       restrict: 'E',
26997       template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
26998       scope: false,
26999       link: function($scope, $elm, $attrs, uiGridCtrl) {
27000         var self = $scope.col.grid;
27001
27002         $scope.headerButtonClick = function(row, evt) {
27003           if ( self.treeBase.expandAll ){
27004             uiGridTreeBaseService.collapseAllRows(self, evt);
27005           } else {
27006             uiGridTreeBaseService.expandAllRows(self, evt);
27007           }
27008         };
27009       }
27010     };
27011   }]);
27012
27013
27014   /**
27015    *  @ngdoc directive
27016    *  @name ui.grid.treeBase.directive:uiGridViewport
27017    *  @element div
27018    *
27019    *  @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
27020    */
27021   module.directive('uiGridViewport',
27022   ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
27023     function ($compile, uiGridConstants, gridUtil, $parse) {
27024       return {
27025         priority: -200, // run after default  directive
27026         scope: false,
27027         compile: function ($elm, $attrs) {
27028           var rowRepeatDiv = angular.element($elm.children().children()[0]);
27029
27030           var existingNgClass = rowRepeatDiv.attr("ng-class");
27031           var newNgClass = '';
27032           if ( existingNgClass ) {
27033             newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
27034           } else {
27035             newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
27036           }
27037           rowRepeatDiv.attr("ng-class", newNgClass);
27038
27039           return {
27040             pre: function ($scope, $elm, $attrs, controllers) {
27041
27042             },
27043             post: function ($scope, $elm, $attrs, controllers) {
27044             }
27045           };
27046         }
27047       };
27048     }]);
27049 })();
27050
27051 (function () {
27052   'use strict';
27053
27054   /**
27055    * @ngdoc overview
27056    * @name ui.grid.treeView
27057    * @description
27058    *
27059    * # ui.grid.treeView
27060    *
27061    * <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>
27062    *
27063    * This module provides a tree view of the data that it is provided, with nodes in that
27064    * tree and leaves.  Unlike grouping, the tree is an inherent property of the data and must
27065    * be provided with your data array.
27066    *
27067    * Design information:
27068    * -------------------
27069    *
27070    * TreeView uses treeBase for the underlying functionality, and is a very thin wrapper around
27071    * that logic.  Most of the design information has now moved to treebase.
27072    * <br/>
27073    * <br/>
27074    *
27075    * <div doc-module-components="ui.grid.treeView"></div>
27076    */
27077
27078   var module = angular.module('ui.grid.treeView', ['ui.grid', 'ui.grid.treeBase']);
27079
27080   /**
27081    *  @ngdoc object
27082    *  @name ui.grid.treeView.constant:uiGridTreeViewConstants
27083    *
27084    *  @description constants available in treeView module, this includes
27085    *  all the constants declared in the treeBase module (these are manually copied
27086    *  as there isn't an easy way to include constants in another constants file, and
27087    *  we don't want to make users include treeBase)
27088    *
27089    */
27090   module.constant('uiGridTreeViewConstants', {
27091     featureName: "treeView",
27092     rowHeaderColName: 'treeBaseRowHeaderCol',
27093     EXPANDED: 'expanded',
27094     COLLAPSED: 'collapsed',
27095     aggregation: {
27096       COUNT: 'count',
27097       SUM: 'sum',
27098       MAX: 'max',
27099       MIN: 'min',
27100       AVG: 'avg'
27101     }
27102   });
27103
27104   /**
27105    *  @ngdoc service
27106    *  @name ui.grid.treeView.service:uiGridTreeViewService
27107    *
27108    *  @description Services for treeView features
27109    */
27110   module.service('uiGridTreeViewService', ['$q', 'uiGridTreeViewConstants', 'uiGridTreeBaseConstants', 'uiGridTreeBaseService', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants',
27111   function ($q, uiGridTreeViewConstants, uiGridTreeBaseConstants, uiGridTreeBaseService, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants) {
27112
27113     var service = {
27114
27115       initializeGrid: function (grid, $scope) {
27116         uiGridTreeBaseService.initializeGrid( grid, $scope );
27117
27118         /**
27119          *  @ngdoc object
27120          *  @name ui.grid.treeView.grid:treeView
27121          *
27122          *  @description Grid properties and functions added for treeView
27123          */
27124         grid.treeView = {};
27125
27126         grid.registerRowsProcessor(service.adjustSorting, 60);
27127
27128         /**
27129          *  @ngdoc object
27130          *  @name ui.grid.treeView.api:PublicApi
27131          *
27132          *  @description Public Api for treeView feature
27133          */
27134         var publicApi = {
27135           events: {
27136             treeView: {
27137             }
27138           },
27139           methods: {
27140             treeView: {
27141             }
27142           }
27143         };
27144
27145         grid.api.registerEventsFromObject(publicApi.events);
27146
27147         grid.api.registerMethodsFromObject(publicApi.methods);
27148
27149       },
27150
27151       defaultGridOptions: function (gridOptions) {
27152         //default option to true unless it was explicitly set to false
27153         /**
27154          *  @ngdoc object
27155          *  @name ui.grid.treeView.api:GridOptions
27156          *
27157          *  @description GridOptions for treeView feature, these are available to be
27158          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
27159          *
27160          *  Many tree options are set on treeBase, make sure to look at that feature in
27161          *  conjunction with these options.
27162          */
27163
27164         /**
27165          *  @ngdoc object
27166          *  @name enableTreeView
27167          *  @propertyOf  ui.grid.treeView.api:GridOptions
27168          *  @description Enable row tree view for entire grid.
27169          *  <br/>Defaults to true
27170          */
27171         gridOptions.enableTreeView = gridOptions.enableTreeView !== false;
27172
27173       },
27174
27175
27176       /**
27177        * @ngdoc function
27178        * @name adjustSorting
27179        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27180        * @description Trees cannot be sorted the same as flat lists of rows -
27181        * trees are sorted recursively within each level - so the children of each
27182        * node are sorted, but not the full set of rows.
27183        *
27184        * To achieve this, we suppress the normal sorting by setting ignoreSort on
27185        * each of the sort columns.  When the treeBase rowsProcessor runs it will then
27186        * unignore these, and will perform a recursive sort against the tree that it builds.
27187        *
27188        * @param {array} renderableRows the rows that we need to pass on through
27189        * @returns {array} renderableRows that we passed on through
27190        */
27191       adjustSorting: function( renderableRows ) {
27192         var grid = this;
27193
27194         grid.columns.forEach( function( column ){
27195           if ( column.sort ){
27196             column.sort.ignoreSort = true;
27197           }
27198         });
27199
27200         return renderableRows;
27201       }
27202
27203     };
27204
27205     return service;
27206
27207   }]);
27208
27209   /**
27210    *  @ngdoc directive
27211    *  @name ui.grid.treeView.directive:uiGridTreeView
27212    *  @element div
27213    *  @restrict A
27214    *
27215    *  @description Adds treeView features to grid
27216    *
27217    *  @example
27218    <example module="app">
27219    <file name="app.js">
27220    var app = angular.module('app', ['ui.grid', 'ui.grid.treeView']);
27221
27222    app.controller('MainCtrl', ['$scope', function ($scope) {
27223       $scope.data = [
27224         { name: 'Bob', title: 'CEO' },
27225             { name: 'Frank', title: 'Lowly Developer' }
27226       ];
27227
27228       $scope.columnDefs = [
27229         {name: 'name', enableCellEdit: true},
27230         {name: 'title', enableCellEdit: true}
27231       ];
27232
27233       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
27234     }]);
27235    </file>
27236    <file name="index.html">
27237    <div ng-controller="MainCtrl">
27238    <div ui-grid="gridOptions" ui-grid-tree-view></div>
27239    </div>
27240    </file>
27241    </example>
27242    */
27243   module.directive('uiGridTreeView', ['uiGridTreeViewConstants', 'uiGridTreeViewService', '$templateCache',
27244   function (uiGridTreeViewConstants, uiGridTreeViewService, $templateCache) {
27245     return {
27246       replace: true,
27247       priority: 0,
27248       require: '^uiGrid',
27249       scope: false,
27250       compile: function () {
27251         return {
27252           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
27253             if (uiGridCtrl.grid.options.enableTreeView !== false){
27254               uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
27255             }
27256           },
27257           post: function ($scope, $elm, $attrs, uiGridCtrl) {
27258
27259           }
27260         };
27261       }
27262     };
27263   }]);
27264 })();
27265
27266 (function () {
27267   'use strict';
27268   
27269   /**
27270    * @ngdoc overview
27271    * @name ui.grid.validate
27272    * @description
27273    *
27274    * # ui.grid.validate
27275    *
27276    * <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>
27277    *
27278    * This module provides the ability to validate cells upon change.
27279    *
27280    * Design information:
27281    * -------------------
27282    *
27283    * Validation is not based on angularjs validation, since it would work only when editing the field.
27284    * 
27285    * Instead it adds custom properties to any field considered as invalid.
27286    *
27287    * <br/>
27288    * <br/>
27289    *
27290    * <div doc-module-components="ui.grid.expandable"></div>
27291    */
27292
27293   var module = angular.module('ui.grid.validate', ['ui.grid']);
27294   
27295   
27296   /**
27297    *  @ngdoc service
27298    *  @name ui.grid.validate.service:uiGridValidateService
27299    *
27300    *  @description Services for validation features
27301    */
27302   module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) {
27303
27304     var service = {
27305       
27306       /**
27307        *  @ngdoc object
27308        *  @name validatorFactories
27309        *  @propertyOf ui.grid.validate.service:uiGridValidateService
27310        *  @description object containing all the factories used to validate data.<br/>
27311        *  These factories will be in the form <br/>
27312        *  ```
27313        *  {
27314        *    validatorFactory: function(argument) {
27315        *                        return function(newValue, oldValue, rowEntity, colDef) {
27316        *                          return true || false || promise
27317        *                        }
27318        *                      },
27319        *    messageFunction: function(argument) {
27320        *                       return string
27321        *                     }
27322        *  }
27323        *  ```
27324        *
27325        * Promises should return true or false as result according to the result of validation.
27326        */
27327       validatorFactories: {},
27328
27329       
27330       /**
27331        * @ngdoc service
27332        * @name setExternalFactoryFunction
27333        * @methodOf ui.grid.validate.service:uiGridValidateService
27334        * @description Adds a way to retrieve validators from an external service
27335        * <p>Validators from this external service have a higher priority than default
27336        * ones
27337        * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a
27338        * validator factory and that returns an object with the same properties as 
27339        * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories}
27340        */
27341       setExternalFactoryFunction: function(externalFactoryFunction) {
27342         service.externalFactoryFunction = externalFactoryFunction;
27343       },
27344       
27345       /**
27346        * @ngdoc service
27347        * @name clearExternalFactory
27348        * @methodOf ui.grid.validate.service:uiGridValidateService
27349        * @description Removes any link to external factory from this service
27350        */
27351       clearExternalFactory: function() {
27352         delete service.externalFactoryFunction;
27353       },
27354
27355       /**
27356        * @ngdoc service
27357        * @name getValidatorFromExternalFactory
27358        * @methodOf ui.grid.validate.service:uiGridValidateService
27359        * @description Retrieves a validator by executing a validatorFactory
27360        * stored in an external service.
27361        * @param {string} name the name of the validator to retrieve
27362        * @param {object} argument an argument to pass to the validator factory
27363        */
27364       getValidatorFromExternalFactory: function(name, argument) {
27365         return service.externalFactoryFunction(name, argument).validatorFactory(argument);
27366       },
27367       
27368       /**
27369        * @ngdoc service
27370        * @name getMessageFromExternalFactory
27371        * @methodOf ui.grid.validate.service:uiGridValidateService
27372        * @description Retrieves a message stored in an external service.
27373        * @param {string} name the name of the validator
27374        * @param {object} argument an argument to pass to the message function
27375        */
27376       getMessageFromExternalFactory: function(name, argument) {
27377         return service.externalFactoryFunction(name, argument).messageFunction(argument);
27378       },
27379       
27380       /**
27381        * @ngdoc service
27382        * @name setValidator
27383        * @methodOf ui.grid.validate.service:uiGridValidateService
27384        * @description  Adds a new validator to the service
27385        * @param {string} name the name of the validator, must be unique
27386        * @param {function} validatorFactory a factory that return a validatorFunction
27387        * @param {function} messageFunction a function that return the error message
27388        */
27389       setValidator: function(name, validatorFactory, messageFunction) {
27390         service.validatorFactories[name] = {
27391           validatorFactory: validatorFactory,
27392           messageFunction: messageFunction
27393         };
27394       },
27395
27396       /**
27397        * @ngdoc service
27398        * @name getValidator
27399        * @methodOf ui.grid.validate.service:uiGridValidateService
27400        * @description Returns a validator registered to the service
27401        * or retrieved from the external factory
27402        * @param {string} name the name of the validator to retrieve
27403        * @param {object} argument an argument to pass to the validator factory
27404        * @returns {object} the validator function
27405        */
27406       getValidator: function(name, argument) {
27407         if (service.externalFactoryFunction) {
27408           var validator = service.getValidatorFromExternalFactory(name, argument);
27409           if (validator) {
27410             return validator;
27411           }
27412         }
27413         if (!service.validatorFactories[name]) {
27414           throw ("Invalid validator name: " + name);
27415         }
27416         return service.validatorFactories[name].validatorFactory(argument);
27417       },
27418
27419       /**
27420        * @ngdoc service
27421        * @name getMessage
27422        * @methodOf ui.grid.validate.service:uiGridValidateService
27423        * @description Returns the error message related to the validator 
27424        * @param {string} name the name of the validator
27425        * @param {object} argument an argument to pass to the message function
27426        * @returns {string} the error message related to the validator
27427        */
27428       getMessage: function(name, argument) {
27429         if (service.externalFactoryFunction) {
27430           var message = service.getMessageFromExternalFactory(name, argument);
27431           if (message) {
27432             return message;
27433           }
27434         }
27435         return service.validatorFactories[name].messageFunction(argument);
27436       },
27437
27438       /**
27439        * @ngdoc service
27440        * @name isInvalid
27441        * @methodOf ui.grid.validate.service:uiGridValidateService
27442        * @description Returns true if the cell (identified by rowEntity, colDef) is invalid 
27443        * @param {object} rowEntity the row entity of the cell
27444        * @param {object} colDef the colDef of the cell
27445        * @returns {boolean} true if the cell is invalid
27446        */
27447       isInvalid: function (rowEntity, colDef) {
27448         return rowEntity['$$invalid'+colDef.name];
27449       },
27450
27451       /**
27452        * @ngdoc service
27453        * @name setInvalid
27454        * @methodOf ui.grid.validate.service:uiGridValidateService
27455        * @description Makes the cell invalid by adding the proper field to the entity
27456        * @param {object} rowEntity the row entity of the cell
27457        * @param {object} colDef the colDef of the cell
27458        */
27459       setInvalid: function (rowEntity, colDef) {
27460         rowEntity['$$invalid'+colDef.name] = true;
27461       },
27462     
27463       /**
27464        * @ngdoc service
27465        * @name setValid
27466        * @methodOf ui.grid.validate.service:uiGridValidateService
27467        * @description Makes the cell valid by removing the proper error field from the entity
27468        * @param {object} rowEntity the row entity of the cell
27469        * @param {object} colDef the colDef of the cell
27470        */
27471       setValid: function (rowEntity, colDef) {
27472         delete rowEntity['$$invalid'+colDef.name];
27473       },
27474
27475       /**
27476        * @ngdoc service
27477        * @name setError
27478        * @methodOf ui.grid.validate.service:uiGridValidateService
27479        * @description Adds the proper error to the entity errors field
27480        * @param {object} rowEntity the row entity of the cell
27481        * @param {object} colDef the colDef of the cell
27482        * @param {string} validatorName the name of the validator that is failing
27483        */
27484       setError: function(rowEntity, colDef, validatorName) {
27485         if (!rowEntity['$$errors'+colDef.name]) {
27486           rowEntity['$$errors'+colDef.name] = {};
27487         }
27488         rowEntity['$$errors'+colDef.name][validatorName] = true;
27489       },
27490
27491       /**
27492        * @ngdoc service
27493        * @name clearError
27494        * @methodOf ui.grid.validate.service:uiGridValidateService
27495        * @description Removes the proper error from the entity errors field
27496        * @param {object} rowEntity the row entity of the cell
27497        * @param {object} colDef the colDef of the cell
27498        * @param {string} validatorName the name of the validator that is failing
27499        */
27500       clearError: function(rowEntity, colDef, validatorName) {
27501         if (!rowEntity['$$errors'+colDef.name]) {
27502           return;
27503         }
27504         if (validatorName in rowEntity['$$errors'+colDef.name]) {
27505             delete rowEntity['$$errors'+colDef.name][validatorName];
27506         }
27507       },
27508       
27509       /**
27510        * @ngdoc function
27511        * @name getErrorMessages
27512        * @methodOf ui.grid.validate.service:uiGridValidateService
27513        * @description returns an array of i18n-ed error messages.
27514        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27515        * @param {object} colDef the column whose errors we are looking for
27516        * @returns {array} An array of strings containing all the error messages for the cell
27517        */
27518       getErrorMessages: function(rowEntity, colDef) {
27519         var errors = [];
27520
27521         if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) {
27522           return errors;
27523         }
27524
27525         Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) {
27526           errors.push(service.getMessage(validatorName, colDef.validators[validatorName]));
27527         });
27528         
27529         return errors;
27530       },
27531       
27532       /**
27533        * @ngdoc function
27534        * @name getFormattedErrors
27535        * @methodOf  ui.grid.validate.service:uiGridValidateService
27536        * @description returns the error i18n-ed and formatted in html to be shown inside the page.
27537        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27538        * @param {object} colDef the column whose errors we are looking for
27539        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27540        * message inside the page (i.e. inside a div)
27541        */
27542       getFormattedErrors: function(rowEntity, colDef) {
27543
27544         var msgString = "";
27545
27546         var errors = service.getErrorMessages(rowEntity, colDef);
27547         
27548         if (!errors.length) {
27549           return;
27550         }
27551         
27552         errors.forEach(function(errorMsg) {
27553           msgString += errorMsg + "<br/>";
27554         });
27555
27556         return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString );
27557       },
27558
27559       /**
27560        * @ngdoc function
27561        * @name getTitleFormattedErrors
27562        * @methodOf ui.grid.validate.service:uiGridValidateService
27563        * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
27564        * title attribute.
27565        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27566        * @param {object} colDef the column whose errors we are looking for
27567        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27568        * message inside an html title attribute
27569        */
27570       getTitleFormattedErrors: function(rowEntity, colDef) {
27571
27572         var newLine = "\n";
27573
27574         var msgString = "";
27575         
27576         var errors = service.getErrorMessages(rowEntity, colDef);
27577         
27578         if (!errors.length) {
27579           return;
27580         }
27581         
27582         errors.forEach(function(errorMsg) {
27583           msgString += errorMsg + newLine;
27584         });
27585
27586         return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString);
27587       },
27588
27589       /**
27590        * @ngdoc function
27591        * @name getTitleFormattedErrors
27592        * @methodOf ui.grid.validate.service:uiGridValidateService
27593        * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors
27594        * @param {object} rowEntity the row entity of the cell we want to run the validators on
27595        * @param {object} colDef the column definition of the cell we want to run the validators on
27596        * @param {object} newValue the value the user just entered
27597        * @param {object} oldValue the value the field had before
27598        */
27599       runValidators: function(rowEntity, colDef, newValue, oldValue, grid) {
27600         
27601         if (newValue === oldValue) {
27602           // If the value has not changed we perform no validation
27603           return;
27604         }
27605         
27606         if (typeof(colDef.name) === 'undefined' || !colDef.name) {
27607           throw new Error('colDef.name is required to perform validation');
27608         }
27609         
27610         service.setValid(rowEntity, colDef);
27611         
27612         var validateClosureFactory = function(rowEntity, colDef, validatorName) {
27613           return function(value) {
27614             if (!value) {
27615               service.setInvalid(rowEntity, colDef);
27616               service.setError(rowEntity, colDef, validatorName);
27617               if (grid) {
27618                 grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue);
27619               }
27620             }
27621           };
27622         };
27623
27624         for (var validatorName in colDef.validators) {
27625           service.clearError(rowEntity, colDef, validatorName);
27626           var msg;
27627           var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]);
27628           // We pass the arguments as oldValue, newValue so they are in the same order 
27629           // as ng-model validators (modelValue, viewValue)
27630           $q.when(validatorFunction(oldValue, newValue, rowEntity, colDef))
27631             .then(validateClosureFactory(rowEntity, colDef, validatorName)
27632           );
27633         }
27634       },
27635
27636       /**
27637        * @ngdoc function
27638        * @name createDefaultValidators
27639        * @methodOf ui.grid.validate.service:uiGridValidateService
27640        * @description adds the basic validators to the list of service validators
27641        */
27642       createDefaultValidators: function() {
27643         service.setValidator('minLength',
27644                              function (argument) {
27645                                return function (oldValue, newValue, rowEntity, colDef) {
27646                                  if (newValue === undefined || newValue === null || newValue === '') {
27647                                    return true;
27648                                  }
27649                                  return newValue.length >= argument;
27650                                };
27651                              },
27652                                function(argument) {
27653                                  return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument);
27654                                });
27655         
27656         service.setValidator('maxLength',
27657                              function (argument) {
27658                                return function (oldValue, newValue, rowEntity, colDef) {
27659                                  if (newValue === undefined || newValue === null || newValue === '') {
27660                                    return true;
27661                                  }
27662                                  return newValue.length <= argument;
27663                                };
27664                              },
27665                              function(threshold) {
27666                                return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold);
27667                              });
27668         
27669         service.setValidator('required',
27670                              function (argument) {
27671                                return function (oldValue, newValue, rowEntity, colDef) {
27672                                  if (argument) {
27673                                    return !(newValue === undefined || newValue === null || newValue === '');
27674                                  }
27675                                  return true;
27676                                };
27677                              },
27678                              function(argument) {
27679                                return i18nService.getSafeText('validate.required');
27680                              });
27681       },
27682
27683       initializeGrid: function (scope, grid) {
27684         grid.validate = {
27685         
27686           isInvalid: service.isInvalid,
27687
27688           getFormattedErrors: service.getFormattedErrors,
27689          
27690           getTitleFormattedErrors: service.getTitleFormattedErrors,
27691
27692           runValidators: service.runValidators
27693         };
27694         
27695         /**
27696          *  @ngdoc object
27697          *  @name ui.grid.validate.api:PublicApi
27698          *
27699          *  @description Public Api for validation feature
27700          */
27701         var publicApi = {
27702           events: {
27703             validate: {
27704               /**
27705                * @ngdoc event
27706                * @name validationFailed
27707                * @eventOf  ui.grid.validate.api:PublicApi
27708                * @description raised when one or more failure happened during validation 
27709                * <pre>
27710                *      gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...})
27711                * </pre>
27712                * @param {object} rowEntity the options.data element whose validation failed
27713                * @param {object} colDef the column whose validation failed
27714                * @param {object} newValue new value
27715                * @param {object} oldValue old value
27716                */
27717               validationFailed: function (rowEntity, colDef, newValue, oldValue) {
27718               }
27719             }
27720           },
27721           methods: {
27722             validate: {
27723               /**
27724                * @ngdoc function
27725                * @name isInvalid
27726                * @methodOf  ui.grid.validate.api:PublicApi
27727                * @description checks if a cell (identified by rowEntity, colDef) is invalid
27728                * @param {object} rowEntity gridOptions.data[] array instance we want to check
27729                * @param {object} colDef the column whose errors we want to check
27730                * @returns {boolean} true if the cell value is not valid
27731                */
27732               isInvalid: function(rowEntity, colDef) {
27733                 return grid.validate.isInvalid(rowEntity, colDef);
27734               },
27735               /**
27736                * @ngdoc function
27737                * @name getErrorMessages
27738                * @methodOf  ui.grid.validate.api:PublicApi
27739                * @description returns an array of i18n-ed error messages.
27740                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27741                * @param {object} colDef the column whose errors we are looking for
27742                * @returns {array} An array of strings containing all the error messages for the cell
27743                */
27744               getErrorMessages: function (rowEntity, colDef) {
27745                 return grid.validate.getErrorMessages(rowEntity, colDef);
27746               },
27747               /**
27748                * @ngdoc function
27749                * @name getFormattedErrors
27750                * @methodOf  ui.grid.validate.api:PublicApi
27751                * @description returns the error i18n-ed and formatted in html to be shown inside the page.
27752                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27753                * @param {object} colDef the column whose errors we are looking for
27754                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27755                * message inside the page (i.e. inside a div)
27756                */
27757               getFormattedErrors: function (rowEntity, colDef) {
27758                 return grid.validate.getFormattedErrors(rowEntity, colDef);
27759               },
27760               /**
27761                * @ngdoc function
27762                * @name getTitleFormattedErrors
27763                * @methodOf  ui.grid.validate.api:PublicApi
27764                * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
27765                * title attribute.
27766                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
27767                * @param {object} colDef the column whose errors we are looking for
27768                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
27769                * message inside an html title attribute
27770                */
27771               getTitleFormattedErrors: function (rowEntity, colDef) {
27772                 return grid.validate.getTitleFormattedErrors(rowEntity, colDef);
27773               }
27774             } 
27775           }
27776         };
27777         
27778         grid.api.registerEventsFromObject(publicApi.events);
27779         grid.api.registerMethodsFromObject(publicApi.methods);
27780
27781         if (grid.edit) {
27782           grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) {
27783             grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid);
27784           });
27785         }
27786
27787         service.createDefaultValidators();
27788       }
27789       
27790     };
27791   
27792     return service;
27793   }]);
27794   
27795   
27796   /**
27797    *  @ngdoc directive
27798    *  @name ui.grid.validate.directive:uiGridValidate
27799    *  @element div
27800    *  @restrict A
27801    *  @description Adds validating features to the ui-grid directive.
27802    *  @example
27803    <example module="app">
27804    <file name="app.js">
27805    var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']);
27806
27807    app.controller('MainCtrl', ['$scope', function ($scope) {
27808       $scope.data = [
27809         { name: 'Bob', title: 'CEO' },
27810             { name: 'Frank', title: 'Lowly Developer' }
27811       ];
27812
27813       $scope.columnDefs = [
27814         {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'},
27815         {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'}
27816       ];
27817     }]);
27818    </file>
27819    <file name="index.html">
27820    <div ng-controller="MainCtrl">
27821    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div>
27822    </div>
27823    </file>
27824    </example>
27825    */
27826
27827   module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) {
27828     return {
27829       priority: 0,
27830       replace: true,
27831       require: '^uiGrid',
27832       scope: false,
27833       compile: function () {
27834         return {
27835           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
27836             uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid);
27837           },
27838           post: function ($scope, $elm, $attrs, uiGridCtrl) {
27839           }
27840         };
27841       }
27842     };
27843   }]);
27844 })();
27845 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
27846   'use strict';
27847
27848   $templateCache.put('ui-grid/ui-grid-filter',
27849     "<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>"
27850   );
27851
27852
27853   $templateCache.put('ui-grid/ui-grid-footer',
27854     "<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>"
27855   );
27856
27857
27858   $templateCache.put('ui-grid/ui-grid-grid-footer',
27859     "<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>"
27860   );
27861
27862
27863   $templateCache.put('ui-grid/ui-grid-group-panel',
27864     "<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>"
27865   );
27866
27867
27868   $templateCache.put('ui-grid/ui-grid-header',
27869     "<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>"
27870   );
27871
27872
27873   $templateCache.put('ui-grid/ui-grid-menu-button',
27874     "<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>"
27875   );
27876
27877
27878   $templateCache.put('ui-grid/ui-grid-no-header',
27879     "<div class=\"ui-grid-top-panel\"></div>"
27880   );
27881
27882
27883   $templateCache.put('ui-grid/ui-grid-row',
27884     "<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>"
27885   );
27886
27887
27888   $templateCache.put('ui-grid/ui-grid',
27889     "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
27890     "      /* Styles for the grid */\n" +
27891     "    }\n" +
27892     "\n" +
27893     "    .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
27894     "      height: {{ grid.options.rowHeight }}px;\n" +
27895     "    }\n" +
27896     "\n" +
27897     "    .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
27898     "      border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
27899     "    }\n" +
27900     "\n" +
27901     "    {{ grid.verticalScrollbarStyles }}\n" +
27902     "    {{ grid.horizontalScrollbarStyles }}\n" +
27903     "\n" +
27904     "    /*\n" +
27905     "    .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
27906     "      padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
27907     "    }\n" +
27908     "    */\n" +
27909     "\n" +
27910     "    {{ 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>"
27911   );
27912
27913
27914   $templateCache.put('ui-grid/uiGridCell',
27915     "<div class=\"ui-grid-cell-contents\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
27916   );
27917
27918
27919   $templateCache.put('ui-grid/uiGridColumnMenu',
27920     "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
27921     "    <div class=\"inner\" ng-show=\"menuShown\">\n" +
27922     "      <ul>\n" +
27923     "        <div ng-show=\"grid.options.enableSorting\">\n" +
27924     "          <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" +
27925     "          <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" +
27926     "          <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
27927     "        </div>\n" +
27928     "      </ul>\n" +
27929     "    </div>\n" +
27930     "  </div> --></div></div>"
27931   );
27932
27933
27934   $templateCache.put('ui-grid/uiGridFooterCell',
27935     "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationText() + ( col.getAggregationValue() CUSTOM_FILTERS ) }}</div></div>"
27936   );
27937
27938
27939   $templateCache.put('ui-grid/uiGridHeaderCell',
27940     "<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 + 1 )  : null}}\" aria-hidden=\"true\"></i> <sub ui-grid-visible=\"isSortPriorityVisible()\" class=\"ui-grid-sort-priority-number\">{{col.sort.priority + 1}}</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>"
27941   );
27942
27943
27944   $templateCache.put('ui-grid/uiGridMenu',
27945     "<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\"><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>"
27946   );
27947
27948
27949   $templateCache.put('ui-grid/uiGridMenuItem',
27950     "<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>"
27951   );
27952
27953
27954   $templateCache.put('ui-grid/uiGridRenderContainer',
27955     "<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>"
27956   );
27957
27958
27959   $templateCache.put('ui-grid/uiGridViewport',
27960     "<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>"
27961   );
27962
27963
27964   $templateCache.put('ui-grid/cellEditor',
27965     "<div><form name=\"inputForm\"><input type=\"INPUT_TYPE\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
27966   );
27967
27968
27969   $templateCache.put('ui-grid/dropdownEditor',
27970     "<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>"
27971   );
27972
27973
27974   $templateCache.put('ui-grid/fileChooserEditor',
27975     "<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>"
27976   );
27977
27978
27979   $templateCache.put('ui-grid/expandableRow',
27980     "<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>"
27981   );
27982
27983
27984   $templateCache.put('ui-grid/expandableRowHeader',
27985     "<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>"
27986   );
27987
27988
27989   $templateCache.put('ui-grid/expandableScrollFiller',
27990     "<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>"
27991   );
27992
27993
27994   $templateCache.put('ui-grid/expandableTopRowHeader',
27995     "<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>"
27996   );
27997
27998
27999   $templateCache.put('ui-grid/csvLink',
28000     "<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>"
28001   );
28002
28003
28004   $templateCache.put('ui-grid/importerMenuItem',
28005     "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
28006   );
28007
28008
28009   $templateCache.put('ui-grid/importerMenuItemContainer',
28010     "<div ui-grid-importer-menu-item></div>"
28011   );
28012
28013
28014   $templateCache.put('ui-grid/pagination',
28015     "<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 ng-class=\"grid.isRTL() ? 'last-triangle' : 'first-triangle'\"><div ng-class=\"grid.isRTL() ? 'last-bar-rtl' : '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 ng-class=\"grid.isRTL() ? 'last-triangle prev-triangle' : '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 ng-class=\"grid.isRTL() ? 'first-triangle next-triangle' : '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 ng-class=\"grid.isRTL() ? 'first-triangle' : 'last-triangle'\"><div ng-class=\"grid.isRTL() ? 'first-bar-rtl' : '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>"
28016   );
28017
28018
28019   $templateCache.put('ui-grid/columnResizer',
28020     "<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>"
28021   );
28022
28023
28024   $templateCache.put('ui-grid/gridFooterSelectedItems',
28025     "<span ng-if=\"grid.selection.selectedCount !== 0 && grid.options.enableFooterTotalSelected\">({{\"search.selectedItems\" | t}} {{grid.selection.selectedCount}})</span>"
28026   );
28027
28028
28029   $templateCache.put('ui-grid/selectionHeaderCell',
28030     "<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>"
28031   );
28032
28033
28034   $templateCache.put('ui-grid/selectionRowHeader',
28035     "<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>"
28036   );
28037
28038
28039   $templateCache.put('ui-grid/selectionRowHeaderButtons',
28040     "<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>"
28041   );
28042
28043
28044   $templateCache.put('ui-grid/selectionSelectAllButtons',
28045     "<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>"
28046   );
28047
28048
28049   $templateCache.put('ui-grid/treeBaseExpandAllButtons',
28050     "<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>"
28051   );
28052
28053
28054   $templateCache.put('ui-grid/treeBaseHeaderCell',
28055     "<div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-tree-base-expand-all-buttons ng-if=\"grid.options.enableExpandAll\"></ui-grid-tree-base-expand-all-buttons></div></div>"
28056   );
28057
28058
28059   $templateCache.put('ui-grid/treeBaseRowHeader',
28060     "<div class=\"ui-grid-cell-contents\"><ui-grid-tree-base-row-header-buttons></ui-grid-tree-base-row-header-buttons></div>"
28061   );
28062
28063
28064   $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
28065     "<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>"
28066   );
28067
28068
28069   $templateCache.put('ui-grid/cellTitleValidator',
28070     "<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>"
28071   );
28072
28073
28074   $templateCache.put('ui-grid/cellTooltipValidator',
28075     "<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>"
28076   );
28077
28078 }]);