[POLICY-73] replace openecomp for policy-engine
[policy/engine.git] / POLICY-SDK-APP / src / main / webapp / app / policyApp / CSS / UIGrid / ui-grid.js
1 /*!
2  * ui-grid - v4.0.2 - 2016-12-30
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
14   /**
15    * @ngdoc object
16    * @name ui.grid.service:uiGridConstants
17    * @description Constants for use across many grid features
18    *
19    */
20
21
22   angular.module('ui.grid').constant('uiGridConstants', {
23     LOG_DEBUG_MESSAGES: true,
24     LOG_WARN_MESSAGES: true,
25     LOG_ERROR_MESSAGES: true,
26     CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
27     COL_FIELD: /COL_FIELD/g,
28     MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
29     TOOLTIP: /title=\"TOOLTIP\"/g,
30     DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
31     TEMPLATE_REGEXP: /<.+>/,
32     FUNC_REGEXP: /(\([^)]*\))?$/,
33     DOT_REGEXP: /\./g,
34     APOS_REGEXP: /'/g,
35     BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
36     COL_CLASS_PREFIX: 'ui-grid-col',
37     ENTITY_BINDING: '$$this',
38     events: {
39       GRID_SCROLL: 'uiGridScroll',
40       COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
41       ITEM_DRAGGING: 'uiGridItemDragStart', // For any item being dragged
42       COLUMN_HEADER_CLICK: 'uiGridColumnHeaderClick'
43     },
44     // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
45     keymap: {
46       TAB: 9,
47       STRG: 17,
48       CAPSLOCK: 20,
49       CTRL: 17,
50       CTRLRIGHT: 18,
51       CTRLR: 18,
52       SHIFT: 16,
53       RETURN: 13,
54       ENTER: 13,
55       BACKSPACE: 8,
56       BCKSP: 8,
57       ALT: 18,
58       ALTR: 17,
59       ALTRIGHT: 17,
60       SPACE: 32,
61       WIN: 91,
62       MAC: 91,
63       FN: null,
64       PG_UP: 33,
65       PG_DOWN: 34,
66       UP: 38,
67       DOWN: 40,
68       LEFT: 37,
69       RIGHT: 39,
70       ESC: 27,
71       DEL: 46,
72       F1: 112,
73       F2: 113,
74       F3: 114,
75       F4: 115,
76       F5: 116,
77       F6: 117,
78       F7: 118,
79       F8: 119,
80       F9: 120,
81       F10: 121,
82       F11: 122,
83       F12: 123
84     },
85      /**
86      * @ngdoc object
87      * @name ASC
88      * @propertyOf ui.grid.service:uiGridConstants
89      * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
90      * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
91      * to configure the sorting direction of the column
92      */
93     ASC: 'asc',
94      /**
95      * @ngdoc object
96      * @name DESC
97      * @propertyOf ui.grid.service:uiGridConstants
98      * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
99      * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
100      * to configure the sorting direction of the column
101      */
102     DESC: 'desc',
103
104
105      /**
106      * @ngdoc object
107      * @name filter
108      * @propertyOf ui.grid.service:uiGridConstants
109      * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_filter columnDef.filter}
110      * to configure filtering on the column
111      *
112      * `SELECT` and `INPUT` are used with the `type` property of the filter, the rest are used to specify
113      * one of the built-in conditions.
114      *
115      * Available `condition` options are:
116      * - `uiGridConstants.filter.STARTS_WITH`
117      * - `uiGridConstants.filter.ENDS_WITH`
118      * - `uiGridConstants.filter.CONTAINS`
119      * - `uiGridConstants.filter.GREATER_THAN`
120      * - `uiGridConstants.filter.GREATER_THAN_OR_EQUAL`
121      * - `uiGridConstants.filter.LESS_THAN`
122      * - `uiGridConstants.filter.LESS_THAN_OR_EQUAL`
123      * - `uiGridConstants.filter.NOT_EQUAL`
124      * - `uiGridConstants.filter.STARTS_WITH`
125      *
126      *
127      * Available `type` options are:
128      * - `uiGridConstants.filter.SELECT` - use a dropdown box for the cell header filter field
129      * - `uiGridConstants.filter.INPUT` - use a text box for the cell header filter field
130      */
131     filter: {
132       STARTS_WITH: 2,
133       ENDS_WITH: 4,
134       EXACT: 8,
135       CONTAINS: 16,
136       GREATER_THAN: 32,
137       GREATER_THAN_OR_EQUAL: 64,
138       LESS_THAN: 128,
139       LESS_THAN_OR_EQUAL: 256,
140       NOT_EQUAL: 512,
141       SELECT: 'select',
142       INPUT: 'input'
143     },
144
145     /**
146      * @ngdoc object
147      * @name aggregationTypes
148      * @propertyOf ui.grid.service:uiGridConstants
149      * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_aggregationType columnDef.aggregationType}
150      * to specify the type of built-in aggregation the column should use.
151      *
152      * Available options are:
153      * - `uiGridConstants.aggregationTypes.sum` - add the values in this column to produce the aggregated value
154      * - `uiGridConstants.aggregationTypes.count` - count the number of rows to produce the aggregated value
155      * - `uiGridConstants.aggregationTypes.avg` - average the values in this column to produce the aggregated value
156      * - `uiGridConstants.aggregationTypes.min` - use the minimum value in this column as the aggregated value
157      * - `uiGridConstants.aggregationTypes.max` - use the maximum value in this column as the aggregated value
158      */
159     aggregationTypes: {
160       sum: 2,
161       count: 4,
162       avg: 8,
163       min: 16,
164       max: 32
165     },
166
167     /**
168      * @ngdoc array
169      * @name CURRENCY_SYMBOLS
170      * @propertyOf ui.grid.service:uiGridConstants
171      * @description A list of all presently circulating currency symbols that was copied from
172      * https://en.wikipedia.org/wiki/Currency_symbol#List_of_presently-circulating_currency_symbols
173      *
174      * Can be used on {@link ui.grid.class:rowSorter} to create a number string regex that ignores currency symbols.
175      */
176     CURRENCY_SYMBOLS: ['¤', '؋', 'Ar', 'Ƀ', '฿', 'B/.', 'Br', 'Bs.', 'Bs.F.', 'GH₵', '¢', 'c', 'Ch.', '₡', 'C$', 'D', 'ден',
177       'دج', '.د.ب', 'د.ع', 'JD', 'د.ك', 'ل.د', 'дин', 'د.ت', 'د.م.', 'د.إ', 'Db', '$', '₫', 'Esc', '€', 'ƒ', 'Ft', 'FBu',
178       'FCFA', 'CFA', 'Fr', 'FRw', 'G', 'gr', '₲', 'h', '₴', '₭', 'Kč', 'kr', 'kn', 'MK', 'ZK', 'Kz', 'K', 'L', 'Le', 'лв',
179       'E', 'lp', 'M', 'KM', 'MT', '₥', 'Nfk', '₦', 'Nu.', 'UM', 'T$', 'MOP$', '₱', 'Pt.', '£', 'ج.م.', 'LL', 'LS', 'P', 'Q',
180       'q', 'R', 'R$', 'ر.ع.', 'ر.ق', 'ر.س', '៛', 'RM', 'p', 'Rf.', '₹', '₨', 'SRe', 'Rp', '₪', 'Ksh', 'Sh.So.', 'USh', 'S/',
181       'SDR', 'сом', '৳     ', 'WS$', '₮', 'VT', '₩', '¥', 'zł'],
182
183     /**
184      * @ngdoc object
185      * @name scrollDirection
186      * @propertyOf ui.grid.service:uiGridConstants
187      * @description Set on {@link ui.grid.class:Grid#properties_scrollDirection Grid.scrollDirection},
188      * to indicate the direction the grid is currently scrolling in
189      *
190      * Available options are:
191      * - `uiGridConstants.scrollDirection.UP` - set when the grid is scrolling up
192      * - `uiGridConstants.scrollDirection.DOWN` - set when the grid is scrolling down
193      * - `uiGridConstants.scrollDirection.LEFT` - set when the grid is scrolling left
194      * - `uiGridConstants.scrollDirection.RIGHT` - set when the grid is scrolling right
195      * - `uiGridConstants.scrollDirection.NONE` - set when the grid is not scrolling, this is the default
196      */
197     scrollDirection: {
198       UP: 'up',
199       DOWN: 'down',
200       LEFT: 'left',
201       RIGHT: 'right',
202       NONE: 'none'
203
204     },
205
206     /**
207      * @ngdoc object
208      * @name dataChange
209      * @propertyOf ui.grid.service:uiGridConstants
210      * @description Used with {@link ui.grid.core.api:PublicApi#methods_notifyDataChange PublicApi.notifyDataChange},
211      * {@link ui.grid.class:Grid#methods_callDataChangeCallbacks Grid.callDataChangeCallbacks},
212      * and {@link ui.grid.class:Grid#methods_registerDataChangeCallback Grid.registerDataChangeCallback}
213      * to specify the type of the event(s).
214      *
215      * Available options are:
216      * - `uiGridConstants.dataChange.ALL` - listeners fired on any of these events, fires listeners on all events.
217      * - `uiGridConstants.dataChange.EDIT` - fired when the data in a cell is edited
218      * - `uiGridConstants.dataChange.ROW` - fired when a row is added or removed
219      * - `uiGridConstants.dataChange.COLUMN` - fired when the column definitions are modified
220      * - `uiGridConstants.dataChange.OPTIONS` - fired when the grid options are modified
221      */
222     dataChange: {
223       ALL: 'all',
224       EDIT: 'edit',
225       ROW: 'row',
226       COLUMN: 'column',
227       OPTIONS: 'options'
228     },
229
230     /**
231      * @ngdoc object
232      * @name scrollbars
233      * @propertyOf ui.grid.service:uiGridConstants
234      * @description Used with {@link ui.grid.class:GridOptions#properties_enableHorizontalScrollbar GridOptions.enableHorizontalScrollbar}
235      * and {@link ui.grid.class:GridOptions#properties_enableVerticalScrollbar GridOptions.enableVerticalScrollbar}
236      * to specify the scrollbar policy for that direction.
237      *
238      * Available options are:
239      * - `uiGridConstants.scrollbars.NEVER` - never show scrollbars in this direction
240      * - `uiGridConstants.scrollbars.ALWAYS` - always show scrollbars in this direction
241      */
242
243     scrollbars: {
244       NEVER: 0,
245       ALWAYS: 1
246       //WHEN_NEEDED: 2
247     }
248   });
249
250 })();
251
252 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
253   var uiGridCell = {
254     priority: 0,
255     scope: false,
256     require: '?^uiGrid',
257     compile: function() {
258       return {
259         pre: function($scope, $elm, $attrs, uiGridCtrl) {
260           function compileTemplate() {
261             var compiledElementFn = $scope.col.compiledElementFn;
262
263             compiledElementFn($scope, function(clonedElement, scope) {
264               $elm.append(clonedElement);
265             });
266           }
267
268           // If the grid controller is present, use it to get the compiled cell template function
269           if (uiGridCtrl && $scope.col.compiledElementFn) {
270              compileTemplate();
271           }
272           // No controller, compile the element manually (for unit tests)
273           else {
274             if ( uiGridCtrl && !$scope.col.compiledElementFn ){
275               // gridUtil.logError('Render has been called before pronapile.  Please log a ui-grid issue');  
276
277               $scope.col.getCompiledElementFn()
278                 .then(function (compiledElementFn) {
279                   compiledElementFn($scope, function(clonedElement, scope) {
280                     $elm.append(clonedElement);
281                   });
282                 });
283             }
284             else {
285               var html = $scope.col.cellTemplate
286                 .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
287                 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
288
289               var cellElement = $compile(html)($scope);
290               $elm.append(cellElement);
291             }
292           }
293         },
294         post: function($scope, $elm, $attrs, uiGridCtrl) {
295           var initColClass = $scope.col.getColClass(false);
296           $elm.addClass(initColClass);
297
298           var classAdded;
299           var updateClass = function( grid ){
300             var contents = $elm;
301             if ( classAdded ){
302               contents.removeClass( classAdded );
303               classAdded = null;
304             }
305
306             if (angular.isFunction($scope.col.cellClass)) {
307               classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
308             }
309             else {
310               classAdded = $scope.col.cellClass;
311             }
312             contents.addClass(classAdded);
313           };
314
315           if ($scope.col.cellClass) {
316             updateClass();
317           }
318           
319           // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
320           var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
321           
322           // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
323           // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
324           var cellChangeFunction = function( n, o ){
325             if ( n !== o ) {
326               if ( classAdded || $scope.col.cellClass ){
327                 updateClass();
328               }
329
330               // See if the column's internal class has changed
331               var newColClass = $scope.col.getColClass(false);
332               if (newColClass !== initColClass) {
333                 $elm.removeClass(initColClass);
334                 $elm.addClass(newColClass);
335                 initColClass = newColClass;
336               }
337             }
338           };
339
340           // TODO(c0bra): Turn this into a deep array watch
341 /*        shouldn't be needed any more given track by col.name
342           var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
343 */
344           var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
345           
346           
347           var deregisterFunction = function() {
348             dataChangeDereg();
349 //            colWatchDereg();
350             rowWatchDereg(); 
351           };
352           
353           $scope.$on( '$destroy', deregisterFunction );
354           $elm.on( '$destroy', deregisterFunction );
355         }
356       };
357     }
358   };
359
360   return uiGridCell;
361 }]);
362
363
364 (function(){
365
366 angular.module('ui.grid')
367 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
368 function ( i18nService, uiGridConstants, gridUtil ) {
369 /**
370  *  @ngdoc service
371  *  @name ui.grid.service:uiGridColumnMenuService
372  *
373  *  @description Services for working with column menus, factored out
374  *  to make the code easier to understand
375  */
376
377   var service = {
378     /**
379      * @ngdoc method
380      * @methodOf ui.grid.service:uiGridColumnMenuService
381      * @name initialize
382      * @description  Sets defaults, puts a reference to the $scope on
383      * the uiGridController
384      * @param {$scope} $scope the $scope from the uiGridColumnMenu
385      * @param {controller} uiGridCtrl the uiGridController for the grid
386      * we're on
387      *
388      */
389     initialize: function( $scope, uiGridCtrl ){
390       $scope.grid = uiGridCtrl.grid;
391
392       // Store a reference to this link/controller in the main uiGrid controller
393       // to allow showMenu later
394       uiGridCtrl.columnMenuScope = $scope;
395
396       // Save whether we're shown or not so the columns can check
397       $scope.menuShown = false;
398     },
399
400
401     /**
402      * @ngdoc method
403      * @methodOf ui.grid.service:uiGridColumnMenuService
404      * @name setColMenuItemWatch
405      * @description  Setup a watch on $scope.col.menuItems, and update
406      * menuItems based on this.  $scope.col needs to be set by the column
407      * before calling the menu.
408      * @param {$scope} $scope the $scope from the uiGridColumnMenu
409      * @param {controller} uiGridCtrl the uiGridController for the grid
410      * we're on
411      *
412      */
413     setColMenuItemWatch: function ( $scope ){
414       var deregFunction = $scope.$watch('col.menuItems', function (n) {
415         if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
416           n.forEach(function (item) {
417             if (typeof(item.context) === 'undefined' || !item.context) {
418               item.context = {};
419             }
420             item.context.col = $scope.col;
421           });
422
423           $scope.menuItems = $scope.defaultMenuItems.concat(n);
424         }
425         else {
426           $scope.menuItems = $scope.defaultMenuItems;
427         }
428       });
429
430       $scope.$on( '$destroy', deregFunction );
431     },
432
433
434     /**
435      * @ngdoc boolean
436      * @name enableSorting
437      * @propertyOf ui.grid.class:GridOptions.columnDef
438      * @description (optional) True by default. When enabled, this setting adds sort
439      * widgets to the column header, allowing sorting of the data in the individual column.
440      */
441     /**
442      * @ngdoc method
443      * @methodOf ui.grid.service:uiGridColumnMenuService
444      * @name sortable
445      * @description  determines whether this column is sortable
446      * @param {$scope} $scope the $scope from the uiGridColumnMenu
447      *
448      */
449     sortable: function( $scope ) {
450       if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
451         return true;
452       }
453       else {
454         return false;
455       }
456     },
457
458     /**
459      * @ngdoc method
460      * @methodOf ui.grid.service:uiGridColumnMenuService
461      * @name isActiveSort
462      * @description  determines whether the requested sort direction is current active, to
463      * allow highlighting in the menu
464      * @param {$scope} $scope the $scope from the uiGridColumnMenu
465      * @param {string} direction the direction that we'd have selected for us to be active
466      *
467      */
468     isActiveSort: function( $scope, direction ){
469       return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
470               typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
471
472     },
473
474     /**
475      * @ngdoc method
476      * @methodOf ui.grid.service:uiGridColumnMenuService
477      * @name suppressRemoveSort
478      * @description  determines whether we should suppress the removeSort option
479      * @param {$scope} $scope the $scope from the uiGridColumnMenu
480      *
481      */
482     suppressRemoveSort: function( $scope ) {
483       if ($scope.col && $scope.col.suppressRemoveSort) {
484         return true;
485       }
486       else {
487         return false;
488       }
489     },
490
491
492     /**
493      * @ngdoc boolean
494      * @name enableHiding
495      * @propertyOf ui.grid.class:GridOptions.columnDef
496      * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
497      * using the column menu or the grid menu.
498      */
499     /**
500      * @ngdoc method
501      * @methodOf ui.grid.service:uiGridColumnMenuService
502      * @name hideable
503      * @description  determines whether a column can be hidden, by checking the enableHiding columnDef option
504      * @param {$scope} $scope the $scope from the uiGridColumnMenu
505      *
506      */
507     hideable: function( $scope ) {
508       if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
509         return false;
510       }
511       else {
512         return true;
513       }
514     },
515
516
517     /**
518      * @ngdoc method
519      * @methodOf ui.grid.service:uiGridColumnMenuService
520      * @name getDefaultMenuItems
521      * @description  returns the default menu items for a column menu
522      * @param {$scope} $scope the $scope from the uiGridColumnMenu
523      *
524      */
525     getDefaultMenuItems: function( $scope ){
526       return [
527         {
528           title: i18nService.getSafeText('sort.ascending'),
529           icon: 'ui-grid-icon-sort-alt-up',
530           action: function($event) {
531             $event.stopPropagation();
532             $scope.sortColumn($event, uiGridConstants.ASC);
533           },
534           shown: function () {
535             return service.sortable( $scope );
536           },
537           active: function() {
538             return service.isActiveSort( $scope, uiGridConstants.ASC);
539           }
540         },
541         {
542           title: i18nService.getSafeText('sort.descending'),
543           icon: 'ui-grid-icon-sort-alt-down',
544           action: function($event) {
545             $event.stopPropagation();
546             $scope.sortColumn($event, uiGridConstants.DESC);
547           },
548           shown: function() {
549             return service.sortable( $scope );
550           },
551           active: function() {
552             return service.isActiveSort( $scope, uiGridConstants.DESC);
553           }
554         },
555         {
556           title: i18nService.getSafeText('sort.remove'),
557           icon: 'ui-grid-icon-cancel',
558           action: function ($event) {
559             $event.stopPropagation();
560             $scope.unsortColumn();
561           },
562           shown: function() {
563             return service.sortable( $scope ) &&
564                    typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
565                    typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
566                   !service.suppressRemoveSort( $scope );
567           }
568         },
569         {
570           title: i18nService.getSafeText('column.hide'),
571           icon: 'ui-grid-icon-cancel',
572           shown: function() {
573             return service.hideable( $scope );
574           },
575           action: function ($event) {
576             $event.stopPropagation();
577             $scope.hideColumn();
578           }
579         }
580       ];
581     },
582
583
584     /**
585      * @ngdoc method
586      * @methodOf ui.grid.service:uiGridColumnMenuService
587      * @name getColumnElementPosition
588      * @description  gets the position information needed to place the column
589      * menu below the column header
590      * @param {$scope} $scope the $scope from the uiGridColumnMenu
591      * @param {GridCol} column the column we want to position below
592      * @param {element} $columnElement the column element we want to position below
593      * @returns {hash} containing left, top, offset, height, width
594      *
595      */
596     getColumnElementPosition: function( $scope, column, $columnElement ){
597       var positionData = {};
598       positionData.left = $columnElement[0].offsetLeft;
599       positionData.top = $columnElement[0].offsetTop;
600       positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
601
602       // Get the grid scrollLeft
603       positionData.offset = 0;
604       if (column.grid.options.offsetLeft) {
605         positionData.offset = column.grid.options.offsetLeft;
606       }
607
608       positionData.height = gridUtil.elementHeight($columnElement, true);
609       positionData.width = gridUtil.elementWidth($columnElement, true);
610
611       return positionData;
612     },
613
614
615     /**
616      * @ngdoc method
617      * @methodOf ui.grid.service:uiGridColumnMenuService
618      * @name repositionMenu
619      * @description  Reposition the menu below the new column.  If the menu has no child nodes
620      * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
621      * later to fix it
622      * @param {$scope} $scope the $scope from the uiGridColumnMenu
623      * @param {GridCol} column the column we want to position below
624      * @param {hash} positionData a hash containing left, top, offset, height, width
625      * @param {element} $elm the column menu element that we want to reposition
626      * @param {element} $columnElement the column element that we want to reposition underneath
627      *
628      */
629     repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
630       var menu = $elm[0].querySelectorAll('.ui-grid-menu');
631
632       // It's possible that the render container of the column we're attaching to is
633       // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
634       // between the render container and the grid
635       var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
636       var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
637
638       var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
639
640       // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
641       var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
642       var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
643
644       if ( menu.length !== 0 ){
645         var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
646         if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
647           myWidth = gridUtil.elementWidth(menu, true);
648           $scope.lastMenuWidth = myWidth;
649           column.lastMenuWidth = myWidth;
650
651           // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
652           // Get the column menu right padding
653           paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
654           $scope.lastMenuPaddingRight = paddingRight;
655           column.lastMenuPaddingRight = paddingRight;
656         }
657       }
658
659       var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
660       if (left < positionData.offset){
661         left = positionData.offset;
662       }
663
664       $elm.css('left', left + 'px');
665       $elm.css('top', (positionData.top + positionData.height) + 'px');
666     }
667
668   };
669
670   return service;
671 }])
672
673
674 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
675 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
676 /**
677  * @ngdoc directive
678  * @name ui.grid.directive:uiGridColumnMenu
679  * @description  Provides the column menu framework, leverages uiGridMenu underneath
680  *
681  */
682
683   var uiGridColumnMenu = {
684     priority: 0,
685     scope: true,
686     require: '^uiGrid',
687     templateUrl: 'ui-grid/uiGridColumnMenu',
688     replace: true,
689     link: function ($scope, $elm, $attrs, uiGridCtrl) {
690       uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
691
692       $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
693
694       // Set the menu items for use with the column menu. The user can later add additional items via the watch
695       $scope.menuItems = $scope.defaultMenuItems;
696       uiGridColumnMenuService.setColMenuItemWatch( $scope );
697
698
699       /**
700        * @ngdoc method
701        * @methodOf ui.grid.directive:uiGridColumnMenu
702        * @name showMenu
703        * @description Shows the column menu.  If the menu is already displayed it
704        * calls the menu to ask it to hide (it will animate), then it repositions the menu
705        * to the right place whilst hidden (it will make an assumption on menu width),
706        * then it asks the menu to show (it will animate), then it repositions the menu again
707        * once we can calculate it's size.
708        * @param {GridCol} column the column we want to position below
709        * @param {element} $columnElement the column element we want to position below
710        */
711       $scope.showMenu = function(column, $columnElement, event) {
712         // Swap to this column
713         $scope.col = column;
714
715         // Get the position information for the column element
716         var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
717
718         if ($scope.menuShown) {
719           // we want to hide, then reposition, then show, but we want to wait for animations
720           // we set a variable, and then rely on the menu-hidden event to call the reposition and show
721           $scope.colElement = $columnElement;
722           $scope.colElementPosition = colElementPosition;
723           $scope.hideThenShow = true;
724
725           $scope.$broadcast('hide-menu', { originalEvent: event });
726         } else {
727           $scope.menuShown = true;
728           uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
729
730           $scope.colElement = $columnElement;
731           $scope.colElementPosition = colElementPosition;
732           $scope.$broadcast('show-menu', { originalEvent: event });
733
734         }
735       };
736
737
738       /**
739        * @ngdoc method
740        * @methodOf ui.grid.directive:uiGridColumnMenu
741        * @name hideMenu
742        * @description Hides the column menu.
743        * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
744        * from the menu itself - in which case don't broadcast again as we'll get
745        * an infinite loop
746        */
747       $scope.hideMenu = function( broadcastTrigger ) {
748         $scope.menuShown = false;
749         if ( !broadcastTrigger ){
750           $scope.$broadcast('hide-menu');
751         }
752       };
753
754
755       $scope.$on('menu-hidden', function() {
756         if ( $scope.hideThenShow ){
757           delete $scope.hideThenShow;
758
759           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
760           $scope.$broadcast('show-menu');
761
762           $scope.menuShown = true;
763         } else {
764           $scope.hideMenu( true );
765
766           if ($scope.col) {
767             //Focus on the menu button
768             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
769           }
770         }
771       });
772
773       $scope.$on('menu-shown', function() {
774         $timeout( function() {
775           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
776           //Focus on the first item
777           gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item', true);
778           delete $scope.colElementPosition;
779           delete $scope.columnElement;
780         }, 200);
781       });
782
783
784       /* Column methods */
785       $scope.sortColumn = function (event, dir) {
786         event.stopPropagation();
787
788         $scope.grid.sortColumn($scope.col, dir, true)
789           .then(function () {
790             $scope.grid.refresh();
791             $scope.hideMenu();
792           });
793       };
794
795       $scope.unsortColumn = function () {
796         $scope.col.unsort();
797
798         $scope.grid.refresh();
799         $scope.hideMenu();
800       };
801
802       //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
803       var setFocusOnHideColumn = function(){
804         $timeout(function(){
805           // Get the UID of the first
806           var focusToGridMenu = function(){
807             return gridUtil.focus.byId('grid-menu', $scope.grid);
808           };
809
810           var thisIndex;
811           $scope.grid.columns.some(function(element, index){
812             if (angular.equals(element, $scope.col)) {
813               thisIndex = index;
814               return true;
815             }
816           });
817
818           var previousVisibleCol;
819           // Try and find the next lower or nearest column to focus on
820           $scope.grid.columns.some(function(element, index){
821             if (!element.visible){
822               return false;
823             } // This columns index is below the current column index
824             else if ( index < thisIndex){
825               previousVisibleCol = element;
826             } // This elements index is above this column index and we haven't found one that is lower
827             else if ( index > thisIndex && !previousVisibleCol) {
828               // This is the next best thing
829               previousVisibleCol = element;
830               // We've found one so use it.
831               return true;
832             } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
833             else if (index > thisIndex && previousVisibleCol) {
834               // We are done.
835               return true;
836             }
837           });
838           // If found then focus on it
839           if (previousVisibleCol){
840             var colClass = previousVisibleCol.getColClass();
841             gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
842               if (reason !== 'canceled'){ // If this is canceled then don't perform the action
843                 //The fallback action is to focus on the grid menu
844                 return focusToGridMenu();
845               }
846             });
847           } else {
848             // Fallback action to focus on the grid menu
849             focusToGridMenu();
850           }
851         });
852       };
853
854       $scope.hideColumn = function () {
855         $scope.col.colDef.visible = false;
856         $scope.col.visible = false;
857
858         $scope.grid.queueGridRefresh();
859         $scope.hideMenu();
860         $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
861         $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
862
863         // We are hiding so the default action of focusing on the button that opened this menu will fail.
864         setFocusOnHideColumn();
865       };
866     },
867
868
869
870     controller: ['$scope', function ($scope) {
871       var self = this;
872
873       $scope.$watch('menuItems', function (n) {
874         self.menuItems = n;
875       });
876     }]
877   };
878
879   return uiGridColumnMenu;
880
881 }]);
882
883 })();
884
885 (function(){
886   'use strict';
887
888   angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
889
890     return {
891       compile: function() {
892         return {
893           pre: function ($scope, $elm, $attrs, controllers) {
894             $scope.col.updateFilters = function( filterable ){
895               $elm.children().remove();
896               if ( filterable ){
897                 var template = $scope.col.filterHeaderTemplate;
898
899                 $elm.append($compile(template)($scope));
900               }
901             };
902
903             $scope.$on( '$destroy', function() {
904               delete $scope.col.updateFilters;
905             });
906           },
907           post: function ($scope, $elm, $attrs, controllers){
908             $scope.aria = i18nService.getSafeText('headerCell.aria');
909             $scope.removeFilter = function(colFilter, index){
910               colFilter.term = null;
911               //Set the focus to the filter input after the action disables the button
912               gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
913             };
914           }
915         };
916       }
917     };
918   }]);
919 })();
920
921 (function () {
922   'use strict';
923
924   angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
925   function ($timeout, gridUtil, uiGridConstants, $compile) {
926     var uiGridFooterCell = {
927       priority: 0,
928       scope: {
929         col: '=',
930         row: '=',
931         renderIndex: '='
932       },
933       replace: true,
934       require: '^uiGrid',
935       compile: function compile(tElement, tAttrs, transclude) {
936         return {
937           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
938             var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
939             $elm.append(cellFooter);
940           },
941           post: function ($scope, $elm, $attrs, uiGridCtrl) {
942             //$elm.addClass($scope.col.getColClass(false));
943             $scope.grid = uiGridCtrl.grid;
944
945             var initColClass = $scope.col.getColClass(false);
946             $elm.addClass(initColClass);
947
948             // apply any footerCellClass
949             var classAdded;
950             var updateClass = function( grid ){
951               var contents = $elm;
952               if ( classAdded ){
953                 contents.removeClass( classAdded );
954                 classAdded = null;
955               }
956   
957               if (angular.isFunction($scope.col.footerCellClass)) {
958                 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
959               }
960               else {
961                 classAdded = $scope.col.footerCellClass;
962               }
963               contents.addClass(classAdded);
964             };
965   
966             if ($scope.col.footerCellClass) {
967               updateClass();
968             }
969
970             $scope.col.updateAggregationValue();
971
972             // Watch for column changes so we can alter the col cell class properly
973 /* shouldn't be needed any more, given track by col.name
974             $scope.$watch('col', function (n, o) {
975               if (n !== o) {
976                 // See if the column's internal class has changed
977                 var newColClass = $scope.col.getColClass(false);
978                 if (newColClass !== initColClass) {
979                   $elm.removeClass(initColClass);
980                   $elm.addClass(newColClass);
981                   initColClass = newColClass;
982                 }
983               }
984             });
985 */
986
987
988             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
989             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
990             // listen for visible rows change and update aggregation values
991             $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
992             $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
993             $scope.$on( '$destroy', dataChangeDereg );
994           }
995         };
996       }
997     };
998
999     return uiGridFooterCell;
1000   }]);
1001
1002 })();
1003
1004 (function () {
1005   'use strict';
1006
1007   angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1008
1009     return {
1010       restrict: 'EA',
1011       replace: true,
1012       // priority: 1000,
1013       require: ['^uiGrid', '^uiGridRenderContainer'],
1014       scope: true,
1015       compile: function ($elm, $attrs) {
1016         return {
1017           pre: function ($scope, $elm, $attrs, controllers) {
1018             var uiGridCtrl = controllers[0];
1019             var containerCtrl = controllers[1];
1020
1021             $scope.grid = uiGridCtrl.grid;
1022             $scope.colContainer = containerCtrl.colContainer;
1023
1024             containerCtrl.footer = $elm;
1025
1026             var footerTemplate = $scope.grid.options.footerTemplate;
1027             gridUtil.getTemplate(footerTemplate)
1028               .then(function (contents) {
1029                 var template = angular.element(contents);
1030
1031                 var newElm = $compile(template)($scope);
1032                 $elm.append(newElm);
1033
1034                 if (containerCtrl) {
1035                   // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1036                   var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
1037
1038                   if (footerViewport) {
1039                     containerCtrl.footerViewport = footerViewport;
1040                   }
1041                 }
1042               });
1043           },
1044
1045           post: function ($scope, $elm, $attrs, controllers) {
1046             var uiGridCtrl = controllers[0];
1047             var containerCtrl = controllers[1];
1048
1049             // gridUtil.logDebug('ui-grid-footer link');
1050
1051             var grid = uiGridCtrl.grid;
1052
1053             // Don't animate footer cells
1054             gridUtil.disableAnimations($elm);
1055
1056             containerCtrl.footer = $elm;
1057
1058             var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
1059             if (footerViewport) {
1060               containerCtrl.footerViewport = footerViewport;
1061             }
1062           }
1063         };
1064       }
1065     };
1066   }]);
1067
1068 })();
1069 (function () {
1070   'use strict';
1071
1072   angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1073
1074     return {
1075       restrict: 'EA',
1076       replace: true,
1077       // priority: 1000,
1078       require: '^uiGrid',
1079       scope: true,
1080       compile: function ($elm, $attrs) {
1081         return {
1082           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1083
1084             $scope.grid = uiGridCtrl.grid;
1085
1086
1087
1088             var footerTemplate = $scope.grid.options.gridFooterTemplate;
1089             gridUtil.getTemplate(footerTemplate)
1090               .then(function (contents) {
1091                 var template = angular.element(contents);
1092
1093                 var newElm = $compile(template)($scope);
1094                 $elm.append(newElm);
1095               });
1096           },
1097
1098           post: function ($scope, $elm, $attrs, controllers) {
1099
1100           }
1101         };
1102       }
1103     };
1104   }]);
1105
1106 })();
1107 (function(){
1108   'use strict';
1109
1110   angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
1111     var defaultTemplate = 'ui-grid/ui-grid-group-panel';
1112
1113     return {
1114       restrict: 'EA',
1115       replace: true,
1116       require: '?^uiGrid',
1117       scope: false,
1118       compile: function($elm, $attrs) {
1119         return {
1120           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1121             var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
1122
1123              gridUtil.getTemplate(groupPanelTemplate)
1124               .then(function (contents) {
1125                 var template = angular.element(contents);
1126                 
1127                 var newElm = $compile(template)($scope);
1128                 $elm.append(newElm);
1129               });
1130           },
1131
1132           post: function ($scope, $elm, $attrs, uiGridCtrl) {
1133             $elm.bind('$destroy', function() {
1134               // scrollUnbinder();
1135             });
1136           }
1137         };
1138       }
1139     };
1140   }]);
1141
1142 })();
1143 (function(){
1144   'use strict';
1145
1146   angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
1147   function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
1148     // Do stuff after mouse has been down this many ms on the header cell
1149     var mousedownTimeout = 500;
1150     var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
1151
1152     var uiGridHeaderCell = {
1153       priority: 0,
1154       scope: {
1155         col: '=',
1156         row: '=',
1157         renderIndex: '='
1158       },
1159       require: ['^uiGrid', '^uiGridRenderContainer'],
1160       replace: true,
1161       compile: function() {
1162         return {
1163           pre: function ($scope, $elm, $attrs) {
1164             var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1165             $elm.append(cellHeader);
1166           },
1167
1168           post: function ($scope, $elm, $attrs, controllers) {
1169             var uiGridCtrl = controllers[0];
1170             var renderContainerCtrl = controllers[1];
1171
1172             $scope.i18n = {
1173               headerCell: i18nService.getSafeText('headerCell'),
1174               sort: i18nService.getSafeText('sort')
1175             };
1176             $scope.isSortPriorityVisible = function() {
1177               //show sort priority if column is sorted and there is at least one other sorted column
1178               return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index){
1179                   return angular.isNumber(element.sort.priority) && element !== $scope.col;
1180                 });
1181             };
1182             $scope.getSortDirectionAriaLabel = function(){
1183               var col = $scope.col;
1184               //Trying to recreate this sort of thing but it was getting messy having it in the template.
1185               //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1186               var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1187               var label = sortDirectionText;
1188
1189               if ($scope.isSortPriorityVisible()) {
1190                 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1191               }
1192               return label;
1193             };
1194
1195             $scope.grid = uiGridCtrl.grid;
1196
1197             $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
1198
1199             var initColClass = $scope.col.getColClass(false);
1200             $elm.addClass(initColClass);
1201
1202             // Hide the menu by default
1203             $scope.menuShown = false;
1204
1205             // Put asc and desc sort directions in scope
1206             $scope.asc = uiGridConstants.ASC;
1207             $scope.desc = uiGridConstants.DESC;
1208
1209             // Store a reference to menu element
1210             var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1211
1212             var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
1213
1214
1215             // apply any headerCellClass
1216             var classAdded;
1217             var previousMouseX;
1218
1219             // filter watchers
1220             var filterDeregisters = [];
1221
1222
1223             /*
1224              * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
1225              * Once we have a down event, we need to work out whether we have a click, a drag, or a
1226              * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
1227              * we ignore it.  A hold would open the menu.
1228              *
1229              * So, on down event, we put in place handlers for move and up events, and a timer.  If the
1230              * timer expires before we see a move or up, then we have a long press and hence a column menu open.
1231              * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
1232              * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
1233              * will handle it.
1234              *
1235              * To deal with touch enabled devices that also have mice, we only create our handlers when
1236              * we get the down event, and we create the corresponding handlers - if we're touchstart then
1237              * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
1238              *
1239              * We also suppress the click action whilst this is happening - otherwise after the mouseup there
1240              * will be a click event and that can cause the column menu to close
1241              *
1242              */
1243
1244             $scope.downFn = function( event ){
1245               event.stopPropagation();
1246
1247               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
1248                 event = event.originalEvent;
1249               }
1250
1251               // Don't show the menu if it's not the left button
1252               if (event.button && event.button !== 0) {
1253                 return;
1254               }
1255               previousMouseX = event.pageX;
1256
1257               $scope.mousedownStartTime = (new Date()).getTime();
1258               $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
1259
1260               $scope.mousedownTimeout.then(function () {
1261                 if ( $scope.colMenu ) {
1262                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
1263                 }
1264               });
1265
1266               uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
1267
1268               $scope.offAllEvents();
1269               if ( event.type === 'touchstart'){
1270                 $document.on('touchend', $scope.upFn);
1271                 $document.on('touchmove', $scope.moveFn);
1272               } else if ( event.type === 'mousedown' ){
1273                 $document.on('mouseup', $scope.upFn);
1274                 $document.on('mousemove', $scope.moveFn);
1275               }
1276             };
1277
1278             $scope.upFn = function( event ){
1279               event.stopPropagation();
1280               $timeout.cancel($scope.mousedownTimeout);
1281               $scope.offAllEvents();
1282               $scope.onDownEvents(event.type);
1283
1284               var mousedownEndTime = (new Date()).getTime();
1285               var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
1286
1287               if (mousedownTime > mousedownTimeout) {
1288                 // long click, handled above with mousedown
1289               }
1290               else {
1291                 // short click
1292                 if ( $scope.sortable ){
1293                   $scope.handleClick(event);
1294                 }
1295               }
1296             };
1297
1298             $scope.moveFn = function( event ){
1299               // Chrome is known to fire some bogus move events.
1300               var changeValue = event.pageX - previousMouseX;
1301               if ( changeValue === 0 ){ return; }
1302
1303               // we're a move, so do nothing and leave for column move (if enabled) to take over
1304               $timeout.cancel($scope.mousedownTimeout);
1305               $scope.offAllEvents();
1306               $scope.onDownEvents(event.type);
1307             };
1308
1309             $scope.clickFn = function ( event ){
1310               event.stopPropagation();
1311               $contentsElm.off('click', $scope.clickFn);
1312             };
1313
1314
1315             $scope.offAllEvents = function(){
1316               $contentsElm.off('touchstart', $scope.downFn);
1317               $contentsElm.off('mousedown', $scope.downFn);
1318
1319               $document.off('touchend', $scope.upFn);
1320               $document.off('mouseup', $scope.upFn);
1321
1322               $document.off('touchmove', $scope.moveFn);
1323               $document.off('mousemove', $scope.moveFn);
1324
1325               $contentsElm.off('click', $scope.clickFn);
1326             };
1327
1328             $scope.onDownEvents = function( type ){
1329               // If there is a previous event, then wait a while before
1330               // activating the other mode - i.e. if the last event was a touch event then
1331               // don't enable mouse events for a wee while (500ms or so)
1332               // Avoids problems with devices that emulate mouse events when you have touch events
1333
1334               switch (type){
1335                 case 'touchmove':
1336                 case 'touchend':
1337                   $contentsElm.on('click', $scope.clickFn);
1338                   $contentsElm.on('touchstart', $scope.downFn);
1339                   $timeout(function(){
1340                     $contentsElm.on('mousedown', $scope.downFn);
1341                   }, changeModeTimeout);
1342                   break;
1343                 case 'mousemove':
1344                 case 'mouseup':
1345                   $contentsElm.on('click', $scope.clickFn);
1346                   $contentsElm.on('mousedown', $scope.downFn);
1347                   $timeout(function(){
1348                     $contentsElm.on('touchstart', $scope.downFn);
1349                   }, changeModeTimeout);
1350                   break;
1351                 default:
1352                   $contentsElm.on('click', $scope.clickFn);
1353                   $contentsElm.on('touchstart', $scope.downFn);
1354                   $contentsElm.on('mousedown', $scope.downFn);
1355               }
1356             };
1357
1358
1359             var updateHeaderOptions = function( grid ){
1360               var contents = $elm;
1361               if ( classAdded ){
1362                 contents.removeClass( classAdded );
1363                 classAdded = null;
1364               }
1365
1366               if (angular.isFunction($scope.col.headerCellClass)) {
1367                 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
1368               }
1369               else {
1370                 classAdded = $scope.col.headerCellClass;
1371               }
1372               contents.addClass(classAdded);
1373
1374               $timeout(function (){
1375                 var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1376                 $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1377               });
1378
1379               // Figure out whether this column is sortable or not
1380               if ($scope.col.enableSorting) {
1381                 $scope.sortable = true;
1382               }
1383               else {
1384                 $scope.sortable = false;
1385               }
1386
1387               // Figure out whether this column is filterable or not
1388               var oldFilterable = $scope.filterable;
1389               if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1390                 $scope.filterable = true;
1391               }
1392               else {
1393                 $scope.filterable = false;
1394               }
1395
1396               if ( oldFilterable !== $scope.filterable){
1397                 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1398                   $scope.col.updateFilters($scope.filterable);
1399                 }
1400
1401                 // if column is filterable add a filter watcher
1402                 if ($scope.filterable) {
1403                   $scope.col.filters.forEach( function(filter, i) {
1404                     filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1405                       if (n !== o) {
1406                         uiGridCtrl.grid.api.core.raise.filterChanged();
1407                         uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1408                         uiGridCtrl.grid.queueGridRefresh();
1409                       }
1410                     }));
1411                   });
1412                   $scope.$on('$destroy', function() {
1413                     filterDeregisters.forEach( function(filterDeregister) {
1414                       filterDeregister();
1415                     });
1416                   });
1417                 } else {
1418                   filterDeregisters.forEach( function(filterDeregister) {
1419                     filterDeregister();
1420                   });
1421                 }
1422
1423               }
1424
1425               // figure out whether we support column menus
1426               if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1427                       $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1428                 $scope.colMenu = true;
1429               } else {
1430                 $scope.colMenu = false;
1431               }
1432
1433               /**
1434               * @ngdoc property
1435               * @name enableColumnMenu
1436               * @propertyOf ui.grid.class:GridOptions.columnDef
1437               * @description if column menus are enabled, controls the column menus for this specific
1438               * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1439               * using this option. If gridOptions.enableColumnMenus === false then you get no column
1440               * menus irrespective of the value of this option ).  Defaults to true.
1441               *
1442               */
1443               /**
1444               * @ngdoc property
1445               * @name enableColumnMenus
1446               * @propertyOf ui.grid.class:GridOptions.columnDef
1447               * @description Override for column menus everywhere - if set to false then you get no
1448               * column menus.  Defaults to true.
1449               *
1450               */
1451
1452               $scope.offAllEvents();
1453
1454               if ($scope.sortable || $scope.colMenu) {
1455                 $scope.onDownEvents();
1456
1457                 $scope.$on('$destroy', function () {
1458                   $scope.offAllEvents();
1459                 });
1460               }
1461             };
1462
1463 /*
1464             $scope.$watch('col', function (n, o) {
1465               if (n !== o) {
1466                 // See if the column's internal class has changed
1467                 var newColClass = $scope.col.getColClass(false);
1468                 if (newColClass !== initColClass) {
1469                   $elm.removeClass(initColClass);
1470                   $elm.addClass(newColClass);
1471                   initColClass = newColClass;
1472                 }
1473               }
1474             });
1475 */
1476             updateHeaderOptions();
1477
1478             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
1479             var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
1480
1481             $scope.$on( '$destroy', dataChangeDereg );
1482
1483             $scope.handleClick = function(event) {
1484               // If the shift key is being held down, add this column to the sort
1485               var add = false;
1486               if (event.shiftKey) {
1487                 add = true;
1488               }
1489
1490               // Sort this column then rebuild the grid's rows
1491               uiGridCtrl.grid.sortColumn($scope.col, add)
1492                 .then(function () {
1493                   if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
1494                   uiGridCtrl.grid.refresh();
1495                 });
1496             };
1497
1498
1499             $scope.toggleMenu = function(event) {
1500               event.stopPropagation();
1501
1502               // If the menu is already showing...
1503               if (uiGridCtrl.columnMenuScope.menuShown) {
1504                 // ... and we're the column the menu is on...
1505                 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1506                   // ... hide it
1507                   uiGridCtrl.columnMenuScope.hideMenu();
1508                 }
1509                 // ... and we're NOT the column the menu is on
1510                 else {
1511                   // ... move the menu to our column
1512                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1513                 }
1514               }
1515               // If the menu is NOT showing
1516               else {
1517                 // ... show it on our column
1518                 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1519               }
1520             };
1521           }
1522         };
1523       }
1524     };
1525
1526     return uiGridHeaderCell;
1527   }]);
1528
1529 })();
1530
1531 (function(){
1532   'use strict';
1533
1534   angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
1535     function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1536     var defaultTemplate = 'ui-grid/ui-grid-header';
1537     var emptyTemplate = 'ui-grid/ui-grid-no-header';
1538
1539     return {
1540       restrict: 'EA',
1541       // templateUrl: 'ui-grid/ui-grid-header',
1542       replace: true,
1543       // priority: 1000,
1544       require: ['^uiGrid', '^uiGridRenderContainer'],
1545       scope: true,
1546       compile: function($elm, $attrs) {
1547         return {
1548           pre: function ($scope, $elm, $attrs, controllers) {
1549             var uiGridCtrl = controllers[0];
1550             var containerCtrl = controllers[1];
1551
1552             $scope.grid = uiGridCtrl.grid;
1553             $scope.colContainer = containerCtrl.colContainer;
1554
1555             updateHeaderReferences();
1556             
1557             var headerTemplate;
1558             if (!$scope.grid.options.showHeader) {
1559               headerTemplate = emptyTemplate;
1560             }
1561             else {
1562               headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
1563             }
1564
1565             gridUtil.getTemplate(headerTemplate)
1566               .then(function (contents) {
1567                 var template = angular.element(contents);
1568                 
1569                 var newElm = $compile(template)($scope);
1570                 $elm.replaceWith(newElm);
1571
1572                 // And update $elm to be the new element
1573                 $elm = newElm;
1574
1575                 updateHeaderReferences();
1576
1577                 if (containerCtrl) {
1578                   // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1579                   var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1580
1581
1582                   if (headerViewport) {
1583                     containerCtrl.headerViewport = headerViewport;
1584                     angular.element(headerViewport).on('scroll', scrollHandler);
1585                     $scope.$on('$destroy', function () {
1586                       angular.element(headerViewport).off('scroll', scrollHandler);
1587                     });
1588                   }
1589                 }
1590
1591                 $scope.grid.queueRefresh();
1592               });
1593
1594             function updateHeaderReferences() {
1595               containerCtrl.header = containerCtrl.colContainer.header = $elm;
1596
1597               var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
1598
1599               if (headerCanvases.length > 0) {
1600                 containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
1601               }
1602               else {
1603                 containerCtrl.headerCanvas = null;
1604               }
1605             }
1606
1607             function scrollHandler(evt) {
1608               if (uiGridCtrl.grid.isScrollingHorizontally) {
1609                 return;
1610               }
1611               var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
1612               var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
1613
1614               var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
1615               scrollEvent.newScrollLeft = newScrollLeft;
1616               if ( horizScrollPercentage > -1 ){
1617                 scrollEvent.x = { percentage: horizScrollPercentage };
1618               }
1619
1620               uiGridCtrl.grid.scrollContainers(null, scrollEvent);
1621             }
1622           },
1623
1624           post: function ($scope, $elm, $attrs, controllers) {
1625             var uiGridCtrl = controllers[0];
1626             var containerCtrl = controllers[1];
1627
1628             // gridUtil.logDebug('ui-grid-header link');
1629
1630             var grid = uiGridCtrl.grid;
1631
1632             // Don't animate header cells
1633             gridUtil.disableAnimations($elm);
1634
1635             function updateColumnWidths() {
1636               // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
1637               // already being populated correctly
1638
1639               var columnCache = containerCtrl.colContainer.visibleColumnCache;
1640               
1641               // Build the CSS
1642               // uiGridCtrl.grid.columns.forEach(function (column) {
1643               var ret = '';
1644               var canvasWidth = 0;
1645               columnCache.forEach(function (column) {
1646                 ret = ret + column.getColClassDefinition();
1647                 canvasWidth += column.drawnWidth;
1648               });
1649
1650               containerCtrl.colContainer.canvasWidth = canvasWidth;
1651               
1652               // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1653               return ret;
1654             }
1655             
1656             containerCtrl.header = $elm;
1657             
1658             var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1659             if (headerViewport) {
1660               containerCtrl.headerViewport = headerViewport;
1661             }
1662
1663             //todo: remove this if by injecting gridCtrl into unit tests
1664             if (uiGridCtrl) {
1665               uiGridCtrl.grid.registerStyleComputation({
1666                 priority: 15,
1667                 func: updateColumnWidths
1668               });
1669             }
1670           }
1671         };
1672       }
1673     };
1674   }]);
1675
1676 })();
1677
1678 (function(){
1679
1680 angular.module('ui.grid')
1681 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
1682   /**
1683    *  @ngdoc service
1684    *  @name ui.grid.gridMenuService
1685    *
1686    *  @description Methods for working with the grid menu
1687    */
1688
1689   var service = {
1690     /**
1691      * @ngdoc method
1692      * @methodOf ui.grid.gridMenuService
1693      * @name initialize
1694      * @description Sets up the gridMenu. Most importantly, sets our
1695      * scope onto the grid object as grid.gridMenuScope, allowing us
1696      * to operate when passed only the grid.  Second most importantly,
1697      * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1698      * on the core api.
1699      * @param {$scope} $scope the scope of this gridMenu
1700      * @param {Grid} grid the grid to which this gridMenu is associated
1701      */
1702     initialize: function( $scope, grid ){
1703       grid.gridMenuScope = $scope;
1704       $scope.grid = grid;
1705       $scope.registeredMenuItems = [];
1706
1707       // not certain this is needed, but would be bad to create a memory leak
1708       $scope.$on('$destroy', function() {
1709         if ( $scope.grid && $scope.grid.gridMenuScope ){
1710           $scope.grid.gridMenuScope = null;
1711         }
1712         if ( $scope.grid ){
1713           $scope.grid = null;
1714         }
1715         if ( $scope.registeredMenuItems ){
1716           $scope.registeredMenuItems = null;
1717         }
1718       });
1719
1720       $scope.registeredMenuItems = [];
1721
1722       /**
1723        * @ngdoc function
1724        * @name addToGridMenu
1725        * @methodOf ui.grid.core.api:PublicApi
1726        * @description add items to the grid menu.  Used by features
1727        * to add their menu items if they are enabled, can also be used by
1728        * end users to add menu items.  This method has the advantage of allowing
1729        * remove again, which can simplify management of which items are included
1730        * in the menu when.  (Noting that in most cases the shown and active functions
1731        * provide a better way to handle visibility of menu items)
1732        * @param {Grid} grid the grid on which we are acting
1733        * @param {array} items menu items in the format as described in the tutorial, with
1734        * the added note that if you want to use remove you must also specify an `id` field,
1735        * which is provided when you want to remove an item.  The id should be unique.
1736        *
1737        */
1738       grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1739
1740       /**
1741        * @ngdoc function
1742        * @name removeFromGridMenu
1743        * @methodOf ui.grid.core.api:PublicApi
1744        * @description Remove an item from the grid menu based on a provided id. Assumes
1745        * that the id is unique, removes only the last instance of that id. Does nothing if
1746        * the specified id is not found
1747        * @param {Grid} grid the grid on which we are acting
1748        * @param {string} id the id we'd like to remove from the menu
1749        *
1750        */
1751       grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1752     },
1753
1754
1755     /**
1756      * @ngdoc function
1757      * @name addToGridMenu
1758      * @propertyOf ui.grid.gridMenuService
1759      * @description add items to the grid menu.  Used by features
1760      * to add their menu items if they are enabled, can also be used by
1761      * end users to add menu items.  This method has the advantage of allowing
1762      * remove again, which can simplify management of which items are included
1763      * in the menu when.  (Noting that in most cases the shown and active functions
1764      * provide a better way to handle visibility of menu items)
1765      * @param {Grid} grid the grid on which we are acting
1766      * @param {array} items menu items in the format as described in the tutorial, with
1767      * the added note that if you want to use remove you must also specify an `id` field,
1768      * which is provided when you want to remove an item.  The id should be unique.
1769      *
1770      */
1771     addToGridMenu: function( grid, menuItems ) {
1772       if ( !angular.isArray( menuItems ) ) {
1773         gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1774       } else {
1775         if ( grid.gridMenuScope ){
1776           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1777           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1778         } else {
1779           gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
1780         }
1781       }
1782     },
1783
1784
1785     /**
1786      * @ngdoc function
1787      * @name removeFromGridMenu
1788      * @methodOf ui.grid.gridMenuService
1789      * @description Remove an item from the grid menu based on a provided id.  Assumes
1790      * that the id is unique, removes only the last instance of that id.  Does nothing if
1791      * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
1792      * then do nothing silently - the desired result is those menu items not be present and they
1793      * aren't.
1794      * @param {Grid} grid the grid on which we are acting
1795      * @param {string} id the id we'd like to remove from the menu
1796      *
1797      */
1798     removeFromGridMenu: function( grid, id ){
1799       var foundIndex = -1;
1800
1801       if ( grid && grid.gridMenuScope ){
1802         grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1803           if ( value.id === id ){
1804             if (foundIndex > -1) {
1805               gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1806             } else {
1807
1808               foundIndex = index;
1809             }
1810           }
1811         });
1812       }
1813
1814       if ( foundIndex > -1 ){
1815         grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1816       }
1817     },
1818
1819
1820     /**
1821      * @ngdoc array
1822      * @name gridMenuCustomItems
1823      * @propertyOf ui.grid.class:GridOptions
1824      * @description (optional) An array of menu items that should be added to
1825      * the gridMenu.  Follow the format documented in the tutorial for column
1826      * menu customisation.  The context provided to the action function will
1827      * include context.grid.  An alternative if working with dynamic menus is to use the
1828      * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1829      * some of the management of items for you.
1830      *
1831      */
1832     /**
1833      * @ngdoc boolean
1834      * @name gridMenuShowHideColumns
1835      * @propertyOf ui.grid.class:GridOptions
1836      * @description true by default, whether the grid menu should allow hide/show
1837      * of columns
1838      *
1839      */
1840     /**
1841      * @ngdoc method
1842      * @methodOf ui.grid.gridMenuService
1843      * @name getMenuItems
1844      * @description Decides the menu items to show in the menu.  This is a
1845      * combination of:
1846      *
1847      * - the default menu items that are always included,
1848      * - any menu items that have been provided through the addMenuItem api. These
1849      *   are typically added by features within the grid
1850      * - any menu items included in grid.options.gridMenuCustomItems.  These can be
1851      *   changed dynamically, as they're always recalculated whenever we show the
1852      *   menu
1853      * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1854      * the information that we need
1855      * @returns {array} an array of menu items that can be shown
1856      */
1857     getMenuItems: function( $scope ) {
1858       var menuItems = [
1859         // this is where we add any menu items we want to always include
1860       ];
1861
1862       if ( $scope.grid.options.gridMenuCustomItems ){
1863         if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1864           gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1865         } else {
1866           menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1867         }
1868       }
1869
1870       var clearFilters = [{
1871         title: i18nService.getSafeText('gridMenu.clearAllFilters'),
1872         action: function ($event) {
1873           $scope.grid.clearAllFilters();
1874         },
1875         shown: function() {
1876           return $scope.grid.options.enableFiltering;
1877         },
1878         order: 100
1879       }];
1880       menuItems = menuItems.concat( clearFilters );
1881
1882       menuItems = menuItems.concat( $scope.registeredMenuItems );
1883
1884       if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1885         menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1886       }
1887
1888       menuItems.sort(function(a, b){
1889         return a.order - b.order;
1890       });
1891
1892       return menuItems;
1893     },
1894
1895
1896     /**
1897      * @ngdoc array
1898      * @name gridMenuTitleFilter
1899      * @propertyOf ui.grid.class:GridOptions
1900      * @description (optional) A function that takes a title string
1901      * (usually the col.displayName), and converts it into a display value.  The function
1902      * must return either a string or a promise.
1903      *
1904      * Used for internationalization of the grid menu column names - for angular-translate
1905      * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1906      * function
1907      * @example
1908      * <pre>
1909      *   gridOptions = {
1910      *     gridMenuTitleFilter: $translate
1911      *   }
1912      * </pre>
1913      */
1914     /**
1915      * @ngdoc method
1916      * @methodOf ui.grid.gridMenuService
1917      * @name showHideColumns
1918      * @description Adds two menu items for each of the columns in columnDefs.  One
1919      * menu item for hide, one menu item for show.  Each is visible when appropriate
1920      * (show when column is not visible, hide when column is visible).  Each toggles
1921      * the visible property on the columnDef using toggleColumnVisibility
1922      * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1923      */
1924     showHideColumns: function( $scope ){
1925       var showHideColumns = [];
1926       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1927         return showHideColumns;
1928       }
1929
1930       // add header for columns
1931       showHideColumns.push({
1932         title: i18nService.getSafeText('gridMenu.columns'),
1933         order: 300
1934       });
1935
1936       $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1937
1938       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1939         if ( colDef.enableHiding !== false ){
1940           // add hide menu item - shows an OK icon as we only show when column is already visible
1941           var menuItem = {
1942             icon: 'ui-grid-icon-ok',
1943             action: function($event) {
1944               $event.stopPropagation();
1945               service.toggleColumnVisibility( this.context.gridCol );
1946             },
1947             shown: function() {
1948               return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1949             },
1950             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1951             leaveOpen: true,
1952             order: 301 + index * 2
1953           };
1954           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1955           showHideColumns.push( menuItem );
1956
1957           // add show menu item - shows no icon as we only show when column is invisible
1958           menuItem = {
1959             icon: 'ui-grid-icon-cancel',
1960             action: function($event) {
1961               $event.stopPropagation();
1962               service.toggleColumnVisibility( this.context.gridCol );
1963             },
1964             shown: function() {
1965               return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1966             },
1967             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1968             leaveOpen: true,
1969             order: 301 + index * 2 + 1
1970           };
1971           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1972           showHideColumns.push( menuItem );
1973         }
1974       });
1975       return showHideColumns;
1976     },
1977
1978
1979     /**
1980      * @ngdoc method
1981      * @methodOf ui.grid.gridMenuService
1982      * @name setMenuItemTitle
1983      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1984      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1985      * putting the result into the title
1986      * @param {object} menuItem the menuItem we want to put the title on
1987      * @param {object} colDef the colDef from which we can get displayName, name or field
1988      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1989      *
1990      */
1991     setMenuItemTitle: function( menuItem, colDef, grid ){
1992       var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
1993
1994       if ( typeof(title) === 'string' ){
1995         menuItem.title = title;
1996       } else if ( title.then ){
1997         // must be a promise
1998         menuItem.title = "";
1999         title.then( function( successValue ) {
2000           menuItem.title = successValue;
2001         }, function( errorValue ) {
2002           menuItem.title = errorValue;
2003         });
2004       } else {
2005         gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
2006         menuItem.title = 'badconfig';
2007       }
2008     },
2009
2010     /**
2011      * @ngdoc method
2012      * @methodOf ui.grid.gridMenuService
2013      * @name toggleColumnVisibility
2014      * @description Toggles the visibility of an individual column.  Expects to be
2015      * provided a context that has on it a gridColumn, which is the column that
2016      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
2017      * @param {GridCol} gridCol the column that we want to toggle
2018      *
2019      */
2020     toggleColumnVisibility: function( gridCol ) {
2021       gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
2022
2023       gridCol.grid.refresh();
2024       gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
2025       gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
2026     }
2027   };
2028
2029   return service;
2030 }])
2031
2032
2033
2034 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
2035 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
2036
2037   return {
2038     priority: 0,
2039     scope: true,
2040     require: ['^uiGrid'],
2041     templateUrl: 'ui-grid/ui-grid-menu-button',
2042     replace: true,
2043
2044     link: function ($scope, $elm, $attrs, controllers) {
2045       var uiGridCtrl = controllers[0];
2046
2047       // For the aria label
2048       $scope.i18n = {
2049         aria: i18nService.getSafeText('gridMenu.aria')
2050       };
2051
2052       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
2053
2054       $scope.shown = false;
2055
2056       $scope.toggleMenu = function () {
2057         if ( $scope.shown ){
2058           $scope.$broadcast('hide-menu');
2059           $scope.shown = false;
2060         } else {
2061           $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
2062           $scope.$broadcast('show-menu');
2063           $scope.shown = true;
2064         }
2065       };
2066
2067       $scope.$on('menu-hidden', function() {
2068         $scope.shown = false;
2069         gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
2070       });
2071     }
2072   };
2073
2074 }]);
2075
2076 })();
2077
2078 (function(){
2079
2080 /**
2081  * @ngdoc directive
2082  * @name ui.grid.directive:uiGridMenu
2083  * @element style
2084  * @restrict A
2085  *
2086  * @description
2087  * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2088  *
2089  * @example
2090  <doc:example module="app">
2091  <doc:source>
2092  <script>
2093  var app = angular.module('app', ['ui.grid']);
2094
2095  app.controller('MainCtrl', ['$scope', function ($scope) {
2096
2097  }]);
2098  </script>
2099
2100  <div ng-controller="MainCtrl">
2101    <div ui-grid-menu shown="true"  ></div>
2102  </div>
2103  </doc:source>
2104  <doc:scenario>
2105  </doc:scenario>
2106  </doc:example>
2107  */
2108 angular.module('ui.grid')
2109
2110 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
2111 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
2112   var uiGridMenu = {
2113     priority: 0,
2114     scope: {
2115       // shown: '&',
2116       menuItems: '=',
2117       autoHide: '=?'
2118     },
2119     require: '?^uiGrid',
2120     templateUrl: 'ui-grid/uiGridMenu',
2121     replace: false,
2122     link: function ($scope, $elm, $attrs, uiGridCtrl) {
2123       $scope.dynamicStyles = '';
2124       if (uiGridCtrl && uiGridCtrl.grid && uiGridCtrl.grid.options && uiGridCtrl.grid.options.gridMenuTemplate) {
2125         var gridMenuTemplate = uiGridCtrl.grid.options.gridMenuTemplate;
2126         gridUtil.getTemplate(gridMenuTemplate).then(function (contents) {
2127           var template = angular.element(contents);
2128           var newElm = $compile(template)($scope);
2129           $elm.replaceWith(newElm);
2130         });
2131       }
2132
2133       var setupHeightStyle = function(gridHeight) {
2134         //menu appears under header row, so substract that height from it's total
2135         // additional 20px for general padding
2136         var gridMenuMaxHeight = gridHeight - uiGridCtrl.grid.headerHeight - 20;
2137         $scope.dynamicStyles = [
2138           '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
2139           'max-height: ' + gridMenuMaxHeight + 'px;',
2140           '}'
2141         ].join(' ');
2142       };
2143
2144       if (uiGridCtrl) {
2145         setupHeightStyle(uiGridCtrl.grid.gridHeight);
2146         uiGridCtrl.grid.api.core.on.gridDimensionChanged($scope, function(oldGridHeight, oldGridWidth, newGridHeight, newGridWidth) {
2147           setupHeightStyle(newGridHeight);
2148         });
2149       }
2150
2151       $scope.i18n = {
2152         close: i18nService.getSafeText('columnMenu.close')
2153       };
2154
2155     // *** Show/Hide functions ******
2156       $scope.showMenu = function(event, args) {
2157         if ( !$scope.shown ){
2158
2159           /*
2160            * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
2161            * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
2162            * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
2163            * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
2164            * on scroll events.
2165            *
2166            * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
2167            * like they do, but angular has a default $animate provider that is just a stub, and that's what's
2168            * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
2169            * angular-translate.js, it's not using it.  You need to test animations in an external application.
2170            */
2171           $scope.shown = true;
2172
2173           $timeout( function() {
2174             $scope.shownMid = true;
2175             $scope.$emit('menu-shown');
2176           });
2177         } else if ( !$scope.shownMid ) {
2178           // we're probably doing a hide then show, so we don't need to wait for ng-if
2179           $scope.shownMid = true;
2180           $scope.$emit('menu-shown');
2181         }
2182
2183         var docEventType = 'click';
2184         if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
2185           docEventType = args.originalEvent.type;
2186         }
2187
2188         // Turn off an existing document click handler
2189         angular.element(document).off('click touchstart', applyHideMenu);
2190         $elm.off('keyup', checkKeyUp);
2191         $elm.off('keydown', checkKeyDown);
2192
2193         // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
2194         $timeout(function() {
2195           angular.element(document).on(docEventType, applyHideMenu);
2196           $elm.on('keyup', checkKeyUp);
2197           $elm.on('keydown', checkKeyDown);
2198
2199         });
2200         //automatically set the focus to the first button element in the now open menu.
2201         gridUtil.focus.bySelector($elm, 'button[type=button]', true);
2202       };
2203
2204
2205       $scope.hideMenu = function(event) {
2206         if ( $scope.shown ){
2207           /*
2208            * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
2209            * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
2210            * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
2211            *
2212            * The user may have clicked on the menu again whilst
2213            * we're waiting, so we check that the mid isn't shown before applying the ng-if.
2214            */
2215           $scope.shownMid = false;
2216           $timeout( function() {
2217             if ( !$scope.shownMid ){
2218               $scope.shown = false;
2219               $scope.$emit('menu-hidden');
2220             }
2221           }, 200);
2222         }
2223
2224         angular.element(document).off('click touchstart', applyHideMenu);
2225         $elm.off('keyup', checkKeyUp);
2226         $elm.off('keydown', checkKeyDown);
2227       };
2228
2229       $scope.$on('hide-menu', function (event, args) {
2230         $scope.hideMenu(event, args);
2231       });
2232
2233       $scope.$on('show-menu', function (event, args) {
2234         $scope.showMenu(event, args);
2235       });
2236
2237
2238     // *** Auto hide when click elsewhere ******
2239       var applyHideMenu = function(){
2240         if ($scope.shown) {
2241           $scope.$apply(function () {
2242             $scope.hideMenu();
2243           });
2244         }
2245       };
2246
2247       // close menu on ESC and keep tab cyclical
2248       var checkKeyUp = function(event) {
2249         if (event.keyCode === 27) {
2250           $scope.hideMenu();
2251         }
2252       };
2253
2254       var checkKeyDown = function(event) {
2255         var setFocus = function(elm) {
2256           elm.focus();
2257           event.preventDefault();
2258           return false;
2259         };
2260         if (event.keyCode === 9) {
2261           var firstMenuItem, lastMenuItem;
2262           var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
2263           if (menuItemButtons.length > 0) {
2264             firstMenuItem = menuItemButtons[0];
2265             lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
2266             if (event.target === lastMenuItem && !event.shiftKey) {
2267               setFocus(firstMenuItem);
2268             } else if (event.target === firstMenuItem && event.shiftKey) {
2269               setFocus(lastMenuItem);
2270             }
2271           }
2272         }
2273       };
2274
2275       if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
2276         $scope.autoHide = true;
2277       }
2278
2279       if ($scope.autoHide) {
2280         angular.element($window).on('resize', applyHideMenu);
2281       }
2282
2283       $scope.$on('$destroy', function unbindEvents() {
2284         angular.element($window).off('resize', applyHideMenu);
2285         angular.element(document).off('click touchstart', applyHideMenu);
2286         $elm.off('keyup', checkKeyUp);
2287         $elm.off('keydown', checkKeyDown);
2288       });
2289
2290       if (uiGridCtrl) {
2291        $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
2292       }
2293
2294       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2295     }
2296   };
2297
2298   return uiGridMenu;
2299 }])
2300
2301 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2302   var uiGridMenuItem = {
2303     priority: 0,
2304     scope: {
2305       name: '=',
2306       active: '=',
2307       action: '=',
2308       icon: '=',
2309       shown: '=',
2310       context: '=',
2311       templateUrl: '=',
2312       leaveOpen: '=',
2313       screenReaderOnly: '='
2314     },
2315     require: ['?^uiGrid'],
2316     templateUrl: 'ui-grid/uiGridMenuItem',
2317     replace: false,
2318     compile: function() {
2319       return {
2320         pre: function ($scope, $elm) {
2321           if ($scope.templateUrl) {
2322             gridUtil.getTemplate($scope.templateUrl)
2323                 .then(function (contents) {
2324                   var template = angular.element(contents);
2325
2326                   var newElm = $compile(template)($scope);
2327                   $elm.replaceWith(newElm);
2328                 });
2329           }
2330         },
2331         post: function ($scope, $elm, $attrs, controllers) {
2332           var uiGridCtrl = controllers[0];
2333
2334           // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
2335           // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
2336           //   throw new TypeError("$scope.shown is defined but not a function");
2337           // }
2338           if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
2339             $scope.shown = function() { return true; };
2340           }
2341
2342           $scope.itemShown = function () {
2343             var context = {};
2344             if ($scope.context) {
2345               context.context = $scope.context;
2346             }
2347
2348             if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2349               context.grid = uiGridCtrl.grid;
2350             }
2351
2352             return $scope.shown.call(context);
2353           };
2354
2355           $scope.itemAction = function($event,title) {
2356             $event.stopPropagation();
2357
2358             if (typeof($scope.action) === 'function') {
2359               var context = {};
2360
2361               if ($scope.context) {
2362                 context.context = $scope.context;
2363               }
2364
2365               // Add the grid to the function call context if the uiGrid controller is present
2366               if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2367                 context.grid = uiGridCtrl.grid;
2368               }
2369
2370               $scope.action.call(context, $event, title);
2371
2372               if ( !$scope.leaveOpen ){
2373                 $scope.$emit('hide-menu');
2374               } else {
2375                 /*
2376                  * XXX: Fix after column refactor
2377                  * Ideally the focus would remain on the item.
2378                  * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2379                  */
2380                 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2381               }
2382             }
2383           };
2384
2385           $scope.i18n = i18nService.get();
2386         }
2387       };
2388     }
2389   };
2390
2391   return uiGridMenuItem;
2392 }]);
2393
2394 })();
2395
2396 (function(){
2397   'use strict';
2398   /**
2399    * @ngdoc overview
2400    * @name ui.grid.directive:uiGridOneBind
2401    * @summary A group of directives that provide a one time bind to a dom element.
2402    * @description A group of directives that provide a one time bind to a dom element.
2403    * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
2404    * This is done to reduce the number of watchers on the dom.
2405    * <br/>
2406    * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
2407    * <pre>
2408         <div ng-init="imageName = 'myImageDir.jpg'">
2409           <img ui-grid-one-bind-src="imageName"></img>
2410         </div>
2411      </pre>
2412    * Will become:
2413    * <pre>
2414        <div ng-init="imageName = 'myImageDir.jpg'">
2415          <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
2416        </div>
2417      </pre>
2418      </br>
2419      <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
2420    * <pre>
2421         <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
2422      </pre>
2423    * Will become:
2424    * <pre>
2425    <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
2426      </pre>
2427      </br>
2428    * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
2429    * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
2430    *
2431    */
2432   //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2433   var oneBinders = angular.module('ui.grid');
2434   angular.forEach([
2435       /**
2436        * @ngdoc directive
2437        * @name ui.grid.directive:uiGridOneBindSrc
2438        * @memberof ui.grid.directive:uiGridOneBind
2439        * @element img
2440        * @restrict A
2441        * @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>.
2442        * @description One time binding for the src dom tag.
2443        *
2444        */
2445       {tag: 'Src', method: 'attr'},
2446       /**
2447        * @ngdoc directive
2448        * @name ui.grid.directive:uiGridOneBindText
2449        * @element div
2450        * @restrict A
2451        * @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>.
2452        * @description One time binding for the text dom tag.
2453        */
2454       {tag: 'Text', method: 'text'},
2455       /**
2456        * @ngdoc directive
2457        * @name ui.grid.directive:uiGridOneBindHref
2458        * @element div
2459        * @restrict A
2460        * @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>.
2461        * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2462        */
2463       {tag: 'Href', method: 'attr'},
2464       /**
2465        * @ngdoc directive
2466        * @name ui.grid.directive:uiGridOneBindClass
2467        * @element div
2468        * @restrict A
2469        * @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>.
2470        * @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.
2471        *                                    this is to prevent the watcher from being removed before the scope is initialized.
2472        * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
2473        * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2474        */
2475       {tag: 'Class', method: 'addClass'},
2476       /**
2477        * @ngdoc directive
2478        * @name ui.grid.directive:uiGridOneBindHtml
2479        * @element div
2480        * @restrict A
2481        * @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>.
2482        * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
2483        */
2484       {tag: 'Html', method: 'html'},
2485       /**
2486        * @ngdoc directive
2487        * @name ui.grid.directive:uiGridOneBindAlt
2488        * @element div
2489        * @restrict A
2490        * @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>.
2491        * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2492        */
2493       {tag: 'Alt', method: 'attr'},
2494       /**
2495        * @ngdoc directive
2496        * @name ui.grid.directive:uiGridOneBindStyle
2497        * @element div
2498        * @restrict A
2499        * @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>.
2500        * @description One time binding for the style dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2501        */
2502       {tag: 'Style', method: 'css'},
2503       /**
2504        * @ngdoc directive
2505        * @name ui.grid.directive:uiGridOneBindValue
2506        * @element div
2507        * @restrict A
2508        * @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>.
2509        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2510        */
2511       {tag: 'Value', method: 'attr'},
2512       /**
2513        * @ngdoc directive
2514        * @name ui.grid.directive:uiGridOneBindId
2515        * @element div
2516        * @restrict A
2517        * @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>.
2518        * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2519        */
2520       {tag: 'Id', method: 'attr'},
2521       /**
2522        * @ngdoc directive
2523        * @name ui.grid.directive:uiGridOneBindIdGrid
2524        * @element div
2525        * @restrict A
2526        * @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>.
2527        * @description One time binding for the id dom tag.
2528        * <h1>Important Note!</h1>
2529        * If the id tag passed as a parameter does <b>not</b> contain the grid id as a substring
2530        * then the directive will search the scope and the parent controller (if it is a uiGridController) for the grid.id value.
2531        * 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.
2532        * This is done in order to ensure uniqueness of id tags across the grid.
2533        * This is to prevent two grids in the same document having duplicate id tags.
2534        */
2535       {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2536       /**
2537        * @ngdoc directive
2538        * @name ui.grid.directive:uiGridOneBindTitle
2539        * @element div
2540        * @restrict A
2541        * @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>.
2542        * @description One time binding for the title dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2543        */
2544       {tag: 'Title', method: 'attr'},
2545       /**
2546        * @ngdoc directive
2547        * @name ui.grid.directive:uiGridOneBindAriaLabel
2548        * @element div
2549        * @restrict A
2550        * @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>.
2551        * @description One time binding for the aria-label dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2552        *<br/>
2553        * <pre>
2554             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text"></div>
2555          </pre>
2556        * Will become:
2557        * <pre>
2558             <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
2559          </pre>
2560        */
2561       {tag: 'Label', method: 'attr', aria:true},
2562       /**
2563        * @ngdoc directive
2564        * @name ui.grid.directive:uiGridOneBindAriaLabelledby
2565        * @element div
2566        * @restrict A
2567        * @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>.
2568        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2569        *<br/>
2570        * <pre>
2571             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId"></div>
2572          </pre>
2573        * Will become:
2574        * <pre>
2575             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
2576          </pre>
2577        */
2578       {tag: 'Labelledby', method: 'attr', aria:true},
2579       /**
2580        * @ngdoc directive
2581        * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
2582        * @element div
2583        * @restrict A
2584        * @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>.
2585        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2586        * 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
2587        * grid id to each one.
2588        *<br/>
2589        * <pre>
2590             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId"></div>
2591          </pre>
2592        * Will become ([grid.id] will be replaced by the actual grid id):
2593        * <pre>
2594             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
2595          </pre>
2596        */
2597       {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2598       /**
2599        * @ngdoc directive
2600        * @name ui.grid.directive:uiGridOneBindAriaDescribedby
2601        * @element ANY
2602        * @restrict A
2603        * @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>.
2604        * @description One time binding for the aria-describedby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2605        *<br/>
2606        * <pre>
2607             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId"></div>
2608          </pre>
2609        * Will become:
2610        * <pre>
2611             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
2612          </pre>
2613        */
2614       {tag: 'Describedby', method: 'attr', aria:true},
2615       /**
2616        * @ngdoc directive
2617        * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
2618        * @element ANY
2619        * @restrict A
2620        * @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>.
2621        * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2622        * 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
2623        * grid id to each one.
2624        *<br/>
2625        * <pre>
2626             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId"></div>
2627          </pre>
2628        * Will become ([grid.id] will be replaced by the actual grid id):
2629        * <pre>
2630             <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
2631          </pre>
2632        */
2633       {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2634     function(v){
2635
2636       var baseDirectiveName = 'uiGridOneBind';
2637       //If it is an aria tag then append the aria label seperately
2638       //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2639       //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.
2640       var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2641       oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2642         return {
2643           restrict: 'A',
2644           require: ['?uiGrid','?^uiGrid'],
2645           link: function(scope, iElement, iAttrs, controllers){
2646             /* Appends the grid id to the beginnig of the value. */
2647             var appendGridId = function(val){
2648               var grid; //Get an instance of the grid if its available
2649               //If its available in the scope then we don't need to try to find it elsewhere
2650               if (scope.grid) {
2651                 grid = scope.grid;
2652               }
2653               //Another possible location to try to find the grid
2654               else if (scope.col && scope.col.grid){
2655                 grid = scope.col.grid;
2656               }
2657               //Last ditch effort: Search through the provided controllers.
2658               else if (!controllers.some( //Go through the controllers till one has the element we need
2659                 function(controller){
2660                   if (controller && controller.grid) {
2661                     grid = controller.grid;
2662                     return true; //We've found the grid
2663                   }
2664               })){
2665                 //We tried our best to find it for you
2666                 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
2667                                  "within the correct scope? Trying to generate id: [gridID]-" + val);
2668                 throw new Error("No valid grid could be found");
2669               }
2670
2671               if (grid){
2672                 var idRegex = new RegExp(grid.id.toString());
2673                 //If the grid id hasn't been appended already in the template declaration
2674                 if (!idRegex.test(val)){
2675                   val = grid.id.toString() + '-' + val;
2676                 }
2677               }
2678               return val;
2679             };
2680
2681             // The watch returns a function to remove itself.
2682             var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2683               if (newV){
2684                 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2685                 if (v.appendGridId) {
2686                   var newIdString = null;
2687                   //Append the id to all of the new ids.
2688                   angular.forEach( newV.split(' '), function(s){
2689                     newIdString = (newIdString ? (newIdString + ' ') : '') +  appendGridId(s);
2690                   });
2691                   newV = newIdString;
2692                 }
2693
2694                 // Append this newValue to the dom element.
2695                 switch (v.method) {
2696                   case 'attr': //The attr method takes two paraams the tag and the value
2697                     if (v.aria) {
2698                       //If it is an aria element then append the aria prefix
2699                       iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
2700                     } else {
2701                       iElement[v.method](v.tag.toLowerCase(),newV);
2702                     }
2703                     break;
2704                   case 'addClass':
2705                     //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2706                     if (angular.isObject(newV) && !angular.isArray(newV)) {
2707                       var results = [];
2708                       var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2709                       angular.forEach(newV, function (value, index) {
2710                         if (value !== null && typeof(value) !== "undefined"){
2711                           nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2712                           if (value) {results.push(index);}
2713                         }
2714                       });
2715                       //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2716                       if (!nonNullFound){
2717                         return; // If not initialized then the watcher should not be removed yet.
2718                       }
2719                       newV = results;
2720                     }
2721
2722                     if (newV) {
2723                       iElement.addClass(angular.isArray(newV) ? newV.join(' ') : newV);
2724                     } else {
2725                       return;
2726                     }
2727                     break;
2728                   default:
2729                     iElement[v.method](newV);
2730                     break;
2731                 }
2732
2733                 //Removes the watcher on itself after the bind
2734                 rmWatcher();
2735               }
2736             // True ensures that equality is determined using angular.equals instead of ===
2737             }, true); //End rm watchers
2738           } //End compile function
2739         }; //End directive return
2740       } // End directive function
2741     ]); //End directive
2742   }); // End angular foreach
2743 })();
2744
2745 (function () {
2746   'use strict';
2747
2748   var module = angular.module('ui.grid');
2749
2750   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2751     function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2752     return {
2753       replace: true,
2754       transclude: true,
2755       templateUrl: 'ui-grid/uiGridRenderContainer',
2756       require: ['^uiGrid', 'uiGridRenderContainer'],
2757       scope: {
2758         containerId: '=',
2759         rowContainerName: '=',
2760         colContainerName: '=',
2761         bindScrollHorizontal: '=',
2762         bindScrollVertical: '=',
2763         enableVerticalScrollbar: '=',
2764         enableHorizontalScrollbar: '='
2765       },
2766       controller: 'uiGridRenderContainer as RenderContainer',
2767       compile: function () {
2768         return {
2769           pre: function prelink($scope, $elm, $attrs, controllers) {
2770
2771             var uiGridCtrl = controllers[0];
2772             var containerCtrl = controllers[1];
2773             var grid = $scope.grid = uiGridCtrl.grid;
2774
2775             // Verify that the render container for this element exists
2776             if (!$scope.rowContainerName) {
2777               throw "No row render container name specified";
2778             }
2779             if (!$scope.colContainerName) {
2780               throw "No column render container name specified";
2781             }
2782
2783             if (!grid.renderContainers[$scope.rowContainerName]) {
2784               throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2785             }
2786             if (!grid.renderContainers[$scope.colContainerName]) {
2787               throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2788             }
2789
2790             var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2791             var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2792
2793             containerCtrl.containerId = $scope.containerId;
2794             containerCtrl.rowContainer = rowContainer;
2795             containerCtrl.colContainer = colContainer;
2796           },
2797           post: function postlink($scope, $elm, $attrs, controllers) {
2798
2799             var uiGridCtrl = controllers[0];
2800             var containerCtrl = controllers[1];
2801
2802             var grid = uiGridCtrl.grid;
2803             var rowContainer = containerCtrl.rowContainer;
2804             var colContainer = containerCtrl.colContainer;
2805             var scrollTop = null;
2806             var scrollLeft = null;
2807
2808
2809             var renderContainer = grid.renderContainers[$scope.containerId];
2810
2811             // Put the container name on this element as a class
2812             $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2813
2814             // Scroll the render container viewport when the mousewheel is used
2815             gridUtil.on.mousewheel($elm, function (event) {
2816               var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2817               if (event.deltaY !== 0) {
2818                 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2819
2820                 scrollTop = containerCtrl.viewport[0].scrollTop;
2821
2822                 // Get the scroll percentage
2823                 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2824                 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2825
2826                 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2827                 //   Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2828                 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2829                   containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2830                 }
2831
2832                 // Keep scrollPercentage within the range 0-1.
2833                 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2834                 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2835
2836                 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2837               }
2838               if (event.deltaX !== 0) {
2839                 var scrollXAmount = event.deltaX * event.deltaFactor;
2840
2841                 // Get the scroll percentage
2842                 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2843                 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2844                 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2845
2846                 // Keep scrollPercentage within the range 0-1.
2847                 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2848                 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2849
2850                 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2851               }
2852
2853               // Let the parent container scroll if the grid is already at the top/bottom
2854               if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2855                   (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2856                 //parent controller scrolls
2857               }
2858               else {
2859                 event.preventDefault();
2860                 event.stopPropagation();
2861                 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2862               }
2863
2864             });
2865
2866             $elm.bind('$destroy', function() {
2867               $elm.unbind('keydown');
2868
2869               ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2870                 $elm.unbind(eventName);
2871               });
2872             });
2873
2874             // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2875             function update() {
2876               var ret = '';
2877
2878               var canvasWidth = colContainer.canvasWidth;
2879               var viewportWidth = colContainer.getViewportWidth();
2880
2881               var canvasHeight = rowContainer.getCanvasHeight();
2882
2883               //add additional height for scrollbar on left and right container
2884               //if ($scope.containerId !== 'body') {
2885               //  canvasHeight -= grid.scrollbarHeight;
2886               //}
2887
2888               var viewportHeight = rowContainer.getViewportHeight();
2889               //shorten the height to make room for a scrollbar placeholder
2890               if (colContainer.needsHScrollbarPlaceholder()) {
2891                 viewportHeight -= grid.scrollbarHeight;
2892               }
2893
2894               var headerViewportWidth,
2895                   footerViewportWidth;
2896               headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2897
2898               // Set canvas dimensions
2899               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2900
2901               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2902
2903               if (renderContainer.explicitHeaderCanvasHeight) {
2904                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2905               }
2906               else {
2907                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2908               }
2909
2910               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2911               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2912
2913               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2914               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2915
2916               return ret;
2917             }
2918
2919             uiGridCtrl.grid.registerStyleComputation({
2920               priority: 6,
2921               func: update
2922             });
2923           }
2924         };
2925       }
2926     };
2927
2928   }]);
2929
2930   module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2931
2932   }]);
2933
2934 })();
2935
2936 (function(){
2937   'use strict';
2938
2939   angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2940     return {
2941       replace: true,
2942       // priority: 2001,
2943       // templateUrl: 'ui-grid/ui-grid-row',
2944       require: ['^uiGrid', '^uiGridRenderContainer'],
2945       scope: {
2946          row: '=uiGridRow',
2947          //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2948          rowRenderIndex: '='
2949       },
2950       compile: function() {
2951         return {
2952           pre: function($scope, $elm, $attrs, controllers) {
2953             var uiGridCtrl = controllers[0];
2954             var containerCtrl = controllers[1];
2955
2956             var grid = uiGridCtrl.grid;
2957
2958             $scope.grid = uiGridCtrl.grid;
2959             $scope.colContainer = containerCtrl.colContainer;
2960
2961             // Function for attaching the template to this scope
2962             var clonedElement, cloneScope;
2963             function compileTemplate() {
2964               $scope.row.getRowTemplateFn.then(function (compiledElementFn) {
2965                 // var compiledElementFn = $scope.row.compiledElementFn;
2966
2967                 // Create a new scope for the contents of this row, so we can destroy it later if need be
2968                 var newScope = $scope.$new();
2969
2970                 compiledElementFn(newScope, function (newElm, scope) {
2971                   // If we already have a cloned element, we need to remove it and destroy its scope
2972                   if (clonedElement) {
2973                     clonedElement.remove();
2974                     cloneScope.$destroy();
2975                   }
2976
2977                   // Empty the row and append the new element
2978                   $elm.empty().append(newElm);
2979
2980                   // Save the new cloned element and scope
2981                   clonedElement = newElm;
2982                   cloneScope = newScope;
2983                 });
2984               });
2985             }
2986
2987             // Initially attach the compiled template to this scope
2988             compileTemplate();
2989
2990             // If the row's compiled element function changes, we need to replace this element's contents with the new compiled template
2991             $scope.$watch('row.getRowTemplateFn', function (newFunc, oldFunc) {
2992               if (newFunc !== oldFunc) {
2993                 compileTemplate();
2994               }
2995             });
2996           },
2997           post: function($scope, $elm, $attrs, controllers) {
2998
2999           }
3000         };
3001       }
3002     };
3003   }]);
3004
3005 })();
3006 (function(){
3007 // 'use strict';
3008
3009   /**
3010    * @ngdoc directive
3011    * @name ui.grid.directive:uiGridStyle
3012    * @element style
3013    * @restrict A
3014    *
3015    * @description
3016    * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
3017    *
3018    * @example
3019    <doc:example module="app">
3020    <doc:source>
3021    <script>
3022    var app = angular.module('app', ['ui.grid']);
3023
3024    app.controller('MainCtrl', ['$scope', function ($scope) {
3025           $scope.myStyle = '.blah { border: 1px solid }';
3026         }]);
3027    </script>
3028
3029    <div ng-controller="MainCtrl">
3030    <style ui-grid-style>{{ myStyle }}</style>
3031    <span class="blah">I am in a box.</span>
3032    </div>
3033    </doc:source>
3034    <doc:scenario>
3035       it('should apply the right class to the element', function () {
3036         element(by.css('.blah')).getCssValue('border-top-width')
3037           .then(function(c) {
3038             expect(c).toContain('1px');
3039           });
3040       });
3041    </doc:scenario>
3042    </doc:example>
3043    */
3044
3045
3046   angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
3047     return {
3048       // restrict: 'A',
3049       // priority: 1000,
3050       // require: '?^uiGrid',
3051       link: function($scope, $elm, $attrs, uiGridCtrl) {
3052         // gridUtil.logDebug('ui-grid-style link');
3053         // if (uiGridCtrl === undefined) {
3054         //    gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
3055         // }
3056
3057         var interpolateFn = $interpolate($elm.text(), true);
3058
3059         if (interpolateFn) {
3060           $scope.$watch(interpolateFn, function(value) {
3061             $elm.text(value);
3062           });
3063         }
3064
3065           // uiGridCtrl.recalcRowStyles = function() {
3066           //   var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
3067           //   var rowHeight = scope.options.rowHeight;
3068
3069           //   var ret = '';
3070           //   var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
3071           //   for (var i = 1; i <= rowStyleCount; i++) {
3072           //     ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
3073           //     offset = offset + rowHeight;
3074           //   }
3075
3076           //   scope.rowStyles = ret;
3077           // };
3078
3079           // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
3080
3081       }
3082     };
3083   }]);
3084
3085 })();
3086
3087 (function(){
3088   'use strict';
3089
3090   angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
3091     function(gridUtil, ScrollEvent, uiGridConstants, $log) {
3092       return {
3093         replace: true,
3094         scope: {},
3095         controllerAs: 'Viewport',
3096         templateUrl: 'ui-grid/uiGridViewport',
3097         require: ['^uiGrid', '^uiGridRenderContainer'],
3098         link: function($scope, $elm, $attrs, controllers) {
3099           // gridUtil.logDebug('viewport post-link');
3100
3101           var uiGridCtrl = controllers[0];
3102           var containerCtrl = controllers[1];
3103
3104           $scope.containerCtrl = containerCtrl;
3105
3106           var rowContainer = containerCtrl.rowContainer;
3107           var colContainer = containerCtrl.colContainer;
3108
3109           var grid = uiGridCtrl.grid;
3110
3111           $scope.grid = uiGridCtrl.grid;
3112
3113           // Put the containers in scope so we can get rows and columns from them
3114           $scope.rowContainer = containerCtrl.rowContainer;
3115           $scope.colContainer = containerCtrl.colContainer;
3116
3117           // Register this viewport with its container
3118           containerCtrl.viewport = $elm;
3119
3120           /**
3121            * @ngdoc function
3122            * @name customScroller
3123            * @methodOf ui.grid.class:GridOptions
3124            * @description (optional) uiGridViewport.on('scroll', scrollHandler) by default.
3125            * A function that allows you to provide your own scroller function. It is particularly helpful if you want to use third party scrollers
3126            * as this allows you to do that.
3127            *
3128            * <div class="alert alert-info" role="alert"> <strong>NOTE:</strong> It is important to remember to always pass in an event object to
3129            * the scrollHandler as the grid scrolling behavior will break without it.</div>
3130            * <h5>Example</h5>
3131            * <pre>
3132            *   $scope.gridOptions = {
3133            *       customScroller: function myScrolling(uiGridViewport, scrollHandler) {
3134            *           uiGridViewport.on('scroll', function myScrollingOverride(event) {
3135            *               // Do something here
3136            *
3137            *               scrollHandler(event);
3138            *           });
3139            *       }
3140            *   };
3141            * </pre>
3142            * @param {object} uiGridViewport Element being scrolled. (this gets passed in by the grid).
3143            * @param {function} scrollHandler Function that needs to be called when scrolling happens. (this gets passed in by the grid).
3144            */
3145           if (grid && grid.options && grid.options.customScroller) {
3146             grid.options.customScroller($elm, scrollHandler);
3147           } else {
3148             $elm.on('scroll', scrollHandler);
3149           }
3150
3151           var ignoreScroll = false;
3152
3153           function scrollHandler(evt) {
3154             //Leaving in this commented code in case it can someday be used
3155             //It does improve performance, but because the horizontal scroll is normalized,
3156             //  using this code will lead to the column header getting slightly out of line with columns
3157             //
3158             //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
3159             //  //don't ask for scrollTop if we just set it
3160             //  ignoreScroll = false;
3161             //  return;
3162             //}
3163             //ignoreScroll = true;
3164
3165             var newScrollTop = $elm[0].scrollTop;
3166             var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
3167
3168             var vertScrollPercentage = rowContainer.scrollVertical(newScrollTop);
3169             var horizScrollPercentage = colContainer.scrollHorizontal(newScrollLeft);
3170
3171             var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
3172             scrollEvent.newScrollLeft = newScrollLeft;
3173             scrollEvent.newScrollTop = newScrollTop;
3174             if ( horizScrollPercentage > -1 ){
3175               scrollEvent.x = { percentage: horizScrollPercentage };
3176             }
3177
3178             if ( vertScrollPercentage > -1 ){
3179               scrollEvent.y = { percentage: vertScrollPercentage };
3180             }
3181
3182             grid.scrollContainers($scope.$parent.containerId, scrollEvent);
3183           }
3184
3185           if ($scope.$parent.bindScrollVertical) {
3186             grid.addVerticalScrollSync($scope.$parent.containerId, syncVerticalScroll);
3187           }
3188
3189           if ($scope.$parent.bindScrollHorizontal) {
3190             grid.addHorizontalScrollSync($scope.$parent.containerId, syncHorizontalScroll);
3191             grid.addHorizontalScrollSync($scope.$parent.containerId + 'header', syncHorizontalHeader);
3192             grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
3193           }
3194
3195           function syncVerticalScroll(scrollEvent){
3196             containerCtrl.prevScrollArgs = scrollEvent;
3197             var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3198             $elm[0].scrollTop = newScrollTop;
3199
3200           }
3201
3202           function syncHorizontalScroll(scrollEvent){
3203             containerCtrl.prevScrollArgs = scrollEvent;
3204             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3205             $elm[0].scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3206           }
3207
3208           function syncHorizontalHeader(scrollEvent){
3209             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3210             if (containerCtrl.headerViewport) {
3211               containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3212             }
3213           }
3214
3215           function syncHorizontalFooter(scrollEvent){
3216             var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3217             if (containerCtrl.footerViewport) {
3218               containerCtrl.footerViewport.scrollLeft =  gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3219             }
3220           }
3221
3222           $scope.$on('$destroy', function unbindEvents() {
3223             $elm.off();
3224           });
3225         },
3226         controller: ['$scope', function ($scope) {
3227           this.rowStyle = function (index) {
3228             var rowContainer = $scope.rowContainer;
3229             var colContainer = $scope.colContainer;
3230
3231             var styles = {};
3232
3233             if (rowContainer.currentTopRow !== 0){
3234               //top offset based on hidden rows count
3235               var translateY = "translateY("+ (rowContainer.currentTopRow * rowContainer.grid.options.rowHeight)  +"px)";
3236               styles['transform'] = translateY;
3237               styles['-webkit-transform'] = translateY;
3238               styles['-ms-transform'] = translateY;
3239             }
3240
3241             if (colContainer.currentFirstColumn !== 0) {
3242               if (colContainer.grid.isRTL()) {
3243                 styles['margin-right'] = colContainer.columnOffset + 'px';
3244               }
3245               else {
3246                 styles['margin-left'] = colContainer.columnOffset + 'px';
3247               }
3248             }
3249
3250             return styles;
3251           };
3252         }]
3253       };
3254     }
3255   ]);
3256
3257 })();
3258
3259 (function() {
3260
3261 angular.module('ui.grid')
3262 .directive('uiGridVisible', function uiGridVisibleAction() {
3263   return function ($scope, $elm, $attr) {
3264     $scope.$watch($attr.uiGridVisible, function (visible) {
3265         // $elm.css('visibility', visible ? 'visible' : 'hidden');
3266         $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3267     });
3268   };
3269 });
3270
3271 })();
3272 (function () {
3273   'use strict';
3274
3275   angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3276                     '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3277     function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3278               $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3279       // gridUtil.logDebug('ui-grid controller');
3280
3281       var self = this;
3282
3283       self.grid = gridClassFactory.createGrid($scope.uiGrid);
3284
3285       //assign $scope.$parent if appScope not already assigned
3286       self.grid.appScope = self.grid.appScope || $scope.$parent;
3287
3288       $elm.addClass('grid' + self.grid.id);
3289       self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
3290
3291
3292       // angular.extend(self.grid.options, );
3293
3294       //all properties of grid are available on scope
3295       $scope.grid = self.grid;
3296
3297       if ($attrs.uiGridColumns) {
3298         $attrs.$observe('uiGridColumns', function(value) {
3299           self.grid.options.columnDefs = value;
3300           self.grid.buildColumns()
3301             .then(function(){
3302               self.grid.preCompileCellTemplates();
3303
3304               self.grid.refreshCanvas(true);
3305             });
3306         });
3307       }
3308
3309
3310       // if fastWatch is set we watch only the length and the reference, not every individual object
3311       var deregFunctions = [];
3312       if (self.grid.options.fastWatch) {
3313         self.uiGrid = $scope.uiGrid;
3314         if (angular.isString($scope.uiGrid.data)) {
3315           deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
3316           deregFunctions.push( $scope.$parent.$watch(function() {
3317             if ( self.grid.appScope[$scope.uiGrid.data] ){
3318               return self.grid.appScope[$scope.uiGrid.data].length;
3319             } else {
3320               return undefined;
3321             }
3322           }, dataWatchFunction) );
3323         } else {
3324           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3325           deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, function(){ dataWatchFunction($scope.uiGrid.data); }) );
3326         }
3327         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3328         deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, function(){ columnDefsWatchFunction($scope.uiGrid.columnDefs); }) );
3329       } else {
3330         if (angular.isString($scope.uiGrid.data)) {
3331           deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
3332         } else {
3333           deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3334         }
3335         deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3336       }
3337
3338
3339       function columnDefsWatchFunction(n, o) {
3340         if (n && n !== o) {
3341           self.grid.options.columnDefs = $scope.uiGrid.columnDefs;
3342           self.grid.buildColumns({ orderByColumnDefs: true })
3343             .then(function(){
3344
3345               self.grid.preCompileCellTemplates();
3346
3347               self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3348             });
3349         }
3350       }
3351
3352       var mostRecentData;
3353
3354       function dataWatchFunction(newData) {
3355         // gridUtil.logDebug('dataWatch fired');
3356         var promises = [];
3357
3358         if ( self.grid.options.fastWatch ){
3359           if (angular.isString($scope.uiGrid.data)) {
3360             newData = self.grid.appScope[$scope.uiGrid.data];
3361           } else {
3362             newData = $scope.uiGrid.data;
3363           }
3364         }
3365
3366         mostRecentData = newData;
3367
3368         if (newData) {
3369           // columns length is greater than the number of row header columns, which don't count because they're created automatically
3370           var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
3371
3372           if (
3373             // If we have no columns
3374             !hasColumns &&
3375             // ... and we don't have a ui-grid-columns attribute, which would define columns for us
3376             !$attrs.uiGridColumns &&
3377             // ... and we have no pre-defined columns
3378             self.grid.options.columnDefs.length === 0 &&
3379             // ... but we DO have data
3380             newData.length > 0
3381           ) {
3382             // ... then build the column definitions from the data that we have
3383             self.grid.buildColumnDefsFromData(newData);
3384           }
3385
3386           // If we haven't built columns before and either have some columns defined or some data defined
3387           if (!hasColumns && (self.grid.options.columnDefs.length > 0 || newData.length > 0)) {
3388             // Build the column set, then pre-compile the column cell templates
3389             promises.push(self.grid.buildColumns()
3390               .then(function() {
3391                 self.grid.preCompileCellTemplates();
3392               }));
3393           }
3394
3395           $q.all(promises).then(function() {
3396             // use most recent data, rather than the potentially outdated data passed into watcher handler
3397             self.grid.modifyRows(mostRecentData)
3398               .then(function () {
3399                 // if (self.viewport) {
3400                   self.grid.redrawInPlace(true);
3401                 // }
3402
3403                 $scope.$evalAsync(function() {
3404                   self.grid.refreshCanvas(true);
3405                   self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
3406                 });
3407               });
3408           });
3409         }
3410       }
3411
3412       var styleWatchDereg = $scope.$watch(function () { return self.grid.styleComputations; }, function() {
3413         self.grid.refreshCanvas(true);
3414       });
3415
3416       $scope.$on('$destroy', function() {
3417         deregFunctions.forEach( function( deregFn ){ deregFn(); });
3418         styleWatchDereg();
3419       });
3420
3421       self.fireEvent = function(eventName, args) {
3422         // Add the grid to the event arguments if it's not there
3423         if (typeof(args) === 'undefined' || args === undefined) {
3424           args = {};
3425         }
3426
3427         if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3428           args.grid = self.grid;
3429         }
3430
3431         $scope.$broadcast(eventName, args);
3432       };
3433
3434       self.innerCompile = function innerCompile(elm) {
3435         $compile(elm)($scope);
3436       };
3437
3438     }]);
3439
3440 /**
3441  *  @ngdoc directive
3442  *  @name ui.grid.directive:uiGrid
3443  *  @element div
3444  *  @restrict EA
3445  *  @param {Object} uiGrid Options for the grid to use
3446  *
3447  *  @description Create a very basic grid.
3448  *
3449  *  @example
3450     <example module="app">
3451       <file name="app.js">
3452         var app = angular.module('app', ['ui.grid']);
3453
3454         app.controller('MainCtrl', ['$scope', function ($scope) {
3455           $scope.data = [
3456             { name: 'Bob', title: 'CEO' },
3457             { name: 'Frank', title: 'Lowly Developer' }
3458           ];
3459         }]);
3460       </file>
3461       <file name="index.html">
3462         <div ng-controller="MainCtrl">
3463           <div ui-grid="{ data: data }"></div>
3464         </div>
3465       </file>
3466     </example>
3467  */
3468 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
3469
3470 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3471 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3472   return {
3473     templateUrl: 'ui-grid/ui-grid',
3474     scope: {
3475       uiGrid: '='
3476     },
3477     replace: true,
3478     transclude: true,
3479     controller: 'uiGridController',
3480     compile: function () {
3481       return {
3482         post: function ($scope, $elm, $attrs, uiGridCtrl) {
3483           var grid = uiGridCtrl.grid;
3484           // Initialize scrollbars (TODO: move to controller??)
3485           uiGridCtrl.scrollbars = [];
3486           grid.element = $elm;
3487
3488
3489           // See if the grid has a rendered width, if not, wait a bit and try again
3490           var sizeCheckInterval = 100; // ms
3491           var maxSizeChecks = 20; // 2 seconds total
3492           var sizeChecks = 0;
3493
3494           // Setup (event listeners) the grid
3495           setup();
3496
3497           // And initialize it
3498           init();
3499
3500           // Mark rendering complete so API events can happen
3501           grid.renderingComplete();
3502
3503           // If the grid doesn't have size currently, wait for a bit to see if it gets size
3504           checkSize();
3505
3506           /*-- Methods --*/
3507
3508           function checkSize() {
3509             // If the grid has no width and we haven't checked more than <maxSizeChecks> times, check again in <sizeCheckInterval> milliseconds
3510             if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
3511               setTimeout(checkSize, sizeCheckInterval);
3512               sizeChecks++;
3513             }
3514             else {
3515               $timeout(init);
3516             }
3517           }
3518
3519           // Setup event listeners and watchers
3520           function setup() {
3521             // Bind to window resize events
3522             angular.element($window).on('resize', gridResize);
3523
3524             // Unbind from window resize events when the grid is destroyed
3525             $elm.on('$destroy', function () {
3526               angular.element($window).off('resize', gridResize);
3527             });
3528
3529             // If we add a left container after render, we need to watch and react
3530             $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3531               if (newValue === oldValue) {
3532                 return;
3533               }
3534               grid.refreshCanvas(true);
3535             });
3536
3537             // If we add a right container after render, we need to watch and react
3538             $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3539               if (newValue === oldValue) {
3540                 return;
3541               }
3542               grid.refreshCanvas(true);
3543             });
3544           }
3545
3546           // Initialize the directive
3547           function init() {
3548             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3549
3550             // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3551             grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3552
3553             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3554
3555             // 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
3556             if (grid.gridHeight <= grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3557               autoAdjustHeight();
3558             }
3559
3560             // Run initial canvas refresh
3561             grid.refreshCanvas(true);
3562           }
3563
3564           // Set the grid's height ourselves in the case that its height would be unusably small
3565           function autoAdjustHeight() {
3566             // Figure out the new height
3567             var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3568             var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
3569             var footerHeight = grid.calcFooterHeight();
3570
3571             var scrollbarHeight = 0;
3572             if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3573               scrollbarHeight = gridUtil.getScrollbarWidth();
3574             }
3575
3576             var maxNumberOfFilters = 0;
3577             // Calculates the maximum number of filters in the columns
3578             angular.forEach(grid.options.columnDefs, function(col) {
3579               if (col.hasOwnProperty('filter')) {
3580                 if (maxNumberOfFilters < 1) {
3581                     maxNumberOfFilters = 1;
3582                 }
3583               }
3584               else if (col.hasOwnProperty('filters')) {
3585                 if (maxNumberOfFilters < col.filters.length) {
3586                     maxNumberOfFilters = col.filters.length;
3587                 }
3588               }
3589             });
3590
3591             if (grid.options.enableFiltering  && !maxNumberOfFilters) {
3592               var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.length && grid.options.columnDefs.every(function(col) {
3593                 return col.enableFiltering === false;
3594               });
3595
3596               if (!allColumnsHaveFilteringTurnedOff) {
3597                 maxNumberOfFilters = 1;
3598               }
3599             }
3600
3601             var filterHeight = maxNumberOfFilters * headerHeight;
3602
3603             var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3604
3605             $elm.css('height', newHeight + 'px');
3606
3607             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3608           }
3609
3610           // Resize the grid on window resize events
3611           function gridResize($event) {
3612             grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3613             grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3614
3615             grid.refreshCanvas(true);
3616           }
3617         }
3618       };
3619     }
3620   };
3621 }
3622
3623 })();
3624
3625 (function(){
3626   'use strict';
3627
3628   // TODO: rename this file to ui-grid-pinned-container.js
3629
3630   angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3631     return {
3632       restrict: 'EA',
3633       replace: true,
3634       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>',
3635       scope: {
3636         side: '=uiGridPinnedContainer'
3637       },
3638       require: '^uiGrid',
3639       compile: function compile() {
3640         return {
3641           post: function ($scope, $elm, $attrs, uiGridCtrl) {
3642             // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3643
3644             var grid = uiGridCtrl.grid;
3645
3646             var myWidth = 0;
3647
3648             $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3649
3650             // Monkey-patch the viewport width function
3651             if ($scope.side === 'left' || $scope.side === 'right') {
3652               grid.renderContainers[$scope.side].getViewportWidth = monkeyPatchedGetViewportWidth;
3653             }
3654
3655             function monkeyPatchedGetViewportWidth() {
3656               /*jshint validthis: true */
3657               var self = this;
3658
3659               var viewportWidth = 0;
3660               self.visibleColumnCache.forEach(function (column) {
3661                 viewportWidth += column.drawnWidth;
3662               });
3663
3664               var adjustment = self.getViewportAdjustment();
3665
3666               viewportWidth = viewportWidth + adjustment.width;
3667
3668               return viewportWidth;
3669             }
3670
3671             function updateContainerWidth() {
3672               if ($scope.side === 'left' || $scope.side === 'right') {
3673                 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3674                 var width = 0;
3675                 for (var i = 0; i < cols.length; i++) {
3676                   var col = cols[i];
3677                   width += col.drawnWidth || col.width || 0;
3678                 }
3679
3680                 return width;
3681               }
3682             }
3683
3684             function updateContainerDimensions() {
3685               var ret = '';
3686
3687               // Column containers
3688               if ($scope.side === 'left' || $scope.side === 'right') {
3689                 myWidth = updateContainerWidth();
3690
3691                 // gridUtil.logDebug('myWidth', myWidth);
3692
3693                 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3694                 $elm.attr('style', null);
3695
3696              //   var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3697
3698                 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; } ';
3699               }
3700
3701               return ret;
3702             }
3703
3704             grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3705               myWidth = updateContainerWidth();
3706
3707               // Subtract our own width
3708               adjustment.width -= myWidth;
3709               adjustment.side = $scope.side;
3710
3711               return adjustment;
3712             });
3713
3714             // Register style computation to adjust for columns in `side`'s render container
3715             grid.registerStyleComputation({
3716               priority: 15,
3717               func: updateContainerDimensions
3718             });
3719           }
3720         };
3721       }
3722     };
3723   }]);
3724 })();
3725
3726 (function(){
3727
3728 angular.module('ui.grid')
3729 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
3730     function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout, ScrollEvent) {
3731
3732   /**
3733    * @ngdoc object
3734    * @name ui.grid.core.api:PublicApi
3735    * @description Public Api for the core grid features
3736    *
3737    */
3738
3739   /**
3740    * @ngdoc function
3741    * @name ui.grid.class:Grid
3742    * @description Grid is the main viewModel.  Any properties or methods needed to maintain state are defined in
3743    * this prototype.  One instance of Grid is created per Grid directive instance.
3744    * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3745    */
3746   var Grid = function Grid(options) {
3747     var self = this;
3748     // Get the id out of the options, then remove it
3749     if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3750       if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3751         throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3752       }
3753     }
3754     else {
3755       throw new Error('No ID provided. An ID must be given when creating a grid.');
3756     }
3757
3758     self.id = options.id;
3759     delete options.id;
3760
3761     // Get default options
3762     self.options = GridOptions.initialize( options );
3763
3764     /**
3765      * @ngdoc object
3766      * @name appScope
3767      * @propertyOf ui.grid.class:Grid
3768      * @description reference to the application scope (the parent scope of the ui-grid element).  Assigned in ui-grid controller
3769      * <br/>
3770      * use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
3771      */
3772     self.appScope = self.options.appScopeProvider;
3773
3774     self.headerHeight = self.options.headerRowHeight;
3775
3776
3777     /**
3778      * @ngdoc object
3779      * @name footerHeight
3780      * @propertyOf ui.grid.class:Grid
3781      * @description returns the total footer height gridFooter + columnFooter
3782      */
3783     self.footerHeight = self.calcFooterHeight();
3784
3785
3786     /**
3787      * @ngdoc object
3788      * @name columnFooterHeight
3789      * @propertyOf ui.grid.class:Grid
3790      * @description returns the total column footer height
3791      */
3792     self.columnFooterHeight = self.calcColumnFooterHeight();
3793
3794     self.rtl = false;
3795     self.gridHeight = 0;
3796     self.gridWidth = 0;
3797     self.columnBuilders = [];
3798     self.rowBuilders = [];
3799     self.rowsProcessors = [];
3800     self.columnsProcessors = [];
3801     self.styleComputations = [];
3802     self.viewportAdjusters = [];
3803     self.rowHeaderColumns = [];
3804     self.dataChangeCallbacks = {};
3805     self.verticalScrollSyncCallBackFns = {};
3806     self.horizontalScrollSyncCallBackFns = {};
3807
3808     // self.visibleRowCache = [];
3809
3810     // Set of 'render' containers for self grid, which can render sets of rows
3811     self.renderContainers = {};
3812
3813     // Create a
3814     self.renderContainers.body = new GridRenderContainer('body', self);
3815
3816     self.cellValueGetterCache = {};
3817
3818     // Cached function to use with custom row templates
3819     self.getRowTemplateFn = null;
3820
3821
3822     //representation of the rows on the grid.
3823     //these are wrapped references to the actual data rows (options.data)
3824     self.rows = [];
3825
3826     //represents the columns on the grid
3827     self.columns = [];
3828
3829     /**
3830      * @ngdoc boolean
3831      * @name isScrollingVertically
3832      * @propertyOf ui.grid.class:Grid
3833      * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3834      */
3835     self.isScrollingVertically = false;
3836
3837     /**
3838      * @ngdoc boolean
3839      * @name isScrollingHorizontally
3840      * @propertyOf ui.grid.class:Grid
3841      * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3842      */
3843     self.isScrollingHorizontally = false;
3844
3845     /**
3846      * @ngdoc property
3847      * @name scrollDirection
3848      * @propertyOf ui.grid.class:Grid
3849      * @description set one of the {@link ui.grid.service:uiGridConstants#properties_scrollDirection uiGridConstants.scrollDirection}
3850      *  values (UP, DOWN, LEFT, RIGHT, NONE), which tells us which direction we are scrolling.
3851      * Set to NONE via debounced method
3852      */
3853     self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3854
3855     //if true, grid will not respond to any scroll events
3856     self.disableScrolling = false;
3857
3858
3859     function vertical (scrollEvent) {
3860       self.isScrollingVertically = false;
3861       self.api.core.raise.scrollEnd(scrollEvent);
3862       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3863     }
3864
3865     var debouncedVertical = gridUtil.debounce(vertical, self.options.scrollDebounce);
3866     var debouncedVerticalMinDelay = gridUtil.debounce(vertical, 0);
3867
3868     function horizontal (scrollEvent) {
3869       self.isScrollingHorizontally = false;
3870       self.api.core.raise.scrollEnd(scrollEvent);
3871       self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3872     }
3873
3874     var debouncedHorizontal = gridUtil.debounce(horizontal, self.options.scrollDebounce);
3875     var debouncedHorizontalMinDelay = gridUtil.debounce(horizontal, 0);
3876
3877
3878     /**
3879      * @ngdoc function
3880      * @name flagScrollingVertically
3881      * @methodOf ui.grid.class:Grid
3882      * @description sets isScrollingVertically to true and sets it to false in a debounced function
3883      */
3884     self.flagScrollingVertically = function(scrollEvent) {
3885       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3886         self.api.core.raise.scrollBegin(scrollEvent);
3887       }
3888       self.isScrollingVertically = true;
3889       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3890         debouncedVerticalMinDelay(scrollEvent);
3891       }
3892       else {
3893         debouncedVertical(scrollEvent);
3894       }
3895     };
3896
3897     /**
3898      * @ngdoc function
3899      * @name flagScrollingHorizontally
3900      * @methodOf ui.grid.class:Grid
3901      * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3902      */
3903     self.flagScrollingHorizontally = function(scrollEvent) {
3904       if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3905         self.api.core.raise.scrollBegin(scrollEvent);
3906       }
3907       self.isScrollingHorizontally = true;
3908       if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3909         debouncedHorizontalMinDelay(scrollEvent);
3910       }
3911       else {
3912         debouncedHorizontal(scrollEvent);
3913       }
3914     };
3915
3916     self.scrollbarHeight = 0;
3917     self.scrollbarWidth = 0;
3918     if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3919       self.scrollbarHeight = gridUtil.getScrollbarWidth();
3920     }
3921
3922     if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3923       self.scrollbarWidth = gridUtil.getScrollbarWidth();
3924     }
3925
3926
3927
3928     self.api = new GridApi(self);
3929
3930     /**
3931      * @ngdoc function
3932      * @name refresh
3933      * @methodOf ui.grid.core.api:PublicApi
3934      * @description Refresh the rendered grid on screen.
3935      * The refresh method re-runs both the columnProcessors and the
3936      * rowProcessors, as well as calling refreshCanvas to update all
3937      * the grid sizing.  In general you should prefer to use queueGridRefresh
3938      * instead, which is basically a debounced version of refresh.
3939      *
3940      * If you only want to resize the grid, not regenerate all the rows
3941      * and columns, you should consider directly calling refreshCanvas instead.
3942      *
3943      * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed
3944      */
3945     self.api.registerMethod( 'core', 'refresh', this.refresh );
3946
3947     /**
3948      * @ngdoc function
3949      * @name queueGridRefresh
3950      * @methodOf ui.grid.core.api:PublicApi
3951      * @description Request a refresh of the rendered grid on screen, if multiple
3952      * calls to queueGridRefresh are made within a digest cycle only one will execute.
3953      * The refresh method re-runs both the columnProcessors and the
3954      * rowProcessors, as well as calling refreshCanvas to update all
3955      * the grid sizing.  In general you should prefer to use queueGridRefresh
3956      * instead, which is basically a debounced version of refresh.
3957      *
3958      */
3959     self.api.registerMethod( 'core', 'queueGridRefresh', this.queueGridRefresh );
3960
3961     /**
3962      * @ngdoc function
3963      * @name refreshRows
3964      * @methodOf ui.grid.core.api:PublicApi
3965      * @description Runs only the rowProcessors, columns remain as they were.
3966      * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
3967      * @returns {promise} promise that is resolved when render completes?
3968      *
3969      */
3970     self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3971
3972     /**
3973      * @ngdoc function
3974      * @name queueRefresh
3975      * @methodOf ui.grid.core.api:PublicApi
3976      * @description Requests execution of refreshCanvas, if multiple requests are made
3977      * during a digest cycle only one will run.  RefreshCanvas updates the grid sizing.
3978      * @returns {promise} promise that is resolved when render completes?
3979      *
3980      */
3981     self.api.registerMethod( 'core', 'queueRefresh', this.queueRefresh );
3982
3983     /**
3984      * @ngdoc function
3985      * @name handleWindowResize
3986      * @methodOf ui.grid.core.api:PublicApi
3987      * @description Trigger a grid resize, normally this would be picked
3988      * up by a watch on window size, but in some circumstances it is necessary
3989      * to call this manually
3990      * @returns {promise} promise that is resolved when render completes?
3991      *
3992      */
3993     self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3994
3995
3996     /**
3997      * @ngdoc function
3998      * @name addRowHeaderColumn
3999      * @methodOf ui.grid.core.api:PublicApi
4000      * @description adds a row header column to the grid
4001      * @param {object} column def
4002      * @param {number} order Determines order of header column on grid.  Lower order means header
4003      * is positioned to the left of higher order headers
4004      *
4005      */
4006     self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
4007
4008     /**
4009      * @ngdoc function
4010      * @name scrollToIfNecessary
4011      * @methodOf ui.grid.core.api:PublicApi
4012      * @description Scrolls the grid to make a certain row and column combo visible,
4013      *   in the case that it is not completely visible on the screen already.
4014      * @param {GridRow} gridRow row to make visible
4015      * @param {GridCol} gridCol column to make visible
4016      * @returns {promise} a promise that is resolved when scrolling is complete
4017      *
4018      */
4019     self.api.registerMethod( 'core', 'scrollToIfNecessary', function(gridRow, gridCol) { return self.scrollToIfNecessary(gridRow, gridCol);} );
4020
4021     /**
4022      * @ngdoc function
4023      * @name scrollTo
4024      * @methodOf ui.grid.core.api:PublicApi
4025      * @description Scroll the grid such that the specified
4026      * row and column is in view
4027      * @param {object} rowEntity gridOptions.data[] array instance to make visible
4028      * @param {object} colDef to make visible
4029      * @returns {promise} a promise that is resolved after any scrolling is finished
4030      */
4031     self.api.registerMethod( 'core', 'scrollTo', function (rowEntity, colDef) { return self.scrollTo(rowEntity, colDef);}  );
4032
4033     /**
4034      * @ngdoc function
4035      * @name registerRowsProcessor
4036      * @methodOf ui.grid.core.api:PublicApi
4037      * @description
4038      * Register a "rows processor" function. When the rows are updated,
4039      * the grid calls each registered "rows processor", which has a chance
4040      * to alter the set of rows (sorting, etc) as long as the count is not
4041      * modified.
4042      *
4043      * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4044      * is run in the context of the grid (i.e. this for the function will be the grid), and must
4045      * return the updated rows list, which is passed to the next processor in the chain
4046      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4047      * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
4048      *
4049      * At present allRowsVisible is running at 50, sort manipulations running at 60-65, filter is running at 100,
4050      * sort is at 200, grouping and treeview at 400-410, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
4051      */
4052     self.api.registerMethod( 'core', 'registerRowsProcessor', this.registerRowsProcessor  );
4053
4054     /**
4055      * @ngdoc function
4056      * @name registerColumnsProcessor
4057      * @methodOf ui.grid.core.api:PublicApi
4058      * @description
4059      * Register a "columns processor" function. When the columns are updated,
4060      * the grid calls each registered "columns processor", which has a chance
4061      * to alter the set of columns as long as the count is not
4062      * modified.
4063      *
4064      * @param {function(renderedColumnsToProcess, rows )} processorFunction columns processor function, which
4065      * is run in the context of the grid (i.e. this for the function will be the grid), and must
4066      * return the updated columns list, which is passed to the next processor in the chain
4067      * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4068      * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
4069      *
4070      * 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)
4071      */
4072     self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor  );
4073
4074
4075
4076     /**
4077      * @ngdoc function
4078      * @name sortHandleNulls
4079      * @methodOf ui.grid.core.api:PublicApi
4080      * @description A null handling method that can be used when building custom sort
4081      * functions
4082      * @example
4083      * <pre>
4084      *   mySortFn = function(a, b) {
4085      *   var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
4086      *   if ( nulls !== null ){
4087      *     return nulls;
4088      *   } else {
4089      *     // your code for sorting here
4090      *   };
4091      * </pre>
4092      * @param {object} a sort value a
4093      * @param {object} b sort value b
4094      * @returns {number} null if there were no nulls/undefineds, otherwise returns
4095      * a sort value that should be passed back from the sort function
4096      *
4097      */
4098     self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
4099
4100
4101     /**
4102      * @ngdoc function
4103      * @name sortChanged
4104      * @methodOf  ui.grid.core.api:PublicApi
4105      * @description The sort criteria on one or more columns has
4106      * changed.  Provides as parameters the grid and the output of
4107      * getColumnSorting, which is an array of gridColumns
4108      * that have sorting on them, sorted in priority order.
4109      *
4110      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
4111      * @param {Function} callBack Will be called when the event is emited. The function passes back the grid and an array of
4112      * columns with sorts on them, in priority order.
4113      *
4114      * @example
4115      * <pre>
4116      *      gridApi.core.on.sortChanged( $scope, function(grid, sortColumns){
4117      *        // do something
4118      *      });
4119      * </pre>
4120      */
4121     self.api.registerEvent( 'core', 'sortChanged' );
4122
4123       /**
4124      * @ngdoc function
4125      * @name columnVisibilityChanged
4126      * @methodOf  ui.grid.core.api:PublicApi
4127      * @description The visibility of a column has changed,
4128      * the column itself is passed out as a parameter of the event.
4129      *
4130      * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
4131      * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
4132      *
4133      * @example
4134      * <pre>
4135      *      gridApi.core.on.columnVisibilityChanged( $scope, function (column) {
4136      *        // do something
4137      *      } );
4138      * </pre>
4139      */
4140     self.api.registerEvent( 'core', 'columnVisibilityChanged' );
4141
4142     /**
4143      * @ngdoc method
4144      * @name notifyDataChange
4145      * @methodOf ui.grid.core.api:PublicApi
4146      * @description Notify the grid that a data or config change has occurred,
4147      * where that change isn't something the grid was otherwise noticing.  This
4148      * might be particularly relevant where you've changed values within the data
4149      * and you'd like cell classes to be re-evaluated, or changed config within
4150      * the columnDef and you'd like headerCellClasses to be re-evaluated.
4151      * @param {string} type one of the
4152      * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4153      * values (ALL, ROW, EDIT, COLUMN), which tells us which refreshes to fire.
4154      *
4155      */
4156     self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
4157
4158     /**
4159      * @ngdoc method
4160      * @name clearAllFilters
4161      * @methodOf ui.grid.core.api:PublicApi
4162      * @description Clears all filters and optionally refreshes the visible rows.
4163      * @param {object} refreshRows Defaults to true.
4164      * @param {object} clearConditions Defaults to false.
4165      * @param {object} clearFlags Defaults to false.
4166      * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
4167      */
4168     self.api.registerMethod('core', 'clearAllFilters', this.clearAllFilters);
4169
4170     self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
4171     self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
4172     self.registerDataChangeCallback( self.updateFooterHeightCallback, [uiGridConstants.dataChange.OPTIONS]);
4173
4174     self.registerStyleComputation({
4175       priority: 10,
4176       func: self.getFooterStyles
4177     });
4178   };
4179
4180    Grid.prototype.calcFooterHeight = function () {
4181      if (!this.hasFooter()) {
4182        return 0;
4183      }
4184
4185      var height = 0;
4186      if (this.options.showGridFooter) {
4187        height += this.options.gridFooterHeight;
4188      }
4189
4190      height += this.calcColumnFooterHeight();
4191
4192      return height;
4193    };
4194
4195    Grid.prototype.calcColumnFooterHeight = function () {
4196      var height = 0;
4197
4198      if (this.options.showColumnFooter) {
4199        height += this.options.columnFooterHeight;
4200      }
4201
4202      return height;
4203    };
4204
4205    Grid.prototype.getFooterStyles = function () {
4206      var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }';
4207      style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }';
4208      return style;
4209    };
4210
4211   Grid.prototype.hasFooter = function () {
4212    return this.options.showGridFooter || this.options.showColumnFooter;
4213   };
4214
4215   /**
4216    * @ngdoc function
4217    * @name isRTL
4218    * @methodOf ui.grid.class:Grid
4219    * @description Returns true if grid is RightToLeft
4220    */
4221   Grid.prototype.isRTL = function () {
4222     return this.rtl;
4223   };
4224
4225
4226   /**
4227    * @ngdoc function
4228    * @name registerColumnBuilder
4229    * @methodOf ui.grid.class:Grid
4230    * @description When the build creates columns from column definitions, the columnbuilders will be called to add
4231    * additional properties to the column.
4232    * @param {function(colDef, col, gridOptions)} columnBuilder function to be called
4233    */
4234   Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
4235     this.columnBuilders.push(columnBuilder);
4236   };
4237
4238   /**
4239    * @ngdoc function
4240    * @name buildColumnDefsFromData
4241    * @methodOf ui.grid.class:Grid
4242    * @description Populates columnDefs from the provided data
4243    * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
4244    */
4245   Grid.prototype.buildColumnDefsFromData = function (dataRows){
4246     this.options.columnDefs =  gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
4247   };
4248
4249   /**
4250    * @ngdoc function
4251    * @name registerRowBuilder
4252    * @methodOf ui.grid.class:Grid
4253    * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
4254    * additional properties to the row.
4255    * @param {function(row, gridOptions)} rowBuilder function to be called
4256    */
4257   Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
4258     this.rowBuilders.push(rowBuilder);
4259   };
4260
4261
4262   /**
4263    * @ngdoc function
4264    * @name registerDataChangeCallback
4265    * @methodOf ui.grid.class:Grid
4266    * @description When a data change occurs, the data change callbacks of the specified type
4267    * will be called.  The rules are:
4268    *
4269    * - when the data watch fires, that is considered a ROW change (the data watch only notices
4270    *   added or removed rows)
4271    * - when the api is called to inform us of a change, the declared type of that change is used
4272    * - when a cell edit completes, the EDIT callbacks are triggered
4273    * - when the columnDef watch fires, the COLUMN callbacks are triggered
4274    * - when the options watch fires, the OPTIONS callbacks are triggered
4275    *
4276    * For a given event:
4277    * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks
4278    * - ROW calls ROW and ALL callbacks
4279    * - EDIT calls EDIT and ALL callbacks
4280    * - COLUMN calls COLUMN and ALL callbacks
4281    * - OPTIONS calls OPTIONS and ALL callbacks
4282    *
4283    * @param {function(grid)} callback function to be called
4284    * @param {array} types the types of data change you want to be informed of.  Values from
4285    * the {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4286    *  values ( ALL, EDIT, ROW, COLUMN, OPTIONS ).  Optional and defaults to ALL
4287    * @returns {function} deregister function - a function that can be called to deregister this callback
4288    */
4289   Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4290     var uid = gridUtil.nextUid();
4291     if ( !types ){
4292       types = [uiGridConstants.dataChange.ALL];
4293     }
4294     if ( !Array.isArray(types)){
4295       gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
4296     }
4297     this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4298
4299     var self = this;
4300     var deregisterFunction = function() {
4301       delete self.dataChangeCallbacks[uid];
4302     };
4303     return deregisterFunction;
4304   };
4305
4306   /**
4307    * @ngdoc function
4308    * @name callDataChangeCallbacks
4309    * @methodOf ui.grid.class:Grid
4310    * @description Calls the callbacks based on the type of data change that
4311    * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
4312    * event type is matching, or if the type is ALL.
4313    * @param {string} type the type of event that occurred - one of the
4314    * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4315    *  values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4316    */
4317   Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4318     angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4319       if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
4320            callback.types.indexOf( type ) !== -1 ||
4321            type === uiGridConstants.dataChange.ALL ) {
4322         if (callback._this) {
4323            callback.callback.apply(callback._this,this);
4324         }
4325         else {
4326           callback.callback( this );
4327         }
4328       }
4329     }, this);
4330   };
4331
4332   /**
4333    * @ngdoc function
4334    * @name notifyDataChange
4335    * @methodOf ui.grid.class:Grid
4336    * @description Notifies us that a data change has occurred, used in the public
4337    * api for users to tell us when they've changed data or some other event that
4338    * our watches cannot pick up
4339    * @param {string} type the type of event that occurred - one of the
4340    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4341    */
4342   Grid.prototype.notifyDataChange = function notifyDataChange(type) {
4343     var constants = uiGridConstants.dataChange;
4344     if ( type === constants.ALL ||
4345          type === constants.COLUMN ||
4346          type === constants.EDIT ||
4347          type === constants.ROW ||
4348          type === constants.OPTIONS ){
4349       this.callDataChangeCallbacks( type );
4350     } else {
4351       gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
4352     }
4353   };
4354
4355
4356   /**
4357    * @ngdoc function
4358    * @name columnRefreshCallback
4359    * @methodOf ui.grid.class:Grid
4360    * @description refreshes the grid when a column refresh
4361    * is notified, which triggers handling of the visible flag.
4362    * This is called on uiGridConstants.dataChange.COLUMN, and is
4363    * registered as a dataChangeCallback in grid.js
4364    * @param {string} name column name
4365    */
4366   Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4367     grid.buildColumns();
4368     grid.queueGridRefresh();
4369   };
4370
4371
4372   /**
4373    * @ngdoc function
4374    * @name processRowsCallback
4375    * @methodOf ui.grid.class:Grid
4376    * @description calls the row processors, specifically
4377    * intended to reset the sorting when an edit is called,
4378    * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4379    * @param {string} name column name
4380    */
4381   Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4382     grid.queueGridRefresh();
4383   };
4384
4385
4386   /**
4387    * @ngdoc function
4388    * @name updateFooterHeightCallback
4389    * @methodOf ui.grid.class:Grid
4390    * @description recalculates the footer height,
4391    * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4392    * @param {string} name column name
4393    */
4394   Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4395     grid.footerHeight = grid.calcFooterHeight();
4396     grid.columnFooterHeight = grid.calcColumnFooterHeight();
4397   };
4398
4399
4400   /**
4401    * @ngdoc function
4402    * @name getColumn
4403    * @methodOf ui.grid.class:Grid
4404    * @description returns a grid column for the column name
4405    * @param {string} name column name
4406    */
4407   Grid.prototype.getColumn = function getColumn(name) {
4408     var columns = this.columns.filter(function (column) {
4409       return column.colDef.name === name;
4410     });
4411     return columns.length > 0 ? columns[0] : null;
4412   };
4413
4414   /**
4415    * @ngdoc function
4416    * @name getColDef
4417    * @methodOf ui.grid.class:Grid
4418    * @description returns a grid colDef for the column name
4419    * @param {string} name column.field
4420    */
4421   Grid.prototype.getColDef = function getColDef(name) {
4422     var colDefs = this.options.columnDefs.filter(function (colDef) {
4423       return colDef.name === name;
4424     });
4425     return colDefs.length > 0 ? colDefs[0] : null;
4426   };
4427
4428   /**
4429    * @ngdoc function
4430    * @name assignTypes
4431    * @methodOf ui.grid.class:Grid
4432    * @description uses the first row of data to assign colDef.type for any types not defined.
4433    */
4434   /**
4435    * @ngdoc property
4436    * @name type
4437    * @propertyOf ui.grid.class:GridOptions.columnDef
4438    * @description the type of the column, used in sorting.  If not provided then the
4439    * grid will guess the type.  Add this only if the grid guessing is not to your
4440    * satisfaction.  One of:
4441    * - 'string'
4442    * - 'boolean'
4443    * - 'number'
4444    * - 'date'
4445    * - 'object'
4446    * - 'numberStr'
4447    * Note that if you choose date, your dates should be in a javascript date type
4448    *
4449    */
4450   Grid.prototype.assignTypes = function(){
4451     var self = this;
4452     self.options.columnDefs.forEach(function (colDef, index) {
4453
4454       //Assign colDef type if not specified
4455       if (!colDef.type) {
4456         var col = new GridColumn(colDef, index, self);
4457         var firstRow = self.rows.length > 0 ? self.rows[0] : null;
4458         if (firstRow) {
4459           colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
4460         }
4461         else {
4462           colDef.type = 'string';
4463         }
4464       }
4465     });
4466   };
4467
4468
4469   /**
4470    * @ngdoc function
4471    * @name isRowHeaderColumn
4472    * @methodOf ui.grid.class:Grid
4473    * @description returns true if the column is a row Header
4474    * @param {object} column column
4475    */
4476   Grid.prototype.isRowHeaderColumn = function isRowHeaderColumn(column) {
4477     return this.rowHeaderColumns.indexOf(column) !== -1;
4478   };
4479
4480   /**
4481   * @ngdoc function
4482   * @name addRowHeaderColumn
4483   * @methodOf ui.grid.class:Grid
4484   * @description adds a row header column to the grid
4485   * @param {object} column def
4486   */
4487   Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef, order) {
4488     var self = this;
4489
4490     //default order
4491     if (order === undefined) {
4492       order = 0;
4493     }
4494
4495     var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
4496     rowHeaderCol.isRowHeader = true;
4497     if (self.isRTL()) {
4498       self.createRightContainer();
4499       rowHeaderCol.renderContainer = 'right';
4500     }
4501     else {
4502       self.createLeftContainer();
4503       rowHeaderCol.renderContainer = 'left';
4504     }
4505
4506     // relies on the default column builder being first in array, as it is instantiated
4507     // as part of grid creation
4508     self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4509       .then(function(){
4510         rowHeaderCol.enableFiltering = false;
4511         rowHeaderCol.enableSorting = false;
4512         rowHeaderCol.enableHiding = false;
4513         rowHeaderCol.headerPriority = order;
4514         self.rowHeaderColumns.push(rowHeaderCol);
4515         self.rowHeaderColumns = self.rowHeaderColumns.sort(function (a, b) {
4516           return a.headerPriority - b.headerPriority;
4517         });
4518
4519         self.buildColumns()
4520           .then( function() {
4521             self.preCompileCellTemplates();
4522             self.queueGridRefresh();
4523           });
4524       });
4525   };
4526
4527   /**
4528    * @ngdoc function
4529    * @name getOnlyDataColumns
4530    * @methodOf ui.grid.class:Grid
4531    * @description returns all columns except for rowHeader columns
4532    */
4533   Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4534     var self = this;
4535     var cols = [];
4536     self.columns.forEach(function (col) {
4537       if (self.rowHeaderColumns.indexOf(col) === -1) {
4538         cols.push(col);
4539       }
4540     });
4541     return cols;
4542   };
4543
4544   /**
4545    * @ngdoc function
4546    * @name buildColumns
4547    * @methodOf ui.grid.class:Grid
4548    * @description creates GridColumn objects from the columnDefinition.  Calls each registered
4549    * columnBuilder to further process the column
4550    * @param {object} options  An object contains options to use when building columns
4551    *
4552    * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
4553    *
4554    * @returns {Promise} a promise to load any needed column resources
4555    */
4556   Grid.prototype.buildColumns = function buildColumns(opts) {
4557     var options = {
4558       orderByColumnDefs: false
4559     };
4560
4561     angular.extend(options, opts);
4562
4563     // gridUtil.logDebug('buildColumns');
4564     var self = this;
4565     var builderPromises = [];
4566     var headerOffset = self.rowHeaderColumns.length;
4567     var i;
4568
4569     // Remove any columns for which a columnDef cannot be found
4570     // Deliberately don't use forEach, as it doesn't like splice being called in the middle
4571     // Also don't cache columns.length, as it will change during this operation
4572     for (i = 0; i < self.columns.length; i++){
4573       if (!self.getColDef(self.columns[i].name)) {
4574         self.columns.splice(i, 1);
4575         i--;
4576       }
4577     }
4578
4579     //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4580     //rowHeaderColumns is ordered by priority so insert in reverse
4581     for (var j = self.rowHeaderColumns.length - 1; j >= 0; j--) {
4582       self.columns.unshift(self.rowHeaderColumns[j]);
4583     }
4584
4585
4586
4587     // look at each column def, and update column properties to match.  If the column def
4588     // doesn't have a column, then splice in a new gridCol
4589     self.options.columnDefs.forEach(function (colDef, index) {
4590       self.preprocessColDef(colDef);
4591       var col = self.getColumn(colDef.name);
4592
4593       if (!col) {
4594         col = new GridColumn(colDef, gridUtil.nextUid(), self);
4595         self.columns.splice(index + headerOffset, 0, col);
4596       }
4597       else {
4598         // tell updateColumnDef that the column was pre-existing
4599         col.updateColumnDef(colDef, false);
4600       }
4601
4602       self.columnBuilders.forEach(function (builder) {
4603         builderPromises.push(builder.call(self, colDef, col, self.options));
4604       });
4605     });
4606
4607     /*** Reorder columns if necessary ***/
4608     if (!!options.orderByColumnDefs) {
4609       // Create a shallow copy of the columns as a cache
4610       var columnCache = self.columns.slice(0);
4611
4612       // We need to allow for the "row headers" when mapping from the column defs array to the columns array
4613       //   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]
4614
4615       // 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
4616       // columns will be shorter than columnDefs.  In this situation we'll avoid an error, but the user will still get an unexpected result
4617       var len = Math.min(self.options.columnDefs.length, self.columns.length);
4618       for (i = 0; i < len; i++) {
4619         // If the column at this index has a different name than the column at the same index in the column defs...
4620         if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) {
4621           // Replace the one in the cache with the appropriate column
4622           columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name);
4623         }
4624         else {
4625           // Otherwise just copy over the one from the initial columns
4626           columnCache[i + headerOffset] = self.columns[i + headerOffset];
4627         }
4628       }
4629
4630       // Empty out the columns array, non-destructively
4631       self.columns.length = 0;
4632
4633       // And splice in the updated, ordered columns from the cache
4634       Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
4635     }
4636
4637     return $q.all(builderPromises).then(function(){
4638       if (self.rows.length > 0){
4639         self.assignTypes();
4640       }
4641     });
4642   };
4643
4644   Grid.prototype.preCompileCellTemplate = function(col) {
4645     var self = this;
4646     var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4647     html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4648
4649     var compiledElementFn = $compile(html);
4650     col.compiledElementFn = compiledElementFn;
4651
4652     if (col.compiledElementFnDefer) {
4653       col.compiledElementFnDefer.resolve(col.compiledElementFn);
4654     }
4655   };
4656
4657 /**
4658  * @ngdoc function
4659  * @name preCompileCellTemplates
4660  * @methodOf ui.grid.class:Grid
4661  * @description pronapiles all cell templates
4662  */
4663   Grid.prototype.preCompileCellTemplates = function() {
4664     var self = this;
4665     self.columns.forEach(function (col) {
4666       if ( col.cellTemplate ){
4667         self.preCompileCellTemplate( col );
4668       } else if ( col.cellTemplatePromise ){
4669         col.cellTemplatePromise.then( function() {
4670           self.preCompileCellTemplate( col );
4671         });
4672       }
4673     });
4674   };
4675
4676   /**
4677    * @ngdoc function
4678    * @name getGridQualifiedColField
4679    * @methodOf ui.grid.class:Grid
4680    * @description Returns the $parse-able accessor for a column within its $scope
4681    * @param {GridColumn} col col object
4682    */
4683   Grid.prototype.getQualifiedColField = function (col) {
4684     var base = 'row.entity';
4685     if ( col.field === uiGridConstants.ENTITY_BINDING ) {
4686       return base;
4687     }
4688     return gridUtil.preEval(base + '.' + col.field);
4689   };
4690
4691   /**
4692    * @ngdoc function
4693    * @name createLeftContainer
4694    * @methodOf ui.grid.class:Grid
4695    * @description creates the left render container if it doesn't already exist
4696    */
4697   Grid.prototype.createLeftContainer = function() {
4698     if (!this.hasLeftContainer()) {
4699       this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
4700     }
4701   };
4702
4703   /**
4704    * @ngdoc function
4705    * @name createRightContainer
4706    * @methodOf ui.grid.class:Grid
4707    * @description creates the right render container if it doesn't already exist
4708    */
4709   Grid.prototype.createRightContainer = function() {
4710     if (!this.hasRightContainer()) {
4711       this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
4712     }
4713   };
4714
4715   /**
4716    * @ngdoc function
4717    * @name hasLeftContainer
4718    * @methodOf ui.grid.class:Grid
4719    * @description returns true if leftContainer exists
4720    */
4721   Grid.prototype.hasLeftContainer = function() {
4722     return this.renderContainers.left !== undefined;
4723   };
4724
4725   /**
4726    * @ngdoc function
4727    * @name hasRightContainer
4728    * @methodOf ui.grid.class:Grid
4729    * @description returns true if rightContainer exists
4730    */
4731   Grid.prototype.hasRightContainer = function() {
4732     return this.renderContainers.right !== undefined;
4733   };
4734
4735
4736       /**
4737    * undocumented function
4738    * @name preprocessColDef
4739    * @methodOf ui.grid.class:Grid
4740    * @description defaults the name property from field to maintain backwards compatibility with 2.x
4741    * validates that name or field is present
4742    */
4743   Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
4744     var self = this;
4745
4746     if (!colDef.field && !colDef.name) {
4747       throw new Error('colDef.name or colDef.field property is required');
4748     }
4749
4750     //maintain backwards compatibility with 2.x
4751     //field was required in 2.x.  now name is required
4752     if (colDef.name === undefined && colDef.field !== undefined) {
4753       // See if the column name already exists:
4754       var newName = colDef.field,
4755         counter = 2;
4756       while (self.getColumn(newName)) {
4757         newName = colDef.field + counter.toString();
4758         counter++;
4759       }
4760       colDef.name = newName;
4761     }
4762   };
4763
4764   // 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
4765   Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
4766     var self = this;
4767
4768     var t = [];
4769     for (var i = 0; i < n.length; i++) {
4770       var nV = nAccessor ? n[i][nAccessor] : n[i];
4771
4772       var found = false;
4773       for (var j = 0; j < o.length; j++) {
4774         var oV = oAccessor ? o[j][oAccessor] : o[j];
4775         if (self.options.rowEquality(nV, oV)) {
4776           found = true;
4777           break;
4778         }
4779       }
4780       if (!found) {
4781         t.push(nV);
4782       }
4783     }
4784
4785     return t;
4786   };
4787
4788   /**
4789    * @ngdoc function
4790    * @name getRow
4791    * @methodOf ui.grid.class:Grid
4792    * @description returns the GridRow that contains the rowEntity
4793    * @param {object} rowEntity the gridOptions.data array element instance
4794    * @param {array} lookInRows [optional] the rows to look in - if not provided then
4795    * looks in grid.rows
4796    */
4797   Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
4798     var self = this;
4799
4800     lookInRows = typeof(lookInRows) === 'undefined' ? self.rows : lookInRows;
4801
4802     var rows = lookInRows.filter(function (row) {
4803       return self.options.rowEquality(row.entity, rowEntity);
4804     });
4805     return rows.length > 0 ? rows[0] : null;
4806   };
4807
4808
4809   /**
4810    * @ngdoc function
4811    * @name modifyRows
4812    * @methodOf ui.grid.class:Grid
4813    * @description creates or removes GridRow objects from the newRawData array.  Calls each registered
4814    * rowBuilder to further process the row
4815    * @param {array} newRawData Modified set of data
4816    *
4817    * This method aims to achieve three things:
4818    * 1. the resulting rows array is in the same order as the newRawData, we'll call
4819    * rowsProcessors immediately after to sort the data anyway
4820    * 2. if we have row hashing available, we try to use the rowHash to find the row
4821    * 3. no memory leaks - rows that are no longer in newRawData need to be garbage collected
4822    *
4823    * The basic logic flow makes use of the newRawData, oldRows and oldHash, and creates
4824    * the newRows and newHash
4825    *
4826    * ```
4827    * newRawData.forEach newEntity
4828    *   if (hashing enabled)
4829    *     check oldHash for newEntity
4830    *   else
4831    *     look for old row directly in oldRows
4832    *   if !oldRowFound     // must be a new row
4833    *     create newRow
4834    *   append to the newRows and add to newHash
4835    *   run the processors
4836    * ```
4837    *
4838    * Rows are identified using the hashKey if configured.  If not configured, then rows
4839    * are identified using the gridOptions.rowEquality function
4840    *
4841    * This method is useful when trying to select rows immediately after loading data without
4842    * using a $timeout/$interval, e.g.:
4843    *
4844    *   $scope.gridOptions.data =  someData;
4845    *   $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
4846    *   $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4847    *
4848    * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
4849    * originally selected rows to be re-selected))
4850    */
4851   Grid.prototype.modifyRows = function modifyRows(newRawData) {
4852     var self = this;
4853     var oldRows = self.rows.slice(0);
4854     var oldRowHash = self.rowHashMap || self.createRowHashMap();
4855     self.rowHashMap = self.createRowHashMap();
4856     self.rows.length = 0;
4857
4858     newRawData.forEach( function( newEntity, i ) {
4859       var newRow, oldRow;
4860
4861       if ( self.options.enableRowHashing ){
4862         // if hashing is enabled, then this row will be in the hash if we already know about it
4863         oldRow = oldRowHash.get( newEntity );
4864       } else {
4865         // otherwise, manually search the oldRows to see if we can find this row
4866         oldRow = self.getRow(newEntity, oldRows);
4867       }
4868
4869       // update newRow to have an entity
4870       if ( oldRow ) {
4871         newRow = oldRow;
4872         newRow.entity = newEntity;
4873       }
4874
4875       // if we didn't find the row, it must be new, so create it
4876       if ( !newRow ){
4877         newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
4878       }
4879
4880       self.rows.push( newRow );
4881       self.rowHashMap.put( newEntity, newRow );
4882     });
4883
4884     self.assignTypes();
4885
4886     var p1 = $q.when(self.processRowsProcessors(self.rows))
4887       .then(function (renderableRows) {
4888         return self.setVisibleRows(renderableRows);
4889       });
4890
4891     var p2 = $q.when(self.processColumnsProcessors(self.columns))
4892       .then(function (renderableColumns) {
4893         return self.setVisibleColumns(renderableColumns);
4894       });
4895
4896     return $q.all([p1, p2]);
4897   };
4898
4899
4900   /**
4901    * Private Undocumented Method
4902    * @name addRows
4903    * @methodOf ui.grid.class:Grid
4904    * @description adds the newRawData array of rows to the grid and calls all registered
4905    * rowBuilders. this keyword will reference the grid
4906    */
4907   Grid.prototype.addRows = function addRows(newRawData) {
4908     var self = this;
4909
4910     var existingRowCount = self.rows.length;
4911     for (var i = 0; i < newRawData.length; i++) {
4912       var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4913
4914       if (self.options.enableRowHashing) {
4915         var found = self.rowHashMap.get(newRow.entity);
4916         if (found) {
4917           found.row = newRow;
4918         }
4919       }
4920
4921       self.rows.push(newRow);
4922     }
4923   };
4924
4925   /**
4926    * @ngdoc function
4927    * @name processRowBuilders
4928    * @methodOf ui.grid.class:Grid
4929    * @description processes all RowBuilders for the gridRow
4930    * @param {GridRow} gridRow reference to gridRow
4931    * @returns {GridRow} the gridRow with all additional behavior added
4932    */
4933   Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4934     var self = this;
4935
4936     self.rowBuilders.forEach(function (builder) {
4937       builder.call(self, gridRow, self.options);
4938     });
4939
4940     return gridRow;
4941   };
4942
4943   /**
4944    * @ngdoc function
4945    * @name registerStyleComputation
4946    * @methodOf ui.grid.class:Grid
4947    * @description registered a styleComputation function
4948    *
4949    * If the function returns a value it will be appended into the grid's `<style>` block
4950    * @param {function($scope)} styleComputation function
4951    */
4952   Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4953     this.styleComputations.push(styleComputationInfo);
4954   };
4955
4956
4957   // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4958   // Grid.prototype.registerRowFilter = function(filter) {
4959   //   // TODO(c0bra): validate filter?
4960
4961   //   this.rowFilters.push(filter);
4962   // };
4963
4964   // Grid.prototype.removeRowFilter = function(filter) {
4965   //   var idx = this.rowFilters.indexOf(filter);
4966
4967   //   if (typeof(idx) !== 'undefined' && idx !== undefined) {
4968   //     this.rowFilters.slice(idx, 1);
4969   //   }
4970   // };
4971
4972   // Grid.prototype.processRowFilters = function(rows) {
4973   //   var self = this;
4974   //   self.rowFilters.forEach(function (filter) {
4975   //     filter.call(self, rows);
4976   //   });
4977   // };
4978
4979
4980   /**
4981    * @ngdoc function
4982    * @name registerRowsProcessor
4983    * @methodOf ui.grid.class:Grid
4984    * @description
4985    *
4986    * Register a "rows processor" function. When the rows are updated,
4987    * the grid calls each registered "rows processor", which has a chance
4988    * to alter the set of rows (sorting, etc) as long as the count is not
4989    * modified.
4990    *
4991    * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4992    * is run in the context of the grid (i.e. this for the function will be the grid), and must
4993    * return the updated rows list, which is passed to the next processor in the chain
4994    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
4995    * for other people to inject rows processors at intermediate priorities.  Lower priority rowsProcessors run earlier.
4996    *
4997    * 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)
4998    *
4999    */
5000   Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor, priority) {
5001     if (!angular.isFunction(processor)) {
5002       throw 'Attempt to register non-function rows processor: ' + processor;
5003     }
5004
5005     this.rowsProcessors.push({processor: processor, priority: priority});
5006     this.rowsProcessors.sort(function sortByPriority( a, b ){
5007       return a.priority - b.priority;
5008     });
5009   };
5010
5011   /**
5012    * @ngdoc function
5013    * @name removeRowsProcessor
5014    * @methodOf ui.grid.class:Grid
5015    * @param {function(renderableRows)} rows processor function
5016    * @description Remove a registered rows processor
5017    */
5018   Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
5019     var idx = -1;
5020     this.rowsProcessors.forEach(function(rowsProcessor, index){
5021       if ( rowsProcessor.processor === processor ){
5022         idx = index;
5023       }
5024     });
5025
5026     if ( idx !== -1 ) {
5027       this.rowsProcessors.splice(idx, 1);
5028     }
5029   };
5030
5031   /**
5032    * Private Undocumented Method
5033    * @name processRowsProcessors
5034    * @methodOf ui.grid.class:Grid
5035    * @param {Array[GridRow]} The array of "renderable" rows
5036    * @param {Array[GridColumn]} The array of columns
5037    * @description Run all the registered rows processors on the array of renderable rows
5038    */
5039   Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
5040     var self = this;
5041
5042     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
5043     var myRenderableRows = renderableRows.slice(0);
5044
5045     // Return myRenderableRows with no processing if we have no rows processors
5046     if (self.rowsProcessors.length === 0) {
5047       return $q.when(myRenderableRows);
5048     }
5049
5050     // Counter for iterating through rows processors
5051     var i = 0;
5052
5053     // Promise for when we're done with all the processors
5054     var finished = $q.defer();
5055
5056     // This function will call the processor in self.rowsProcessors at index 'i', and then
5057     //   when done will call the next processor in the list, using the output from the processor
5058     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
5059     //
5060     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
5061     //   the result.
5062     function startProcessor(i, renderedRowsToProcess) {
5063       // Get the processor at 'i'
5064       var processor = self.rowsProcessors[i].processor;
5065
5066       // Call the processor, passing in the rows to process and the current columns
5067       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
5068       return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
5069         .then(function handleProcessedRows(processedRows) {
5070           // Check for errors
5071           if (!processedRows) {
5072             throw "Processor at index " + i + " did not return a set of renderable rows";
5073           }
5074
5075           if (!angular.isArray(processedRows)) {
5076             throw "Processor at index " + i + " did not return an array";
5077           }
5078
5079           // Processor is done, increment the counter
5080           i++;
5081
5082           // If we're not done with the processors, call the next one
5083           if (i <= self.rowsProcessors.length - 1) {
5084             return startProcessor(i, processedRows);
5085           }
5086           // We're done! Resolve the 'finished' promise
5087           else {
5088             finished.resolve(processedRows);
5089           }
5090         });
5091     }
5092
5093     // Start on the first processor
5094     startProcessor(0, myRenderableRows);
5095
5096     return finished.promise;
5097   };
5098
5099   Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
5100     var self = this;
5101
5102     // Reset all the render container row caches
5103     for (var i in self.renderContainers) {
5104       var container = self.renderContainers[i];
5105
5106       container.canvasHeightShouldUpdate = true;
5107
5108       if ( typeof(container.visibleRowCache) === 'undefined' ){
5109         container.visibleRowCache = [];
5110       } else {
5111         container.visibleRowCache.length = 0;
5112       }
5113     }
5114
5115     // rows.forEach(function (row) {
5116     for (var ri = 0; ri < rows.length; ri++) {
5117       var row = rows[ri];
5118
5119       var targetContainer = (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) ? row.renderContainer : 'body';
5120
5121       // If the row is visible
5122       if (row.visible) {
5123         self.renderContainers[targetContainer].visibleRowCache.push(row);
5124       }
5125     }
5126     self.api.core.raise.rowsVisibleChanged(this.api);
5127     self.api.core.raise.rowsRendered(this.api);
5128   };
5129
5130   /**
5131    * @ngdoc function
5132    * @name registerColumnsProcessor
5133    * @methodOf ui.grid.class:Grid
5134    * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
5135    * is run in the context of the grid (i.e. this for the function will be the grid), and
5136    * which must return an updated renderedColumnsToProcess which can be passed to the next processor
5137    * in the chain
5138    * @param {number} priority the priority of this processor.  In general we try to do them in 100s to leave room
5139    * for other people to inject columns processors at intermediate priorities.  Lower priority columnsProcessors run earlier.
5140    *
5141    * 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)
5142    * @description
5143
5144      Register a "columns processor" function. When the columns are updated,
5145      the grid calls each registered "columns processor", which has a chance
5146      to alter the set of columns, as long as the count is not modified.
5147    */
5148   Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor, priority) {
5149     if (!angular.isFunction(processor)) {
5150       throw 'Attempt to register non-function rows processor: ' + processor;
5151     }
5152
5153     this.columnsProcessors.push({processor: processor, priority: priority});
5154     this.columnsProcessors.sort(function sortByPriority( a, b ){
5155       return a.priority - b.priority;
5156     });
5157   };
5158
5159   Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
5160     var idx = this.columnsProcessors.indexOf(processor);
5161
5162     if (typeof(idx) !== 'undefined' && idx !== undefined) {
5163       this.columnsProcessors.splice(idx, 1);
5164     }
5165   };
5166
5167   Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
5168     var self = this;
5169
5170     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
5171     var myRenderableColumns = renderableColumns.slice(0);
5172
5173     // Return myRenderableRows with no processing if we have no rows processors
5174     if (self.columnsProcessors.length === 0) {
5175       return $q.when(myRenderableColumns);
5176     }
5177
5178     // Counter for iterating through rows processors
5179     var i = 0;
5180
5181     // Promise for when we're done with all the processors
5182     var finished = $q.defer();
5183
5184     // This function will call the processor in self.rowsProcessors at index 'i', and then
5185     //   when done will call the next processor in the list, using the output from the processor
5186     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
5187     //
5188     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
5189     //   the result.
5190     function startProcessor(i, renderedColumnsToProcess) {
5191       // Get the processor at 'i'
5192       var processor = self.columnsProcessors[i].processor;
5193
5194       // Call the processor, passing in the rows to process and the current columns
5195       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
5196       return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
5197         .then(function handleProcessedRows(processedColumns) {
5198           // Check for errors
5199           if (!processedColumns) {
5200             throw "Processor at index " + i + " did not return a set of renderable rows";
5201           }
5202
5203           if (!angular.isArray(processedColumns)) {
5204             throw "Processor at index " + i + " did not return an array";
5205           }
5206
5207           // Processor is done, increment the counter
5208           i++;
5209
5210           // If we're not done with the processors, call the next one
5211           if (i <= self.columnsProcessors.length - 1) {
5212             return startProcessor(i, myRenderableColumns);
5213           }
5214           // We're done! Resolve the 'finished' promise
5215           else {
5216             finished.resolve(myRenderableColumns);
5217           }
5218         });
5219     }
5220
5221     // Start on the first processor
5222     startProcessor(0, myRenderableColumns);
5223
5224     return finished.promise;
5225   };
5226
5227   Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
5228     // gridUtil.logDebug('setVisibleColumns');
5229
5230     var self = this;
5231
5232     // Reset all the render container row caches
5233     for (var i in self.renderContainers) {
5234       var container = self.renderContainers[i];
5235
5236       container.visibleColumnCache.length = 0;
5237     }
5238
5239     for (var ci = 0; ci < columns.length; ci++) {
5240       var column = columns[ci];
5241
5242       // If the column is visible
5243       if (column.visible) {
5244         // If the column has a container specified
5245         if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
5246           self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
5247         }
5248         // If not, put it into the body container
5249         else {
5250           self.renderContainers.body.visibleColumnCache.push(column);
5251         }
5252       }
5253     }
5254   };
5255
5256   /**
5257    * @ngdoc function
5258    * @name handleWindowResize
5259    * @methodOf ui.grid.class:Grid
5260    * @description Triggered when the browser window resizes; automatically resizes the grid
5261    * @returns {Promise} A resolved promise once the window resize has completed.
5262    */
5263   Grid.prototype.handleWindowResize = function handleWindowResize($event) {
5264     var self = this;
5265
5266     self.gridWidth = gridUtil.elementWidth(self.element);
5267     self.gridHeight = gridUtil.elementHeight(self.element);
5268
5269     return self.queueRefresh();
5270   };
5271
5272   /**
5273    * @ngdoc function
5274    * @name queueRefresh
5275    * @methodOf ui.grid.class:Grid
5276    * @description queues a grid refreshCanvas, a way of debouncing all the refreshes we might otherwise issue
5277    */
5278   Grid.prototype.queueRefresh = function queueRefresh() {
5279     var self = this;
5280
5281     if (self.refreshCanceller) {
5282       $timeout.cancel(self.refreshCanceller);
5283     }
5284
5285     self.refreshCanceller = $timeout(function () {
5286       self.refreshCanvas(true);
5287     });
5288
5289     self.refreshCanceller.then(function () {
5290       self.refreshCanceller = null;
5291     });
5292
5293     return self.refreshCanceller;
5294   };
5295
5296
5297   /**
5298    * @ngdoc function
5299    * @name queueGridRefresh
5300    * @methodOf ui.grid.class:Grid
5301    * @description queues a grid refresh, a way of debouncing all the refreshes we might otherwise issue
5302    */
5303   Grid.prototype.queueGridRefresh = function queueGridRefresh() {
5304     var self = this;
5305
5306     if (self.gridRefreshCanceller) {
5307       $timeout.cancel(self.gridRefreshCanceller);
5308     }
5309
5310     self.gridRefreshCanceller = $timeout(function () {
5311       self.refresh(true);
5312     });
5313
5314     self.gridRefreshCanceller.then(function () {
5315       self.gridRefreshCanceller = null;
5316     });
5317
5318     return self.gridRefreshCanceller;
5319   };
5320
5321
5322   /**
5323    * @ngdoc function
5324    * @name updateCanvasHeight
5325    * @methodOf ui.grid.class:Grid
5326    * @description flags all render containers to update their canvas height
5327    */
5328   Grid.prototype.updateCanvasHeight = function updateCanvasHeight() {
5329     var self = this;
5330
5331     for (var containerId in self.renderContainers) {
5332       if (self.renderContainers.hasOwnProperty(containerId)) {
5333         var container = self.renderContainers[containerId];
5334         container.canvasHeightShouldUpdate = true;
5335       }
5336     }
5337   };
5338
5339   /**
5340    * @ngdoc function
5341    * @name buildStyles
5342    * @methodOf ui.grid.class:Grid
5343    * @description calls each styleComputation function
5344    */
5345   // TODO: this used to take $scope, but couldn't see that it was used
5346   Grid.prototype.buildStyles = function buildStyles() {
5347     // gridUtil.logDebug('buildStyles');
5348
5349     var self = this;
5350
5351     self.customStyles = '';
5352
5353     self.styleComputations
5354       .sort(function(a, b) {
5355         if (a.priority === null) { return 1; }
5356         if (b.priority === null) { return -1; }
5357         if (a.priority === null && b.priority === null) { return 0; }
5358         return a.priority - b.priority;
5359       })
5360       .forEach(function (compInfo) {
5361         // this used to provide $scope as a second parameter, but I couldn't find any
5362         // style builders that used it, so removed it as part of moving to grid from controller
5363         var ret = compInfo.func.call(self);
5364
5365         if (angular.isString(ret)) {
5366           self.customStyles += '\n' + ret;
5367         }
5368       });
5369   };
5370
5371
5372   Grid.prototype.minColumnsToRender = function minColumnsToRender() {
5373     var self = this;
5374     var viewport = this.getViewportWidth();
5375
5376     var min = 0;
5377     var totalWidth = 0;
5378     self.columns.forEach(function(col, i) {
5379       if (totalWidth < viewport) {
5380         totalWidth += col.drawnWidth;
5381         min++;
5382       }
5383       else {
5384         var currWidth = 0;
5385         for (var j = i; j >= i - min; j--) {
5386           currWidth += self.columns[j].drawnWidth;
5387         }
5388         if (currWidth < viewport) {
5389           min++;
5390         }
5391       }
5392     });
5393
5394     return min;
5395   };
5396
5397   Grid.prototype.getBodyHeight = function getBodyHeight() {
5398     // Start with the viewportHeight
5399     var bodyHeight = this.getViewportHeight();
5400
5401     // Add the horizontal scrollbar height if there is one
5402     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5403     //  bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5404     //}
5405
5406     return bodyHeight;
5407   };
5408
5409   // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
5410   // TODO(c0bra): account for footer height
5411   Grid.prototype.getViewportHeight = function getViewportHeight() {
5412     var self = this;
5413
5414     var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
5415
5416     // Account for native horizontal scrollbar, if present
5417     //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5418     //  viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5419     //}
5420
5421     var adjustment = self.getViewportAdjustment();
5422
5423     viewPortHeight = viewPortHeight + adjustment.height;
5424
5425     //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5426
5427     return viewPortHeight;
5428   };
5429
5430   Grid.prototype.getViewportWidth = function getViewportWidth() {
5431     var self = this;
5432
5433     var viewPortWidth = this.gridWidth;
5434
5435     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5436     //  viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5437     //}
5438
5439     var adjustment = self.getViewportAdjustment();
5440
5441     viewPortWidth = viewPortWidth + adjustment.width;
5442
5443     //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5444
5445     return viewPortWidth;
5446   };
5447
5448   Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5449     var viewPortWidth = this.getViewportWidth();
5450
5451     //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5452     //  viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5453     //}
5454
5455     return viewPortWidth;
5456   };
5457
5458   Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
5459     this.verticalScrollSyncCallBackFns[containerId] = callBackFn;
5460   };
5461
5462   Grid.prototype.addHorizontalScrollSync = function (containerId, callBackFn) {
5463     this.horizontalScrollSyncCallBackFns[containerId] = callBackFn;
5464   };
5465
5466 /**
5467  * Scroll needed containers by calling their ScrollSyncs
5468  * @param sourceContainerId the containerId that has already set it's top/left.
5469  *         can be empty string which means all containers need to set top/left
5470  * @param scrollEvent
5471  */
5472   Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5473
5474     if (scrollEvent.y) {
5475       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5476       var verts = ['body','left', 'right'];
5477
5478       this.flagScrollingVertically(scrollEvent);
5479
5480       if (sourceContainerId === 'body') {
5481         verts = ['left', 'right'];
5482       }
5483       else if (sourceContainerId === 'left') {
5484         verts = ['body', 'right'];
5485       }
5486       else if (sourceContainerId === 'right') {
5487         verts = ['body', 'left'];
5488       }
5489
5490       for (var i = 0; i < verts.length; i++) {
5491         var id = verts[i];
5492         if (this.verticalScrollSyncCallBackFns[id]) {
5493           this.verticalScrollSyncCallBackFns[id](scrollEvent);
5494         }
5495       }
5496
5497     }
5498
5499     if (scrollEvent.x) {
5500       //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5501       var horizs = ['body','bodyheader', 'bodyfooter'];
5502
5503       this.flagScrollingHorizontally(scrollEvent);
5504       if (sourceContainerId === 'body') {
5505         horizs = ['bodyheader', 'bodyfooter'];
5506       }
5507
5508       for (var j = 0; j < horizs.length; j++) {
5509         var idh = horizs[j];
5510         if (this.horizontalScrollSyncCallBackFns[idh]) {
5511           this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
5512         }
5513       }
5514
5515     }
5516
5517   };
5518
5519   Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5520     this.viewportAdjusters.push(func);
5521   };
5522
5523   Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5524     var idx = this.viewportAdjusters.indexOf(func);
5525
5526     if (typeof(idx) !== 'undefined' && idx !== undefined) {
5527       this.viewportAdjusters.splice(idx, 1);
5528     }
5529   };
5530
5531   Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
5532     var self = this;
5533
5534     var adjustment = { height: 0, width: 0 };
5535
5536     self.viewportAdjusters.forEach(function (func) {
5537       adjustment = func.call(this, adjustment);
5538     });
5539
5540     return adjustment;
5541   };
5542
5543   Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5544     // var count = 0;
5545
5546     // this.rows.forEach(function (row) {
5547     //   if (row.visible) {
5548     //     count++;
5549     //   }
5550     // });
5551
5552     // return this.visibleRowCache.length;
5553     return this.renderContainers.body.visibleRowCache.length;
5554   };
5555
5556    Grid.prototype.getVisibleRows = function getVisibleRows() {
5557     return this.renderContainers.body.visibleRowCache;
5558    };
5559
5560   Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5561     // var count = 0;
5562
5563     // this.rows.forEach(function (row) {
5564     //   if (row.visible) {
5565     //     count++;
5566     //   }
5567     // });
5568
5569     // return this.visibleRowCache.length;
5570     return this.renderContainers.body.visibleColumnCache.length;
5571   };
5572
5573
5574   Grid.prototype.searchRows = function searchRows(renderableRows) {
5575     return rowSearcher.search(this, renderableRows, this.columns);
5576   };
5577
5578   Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
5579     return rowSorter.sort(this, renderableRows, this.columns);
5580   };
5581
5582   /**
5583    * @ngdoc function
5584    * @name getCellValue
5585    * @methodOf ui.grid.class:Grid
5586    * @description Gets the value of a cell for a particular row and column
5587    * @param {GridRow} row Row to access
5588    * @param {GridColumn} col Column to access
5589    */
5590   Grid.prototype.getCellValue = function getCellValue(row, col){
5591     if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
5592       return row.entity[ '$$' + col.uid].rendered;
5593     } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5594       return row.entity[col.field];
5595     } else {
5596       if (!col.cellValueGetterCache) {
5597         col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
5598       }
5599
5600       return col.cellValueGetterCache(row);
5601     }
5602   };
5603
5604   /**
5605    * @ngdoc function
5606    * @name getCellDisplayValue
5607    * @methodOf ui.grid.class:Grid
5608    * @description Gets the displayed value of a cell after applying any the `cellFilter`
5609    * @param {GridRow} row Row to access
5610    * @param {GridColumn} col Column to access
5611    */
5612   Grid.prototype.getCellDisplayValue = function getCellDisplayValue(row, col) {
5613     if ( !col.cellDisplayGetterCache ) {
5614       var custom_filter = col.cellFilter ? " | " + col.cellFilter : "";
5615
5616       if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
5617         col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5618       } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5619         col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5620       } else {
5621         col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
5622       }
5623     }
5624
5625     return col.cellDisplayGetterCache(row);
5626   };
5627
5628
5629   Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
5630     var self = this,
5631         p = 0;
5632
5633     self.columns.forEach(function (col) {
5634       if (col.sort && col.sort.priority !== undefined && col.sort.priority >= p) {
5635         p = col.sort.priority + 1;
5636       }
5637     });
5638
5639     return p;
5640   };
5641
5642   /**
5643    * @ngdoc function
5644    * @name resetColumnSorting
5645    * @methodOf ui.grid.class:Grid
5646    * @description Return the columns that the grid is currently being sorted by
5647    * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
5648    */
5649   Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
5650     var self = this;
5651
5652     self.columns.forEach(function (col) {
5653       if (col !== excludeCol && !col.suppressRemoveSort) {
5654         col.sort = {};
5655       }
5656     });
5657   };
5658
5659   /**
5660    * @ngdoc function
5661    * @name getColumnSorting
5662    * @methodOf ui.grid.class:Grid
5663    * @description Return the columns that the grid is currently being sorted by
5664    * @returns {Array[GridColumn]} An array of GridColumn objects
5665    */
5666   Grid.prototype.getColumnSorting = function getColumnSorting() {
5667     var self = this;
5668
5669     var sortedCols = [], myCols;
5670
5671     // Iterate through all the columns, sorted by priority
5672     // Make local copy of column list, because sorting is in-place and we do not want to
5673     // change the original sequence of columns
5674     myCols = self.columns.slice(0);
5675     myCols.sort(rowSorter.prioritySort).forEach(function (col) {
5676       if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
5677         sortedCols.push(col);
5678       }
5679     });
5680
5681     return sortedCols;
5682   };
5683
5684   /**
5685    * @ngdoc function
5686    * @name sortColumn
5687    * @methodOf ui.grid.class:Grid
5688    * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
5689    * Emits the sortChanged event whenever the sort criteria are changed.
5690    * @param {GridColumn} column Column to set the sorting on
5691    * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
5692    *   If not provided, the column will iterate through the sort directions
5693    *   specified in the {@link ui.grid.class:GridOptions.columnDef#sortDirectionCycle sortDirectionCycle} attribute.
5694    * @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
5695    *   by this column only
5696    * @returns {Promise} A resolved promise that supplies the column.
5697    */
5698
5699   Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
5700     var self = this,
5701         direction = null;
5702
5703     if (typeof(column) === 'undefined' || !column) {
5704       throw new Error('No column parameter provided');
5705     }
5706
5707     // Second argument can either be a direction or whether to add this column to the existing sort.
5708     //   If it's a boolean, it's an add, otherwise, it's a direction
5709     if (typeof(directionOrAdd) === 'boolean') {
5710       add = directionOrAdd;
5711     }
5712     else {
5713       direction = directionOrAdd;
5714     }
5715
5716     if (!add) {
5717       self.resetColumnSorting(column);
5718       column.sort.priority = undefined;
5719       // Get the actual priority since there may be columns which have suppressRemoveSort set
5720       column.sort.priority = self.getNextColumnSortPriority();
5721     }
5722     else if (column.sort.priority === undefined){
5723       column.sort.priority = self.getNextColumnSortPriority();
5724     }
5725
5726     if (!direction) {
5727       // Find the current position in the cycle (or -1).
5728       var i = column.sortDirectionCycle.indexOf(column.sort.direction ? column.sort.direction : null);
5729       // Proceed to the next position in the cycle (or start at the beginning).
5730       i = (i+1) % column.sortDirectionCycle.length;
5731       // If suppressRemoveSort is set, and the next position in the cycle would
5732       // remove the sort, skip it.
5733       if (column.colDef && column.suppressRemoveSort && !column.sortDirectionCycle[i]) {
5734         i = (i+1) % column.sortDirectionCycle.length;
5735       }
5736
5737       if (column.sortDirectionCycle[i]) {
5738         column.sort.direction = column.sortDirectionCycle[i];
5739       } else {
5740         removeSortOfColumn(column, self);
5741       }
5742     }
5743     else {
5744       column.sort.direction = direction;
5745     }
5746
5747     self.api.core.raise.sortChanged( self, self.getColumnSorting() );
5748
5749     return $q.when(column);
5750   };
5751
5752   var removeSortOfColumn = function removeSortOfColumn(column, grid) {
5753     //Decrease priority for every col where priority is higher than the removed sort's priority.
5754     grid.columns.forEach(function (col) {
5755       if (col.sort && col.sort.priority !== undefined && col.sort.priority > column.sort.priority) {
5756         col.sort.priority -= 1;
5757       }
5758     });
5759
5760     //Remove sort
5761     column.sort = {};
5762   };
5763
5764   /**
5765    * communicate to outside world that we are done with initial rendering
5766    */
5767   Grid.prototype.renderingComplete = function(){
5768     if (angular.isFunction(this.options.onRegisterApi)) {
5769       this.options.onRegisterApi(this.api);
5770     }
5771     this.api.core.raise.renderingComplete( this.api );
5772   };
5773
5774   Grid.prototype.createRowHashMap = function createRowHashMap() {
5775     var self = this;
5776
5777     var hashMap = new RowHashMap();
5778     hashMap.grid = self;
5779
5780     return hashMap;
5781   };
5782
5783
5784   /**
5785    * @ngdoc function
5786    * @name refresh
5787    * @methodOf ui.grid.class:Grid
5788    * @description Refresh the rendered grid on screen.
5789    * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed.
5790    */
5791   Grid.prototype.refresh = function refresh(rowsAltered) {
5792     var self = this;
5793
5794     var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
5795       self.setVisibleRows(renderableRows);
5796     });
5797
5798     var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
5799       self.setVisibleColumns(renderableColumns);
5800     });
5801
5802     return $q.all([p1, p2]).then(function () {
5803       self.redrawInPlace(rowsAltered);
5804
5805       self.refreshCanvas(true);
5806     });
5807   };
5808
5809   /**
5810    * @ngdoc function
5811    * @name refreshRows
5812    * @methodOf ui.grid.class:Grid
5813    * @description Refresh the rendered rows on screen?  Note: not functional at present
5814    * @returns {promise} promise that is resolved when render completes?
5815    *
5816    */
5817   Grid.prototype.refreshRows = function refreshRows() {
5818     var self = this;
5819
5820     return self.processRowsProcessors(self.rows)
5821       .then(function (renderableRows) {
5822         self.setVisibleRows(renderableRows);
5823
5824         self.redrawInPlace();
5825
5826         self.refreshCanvas( true );
5827       });
5828   };
5829
5830   /**
5831    * @ngdoc function
5832    * @name refreshCanvas
5833    * @methodOf ui.grid.class:Grid
5834    * @description Builds all styles and recalculates much of the grid sizing
5835    * @param {object} buildStyles optional parameter.  Use TBD
5836    * @returns {promise} promise that is resolved when the canvas
5837    * has been refreshed
5838    *
5839    */
5840   Grid.prototype.refreshCanvas = function(buildStyles) {
5841     var self = this;
5842
5843     if (buildStyles) {
5844       self.buildStyles();
5845     }
5846
5847     var p = $q.defer();
5848
5849     // Get all the header heights
5850     var containerHeadersToRecalc = [];
5851     for (var containerId in self.renderContainers) {
5852       if (self.renderContainers.hasOwnProperty(containerId)) {
5853         var container = self.renderContainers[containerId];
5854
5855         // Skip containers that have no canvasWidth set yet
5856         if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5857           continue;
5858         }
5859
5860         if (container.header || container.headerCanvas) {
5861           container.explicitHeaderHeight = container.explicitHeaderHeight || null;
5862           container.explicitHeaderCanvasHeight = container.explicitHeaderCanvasHeight || null;
5863
5864           containerHeadersToRecalc.push(container);
5865         }
5866       }
5867     }
5868
5869     /*
5870      *
5871      * Here we loop through the headers, measuring each element as well as any header "canvas" it has within it.
5872      *
5873      * If any header is less than the largest header height, it will be resized to that so that we don't have headers
5874      * with different heights, which looks like a rendering problem
5875      *
5876      * We'll do the same thing with the header canvases, and give the header CELLS an explicit height if their canvas
5877      * is smaller than the largest canvas height. That was header cells without extra controls like filtering don't
5878      * appear shorter than other cells.
5879      *
5880      */
5881     if (containerHeadersToRecalc.length > 0) {
5882       // Build the styles without the explicit header heights
5883       if (buildStyles) {
5884         self.buildStyles();
5885       }
5886
5887       // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
5888       $timeout(function() {
5889         // var oldHeaderHeight = self.grid.headerHeight;
5890         // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
5891
5892         var rebuildStyles = false;
5893
5894         // Get all the header heights
5895         var maxHeaderHeight = 0;
5896         var maxHeaderCanvasHeight = 0;
5897         var i, container;
5898         var getHeight = function(oldVal, newVal){
5899           if ( oldVal !== newVal){
5900             rebuildStyles = true;
5901           }
5902           return newVal;
5903         };
5904         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5905           container = containerHeadersToRecalc[i];
5906
5907           // Skip containers that have no canvasWidth set yet
5908           if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5909             continue;
5910           }
5911
5912           if (container.header) {
5913             var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5914
5915             // 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
5916             var topBorder = gridUtil.getBorderSize(container.header, 'top');
5917             var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
5918             var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
5919
5920             innerHeaderHeight  = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
5921
5922             container.innerHeaderHeight = innerHeaderHeight;
5923
5924             // If the header doesn't have an explicit height set, save the largest header height for use later
5925             //   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
5926             if (!container.explicitHeaderHeight && innerHeaderHeight > maxHeaderHeight) {
5927               maxHeaderHeight = innerHeaderHeight;
5928             }
5929           }
5930
5931           if (container.headerCanvas) {
5932             var headerCanvasHeight = container.headerCanvasHeight = getHeight(container.headerCanvasHeight, parseInt(gridUtil.outerElementHeight(container.headerCanvas), 10));
5933
5934
5935             // If the header doesn't have an explicit canvas height, save the largest header canvas height for use later
5936             //   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
5937             if (!container.explicitHeaderCanvasHeight && headerCanvasHeight > maxHeaderCanvasHeight) {
5938               maxHeaderCanvasHeight = headerCanvasHeight;
5939             }
5940           }
5941         }
5942
5943         // Go through all the headers
5944         for (i = 0; i < containerHeadersToRecalc.length; i++) {
5945           container = containerHeadersToRecalc[i];
5946
5947           /* If:
5948               1. We have a max header height
5949               2. This container has a header height defined
5950               3. And either this container has an explicit header height set, OR its header height is less than the max
5951
5952               then:
5953
5954               Give this container's header an explicit height so it will line up with the tallest header
5955           */
5956           if (
5957             maxHeaderHeight > 0 && typeof(container.headerHeight) !== 'undefined' && container.headerHeight !== null &&
5958             (container.explicitHeaderHeight || container.headerHeight < maxHeaderHeight)
5959           ) {
5960             container.explicitHeaderHeight = getHeight(container.explicitHeaderHeight, maxHeaderHeight);
5961           }
5962
5963           // Do the same as above except for the header canvas
5964           if (
5965             maxHeaderCanvasHeight > 0 && typeof(container.headerCanvasHeight) !== 'undefined' && container.headerCanvasHeight !== null &&
5966             (container.explicitHeaderCanvasHeight || container.headerCanvasHeight < maxHeaderCanvasHeight)
5967           ) {
5968             container.explicitHeaderCanvasHeight = getHeight(container.explicitHeaderCanvasHeight, maxHeaderCanvasHeight);
5969           }
5970         }
5971
5972         // Rebuild styles if the header height has changed
5973         //   The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
5974         if (buildStyles && rebuildStyles) {
5975           self.buildStyles();
5976         }
5977
5978         p.resolve();
5979       });
5980     }
5981     else {
5982       // Timeout still needs to be here to trigger digest after styles have been rebuilt
5983       $timeout(function() {
5984         p.resolve();
5985       });
5986     }
5987
5988     return p.promise;
5989   };
5990
5991
5992   /**
5993    * @ngdoc function
5994    * @name redrawCanvas
5995    * @methodOf ui.grid.class:Grid
5996    * @description Redraw the rows and columns based on our current scroll position
5997    * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
5998    *
5999    */
6000   Grid.prototype.redrawInPlace = function redrawInPlace(rowsAdded) {
6001     // gridUtil.logDebug('redrawInPlace');
6002
6003     var self = this;
6004
6005     for (var i in self.renderContainers) {
6006       var container = self.renderContainers[i];
6007
6008       // gridUtil.logDebug('redrawing container', i);
6009
6010       if (rowsAdded) {
6011         container.adjustRows(container.prevScrollTop, null);
6012         container.adjustColumns(container.prevScrollLeft, null);
6013       }
6014       else {
6015         container.adjustRows(null, container.prevScrolltopPercentage);
6016         container.adjustColumns(null, container.prevScrollleftPercentage);
6017       }
6018     }
6019   };
6020
6021     /**
6022      * @ngdoc function
6023      * @name hasLeftContainerColumns
6024      * @methodOf ui.grid.class:Grid
6025      * @description returns true if leftContainer has columns
6026      */
6027     Grid.prototype.hasLeftContainerColumns = function () {
6028       return this.hasLeftContainer() && this.renderContainers.left.renderedColumns.length > 0;
6029     };
6030
6031     /**
6032      * @ngdoc function
6033      * @name hasRightContainerColumns
6034      * @methodOf ui.grid.class:Grid
6035      * @description returns true if rightContainer has columns
6036      */
6037     Grid.prototype.hasRightContainerColumns = function () {
6038       return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
6039     };
6040
6041     /**
6042      * @ngdoc method
6043      * @methodOf  ui.grid.class:Grid
6044      * @name scrollToIfNecessary
6045      * @description Scrolls the grid to make a certain row and column combo visible,
6046      *   in the case that it is not completely visible on the screen already.
6047      * @param {GridRow} gridRow row to make visible
6048      * @param {GridCol} gridCol column to make visible
6049      * @returns {promise} a promise that is resolved when scrolling is complete
6050      */
6051     Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
6052       var self = this;
6053
6054       var scrollEvent = new ScrollEvent(self, 'uiGrid.scrollToIfNecessary');
6055
6056       // Alias the visible row and column caches
6057       var visRowCache = self.renderContainers.body.visibleRowCache;
6058       var visColCache = self.renderContainers.body.visibleColumnCache;
6059
6060       /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
6061
6062       // 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
6063       var topBound = self.renderContainers.body.prevScrollTop + self.headerHeight;
6064
6065       // Don't the let top boundary be less than 0
6066       topBound = (topBound < 0) ? 0 : topBound;
6067
6068       // The left boundary is the current X scroll position
6069       var leftBound = self.renderContainers.body.prevScrollLeft;
6070
6071       // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
6072       //   Basically this is the viewport height added on to the scroll position
6073       var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight -  self.scrollbarWidth;
6074
6075       // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
6076       //if (self.horizontalScrollbarHeight) {
6077       //  bottomBound = bottomBound - self.horizontalScrollbarHeight;
6078       //}
6079
6080       // The right position is the current X scroll position minus the grid width
6081       var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
6082
6083       // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
6084       //if (self.verticalScrollbarWidth) {
6085       //  rightBound = rightBound - self.verticalScrollbarWidth;
6086       //}
6087
6088       // We were given a row to scroll to
6089       if (gridRow !== null) {
6090         // This is the index of the row we want to scroll to, within the list of rows that can be visible
6091         var seekRowIndex = visRowCache.indexOf(gridRow);
6092
6093         // Total vertical scroll length of the grid
6094         var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
6095
6096         // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
6097         //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
6098         //  scrollLength = scrollLength + self.horizontalScrollbarHeight;
6099         //}
6100
6101         // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
6102         var pixelsToSeeRow = (seekRowIndex * self.options.rowHeight + self.headerHeight);
6103
6104         // Don't let the pixels required to see the row be less than zero
6105         pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
6106
6107         var scrollPixels, percentage;
6108
6109         // 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...
6110         if (pixelsToSeeRow < topBound) {
6111           // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
6112           //   to get the full position we need
6113           scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
6114
6115           // Turn the scroll position into a percentage and make it an argument for a scroll event
6116           percentage = scrollPixels / scrollLength;
6117           scrollEvent.y = { percentage: percentage  };
6118         }
6119         // 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...
6120         else if (pixelsToSeeRow > bottomBound) {
6121           // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
6122           //   to get the full position we need
6123           scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
6124
6125           // Turn the scroll position into a percentage and make it an argument for a scroll event
6126           percentage = scrollPixels / scrollLength;
6127           scrollEvent.y = { percentage: percentage  };
6128         }
6129       }
6130
6131       // We were given a column to scroll to
6132       if (gridCol !== null) {
6133         // This is the index of the column we want to scroll to, within the list of columns that can be visible
6134         var seekColumnIndex = visColCache.indexOf(gridCol);
6135
6136         // Total horizontal scroll length of the grid
6137         var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
6138
6139         // This is the minimum amount of pixels we need to scroll horizontal in order to see this column
6140         var columnLeftEdge = 0;
6141         for (var i = 0; i < seekColumnIndex; i++) {
6142           var col = visColCache[i];
6143           columnLeftEdge += col.drawnWidth;
6144         }
6145         columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
6146
6147         var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
6148
6149         // Don't let the pixels required to see the column be less than zero
6150         columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
6151
6152         var horizScrollPixels, horizPercentage;
6153
6154         // If the scroll position we need to see the column is LESS than the left boundary, i.e. obscured before the left of the self...
6155         if (columnLeftEdge < leftBound) {
6156           // Get the different between the left boundary and the required scroll position and subtract it from the current scroll position\
6157           //   to get the full position we need
6158           horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
6159
6160           // Turn the scroll position into a percentage and make it an argument for a scroll event
6161           horizPercentage = horizScrollPixels / horizScrollLength;
6162           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
6163           scrollEvent.x = { percentage: horizPercentage  };
6164         }
6165         // Otherwise if the scroll position we need to see the column is MORE than the right boundary, i.e. obscured after the right of the self...
6166         else if (columnRightEdge > rightBound) {
6167           // Get the different between the right boundary and the required scroll position and add it to the current scroll position
6168           //   to get the full position we need
6169           horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
6170
6171           // Turn the scroll position into a percentage and make it an argument for a scroll event
6172           horizPercentage = horizScrollPixels / horizScrollLength;
6173           horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
6174           scrollEvent.x = { percentage: horizPercentage  };
6175         }
6176       }
6177
6178       var deferred = $q.defer();
6179
6180       // If we need to scroll on either the x or y axes, fire a scroll event
6181       if (scrollEvent.y || scrollEvent.x) {
6182         scrollEvent.withDelay = false;
6183         self.scrollContainers('',scrollEvent);
6184         var dereg = self.api.core.on.scrollEnd(null,function() {
6185           deferred.resolve(scrollEvent);
6186           dereg();
6187         });
6188       }
6189       else {
6190         deferred.resolve();
6191       }
6192
6193       return deferred.promise;
6194     };
6195
6196     /**
6197      * @ngdoc method
6198      * @methodOf ui.grid.class:Grid
6199      * @name scrollTo
6200      * @description Scroll the grid such that the specified
6201      * row and column is in view
6202      * @param {object} rowEntity gridOptions.data[] array instance to make visible
6203      * @param {object} colDef to make visible
6204      * @returns {promise} a promise that is resolved after any scrolling is finished
6205      */
6206     Grid.prototype.scrollTo = function (rowEntity, colDef) {
6207       var gridRow = null, gridCol = null;
6208
6209       if (rowEntity !== null && typeof(rowEntity) !== 'undefined' ) {
6210         gridRow = this.getRow(rowEntity);
6211       }
6212
6213       if (colDef !== null && typeof(colDef) !== 'undefined' ) {
6214         gridCol = this.getColumn(colDef.name ? colDef.name : colDef.field);
6215       }
6216       return this.scrollToIfNecessary(gridRow, gridCol);
6217     };
6218
6219   /**
6220    * @ngdoc function
6221    * @name clearAllFilters
6222    * @methodOf ui.grid.class:Grid
6223    * @description Clears all filters and optionally refreshes the visible rows.
6224    * @param {object} refreshRows Defaults to true.
6225    * @param {object} clearConditions Defaults to false.
6226    * @param {object} clearFlags Defaults to false.
6227    * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
6228    */
6229   Grid.prototype.clearAllFilters = function clearAllFilters(refreshRows, clearConditions, clearFlags) {
6230     // Default `refreshRows` to true because it will be the most commonly desired behaviour.
6231     if (refreshRows === undefined) {
6232       refreshRows = true;
6233     }
6234     if (clearConditions === undefined) {
6235       clearConditions = false;
6236     }
6237     if (clearFlags === undefined) {
6238       clearFlags = false;
6239     }
6240
6241     this.columns.forEach(function(column) {
6242       column.filters.forEach(function(filter) {
6243         filter.term = undefined;
6244
6245         if (clearConditions) {
6246           filter.condition = undefined;
6247         }
6248
6249         if (clearFlags) {
6250           filter.flags = undefined;
6251         }
6252       });
6253     });
6254
6255     if (refreshRows) {
6256       return this.refreshRows();
6257     }
6258   };
6259
6260
6261       // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
6262   function RowHashMap() {}
6263
6264   RowHashMap.prototype = {
6265     /**
6266      * Store key value pair
6267      * @param key key to store can be any type
6268      * @param value value to store can be any type
6269      */
6270     put: function(key, value) {
6271       this[this.grid.options.rowIdentity(key)] = value;
6272     },
6273
6274     /**
6275      * @param key
6276      * @returns {Object} the value for the key
6277      */
6278     get: function(key) {
6279       return this[this.grid.options.rowIdentity(key)];
6280     },
6281
6282     /**
6283      * Remove the key/value pair
6284      * @param key
6285      */
6286     remove: function(key) {
6287       var value = this[key = this.grid.options.rowIdentity(key)];
6288       delete this[key];
6289       return value;
6290     }
6291   };
6292
6293
6294
6295   return Grid;
6296
6297 }]);
6298
6299 })();
6300
6301 (function () {
6302
6303   angular.module('ui.grid')
6304     .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6305       function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6306         /**
6307          * @ngdoc function
6308          * @name ui.grid.class:GridApi
6309          * @description GridApi provides the ability to register public methods events inside the grid and allow
6310          * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6311          * <br/>
6312          * To listen to events, you must add a callback to gridOptions.onRegisterApi
6313          * <pre>
6314          *   $scope.gridOptions.onRegisterApi = function(gridApi){
6315          *      gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6316          *          $log.log('navigation event');
6317          *      });
6318          *   };
6319          * </pre>
6320          * @param {object} grid grid that owns api
6321          */
6322         var GridApi = function GridApi(grid) {
6323           this.grid = grid;
6324           this.listeners = [];
6325
6326           /**
6327            * @ngdoc function
6328            * @name renderingComplete
6329            * @methodOf  ui.grid.core.api:PublicApi
6330            * @description Rendering is complete, called at the same
6331            * time as `onRegisterApi`, but provides a way to obtain
6332            * that same event within features without stopping end
6333            * users from getting at the onRegisterApi method.
6334            *
6335            * Included in gridApi so that it's always there - otherwise
6336            * there is still a timing problem with when a feature can
6337            * call this.
6338            *
6339            * @param {GridApi} gridApi the grid api, as normally
6340            * returned in the onRegisterApi method
6341            *
6342            * @example
6343            * <pre>
6344            *      gridApi.core.on.renderingComplete( grid );
6345            * </pre>
6346            */
6347           this.registerEvent( 'core', 'renderingComplete' );
6348
6349           /**
6350            * @ngdoc event
6351            * @name filterChanged
6352            * @eventOf  ui.grid.core.api:PublicApi
6353            * @description  is raised after the filter is changed.  The nature
6354            * of the watch expression doesn't allow notification of what changed,
6355            * so the receiver of this event will need to re-extract the filter
6356            * conditions from the columns.
6357            *
6358            */
6359           this.registerEvent( 'core', 'filterChanged' );
6360
6361           /**
6362            * @ngdoc function
6363            * @name setRowInvisible
6364            * @methodOf  ui.grid.core.api:PublicApi
6365            * @description Sets an override on the row to make it always invisible,
6366            * which will override any filtering or other visibility calculations.
6367            * If the row is currently visible then sets it to invisible and calls
6368            * both grid refresh and emits the rowsVisibleChanged event
6369            * @param {GridRow} row the row we want to make invisible
6370            */
6371           this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6372
6373           /**
6374            * @ngdoc function
6375            * @name clearRowInvisible
6376            * @methodOf  ui.grid.core.api:PublicApi
6377            * @description Clears any override on visibility for the row so that it returns to
6378            * using normal filtering and other visibility calculations.
6379            * If the row is currently invisible then sets it to visible and calls
6380            * both grid refresh and emits the rowsVisibleChanged event
6381            * TODO: if a filter is active then we can't just set it to visible?
6382            * @param {GridRow} row the row we want to make visible
6383            */
6384           this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6385
6386           /**
6387            * @ngdoc function
6388            * @name getVisibleRows
6389            * @methodOf  ui.grid.core.api:PublicApi
6390            * @description Returns all visible rows
6391            * @param {Grid} grid the grid you want to get visible rows from
6392            * @returns {array} an array of gridRow
6393            */
6394           this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6395
6396           /**
6397            * @ngdoc event
6398            * @name rowsVisibleChanged
6399            * @eventOf  ui.grid.core.api:PublicApi
6400            * @description  is raised after the rows that are visible
6401            * change.  The filtering is zero-based, so it isn't possible
6402            * to say which rows changed (unlike in the selection feature).
6403            * We can plausibly know which row was changed when setRowInvisible
6404            * is called, but in that situation the user already knows which row
6405            * they changed.  When a filter runs we don't know what changed,
6406            * and that is the one that would have been useful.
6407            *
6408            */
6409           this.registerEvent( 'core', 'rowsVisibleChanged' );
6410
6411           /**
6412            * @ngdoc event
6413            * @name rowsRendered
6414            * @eventOf  ui.grid.core.api:PublicApi
6415            * @description  is raised after the cache of visible rows is changed.
6416            */
6417           this.registerEvent( 'core', 'rowsRendered' );
6418
6419
6420           /**
6421            * @ngdoc event
6422            * @name scrollBegin
6423            * @eventOf  ui.grid.core.api:PublicApi
6424            * @description  is raised when scroll begins.  Is throttled, so won't be raised too frequently
6425            */
6426           this.registerEvent( 'core', 'scrollBegin' );
6427
6428           /**
6429            * @ngdoc event
6430            * @name scrollEnd
6431            * @eventOf  ui.grid.core.api:PublicApi
6432            * @description  is raised when scroll has finished.  Is throttled, so won't be raised too frequently
6433            */
6434           this.registerEvent( 'core', 'scrollEnd' );
6435
6436           /**
6437            * @ngdoc event
6438            * @name canvasHeightChanged
6439            * @eventOf  ui.grid.core.api:PublicApi
6440            * @description  is raised when the canvas height has changed
6441            * <br/>
6442            * arguments: oldHeight, newHeight
6443            */
6444           this.registerEvent( 'core', 'canvasHeightChanged');
6445
6446           /**
6447            * @ngdoc event
6448            * @name gridDimensionChanged
6449            * @eventOf  ui.grid.core.api:PublicApi
6450            * @description  is raised when the grid dimensions have changed (when autoResize is on)
6451            * <br/>
6452            * arguments: oldGridHeight, oldGridWidth, newGridHeight, newGridWidth
6453            */
6454           this.registerEvent( 'core', 'gridDimensionChanged');
6455         };
6456
6457         /**
6458          * @ngdoc function
6459          * @name ui.grid.class:suppressEvents
6460          * @methodOf ui.grid.class:GridApi
6461          * @description Used to execute a function while disabling the specified event listeners.
6462          * Disables the listenerFunctions, executes the callbackFn, and then enables
6463          * the listenerFunctions again
6464          * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
6465          * functions that were used in the .on.eventName method
6466          * @param {object} callBackFn function to execute
6467          * @example
6468          * <pre>
6469          *    var navigate = function (newRowCol, oldRowCol){
6470          *       //do something on navigate
6471          *    }
6472          *
6473          *    gridApi.cellNav.on.navigate(scope,navigate);
6474          *
6475          *
6476          *    //call the scrollTo event and suppress our navigate listener
6477          *    //scrollTo will still raise the event for other listeners
6478          *    gridApi.suppressEvents(navigate, function(){
6479          *       gridApi.cellNav.scrollTo(aRow, aCol);
6480          *    });
6481          *
6482          * </pre>
6483          */
6484         GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6485           var self = this;
6486           var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6487
6488           //find all registered listeners
6489           var foundListeners = self.listeners.filter(function(listener) {
6490             return listeners.some(function(l) {
6491               return listener.handler === l;
6492             });
6493           });
6494
6495           //deregister all the listeners
6496           foundListeners.forEach(function(l){
6497             l.dereg();
6498           });
6499
6500           callBackFn();
6501
6502           //reregister all the listeners
6503           foundListeners.forEach(function(l){
6504               l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
6505           });
6506
6507         };
6508
6509         /**
6510          * @ngdoc function
6511          * @name registerEvent
6512          * @methodOf ui.grid.class:GridApi
6513          * @description Registers a new event for the given feature.  The event will get a
6514          * .raise and .on prepended to it
6515          * <br>
6516          * .raise.eventName() - takes no arguments
6517          * <br/>
6518          * <br/>
6519          * .on.eventName(scope, callBackFn, _this)
6520          * <br/>
6521          * scope - a scope reference to add a deregister call to the scopes .$on('destroy').  Scope is optional and can be a null value,
6522          * but in this case you must deregister it yourself via the returned deregister function
6523          * <br/>
6524          * callBackFn - The function to call
6525          * <br/>
6526          * _this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
6527          * <br/>
6528          * .on.eventName returns a dereg funtion that will remove the listener.  It's not necessary to use it as the listener
6529          * will be removed when the scope is destroyed.
6530          * @param {string} featureName name of the feature that raises the event
6531          * @param {string} eventName  name of the event
6532          */
6533         GridApi.prototype.registerEvent = function (featureName, eventName) {
6534           var self = this;
6535           if (!self[featureName]) {
6536             self[featureName] = {};
6537           }
6538
6539           var feature = self[featureName];
6540           if (!feature.on) {
6541             feature.on = {};
6542             feature.raise = {};
6543           }
6544
6545           var eventId = self.grid.id + featureName + eventName;
6546
6547           // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
6548           feature.raise[eventName] = function () {
6549             $rootScope.$emit.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
6550           };
6551
6552           // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
6553           feature.on[eventName] = function (scope, handler, _this) {
6554             if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6555               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');
6556               return;
6557             }
6558             var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
6559
6560             //track our listener so we can turn off and on
6561             var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6562             self.listeners.push(listener);
6563
6564             var removeListener = function(){
6565               listener.dereg();
6566               var index = self.listeners.indexOf(listener);
6567               self.listeners.splice(index,1);
6568             };
6569
6570             //destroy tracking when scope is destroyed
6571             if (scope) {
6572               scope.$on('$destroy', function() {
6573                 removeListener();
6574               });
6575             }
6576
6577
6578             return removeListener;
6579           };
6580         };
6581
6582         function registerEventWithAngular(eventId, handler, grid, _this) {
6583           return $rootScope.$on(eventId, function (event) {
6584             var args = Array.prototype.slice.call(arguments);
6585             args.splice(0, 1); //remove evt argument
6586             handler.apply(_this ? _this : grid.api, args);
6587           });
6588         }
6589
6590         /**
6591          * @ngdoc function
6592          * @name registerEventsFromObject
6593          * @methodOf ui.grid.class:GridApi
6594          * @description Registers features and events from a simple objectMap.
6595          * eventObjectMap must be in this format (multiple features allowed)
6596          * <pre>
6597          * {featureName:
6598          *        {
6599          *          eventNameOne:function(args){},
6600          *          eventNameTwo:function(args){}
6601          *        }
6602          *  }
6603          * </pre>
6604          * @param {object} eventObjectMap map of feature/event names
6605          */
6606         GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6607           var self = this;
6608           var features = [];
6609           angular.forEach(eventObjectMap, function (featProp, featPropName) {
6610             var feature = {name: featPropName, events: []};
6611             angular.forEach(featProp, function (prop, propName) {
6612               feature.events.push(propName);
6613             });
6614             features.push(feature);
6615           });
6616
6617           features.forEach(function (feature) {
6618             feature.events.forEach(function (event) {
6619               self.registerEvent(feature.name, event);
6620             });
6621           });
6622
6623         };
6624
6625         /**
6626          * @ngdoc function
6627          * @name registerMethod
6628          * @methodOf ui.grid.class:GridApi
6629          * @description Registers a new event for the given feature
6630          * @param {string} featureName name of the feature
6631          * @param {string} methodName  name of the method
6632          * @param {object} callBackFn function to execute
6633          * @param {object} _this binds callBackFn 'this' to _this.  Defaults to gridApi.grid
6634          */
6635         GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, _this) {
6636           if (!this[featureName]) {
6637             this[featureName] = {};
6638           }
6639
6640           var feature = this[featureName];
6641
6642           feature[methodName] = gridUtil.createBoundedWrapper(_this || this.grid, callBackFn);
6643         };
6644
6645         /**
6646          * @ngdoc function
6647          * @name registerMethodsFromObject
6648          * @methodOf ui.grid.class:GridApi
6649          * @description Registers features and methods from a simple objectMap.
6650          * eventObjectMap must be in this format (multiple features allowed)
6651          * <br>
6652          * {featureName:
6653          *        {
6654          *          methodNameOne:function(args){},
6655          *          methodNameTwo:function(args){}
6656          *        }
6657          * @param {object} eventObjectMap map of feature/event names
6658          * @param {object} _this binds this to _this for all functions.  Defaults to gridApi.grid
6659          */
6660         GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
6661           var self = this;
6662           var features = [];
6663           angular.forEach(methodMap, function (featProp, featPropName) {
6664             var feature = {name: featPropName, methods: []};
6665             angular.forEach(featProp, function (prop, propName) {
6666               feature.methods.push({name: propName, fn: prop});
6667             });
6668             features.push(feature);
6669           });
6670
6671           features.forEach(function (feature) {
6672             feature.methods.forEach(function (method) {
6673               self.registerMethod(feature.name, method.name, method.fn, _this);
6674             });
6675           });
6676
6677         };
6678
6679         return GridApi;
6680
6681       }]);
6682
6683 })();
6684
6685 (function(){
6686
6687 angular.module('ui.grid')
6688 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
6689
6690   /**
6691    * ******************************************************************************************
6692    * PaulL1: Ugly hack here in documentation.  These properties are clearly properties of GridColumn,
6693    * and need to be noted as such for those extending and building ui-grid itself.
6694    * However, from an end-developer perspective, they interact with all these through columnDefs,
6695    * and they really need to be documented there.  I feel like they're relatively static, and
6696    * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
6697    * comment block.  Ugh.
6698    *
6699    */
6700
6701   /**
6702    * @ngdoc property
6703    * @name name
6704    * @propertyOf ui.grid.class:GridColumn
6705    * @description (mandatory) each column should have a name, although for backward
6706    * compatibility with 2.x name can be omitted if field is present
6707    *
6708    */
6709
6710   /**
6711    * @ngdoc property
6712    * @name name
6713    * @propertyOf ui.grid.class:GridOptions.columnDef
6714    * @description (mandatory) each column should have a name, although for backward
6715    * compatibility with 2.x name can be omitted if field is present
6716    *
6717    */
6718
6719   /**
6720    * @ngdoc property
6721    * @name displayName
6722    * @propertyOf ui.grid.class:GridColumn
6723    * @description Column name that will be shown in the header.  If displayName is not
6724    * provided then one is generated using the name.
6725    *
6726    */
6727
6728   /**
6729    * @ngdoc property
6730    * @name displayName
6731    * @propertyOf ui.grid.class:GridOptions.columnDef
6732    * @description Column name that will be shown in the header.  If displayName is not
6733    * provided then one is generated using the name.
6734    *
6735    */
6736
6737   /**
6738    * @ngdoc property
6739    * @name field
6740    * @propertyOf ui.grid.class:GridColumn
6741    * @description field must be provided if you wish to bind to a
6742    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6743    * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
6744    * See the angular docs on binding expressions.
6745    *
6746    */
6747
6748   /**
6749    * @ngdoc property
6750    * @name field
6751    * @propertyOf ui.grid.class:GridOptions.columnDef
6752    * @description field must be provided if you wish to bind to a
6753    * property in the data source.  Should be an angular expression that evaluates against grid.options.data
6754    * 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.    *
6755    */
6756
6757   /**
6758    * @ngdoc property
6759    * @name filter
6760    * @propertyOf ui.grid.class:GridColumn
6761    * @description Filter on this column.
6762    *
6763    * Available built-in conditions and types are listed under {@link jui.grid.service:uiGridConstants#properties_filter uiGridOptions.filter}
6764    * @example
6765    * <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>
6766    *
6767    */
6768    
6769   /** 
6770    * @ngdoc property
6771    * @name extraStyle
6772    * @propertyOf ui.grid.class:GridColumn
6773    * @description additional on this column.  
6774    * @example
6775    * <pre>{extraStyle: {display:'table-cell'}}</pre>
6776    *
6777    */   
6778     
6779   /**
6780    * @ngdoc object
6781    * @name ui.grid.class:GridColumn
6782    * @description Represents the viewModel for each column.  Any state or methods needed for a Grid Column
6783    * are defined on this prototype
6784    * @param {ColumnDef} colDef the column def to associate with this column
6785    * @param {number} uid the unique and immutable uid we'd like to allocate to this column
6786    * @param {Grid} grid the grid we'd like to create this column in
6787    */
6788   function GridColumn(colDef, uid, grid) {
6789     var self = this;
6790
6791     self.grid = grid;
6792     self.uid = uid;
6793
6794     self.updateColumnDef(colDef, true);
6795
6796     self.aggregationValue = undefined;
6797
6798     // The footer cell registers to listen for the rowsRendered event, and calls this.  Needed to be
6799     // in something with a scope so that the dereg would get called
6800     self.updateAggregationValue = function() {
6801
6802      // gridUtil.logDebug('getAggregationValue for Column ' + self.colDef.name);
6803
6804       /**
6805        * @ngdoc property
6806        * @name aggregationType
6807        * @propertyOf ui.grid.class:GridOptions.columnDef
6808        * @description The aggregation that you'd like to show in the columnFooter for this
6809        * column.  Valid values are in
6810        * {@link ui.grid.service:uiGridConstants#properties_aggregationTypes uiGridConstants.aggregationTypes},
6811        * and currently include `uiGridConstants.aggregationTypes.count`,
6812        * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
6813        * `uiGridConstants.aggregationTypes.max`.
6814        *
6815        * You can also provide a function as the aggregation type, in this case your function needs to accept the full
6816        * set of visible rows, and return a value that should be shown
6817        */
6818       if (!self.aggregationType) {
6819         self.aggregationValue = undefined;
6820         return;
6821       }
6822
6823       var result = 0;
6824       var visibleRows = self.grid.getVisibleRows();
6825
6826       var cellValues = function(){
6827         var values = [];
6828         visibleRows.forEach(function (row) {
6829           var cellValue = self.grid.getCellValue(row, self);
6830           var cellNumber = Number(cellValue);
6831           if (!isNaN(cellNumber)) {
6832             values.push(cellNumber);
6833           }
6834         });
6835         return values;
6836       };
6837
6838       if (angular.isFunction(self.aggregationType)) {
6839         self.aggregationValue = self.aggregationType(visibleRows, self);
6840       }
6841       else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6842         self.aggregationValue = self.grid.getVisibleRowCount();
6843       }
6844       else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6845         cellValues().forEach(function (value) {
6846           result += value;
6847         });
6848         self.aggregationValue = result;
6849       }
6850       else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6851         cellValues().forEach(function (value) {
6852           result += value;
6853         });
6854         result = result / cellValues().length;
6855         self.aggregationValue = result;
6856       }
6857       else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6858         self.aggregationValue = Math.min.apply(null, cellValues());
6859       }
6860       else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6861         self.aggregationValue = Math.max.apply(null, cellValues());
6862       }
6863       else {
6864         self.aggregationValue = '\u00A0';
6865       }
6866     };
6867
6868 //     var throttledUpdateAggregationValue = gridUtil.throttle(updateAggregationValue, self.grid.options.aggregationCalcThrottle, { trailing: true, context: self.name });
6869
6870     /**
6871      * @ngdoc function
6872      * @name getAggregationValue
6873      * @methodOf ui.grid.class:GridColumn
6874      * @description gets the aggregation value based on the aggregation type for this column.
6875      * Debounced using scrollDebounce option setting
6876      */
6877     this.getAggregationValue =  function() {
6878 //      if (!self.grid.isScrollingVertically && !self.grid.isScrollingHorizontally) {
6879 //        throttledUpdateAggregationValue();
6880 //      }
6881
6882       return self.aggregationValue;
6883     };
6884   }
6885
6886   /**
6887    * @ngdoc function
6888    * @name hideColumn
6889    * @methodOf ui.grid.class:GridColumn
6890    * @description Hides the column by setting colDef.visible = false
6891    */
6892   GridColumn.prototype.hideColumn = function() {
6893     this.colDef.visible = false;
6894   };
6895   
6896
6897   /**
6898    * @ngdoc method
6899    * @methodOf ui.grid.class:GridColumn
6900    * @name setPropertyOrDefault
6901    * @description Sets a property on the column using the passed in columnDef, and
6902    * setting the defaultValue if the value cannot be found on the colDef
6903    * @param {ColumnDef} colDef the column def to look in for the property value
6904    * @param {string} propName the property name we'd like to set
6905    * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
6906    */
6907   GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
6908     var self = this;
6909
6910     // Use the column definition filter if we were passed it
6911     if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
6912       self[propName] = colDef[propName];
6913     }
6914     // Otherwise use our own if it's set
6915     else if (typeof(self[propName]) !== 'undefined') {
6916       self[propName] = self[propName];
6917     }
6918     // Default to empty object for the filter
6919     else {
6920       self[propName] = defaultValue ? defaultValue : {};
6921     }
6922   };
6923
6924
6925
6926   /**
6927    * @ngdoc property
6928    * @name width
6929    * @propertyOf ui.grid.class:GridOptions.columnDef
6930    * @description sets the column width.  Can be either
6931    * a number or a percentage, or an * for auto.
6932    * @example
6933    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
6934    *                                          { field: 'field2', width: '20%'},
6935    *                                          { field: 'field3', width: '*' }]; </pre>
6936    *
6937    */
6938
6939   /**
6940    * @ngdoc property
6941    * @name minWidth
6942    * @propertyOf ui.grid.class:GridOptions.columnDef
6943    * @description sets the minimum column width.  Should be a number.
6944    * @example
6945    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
6946    *
6947    */
6948
6949   /**
6950    * @ngdoc property
6951    * @name maxWidth
6952    * @propertyOf ui.grid.class:GridOptions.columnDef
6953    * @description sets the maximum column width.  Should be a number.
6954    * @example
6955    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
6956    *
6957    */
6958
6959   /**
6960    * @ngdoc property
6961    * @name visible
6962    * @propertyOf ui.grid.class:GridOptions.columnDef
6963    * @description sets whether or not the column is visible
6964    * </br>Default is true
6965    * @example
6966    * <pre>  $scope.gridOptions.columnDefs = [
6967    *     { field: 'field1', visible: true},
6968    *     { field: 'field2', visible: false }
6969    *   ]; </pre>
6970    *
6971    */
6972
6973  /**
6974   * @ngdoc property
6975   * @name sort
6976   * @propertyOf ui.grid.class:GridOptions.columnDef
6977   * @description An object of sort information, attributes are:
6978   *
6979   * - direction: values are {@link ui.grid.service:uiGridConstants#properties_ASC uiGridConstants.ASC}
6980   *  or {@link ui.grid.service:uiGridConstants#properties_DESC uiGridConstants.DESC}
6981   * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6982   * - priority: says what order to sort the columns in (lower priority gets sorted first).
6983   * @example
6984   * <pre>
6985   *   $scope.gridOptions.columnDefs = [{
6986   *     field: 'field1',
6987   *     sort: {
6988   *       direction: uiGridConstants.ASC,
6989   *       ignoreSort: true,
6990   *       priority: 0
6991   *      }
6992   *   }];
6993   * </pre>
6994   */
6995
6996
6997   /**
6998    * @ngdoc property
6999    * @name sortingAlgorithm
7000    * @propertyOf ui.grid.class:GridOptions.columnDef
7001    * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
7002    * like any normal sorting function with additional 'rowA', 'rowB', and 'direction' parameters
7003    * that are the row objects and the current direction of the sort respectively.
7004    *
7005    */
7006
7007   /**
7008    * @ngdoc array
7009    * @name filters
7010    * @propertyOf ui.grid.class:GridOptions.columnDef
7011    * @description Specify multiple filter fields.
7012    * @example
7013    * <pre>$scope.gridOptions.columnDefs = [
7014    *   {
7015    *     field: 'field1', filters: [
7016    *       {
7017    *         term: 'aa',
7018    *         condition: uiGridConstants.filter.STARTS_WITH,
7019    *         placeholder: 'starts with...',
7020    *         ariaLabel: 'Filter for field1',
7021    *         flags: { caseSensitive: false },
7022    *         type: uiGridConstants.filter.SELECT,
7023    *         selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
7024    *       },
7025    *       {
7026    *         condition: uiGridConstants.filter.ENDS_WITH,
7027    *         placeholder: 'ends with...'
7028    *       }
7029    *     ]
7030    *   }
7031    * ]; </pre>
7032    *
7033    *
7034    */
7035
7036   /**
7037    * @ngdoc array
7038    * @name filters
7039    * @propertyOf ui.grid.class:GridColumn
7040    * @description Filters for this column. Includes 'term' property bound to filter input elements.
7041    * @example
7042    * <pre>[
7043    *   {
7044    *     term: 'foo', // ngModel for <input>
7045    *     condition: uiGridConstants.filter.STARTS_WITH,
7046    *     placeholder: 'starts with...',
7047    *     ariaLabel: 'Filter for foo',
7048    *     flags: { caseSensitive: false },
7049    *     type: uiGridConstants.filter.SELECT,
7050    *     selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
7051    *   },
7052    *   {
7053    *     term: 'baz',
7054    *     condition: uiGridConstants.filter.ENDS_WITH,
7055    *     placeholder: 'ends with...'
7056    *   }
7057    * ] </pre>
7058    *
7059    *
7060    */
7061
7062   /**
7063    * @ngdoc array
7064    * @name menuItems
7065    * @propertyOf ui.grid.class:GridOptions.columnDef
7066    * @description used to add menu items to a column.  Refer to the tutorial on this
7067    * functionality.  A number of settings are supported:
7068    *
7069    * - title: controls the title that is displayed in the menu
7070    * - icon: the icon shown alongside that title
7071    * - action: the method to call when the menu is clicked
7072    * - shown: a function to evaluate to determine whether or not to show the item
7073    * - active: a function to evaluate to determine whether or not the item is currently selected
7074    * - context: context to pass to the action function, available in this.context in your handler
7075    * - leaveOpen: if set to true, the menu should stay open after the action, defaults to false
7076    * @example
7077    * <pre>  $scope.gridOptions.columnDefs = [
7078    *   { field: 'field1', menuItems: [
7079    *     {
7080    *       title: 'Outer Scope Alert',
7081    *       icon: 'ui-grid-icon-info-circled',
7082    *       action: function($event) {
7083    *         this.context.blargh(); // $scope.blargh() would work too, this is just an example
7084    *       },
7085    *       shown: function() { return true; },
7086    *       active: function() { return true; },
7087    *       context: $scope
7088    *     },
7089    *     {
7090    *       title: 'Grid ID',
7091    *       action: function() {
7092    *         alert('Grid ID: ' + this.grid.id);
7093    *       }
7094    *     }
7095    *   ] }]; </pre>
7096    *
7097    */
7098
7099   /**
7100    * @ngdoc method
7101    * @methodOf ui.grid.class:GridColumn
7102    * @name updateColumnDef
7103    * @description Moves settings from the columnDef down onto the column,
7104    * and sets properties as appropriate
7105    * @param {ColumnDef} colDef the column def to look in for the property value
7106    * @param {boolean} isNew whether the column is being newly created, if not
7107    * we're updating an existing column, and some items such as the sort shouldn't
7108    * be copied down
7109    */
7110   GridColumn.prototype.updateColumnDef = function(colDef, isNew) {
7111     var self = this;
7112
7113     self.colDef = colDef;
7114
7115     if (colDef.name === undefined) {
7116       throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
7117     }
7118
7119     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
7120
7121     if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
7122       var colDefWidth = colDef.width;
7123       var parseErrorMsg = "Cannot parse column width '" + colDefWidth + "' for column named '" + colDef.name + "'";
7124       self.hasCustomWidth = false;
7125
7126       if (!angular.isString(colDefWidth) && !angular.isNumber(colDefWidth)) {
7127         self.width = '*';
7128       } else if (angular.isString(colDefWidth)) {
7129         // See if it ends with a percent
7130         if (gridUtil.endsWith(colDefWidth, '%')) {
7131           // If so we should be able to parse the non-percent-sign part to a number
7132           var percentStr = colDefWidth.replace(/%/g, '');
7133           var percent = parseInt(percentStr, 10);
7134           if (isNaN(percent)) {
7135             throw new Error(parseErrorMsg);
7136           }
7137           self.width = colDefWidth;
7138         }
7139         // And see if it's a number string
7140         else if (colDefWidth.match(/^(\d+)$/)) {
7141           self.width = parseInt(colDefWidth.match(/^(\d+)$/)[1], 10);
7142         }
7143         // Otherwise it should be a string of asterisks
7144         else if (colDefWidth.match(/^\*+$/)) {
7145           self.width = colDefWidth;
7146         }
7147         // No idea, throw an Error
7148         else {
7149           throw new Error(parseErrorMsg);
7150         }
7151       }
7152       // Is a number, use it as the width
7153       else {
7154         self.width = colDefWidth;
7155       }
7156     }
7157
7158     ['minWidth', 'maxWidth'].forEach(function (name) {
7159       var minOrMaxWidth = colDef[name];
7160       var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
7161
7162       if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
7163         //Sets default minWidth and maxWidth values
7164         self[name] = ((name === 'minWidth') ? 30 : 9000);
7165       } else if (angular.isString(minOrMaxWidth)) {
7166         if (minOrMaxWidth.match(/^(\d+)$/)) {
7167           self[name] = parseInt(minOrMaxWidth.match(/^(\d+)$/)[1], 10);
7168         } else {
7169           throw new Error(parseErrorMsg);
7170         }
7171       } else {
7172         self[name] = minOrMaxWidth;
7173       }
7174     });
7175
7176     //use field if it is defined; name if it is not
7177     self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
7178
7179     if ( typeof( self.field ) !== 'string' ){
7180       gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
7181     }
7182
7183     self.name = colDef.name;
7184
7185     // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
7186     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
7187
7188     //self.originalIndex = index;
7189
7190     self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
7191     self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
7192
7193     /**
7194      * @ngdoc property
7195      * @name cellTooltip
7196      * @propertyOf ui.grid.class:GridOptions.columnDef
7197      * @description Whether or not to show a tooltip when a user hovers over the cell.
7198      * If set to false, no tooltip.  If true, the cell value is shown in the tooltip (useful
7199      * if you have long values in your cells), if a function then that function is called
7200      * passing in the row and the col `cellTooltip( row, col )`, and the return value is shown in the tooltip,
7201      * if it is a static string then displays that static string.
7202      *
7203      * Defaults to false
7204      *
7205      */
7206     if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
7207       self.cellTooltip = false;
7208     } else if ( colDef.cellTooltip === true ){
7209       self.cellTooltip = function(row, col) {
7210         return self.grid.getCellValue( row, col );
7211       };
7212     } else if (typeof(colDef.cellTooltip) === 'function' ){
7213       self.cellTooltip = colDef.cellTooltip;
7214     } else {
7215       self.cellTooltip = function ( row, col ){
7216         return col.colDef.cellTooltip;
7217       };
7218     }
7219
7220     /**
7221      * @ngdoc property
7222      * @name headerTooltip
7223      * @propertyOf ui.grid.class:GridOptions.columnDef
7224      * @description Whether or not to show a tooltip when a user hovers over the header cell.
7225      * If set to false, no tooltip.  If true, the displayName is shown in the tooltip (useful
7226      * if you have long values in your headers), if a function then that function is called
7227      * passing in the row and the col `headerTooltip( col )`, and the return value is shown in the tooltip,
7228      * if a static string then shows that static string.
7229      *
7230      * Defaults to false
7231      *
7232      */
7233     if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
7234       self.headerTooltip = false;
7235     } else if ( colDef.headerTooltip === true ){
7236       self.headerTooltip = function(col) {
7237         return col.displayName;
7238       };
7239     } else if (typeof(colDef.headerTooltip) === 'function' ){
7240       self.headerTooltip = colDef.headerTooltip;
7241     } else {
7242       self.headerTooltip = function ( col ) {
7243         return col.colDef.headerTooltip;
7244       };
7245     }
7246
7247
7248     /**
7249      * @ngdoc property
7250      * @name footerCellClass
7251      * @propertyOf ui.grid.class:GridOptions.columnDef
7252      * @description footerCellClass can be a string specifying the class to append to a cell
7253      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7254      *
7255      */
7256     self.footerCellClass = colDef.footerCellClass;
7257
7258     /**
7259      * @ngdoc property
7260      * @name cellClass
7261      * @propertyOf ui.grid.class:GridOptions.columnDef
7262      * @description cellClass can be a string specifying the class to append to a cell
7263      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7264      *
7265      */
7266     self.cellClass = colDef.cellClass;
7267
7268     /**
7269      * @ngdoc property
7270      * @name headerCellClass
7271      * @propertyOf ui.grid.class:GridOptions.columnDef
7272      * @description headerCellClass can be a string specifying the class to append to a cell
7273      * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7274      *
7275      */
7276     self.headerCellClass = colDef.headerCellClass;
7277
7278     /**
7279      * @ngdoc property
7280      * @name cellFilter
7281      * @propertyOf ui.grid.class:GridOptions.columnDef
7282      * @description cellFilter is a filter to apply to the content of each cell
7283      * @example
7284      * <pre>
7285      *   gridOptions.columnDefs[0].cellFilter = 'date'
7286      *
7287      */
7288     self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
7289
7290     /**
7291      * @ngdoc boolean
7292      * @name sortCellFiltered
7293      * @propertyOf ui.grid.class:GridOptions.columnDef
7294      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7295      * sorting the data. Note that when using this option uiGrid will assume that the displayed value is
7296      * a string, and use the {@link ui.grid.class:RowSorter#sortAlpha sortAlpha} `sortFn`. It is possible
7297      * 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}
7298      * for the column which hanldes the returned type. You may specify one of the `sortingAlgorithms`
7299      * found in the {@link ui.grid.RowSorter rowSorter} service.
7300      */
7301     self.sortCellFiltered = colDef.sortCellFiltered ? true : false;
7302
7303     /**
7304      * @ngdoc boolean
7305      * @name filterCellFiltered
7306      * @propertyOf ui.grid.class:GridOptions.columnDef
7307      * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7308      * applying "search" `filters`.
7309      */
7310     self.filterCellFiltered = colDef.filterCellFiltered ? true : false;
7311
7312     /**
7313      * @ngdoc property
7314      * @name headerCellFilter
7315      * @propertyOf ui.grid.class:GridOptions.columnDef
7316      * @description headerCellFilter is a filter to apply to the content of the column header
7317      * @example
7318      * <pre>
7319      *   gridOptions.columnDefs[0].headerCellFilter = 'translate'
7320      *
7321      */
7322     self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
7323
7324     /**
7325      * @ngdoc property
7326      * @name footerCellFilter
7327      * @propertyOf ui.grid.class:GridOptions.columnDef
7328      * @description footerCellFilter is a filter to apply to the content of the column footer
7329      * @example
7330      * <pre>
7331      *   gridOptions.columnDefs[0].footerCellFilter = 'date'
7332      *
7333      */
7334     self.footerCellFilter = colDef.footerCellFilter ? colDef.footerCellFilter : "";
7335
7336     self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
7337
7338     self.headerClass = colDef.headerClass;
7339     //self.cursor = self.sortable ? 'pointer' : 'default';
7340
7341     // Turn on sorting by default
7342     self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : self.grid.options.enableSorting;
7343     self.sortingAlgorithm = colDef.sortingAlgorithm;
7344
7345     /**
7346      * @ngdoc property
7347      * @name sortDirectionCycle
7348      * @propertyOf ui.grid.class:GridOptions.columnDef
7349      * @description (optional) An array of {@link ui.grid.service:uiGridConstants#properties_ASC sort directions},
7350      * specifying the order that they should cycle through as the user repeatedly clicks on the column heading.
7351      * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
7352      * refers to the unsorted state. This does not affect the initial sort
7353      * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
7354      * property for that. If
7355      * {@link ui.grid.class:GridOptions.columnDef#suppressRemoveSort suppressRemoveSort}
7356      * is also set, the unsorted state will be skipped even if it is listed here.
7357      * Each direction may not appear in the list more than once (e.g. `[ASC,
7358      * DESC, DESC]` is not allowed), and the list may not be empty.
7359      */
7360     self.sortDirectionCycle = typeof(colDef.sortDirectionCycle) !== 'undefined' ?
7361       colDef.sortDirectionCycle :
7362       [null, uiGridConstants.ASC, uiGridConstants.DESC];
7363
7364     /**
7365      * @ngdoc boolean
7366      * @name suppressRemoveSort
7367      * @propertyOf ui.grid.class:GridOptions.columnDef
7368      * @description (optional) False by default. When enabled, this setting hides the removeSort option
7369      * in the menu, and prevents users from manually removing the sort
7370      */
7371     if ( typeof(self.suppressRemoveSort) === 'undefined'){
7372       self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
7373     }
7374
7375     /**
7376      * @ngdoc property
7377      * @name enableFiltering
7378      * @propertyOf ui.grid.class:GridOptions.columnDef
7379      * @description turn off filtering for an individual column, where
7380      * you've turned on filtering for the overall grid
7381      * @example
7382      * <pre>
7383      *   gridOptions.columnDefs[0].enableFiltering = false;
7384      *
7385      */
7386     // Turn on filtering by default (it's disabled by default at the Grid level)
7387     self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
7388
7389     // self.menuItems = colDef.menuItems;
7390     self.setPropertyOrDefault(colDef, 'menuItems', []);
7391
7392     // Use the column definition sort if we were passed it, but only if this is a newly added column
7393     if ( isNew ){
7394       self.setPropertyOrDefault(colDef, 'sort');
7395     }
7396
7397     // Set up default filters array for when one is not provided.
7398     //   In other words, this (in column def):
7399     //
7400     //       filter: { term: 'something', flags: {}, condition: [CONDITION] }
7401     //
7402     //   is just shorthand for this:
7403     //
7404     //       filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
7405     //
7406     var defaultFilters = [];
7407     if (colDef.filter) {
7408       defaultFilters.push(colDef.filter);
7409     }
7410     else if ( colDef.filters ){
7411       defaultFilters = colDef.filters;
7412     } else {
7413       // Add an empty filter definition object, which will
7414       // translate to a guessed condition and no pre-populated
7415       // value for the filter <input>.
7416       defaultFilters.push({});
7417     }
7418
7419     /**
7420      * @ngdoc property
7421      * @name filter
7422      * @propertyOf ui.grid.class:GridOptions.columnDef
7423      * @description Specify a single filter field on this column.
7424      *
7425      * A filter consists of a condition, a term, and a placeholder:
7426      *
7427      * - condition defines how rows are chosen as matching the filter term. This can be set to
7428      * one of the constants in {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter},
7429      * or you can supply a custom filter function
7430      * that gets passed the following arguments: [searchTerm, cellValue, row, column].
7431      * - term: If set, the filter field will be pre-populated
7432      * with this value.
7433      * - placeholder: String that will be set to the `<input>.placeholder` attribute.
7434      * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
7435      * - noTerm: set this to true if you have defined a custom function in condition, and
7436      * your custom function doesn't require a term (so it can run even when the term is null)
7437      * - rawTerm: set this to true if you have defined a custom function in condition, and
7438      * your custom function requires access to the raw unmodified search term that was entered
7439      * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
7440      * case sensitive matching
7441      * - type: defaults to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.INPUT},
7442      * which gives a text box.  If set to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.SELECT}
7443      * then a select box will be shown with options selectOptions
7444      * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`.  No i18n filter is provided, you need
7445      * to perform the i18n on the values before you provide them
7446      * - disableCancelFilterButton: defaults to false. If set to true then the 'x' button that cancels/clears the filter
7447      * will not be shown.
7448      * @example
7449      * <pre>$scope.gridOptions.columnDefs = [
7450      *   {
7451      *     field: 'field1',
7452      *     filter: {
7453      *       term: 'xx',
7454      *       condition: uiGridConstants.filter.STARTS_WITH,
7455      *       placeholder: 'starts with...',
7456      *       ariaLabel: 'Starts with filter for field1',
7457      *       flags: { caseSensitive: false },
7458      *       type: uiGridConstants.filter.SELECT,
7459      *       selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ],
7460      *       disableCancelFilterButton: true
7461      *     }
7462      *   }
7463      * ]; </pre>
7464      *
7465      */
7466
7467     /*
7468
7469
7470     /*
7471
7472       self.filters = [
7473         {
7474           term: 'search term'
7475           condition: uiGridConstants.filter.CONTAINS,
7476           placeholder: 'my placeholder',
7477           ariaLabel: 'Starts with filter for field1',
7478           flags: {
7479             caseSensitive: true
7480           }
7481         }
7482       ]
7483
7484     */
7485
7486     // Only set filter if this is a newly added column, if we're updating an existing
7487     // column then we don't want to put the default filter back if the user may have already
7488     // removed it.
7489     // However, we do want to keep the settings if they change, just not the term
7490     if ( isNew ) {
7491       self.setPropertyOrDefault(colDef, 'filter');
7492       self.setPropertyOrDefault(colDef, 'extraStyle');
7493       self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
7494     } else if ( self.filters.length === defaultFilters.length ) {
7495       self.filters.forEach( function( filter, index ){
7496         if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
7497           filter.placeholder = defaultFilters[index].placeholder;
7498         }
7499         if (typeof(defaultFilters[index].ariaLabel) !== 'undefined') {
7500           filter.ariaLabel = defaultFilters[index].ariaLabel;
7501         }
7502         if (typeof(defaultFilters[index].flags) !== 'undefined') {
7503           filter.flags = defaultFilters[index].flags;
7504         }
7505         if (typeof(defaultFilters[index].type) !== 'undefined') {
7506           filter.type = defaultFilters[index].type;
7507         }
7508         if (typeof(defaultFilters[index].selectOptions) !== 'undefined') {
7509           filter.selectOptions = defaultFilters[index].selectOptions;
7510         }
7511       });
7512     }
7513   };
7514
7515   /**
7516    * @ngdoc function
7517    * @name unsort
7518    * @methodOf ui.grid.class:GridColumn
7519    * @description Removes column from the grid sorting
7520    */
7521   GridColumn.prototype.unsort = function () {
7522     this.sort = {};
7523     this.grid.api.core.raise.sortChanged( this.grid, this.grid.getColumnSorting() );
7524   };
7525
7526
7527   /**
7528    * @ngdoc function
7529    * @name getColClass
7530    * @methodOf ui.grid.class:GridColumn
7531    * @description Returns the class name for the column
7532    * @param {bool} prefixDot  if true, will return .className instead of className
7533    */
7534   GridColumn.prototype.getColClass = function (prefixDot) {
7535     var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
7536
7537     return prefixDot ? '.' + cls : cls;
7538   };
7539
7540     /**
7541      * @ngdoc function
7542      * @name isPinnedLeft
7543      * @methodOf ui.grid.class:GridColumn
7544      * @description Returns true if column is in the left render container
7545      */
7546     GridColumn.prototype.isPinnedLeft = function () {
7547       return this.renderContainer === 'left';
7548     };
7549
7550     /**
7551      * @ngdoc function
7552      * @name isPinnedRight
7553      * @methodOf ui.grid.class:GridColumn
7554      * @description Returns true if column is in the right render container
7555      */
7556     GridColumn.prototype.isPinnedRight = function () {
7557       return this.renderContainer === 'right';
7558     };
7559
7560
7561     /**
7562    * @ngdoc function
7563    * @name getColClassDefinition
7564    * @methodOf ui.grid.class:GridColumn
7565    * @description Returns the class definition for th column
7566    */
7567   GridColumn.prototype.getColClassDefinition = function () {
7568     return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { min-width: ' + this.drawnWidth + 'px; max-width: ' + this.drawnWidth + 'px; }';
7569   };
7570
7571   /**
7572    * @ngdoc function
7573    * @name getRenderContainer
7574    * @methodOf ui.grid.class:GridColumn
7575    * @description Returns the render container object that this column belongs to.
7576    *
7577    * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
7578    */
7579   GridColumn.prototype.getRenderContainer = function getRenderContainer() {
7580     var self = this;
7581
7582     var containerId = self.renderContainer;
7583
7584     if (containerId === null || containerId === '' || containerId === undefined) {
7585       containerId = 'body';
7586     }
7587
7588     return self.grid.renderContainers[containerId];
7589   };
7590
7591   /**
7592    * @ngdoc function
7593    * @name showColumn
7594    * @methodOf ui.grid.class:GridColumn
7595    * @description Makes the column visible by setting colDef.visible = true
7596    */
7597   GridColumn.prototype.showColumn = function() {
7598       this.colDef.visible = true;
7599   };
7600
7601
7602   /**
7603    * @ngdoc property
7604    * @name aggregationHideLabel
7605    * @propertyOf ui.grid.class:GridOptions.columnDef
7606    * @description defaults to false, if set to true hides the label text
7607    * in the aggregation footer, so only the value is displayed.
7608    *
7609    */
7610   /**
7611    * @ngdoc function
7612    * @name getAggregationText
7613    * @methodOf ui.grid.class:GridColumn
7614    * @description Gets the aggregation label from colDef.aggregationLabel if
7615    * specified or by using i18n, including deciding whether or not to display
7616    * based on colDef.aggregationHideLabel.
7617    *
7618    * @param {string} label the i18n lookup value to use for the column label
7619    *
7620    */
7621   GridColumn.prototype.getAggregationText = function () {
7622     var self = this;
7623     if ( self.colDef.aggregationHideLabel ){
7624       return '';
7625     }
7626     else if ( self.colDef.aggregationLabel ) {
7627       return self.colDef.aggregationLabel;
7628     }
7629     else {
7630       switch ( self.colDef.aggregationType ){
7631         case uiGridConstants.aggregationTypes.count:
7632           return i18nService.getSafeText('aggregation.count');
7633         case uiGridConstants.aggregationTypes.sum:
7634           return i18nService.getSafeText('aggregation.sum');
7635         case uiGridConstants.aggregationTypes.avg:
7636           return i18nService.getSafeText('aggregation.avg');
7637         case uiGridConstants.aggregationTypes.min:
7638           return i18nService.getSafeText('aggregation.min');
7639         case uiGridConstants.aggregationTypes.max:
7640           return i18nService.getSafeText('aggregation.max');
7641         default:
7642           return '';
7643       }
7644     }
7645   };
7646
7647   GridColumn.prototype.getCellTemplate = function () {
7648     var self = this;
7649
7650     return self.cellTemplatePromise;
7651   };
7652
7653   GridColumn.prototype.getCompiledElementFn = function () {
7654     var self = this;
7655
7656     return self.compiledElementFnDefer.promise;
7657   };
7658
7659   return GridColumn;
7660 }]);
7661
7662 })();
7663
7664   (function(){
7665
7666 angular.module('ui.grid')
7667 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7668
7669   /**
7670    * @ngdoc function
7671    * @name ui.grid.class:GridOptions
7672    * @description Default GridOptions class.  GridOptions are defined by the application developer and overlaid
7673    * over this object.  Setting gridOptions within your controller is the most common method for an application
7674    * developer to configure the behaviour of their ui-grid
7675    *
7676    * @example To define your gridOptions within your controller:
7677    * <pre>$scope.gridOptions = {
7678    *   data: $scope.myData,
7679    *   columnDefs: [
7680    *     { name: 'field1', displayName: 'pretty display name' },
7681    *     { name: 'field2', visible: false }
7682    *  ]
7683    * };</pre>
7684    *
7685    * You can then use this within your html template, when you define your grid:
7686    * <pre>&lt;div ui-grid="gridOptions"&gt;&lt;/div&gt;</pre>
7687    *
7688    * To provide default options for all of the grids within your application, use an angular
7689    * decorator to modify the GridOptions factory.
7690    * <pre>
7691    * app.config(function($provide){
7692    *   $provide.decorator('GridOptions',function($delegate){
7693    *     var gridOptions;
7694    *     gridOptions = angular.copy($delegate);
7695    *     gridOptions.initialize = function(options) {
7696    *       var initOptions;
7697    *       initOptions = $delegate.initialize(options);
7698    *       initOptions.enableColumnMenus = false;
7699    *       return initOptions;
7700    *     };
7701    *     return gridOptions;
7702    *   });
7703    * });
7704    * </pre>
7705    */
7706   return {
7707     initialize: function( baseOptions ){
7708       /**
7709        * @ngdoc function
7710        * @name onRegisterApi
7711        * @propertyOf ui.grid.class:GridOptions
7712        * @description A callback that returns the gridApi once the grid is instantiated, which is
7713        * then used to interact with the grid programatically.
7714        *
7715        * Note that the gridApi.core.renderingComplete event is identical to this
7716        * callback, but has the advantage that it can be called from multiple places
7717        * if needed
7718        *
7719        * @example
7720        * <pre>
7721        *   $scope.gridOptions.onRegisterApi = function ( gridApi ) {
7722        *     $scope.gridApi = gridApi;
7723        *     $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid );
7724        *   };
7725        * </pre>
7726        *
7727        */
7728       baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
7729
7730       /**
7731        * @ngdoc object
7732        * @name data
7733        * @propertyOf ui.grid.class:GridOptions
7734        * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
7735        * the grid.
7736        *
7737        * Most commonly the data is an array of objects, where each object has a number of attributes.
7738        * Each attribute automatically becomes a column in your grid.  This array could, for example, be sourced from
7739        * an angularJS $resource query request.  The array can also contain complex objects, refer the binding tutorial
7740        * for examples of that.
7741        *
7742        * The most flexible usage is to set your data on $scope:
7743        *
7744        * `$scope.data = data;`
7745        *
7746        * And then direct the grid to resolve whatever is in $scope.data:
7747        *
7748        * `$scope.gridOptions.data = 'data';`
7749        *
7750        * This is the most flexible approach as it allows you to replace $scope.data whenever you feel like it without
7751        * getting pointer issues.
7752        *
7753        * Alternatively you can directly set the data array:
7754        *
7755        * `$scope.gridOptions.data = [ ];`
7756        * or
7757        *
7758        * `$http.get('/data/100.json')
7759        * .success(function(data) {
7760        *   $scope.myData = data;
7761        *   $scope.gridOptions.data = $scope.myData;
7762        *  });`
7763        *
7764        * Where you do this, you need to take care in updating the data - you can't just update `$scope.myData` to some other
7765        * array, you need to update $scope.gridOptions.data to point to that new array as well.
7766        *
7767        */
7768       baseOptions.data = baseOptions.data || [];
7769
7770       /**
7771        * @ngdoc array
7772        * @name columnDefs
7773        * @propertyOf  ui.grid.class:GridOptions
7774        * @description Array of columnDef objects.  Only required property is name.
7775        * The individual options available in columnDefs are documented in the
7776        * {@link ui.grid.class:GridOptions.columnDef columnDef} section
7777        * </br>_field property can be used in place of name for backwards compatibility with 2.x_
7778        *  @example
7779        *
7780        * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7781        *
7782        */
7783       baseOptions.columnDefs = baseOptions.columnDefs || [];
7784
7785       /**
7786        * @ngdoc object
7787        * @name ui.grid.class:GridOptions.columnDef
7788        * @description Definition / configuration of an individual column, which would typically be
7789        * one of many column definitions within the gridOptions.columnDefs array
7790        * @example
7791        * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7792        *
7793        */
7794
7795
7796       /**
7797        * @ngdoc array
7798        * @name excludeProperties
7799        * @propertyOf  ui.grid.class:GridOptions
7800        * @description Array of property names in data to ignore when auto-generating column names.  Provides the
7801        * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
7802        * to exclude.
7803        *
7804        * If columnDefs is defined, this will be ignored.
7805        *
7806        * Defaults to ['$$hashKey']
7807        */
7808
7809       baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
7810
7811       /**
7812        * @ngdoc boolean
7813        * @name enableRowHashing
7814        * @propertyOf ui.grid.class:GridOptions
7815        * @description True by default. When enabled, this setting allows uiGrid to add
7816        * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
7817        * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
7818        *
7819        * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
7820        * 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
7821        * and are altering the data set often.
7822        */
7823       baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
7824
7825       /**
7826        * @ngdoc function
7827        * @name rowIdentity
7828        * @methodOf ui.grid.class:GridOptions
7829        * @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).
7830        *
7831        * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
7832        */
7833       baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
7834         return gridUtil.hashKey(row);
7835       };
7836
7837       /**
7838        * @ngdoc function
7839        * @name getRowIdentity
7840        * @methodOf ui.grid.class:GridOptions
7841        * @description This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
7842        *
7843        * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
7844        */
7845       baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
7846         return row.$$hashKey;
7847       };
7848
7849       /**
7850        * @ngdoc property
7851        * @name flatEntityAccess
7852        * @propertyOf ui.grid.class:GridOptions
7853        * @description Set to true if your columns are all related directly to fields in a flat object structure - i.e.
7854        * each of your columns associate directly with a property on each of the entities in your data array.
7855        *
7856        * In that situation we can avoid all the logic associated with complex binding to functions or to properties of sub-objects,
7857        * which can provide a significant speed improvement with large data sets when filtering or sorting.
7858        *
7859        * By default false
7860        */
7861       baseOptions.flatEntityAccess = baseOptions.flatEntityAccess === true;
7862
7863       /**
7864        * @ngdoc property
7865        * @name showHeader
7866        * @propertyOf ui.grid.class:GridOptions
7867        * @description True by default. When set to false, this setting will replace the
7868        * standard header template with '<div></div>', resulting in no header being shown.
7869        */
7870       baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
7871
7872       /* (NOTE): Don't show this in the docs. We only use it internally
7873        * @ngdoc property
7874        * @name headerRowHeight
7875        * @propertyOf ui.grid.class:GridOptions
7876        * @description The height of the header in pixels, defaults to 30
7877        *
7878        */
7879       if (!baseOptions.showHeader) {
7880         baseOptions.headerRowHeight = 0;
7881       }
7882       else {
7883         baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
7884       }
7885
7886       /**
7887        * @ngdoc property
7888        * @name rowHeight
7889        * @propertyOf ui.grid.class:GridOptions
7890        * @description The height of the row in pixels,  Can be passed as integer or string. defaults to 30.
7891        *
7892        */
7893
7894       if (typeof baseOptions.rowHeight === "string") {
7895         baseOptions.rowHeight = parseInt(baseOptions.rowHeight) || 30;
7896       }
7897
7898       else {
7899         baseOptions.rowHeight = baseOptions.rowHeight || 30;
7900       }
7901
7902       /**
7903        * @ngdoc integer
7904        * @name minRowsToShow
7905        * @propertyOf ui.grid.class:GridOptions
7906        * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
7907        */
7908       baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
7909
7910       /**
7911        * @ngdoc property
7912        * @name showGridFooter
7913        * @propertyOf ui.grid.class:GridOptions
7914        * @description Whether or not to show the footer, defaults to false
7915        * The footer display Total Rows and Visible Rows (filtered rows)
7916        */
7917       baseOptions.showGridFooter = baseOptions.showGridFooter === true;
7918
7919       /**
7920        * @ngdoc property
7921        * @name showColumnFooter
7922        * @propertyOf ui.grid.class:GridOptions
7923        * @description Whether or not to show the column footer, defaults to false
7924        * The column footer displays column aggregates
7925        */
7926       baseOptions.showColumnFooter = baseOptions.showColumnFooter === true;
7927
7928       /**
7929        * @ngdoc property
7930        * @name columnFooterHeight
7931        * @propertyOf ui.grid.class:GridOptions
7932        * @description The height of the footer rows (column footer and grid footer) in pixels
7933        *
7934        */
7935       baseOptions.columnFooterHeight = typeof(baseOptions.columnFooterHeight) !== "undefined" ? baseOptions.columnFooterHeight : 30;
7936       baseOptions.gridFooterHeight = typeof(baseOptions.gridFooterHeight) !== "undefined" ? baseOptions.gridFooterHeight : 30;
7937
7938       baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
7939
7940       /**
7941        * @ngdoc property
7942        * @name maxVisibleColumnCount
7943        * @propertyOf ui.grid.class:GridOptions
7944        * @description Defaults to 200
7945        *
7946        */
7947       baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
7948
7949       /**
7950        * @ngdoc property
7951        * @name virtualizationThreshold
7952        * @propertyOf ui.grid.class:GridOptions
7953        * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
7954        */
7955       baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
7956
7957       /**
7958        * @ngdoc property
7959        * @name columnVirtualizationThreshold
7960        * @propertyOf ui.grid.class:GridOptions
7961        * @description Turn virtualization on when number of columns goes over this number, defaults to 10
7962        */
7963       baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
7964
7965       /**
7966        * @ngdoc property
7967        * @name excessRows
7968        * @propertyOf ui.grid.class:GridOptions
7969        * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
7970        * Defaults to 4
7971        */
7972       baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7973       /**
7974        * @ngdoc property
7975        * @name scrollThreshold
7976        * @propertyOf ui.grid.class:GridOptions
7977        * @description Defaults to 4
7978        */
7979       baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
7980
7981       /**
7982        * @ngdoc property
7983        * @name excessColumns
7984        * @propertyOf ui.grid.class:GridOptions
7985        * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
7986        * Defaults to 4
7987        */
7988       baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7989       /**
7990        * @ngdoc property
7991        * @name horizontalScrollThreshold
7992        * @propertyOf ui.grid.class:GridOptions
7993        * @description Defaults to 4
7994        */
7995       baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
7996
7997
7998       /**
7999        * @ngdoc property
8000        * @name aggregationCalcThrottle
8001        * @propertyOf ui.grid.class:GridOptions
8002        * @description Default time in milliseconds to throttle aggregation calcuations, defaults to 500ms
8003        */
8004       baseOptions.aggregationCalcThrottle = typeof(baseOptions.aggregationCalcThrottle) !== "undefined" ? baseOptions.aggregationCalcThrottle : 500;
8005
8006       /**
8007        * @ngdoc property
8008        * @name wheelScrollThrottle
8009        * @propertyOf ui.grid.class:GridOptions
8010        * @description Default time in milliseconds to throttle scroll events to, defaults to 70ms
8011        */
8012       baseOptions.wheelScrollThrottle = typeof(baseOptions.wheelScrollThrottle) !== "undefined" ? baseOptions.wheelScrollThrottle : 70;
8013
8014
8015       /**
8016        * @ngdoc property
8017        * @name scrollDebounce
8018        * @propertyOf ui.grid.class:GridOptions
8019        * @description Default time in milliseconds to debounce scroll events, defaults to 300ms
8020        */
8021       baseOptions.scrollDebounce = typeof(baseOptions.scrollDebounce) !== "undefined" ? baseOptions.scrollDebounce : 300;
8022
8023       /**
8024        * @ngdoc boolean
8025        * @name enableSorting
8026        * @propertyOf ui.grid.class:GridOptions
8027        * @description True by default. When enabled, this setting adds sort
8028        * widgets to the column headers, allowing sorting of the data for the entire grid.
8029        * Sorting can then be disabled / enabled on individual columns using the columnDefs,
8030        * if it set, it will override GridOptions enableSorting setting.
8031        */
8032       baseOptions.enableSorting = baseOptions.enableSorting !== false;
8033
8034       /**
8035        * @ngdoc boolean
8036        * @name enableFiltering
8037        * @propertyOf ui.grid.class:GridOptions
8038        * @description False by default. When enabled, this setting adds filter
8039        * boxes to each column header, allowing filtering within the column for the entire grid.
8040        * Filtering can then be disabled on individual columns using the columnDefs.
8041        */
8042       baseOptions.enableFiltering = baseOptions.enableFiltering === true;
8043
8044       /**
8045        * @ngdoc boolean
8046        * @name enableColumnMenus
8047        * @propertyOf ui.grid.class:GridOptions
8048        * @description True by default. When enabled, this setting displays a column
8049        * menu within each column.
8050        */
8051       baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
8052
8053       /**
8054        * @ngdoc boolean
8055        * @name enableVerticalScrollbar
8056        * @propertyOf ui.grid.class:GridOptions
8057        * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8058        * This settings controls the vertical scrollbar for the grid.
8059        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8060        */
8061       baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
8062
8063       /**
8064        * @ngdoc boolean
8065        * @name enableHorizontalScrollbar
8066        * @propertyOf ui.grid.class:GridOptions
8067        * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8068        * This settings controls the horizontal scrollbar for the grid.
8069        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8070        */
8071       baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
8072
8073       /**
8074        * @ngdoc boolean
8075        * @name enableMinHeightCheck
8076        * @propertyOf ui.grid.class:GridOptions
8077        * @description True by default. When enabled, a newly initialized grid will check to see if it is tall enough to display
8078        * at least one row of data.  If the grid is not tall enough, it will resize the DOM element to display minRowsToShow number
8079        * of rows.
8080        */
8081        baseOptions.enableMinHeightCheck = baseOptions.enableMinHeightCheck !== false;
8082
8083       /**
8084        * @ngdoc boolean
8085        * @name minimumColumnSize
8086        * @propertyOf ui.grid.class:GridOptions
8087        * @description Columns can't be smaller than this, defaults to 10 pixels
8088        */
8089       baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
8090
8091       /**
8092        * @ngdoc function
8093        * @name rowEquality
8094        * @methodOf ui.grid.class:GridOptions
8095        * @description By default, rows are compared using object equality.  This option can be overridden
8096        * to compare on any data item property or function
8097        * @param {object} entityA First Data Item to compare
8098        * @param {object} entityB Second Data Item to compare
8099        */
8100       baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
8101         return entityA === entityB;
8102       };
8103
8104       /**
8105        * @ngdoc string
8106        * @name headerTemplate
8107        * @propertyOf ui.grid.class:GridOptions
8108        * @description Null by default. When provided, this setting uses a custom header
8109        * template, rather than the default template. Can be set to either the name of a template file:
8110        * <pre>  $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
8111        * inline html
8112        * <pre>  $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
8113        * or the id of a pronapiled template (TBD how to use this).
8114        * </br>Refer to the custom header tutorial for more information.
8115        * If you want no header at all, you can set to an empty div:
8116        * <pre>  $scope.gridOptions.headerTemplate = '<div></div>';</pre>
8117        *
8118        * If you want to only have a static header, then you can set to static content.  If
8119        * you want to tailor the existing column headers, then you should look at the
8120        * current 'ui-grid-header.html' template in github as your starting point.
8121        *
8122        */
8123       baseOptions.headerTemplate = baseOptions.headerTemplate || null;
8124
8125       /**
8126        * @ngdoc string
8127        * @name footerTemplate
8128        * @propertyOf ui.grid.class:GridOptions
8129        * @description (optional) ui-grid/ui-grid-footer by default.  This footer shows the per-column
8130        * aggregation totals.
8131        * 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
8132        * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
8133        * of a pronapiled template (TBD how to use this).  Refer to the custom footer tutorial for more information.
8134        */
8135       baseOptions.footerTemplate = baseOptions.footerTemplate || 'ui-grid/ui-grid-footer';
8136
8137       /**
8138        * @ngdoc string
8139        * @name gridFooterTemplate
8140        * @propertyOf ui.grid.class:GridOptions
8141        * @description (optional) ui-grid/ui-grid-grid-footer by default. This template by default shows the
8142        * total items at the bottom of the grid, and the selected items if selection is enabled.
8143        */
8144       baseOptions.gridFooterTemplate = baseOptions.gridFooterTemplate || 'ui-grid/ui-grid-grid-footer';
8145
8146       /**
8147        * @ngdoc string
8148        * @name rowTemplate
8149        * @propertyOf ui.grid.class:GridOptions
8150        * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
8151        * custom row template.  Can be set to either the name of a template file:
8152        * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
8153        * inline html
8154        * <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>
8155        * or the id of a pronapiled template (TBD how to use this) can be provided.
8156        * </br>Refer to the custom row template tutorial for more information.
8157        */
8158       baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
8159
8160       /**
8161       * @ngdoc string
8162       * @name gridMenuTemplate
8163       * @propertyOf ui.grid.class:GridOptions
8164       * @description 'ui-grid/uiGridMenu' by default. When provided, this setting uses a
8165       * custom grid menu template.
8166       */
8167       baseOptions.gridMenuTemplate = baseOptions.gridMenuTemplate || 'ui-grid/uiGridMenu';
8168
8169       /**
8170        * @ngdoc object
8171        * @name appScopeProvider
8172        * @propertyOf ui.grid.class:GridOptions
8173        * @description by default, the parent scope of the ui-grid element will be assigned to grid.appScope
8174        * this property allows you to assign any reference you want to grid.appScope
8175        */
8176       baseOptions.appScopeProvider = baseOptions.appScopeProvider || null;
8177
8178       return baseOptions;
8179     }
8180   };
8181
8182
8183 }]);
8184
8185 })();
8186
8187 (function(){
8188
8189 angular.module('ui.grid')
8190
8191   /**
8192    * @ngdoc function
8193    * @name ui.grid.class:GridRenderContainer
8194    * @description The grid has render containers, allowing the ability to have pinned columns.  If the grid
8195    * is right-to-left then there may be a right render container, if left-to-right then there may
8196    * be a left render container.  There is always a body render container.
8197    * @param {string} name The name of the render container ('body', 'left', or 'right')
8198    * @param {Grid} grid the grid the render container is in
8199    * @param {object} options the render container options
8200    */
8201 .factory('GridRenderContainer', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
8202   function GridRenderContainer(name, grid, options) {
8203     var self = this;
8204
8205     // if (gridUtil.type(grid) !== 'Grid') {
8206     //   throw new Error('Grid argument is not a Grid object');
8207     // }
8208
8209     self.name = name;
8210
8211     self.grid = grid;
8212
8213     // self.rowCache = [];
8214     // self.columnCache = [];
8215
8216     self.visibleRowCache = [];
8217     self.visibleColumnCache = [];
8218
8219     self.renderedRows = [];
8220     self.renderedColumns = [];
8221
8222     self.prevScrollTop = 0;
8223     self.prevScrolltopPercentage = 0;
8224     self.prevRowScrollIndex = 0;
8225
8226     self.prevScrollLeft = 0;
8227     self.prevScrollleftPercentage = 0;
8228     self.prevColumnScrollIndex = 0;
8229
8230     self.columnStyles = "";
8231
8232     self.viewportAdjusters = [];
8233
8234     /**
8235      *  @ngdoc boolean
8236      *  @name hasHScrollbar
8237      *  @propertyOf  ui.grid.class:GridRenderContainer
8238      *  @description flag to signal that container has a horizontal scrollbar
8239      */
8240     self.hasHScrollbar = false;
8241
8242     /**
8243      *  @ngdoc boolean
8244      *  @name hasVScrollbar
8245      *  @propertyOf  ui.grid.class:GridRenderContainer
8246      *  @description flag to signal that container has a vertical scrollbar
8247      */
8248     self.hasVScrollbar = false;
8249
8250     /**
8251      *  @ngdoc boolean
8252      *  @name canvasHeightShouldUpdate
8253      *  @propertyOf  ui.grid.class:GridRenderContainer
8254      *  @description flag to signal that container should recalculate the canvas size
8255      */
8256     self.canvasHeightShouldUpdate = true;
8257
8258     /**
8259      *  @ngdoc boolean
8260      *  @name canvasHeight
8261      *  @propertyOf  ui.grid.class:GridRenderContainer
8262      *  @description last calculated canvas height value
8263      */
8264     self.$$canvasHeight = 0;
8265
8266     if (options && angular.isObject(options)) {
8267       angular.extend(self, options);
8268     }
8269
8270     grid.registerStyleComputation({
8271       priority: 5,
8272       func: function () {
8273         self.updateColumnWidths();
8274         return self.columnStyles;
8275       }
8276     });
8277   }
8278
8279
8280   GridRenderContainer.prototype.reset = function reset() {
8281     // this.rowCache.length = 0;
8282     // this.columnCache.length = 0;
8283
8284     this.visibleColumnCache.length = 0;
8285     this.visibleRowCache.length = 0;
8286
8287     this.renderedRows.length = 0;
8288     this.renderedColumns.length = 0;
8289   };
8290
8291   // TODO(c0bra): calculate size?? Should this be in a stackable directive?
8292
8293
8294   GridRenderContainer.prototype.containsColumn = function (col) {
8295      return this.visibleColumnCache.indexOf(col) !== -1;
8296   };
8297
8298   GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
8299     var self = this;
8300     var minRows = 0;
8301     var rowAddedHeight = 0;
8302     var viewPortHeight = self.getViewportHeight();
8303     for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
8304       rowAddedHeight += self.visibleRowCache[i].height;
8305       minRows++;
8306     }
8307     return minRows;
8308   };
8309
8310   GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
8311     var self = this;
8312     var viewportWidth = this.getViewportWidth();
8313
8314     var min = 0;
8315     var totalWidth = 0;
8316     // self.columns.forEach(function(col, i) {
8317     for (var i = 0; i < self.visibleColumnCache.length; i++) {
8318       var col = self.visibleColumnCache[i];
8319
8320       if (totalWidth < viewportWidth) {
8321         totalWidth += col.drawnWidth ? col.drawnWidth : 0;
8322         min++;
8323       }
8324       else {
8325         var currWidth = 0;
8326         for (var j = i; j >= i - min; j--) {
8327           currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
8328         }
8329         if (currWidth < viewportWidth) {
8330           min++;
8331         }
8332       }
8333     }
8334
8335     return min;
8336   };
8337
8338   GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
8339     return this.visibleRowCache.length;
8340   };
8341
8342   /**
8343    * @ngdoc function
8344    * @name registerViewportAdjuster
8345    * @methodOf ui.grid.class:GridRenderContainer
8346    * @description Registers an adjuster to the render container's available width or height.  Adjusters are used
8347    * to tell the render container that there is something else consuming space, and to adjust it's size
8348    * appropriately.
8349    * @param {function} func the adjuster function we want to register
8350    */
8351
8352   GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
8353     this.viewportAdjusters.push(func);
8354   };
8355
8356   /**
8357    * @ngdoc function
8358    * @name removeViewportAdjuster
8359    * @methodOf ui.grid.class:GridRenderContainer
8360    * @description Removes an adjuster, should be used when your element is destroyed
8361    * @param {function} func the adjuster function we want to remove
8362    */
8363   GridRenderContainer.prototype.removeViewportAdjuster = function removeViewportAdjuster(func) {
8364     var idx = this.viewportAdjusters.indexOf(func);
8365
8366     if (idx > -1) {
8367       this.viewportAdjusters.splice(idx, 1);
8368     }
8369   };
8370
8371   /**
8372    * @ngdoc function
8373    * @name getViewportAdjustment
8374    * @methodOf ui.grid.class:GridRenderContainer
8375    * @description Gets the adjustment based on the viewportAdjusters.
8376    * @returns {object} a hash of { height: x, width: y }.  Usually the values will be negative
8377    */
8378   GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
8379     var self = this;
8380
8381     var adjustment = { height: 0, width: 0 };
8382
8383     self.viewportAdjusters.forEach(function (func) {
8384       adjustment = func.call(this, adjustment);
8385     });
8386
8387     return adjustment;
8388   };
8389
8390   GridRenderContainer.prototype.getMargin = function getMargin(side) {
8391     var self = this;
8392
8393     var amount = 0;
8394
8395     self.viewportAdjusters.forEach(function (func) {
8396       var adjustment = func.call(this, { height: 0, width: 0 });
8397
8398       if (adjustment.side && adjustment.side === side) {
8399         amount += adjustment.width * -1;
8400       }
8401     });
8402
8403     return amount;
8404   };
8405
8406   GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
8407     var self = this;
8408
8409     var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
8410
8411     var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
8412
8413
8414     var adjustment = self.getViewportAdjustment();
8415
8416     viewPortHeight = viewPortHeight + adjustment.height;
8417
8418     return viewPortHeight;
8419   };
8420
8421   GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
8422     var self = this;
8423
8424     var viewportWidth = self.grid.gridWidth;
8425
8426     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8427     //  viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8428     //}
8429
8430     // var viewportWidth = 0;\
8431     // self.visibleColumnCache.forEach(function (column) {
8432     //   viewportWidth += column.drawnWidth;
8433     // });
8434
8435     var adjustment = self.getViewportAdjustment();
8436
8437     viewportWidth = viewportWidth + adjustment.width;
8438
8439     return viewportWidth;
8440   };
8441
8442   GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8443     var self = this;
8444
8445     var viewportWidth = this.getViewportWidth();
8446
8447     //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8448     //  viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8449     //}
8450
8451     // var adjustment = self.getViewportAdjustment();
8452     // viewPortWidth = viewPortWidth + adjustment.width;
8453
8454     return viewportWidth;
8455   };
8456
8457
8458   /**
8459    * @ngdoc function
8460    * @name getCanvasHeight
8461    * @methodOf ui.grid.class:GridRenderContainer
8462    * @description Returns the total canvas height.   Only recalculates if canvasHeightShouldUpdate = false
8463    * @returns {number} total height of all the visible rows in the container
8464    */
8465   GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
8466     var self = this;
8467
8468     if (!self.canvasHeightShouldUpdate) {
8469       return self.$$canvasHeight;
8470     }
8471
8472     var oldCanvasHeight = self.$$canvasHeight;
8473
8474     self.$$canvasHeight =  0;
8475
8476     self.visibleRowCache.forEach(function(row){
8477       self.$$canvasHeight += row.height;
8478     });
8479
8480
8481     self.canvasHeightShouldUpdate = false;
8482
8483     self.grid.api.core.raise.canvasHeightChanged(oldCanvasHeight, self.$$canvasHeight);
8484
8485     return self.$$canvasHeight;
8486   };
8487
8488   GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8489     return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight !== 0 ? this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight : -1;
8490   };
8491
8492   GridRenderContainer.prototype.getHorizontalScrollLength = function getHorizontalScrollLength() {
8493     return this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth !== 0 ? this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth : -1;
8494   };
8495
8496   GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
8497     var self = this;
8498
8499     var ret = self.canvasWidth;
8500
8501     return ret;
8502   };
8503
8504   GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
8505     this.renderedRows.length = newRows.length;
8506     for (var i = 0; i < newRows.length; i++) {
8507       this.renderedRows[i] = newRows[i];
8508     }
8509   };
8510
8511   GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8512     var self = this;
8513
8514     // OLD:
8515     this.renderedColumns.length = newColumns.length;
8516     for (var i = 0; i < newColumns.length; i++) {
8517       this.renderedColumns[i] = newColumns[i];
8518     }
8519
8520     this.updateColumnOffset();
8521   };
8522
8523   GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
8524     // Calculate the width of the columns on the left side that are no longer rendered.
8525     //  That will be the offset for the columns as we scroll horizontally.
8526     var hiddenColumnsWidth = 0;
8527     for (var i = 0; i < this.currentFirstColumn; i++) {
8528       hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
8529     }
8530
8531     this.columnOffset = hiddenColumnsWidth;
8532   };
8533
8534   GridRenderContainer.prototype.scrollVertical = function (newScrollTop) {
8535     var vertScrollPercentage = -1;
8536
8537     if (newScrollTop !== this.prevScrollTop) {
8538       var yDiff = newScrollTop - this.prevScrollTop;
8539
8540       if (yDiff > 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; }
8541       if (yDiff < 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.UP; }
8542
8543       var vertScrollLength = this.getVerticalScrollLength();
8544
8545       vertScrollPercentage = newScrollTop / vertScrollLength;
8546
8547       // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8548
8549       if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
8550       if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
8551
8552       this.adjustScrollVertical(newScrollTop, vertScrollPercentage);
8553       return vertScrollPercentage;
8554     }
8555   };
8556
8557   GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8558     var horizScrollPercentage = -1;
8559
8560     // Handle RTL here
8561
8562     if (newScrollLeft !== this.prevScrollLeft) {
8563       var xDiff = newScrollLeft - this.prevScrollLeft;
8564
8565       if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
8566       if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
8567
8568       var horizScrollLength = this.getHorizontalScrollLength();
8569       if (horizScrollLength !== 0) {
8570         horizScrollPercentage = newScrollLeft / horizScrollLength;
8571       }
8572       else {
8573         horizScrollPercentage = 0;
8574       }
8575
8576       this.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
8577       return horizScrollPercentage;
8578     }
8579   };
8580
8581   GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
8582     if (this.prevScrollTop === scrollTop && !force) {
8583       return;
8584     }
8585
8586     if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
8587       scrollTop = (this.getCanvasHeight() - this.getViewportHeight()) * scrollPercentage;
8588     }
8589
8590     this.adjustRows(scrollTop, scrollPercentage, false);
8591
8592     this.prevScrollTop = scrollTop;
8593     this.prevScrolltopPercentage = scrollPercentage;
8594
8595     this.grid.queueRefresh();
8596   };
8597
8598   GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
8599     if (this.prevScrollLeft === scrollLeft && !force) {
8600       return;
8601     }
8602
8603     if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
8604       scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
8605     }
8606
8607     this.adjustColumns(scrollLeft, scrollPercentage);
8608
8609     this.prevScrollLeft = scrollLeft;
8610     this.prevScrollleftPercentage = scrollPercentage;
8611
8612     this.grid.queueRefresh();
8613   };
8614
8615   GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) {
8616     var self = this;
8617
8618     var minRows = self.minRowsToRender();
8619
8620     var rowCache = self.visibleRowCache;
8621
8622     var maxRowIndex = rowCache.length - minRows;
8623
8624     // console.log('scroll%1', scrollPercentage);
8625
8626     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
8627     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
8628       scrollPercentage = scrollTop / self.getVerticalScrollLength();
8629     }
8630
8631     var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8632
8633     // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
8634
8635     // Define a max row index that we can't scroll past
8636     if (rowIndex > maxRowIndex) {
8637       rowIndex = maxRowIndex;
8638     }
8639
8640     var newRange = [];
8641     if (rowCache.length > self.grid.options.virtualizationThreshold) {
8642       if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) {
8643         // Have we hit the threshold going down?
8644         if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8645           return;
8646         }
8647         //Have we hit the threshold going up?
8648         if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8649           return;
8650         }
8651       }
8652       var rangeStart = {};
8653       var rangeEnd = {};
8654
8655       rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8656       rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8657
8658       newRange = [rangeStart, rangeEnd];
8659     }
8660     else {
8661       var maxLen = self.visibleRowCache.length;
8662       newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
8663     }
8664
8665     self.updateViewableRowRange(newRange);
8666
8667     self.prevRowScrollIndex = rowIndex;
8668   };
8669
8670   GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
8671     var self = this;
8672
8673     var minCols = self.minColumnsToRender();
8674
8675     var columnCache = self.visibleColumnCache;
8676     var maxColumnIndex = columnCache.length - minCols;
8677
8678     // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
8679     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8680       scrollPercentage = scrollLeft / self.getHorizontalScrollLength();
8681     }
8682
8683     var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
8684
8685     // Define a max row index that we can't scroll past
8686     if (colIndex > maxColumnIndex) {
8687       colIndex = maxColumnIndex;
8688     }
8689
8690     var newRange = [];
8691     if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8692       /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8693        * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8694       // Have we hit the threshold going down?
8695       if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8696         return;
8697       }
8698       //Have we hit the threshold going up?
8699       if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8700         return;
8701       }*/
8702
8703       var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
8704       var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
8705
8706       newRange = [rangeStart, rangeEnd];
8707     }
8708     else {
8709       var maxLen = self.visibleColumnCache.length;
8710
8711       newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
8712     }
8713
8714     self.updateViewableColumnRange(newRange);
8715
8716     self.prevColumnScrollIndex = colIndex;
8717   };
8718
8719   // Method for updating the visible rows
8720   GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
8721     // Slice out the range of rows from the data
8722     // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
8723     var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
8724
8725     // Define the top-most rendered row
8726     this.currentTopRow = renderedRange[0];
8727
8728     this.setRenderedRows(rowArr);
8729   };
8730
8731   // Method for updating the visible columns
8732   GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
8733     // Slice out the range of rows from the data
8734     // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
8735     var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
8736
8737     // Define the left-most rendered columns
8738     this.currentFirstColumn = renderedRange[0];
8739
8740     this.setRenderedColumns(columnArr);
8741   };
8742
8743   GridRenderContainer.prototype.headerCellWrapperStyle = function () {
8744     var self = this;
8745
8746     if (self.currentFirstColumn !== 0) {
8747       var offset = self.columnOffset;
8748
8749       if (self.grid.isRTL()) {
8750         return { 'margin-right': offset + 'px' };
8751       }
8752       else {
8753         return { 'margin-left': offset + 'px' };
8754       }
8755     }
8756
8757     return null;
8758   };
8759
8760     /**
8761      *  @ngdoc boolean
8762      *  @name updateColumnWidths
8763      *  @propertyOf  ui.grid.class:GridRenderContainer
8764      *  @description Determine the appropriate column width of each column across all render containers.
8765      *
8766      *  Column width is easy when each column has a specified width.  When columns are variable width (i.e.
8767      *  have an * or % of the viewport) then we try to calculate so that things fit in.  The problem is that
8768      *  we have multiple render containers, and we don't want one render container to just take the whole viewport
8769      *  when it doesn't need to - we want things to balance out across the render containers.
8770      *
8771      *  To do this, we use this method to calculate all the renderContainers, recognising that in a given render
8772      *  cycle it'll get called once per render container, so it needs to return the same values each time.
8773      *
8774      *  The constraints on this method are therefore:
8775      *  - must return the same value when called multiple times, to do this it needs to rely on properties of the
8776      *    columns, but not properties that change when this is called (so it shouldn't rely on drawnWidth)
8777      *
8778      *  The general logic of this method is:
8779      *  - calculate our total available width
8780      *  - look at all the columns across all render containers, and work out which have widths and which have
8781      *    constraints such as % or * or something else
8782      *  - for those with *, count the total number of * we see and add it onto a running total, add this column to an * array
8783      *  - for those with a %, allocate the % as a percentage of the viewport, having consideration of min and max
8784      *  - for those with manual width (in pixels) we set the drawnWidth to the specified width
8785      *  - we end up with an asterisks array still to process
8786      *  - we look at our remaining width.  If it's greater than zero, we divide it up among the asterisk columns, then process
8787      *    them for min and max width constraints
8788      *  - if it's zero or less, we set the asterisk columns to their minimum widths
8789      *  - we use parseInt quite a bit, as we try to make all our column widths integers
8790      */
8791   GridRenderContainer.prototype.updateColumnWidths = function () {
8792     var self = this;
8793
8794     var asterisksArray = [],
8795         asteriskNum = 0,
8796         usedWidthSum = 0,
8797         ret = '';
8798
8799     // Get the width of the viewport
8800     var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
8801
8802     // get all the columns across all render containers, we have to calculate them all or one render container
8803     // could consume the whole viewport
8804     var columnCache = [];
8805     angular.forEach(self.grid.renderContainers, function( container, name){
8806       columnCache = columnCache.concat(container.visibleColumnCache);
8807     });
8808
8809     // look at each column, process any manual values or %, put the * into an array to look at later
8810     columnCache.forEach(function(column, i) {
8811       var width = 0;
8812       // Skip hidden columns
8813       if (!column.visible) { return; }
8814
8815       if (angular.isNumber(column.width)) {
8816         // pixel width, set to this value
8817         width = parseInt(column.width, 10);
8818         usedWidthSum = usedWidthSum + width;
8819         column.drawnWidth = width;
8820
8821       } else if (gridUtil.endsWith(column.width, "%")) {
8822         // percentage width, set to percentage of the viewport
8823         width = parseFloat(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8824
8825         if ( width > column.maxWidth ){
8826           width = column.maxWidth;
8827         }
8828
8829         if ( width < column.minWidth ){
8830           width = column.minWidth;
8831         }
8832
8833         usedWidthSum = usedWidthSum + width;
8834         column.drawnWidth = width;
8835       } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
8836         // is an asterisk column, the gridColumn already checked the string consists only of '****'
8837         asteriskNum = asteriskNum + column.width.length;
8838         asterisksArray.push(column);
8839       }
8840     });
8841
8842     // Get the remaining width (available width subtracted by the used widths sum)
8843     var remainingWidth = availableWidth - usedWidthSum;
8844
8845     var i, column, colWidth;
8846
8847     if (asterisksArray.length > 0) {
8848       // the width that each asterisk value would be assigned (this can be negative)
8849       var asteriskVal = remainingWidth / asteriskNum;
8850
8851       asterisksArray.forEach(function( column ){
8852         var width = parseInt(column.width.length * asteriskVal, 10);
8853
8854         if ( width > column.maxWidth ){
8855           width = column.maxWidth;
8856         }
8857
8858         if ( width < column.minWidth ){
8859           width = column.minWidth;
8860         }
8861
8862         usedWidthSum = usedWidthSum + width;
8863         column.drawnWidth = width;
8864       });
8865     }
8866
8867     // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8868     // calculated widths would have the grid narrower than the available space,
8869     // dole the remainder out one by one to make everything fit
8870     var processColumnUpwards = function(column){
8871       if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8872         column.drawnWidth++;
8873         usedWidthSum++;
8874         leftoverWidth--;
8875         columnsToChange = true;
8876       }
8877     };
8878
8879     var leftoverWidth = availableWidth - usedWidthSum;
8880     var columnsToChange = true;
8881
8882     while (leftoverWidth > 0 && columnsToChange) {
8883       columnsToChange = false;
8884       asterisksArray.forEach(processColumnUpwards);
8885     }
8886
8887     // We can end up with too much width even though some columns aren't at their max width, in this situation
8888     // we can trim the columns a little
8889     var processColumnDownwards = function(column){
8890       if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8891         column.drawnWidth--;
8892         usedWidthSum--;
8893         excessWidth--;
8894         columnsToChange = true;
8895       }
8896     };
8897
8898     var excessWidth =  usedWidthSum - availableWidth;
8899     columnsToChange = true;
8900
8901     while (excessWidth > 0 && columnsToChange) {
8902       columnsToChange = false;
8903       asterisksArray.forEach(processColumnDownwards);
8904     }
8905
8906
8907     // all that was across all the renderContainers, now we need to work out what that calculation decided for
8908     // our renderContainer
8909     var canvasWidth = 0;
8910     self.visibleColumnCache.forEach(function(column){
8911       if ( column.visible ){
8912         canvasWidth = canvasWidth + column.drawnWidth;
8913       }
8914     });
8915
8916     // Build the CSS
8917     columnCache.forEach(function (column) {
8918       ret = ret + column.getColClassDefinition();
8919     });
8920
8921     self.canvasWidth = canvasWidth;
8922
8923     // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
8924     // return ret;
8925
8926     // Set this render container's column styles so they can be used in style computation
8927     this.columnStyles = ret;
8928   };
8929
8930   GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8931     return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8932   };
8933
8934   GridRenderContainer.prototype.getViewportStyle = function () {
8935     var self = this;
8936     var styles = {};
8937
8938     self.hasHScrollbar = false;
8939     self.hasVScrollbar = false;
8940
8941     if (self.grid.disableScrolling) {
8942       styles['overflow-x'] = 'hidden';
8943       styles['overflow-y'] = 'hidden';
8944       return styles;
8945     }
8946
8947     if (self.name === 'body') {
8948       self.hasHScrollbar = self.grid.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER;
8949       if (!self.grid.isRTL()) {
8950         if (!self.grid.hasRightContainerColumns()) {
8951           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8952         }
8953       }
8954       else {
8955         if (!self.grid.hasLeftContainerColumns()) {
8956           self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8957         }
8958       }
8959     }
8960     else if (self.name === 'left') {
8961       self.hasVScrollbar = self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8962     }
8963     else {
8964       self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8965     }
8966
8967     styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8968     styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8969
8970
8971     return styles;
8972
8973
8974   };
8975
8976   return GridRenderContainer;
8977 }]);
8978
8979 })();
8980
8981 (function(){
8982
8983 angular.module('ui.grid')
8984 .factory('GridRow', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
8985
8986    /**
8987    * @ngdoc function
8988    * @name ui.grid.class:GridRow
8989    * @description GridRow is the viewModel for one logical row on the grid.  A grid Row is not necessarily a one-to-one
8990    * relation to gridOptions.data.
8991    * @param {object} entity the array item from GridOptions.data
8992    * @param {number} index the current position of the row in the array
8993    * @param {Grid} reference to the parent grid
8994    */
8995   function GridRow(entity, index, grid) {
8996
8997      /**
8998       *  @ngdoc object
8999       *  @name grid
9000       *  @propertyOf  ui.grid.class:GridRow
9001       *  @description A reference back to the grid
9002       */
9003      this.grid = grid;
9004
9005      /**
9006       *  @ngdoc object
9007       *  @name entity
9008       *  @propertyOf  ui.grid.class:GridRow
9009       *  @description A reference to an item in gridOptions.data[]
9010       */
9011     this.entity = entity;
9012
9013      /**
9014       *  @ngdoc object
9015       *  @name uid
9016       *  @propertyOf  ui.grid.class:GridRow
9017       *  @description  UniqueId of row
9018       */
9019      this.uid = gridUtil.nextUid();
9020
9021      /**
9022       *  @ngdoc object
9023       *  @name visible
9024       *  @propertyOf  ui.grid.class:GridRow
9025       *  @description If true, the row will be rendered
9026       */
9027     // Default to true
9028     this.visible = true;
9029
9030
9031     this.$$height = grid.options.rowHeight;
9032
9033   }
9034
9035     /**
9036      *  @ngdoc object
9037      *  @name height
9038      *  @propertyOf  ui.grid.class:GridRow
9039      *  @description height of each individual row. changing the height will flag all
9040      *  row renderContainers to recalculate their canvas height
9041      */
9042     Object.defineProperty(GridRow.prototype, 'height', {
9043       get: function() {
9044         return this.$$height;
9045       },
9046       set: function(height) {
9047         if (height !== this.$$height) {
9048           this.grid.updateCanvasHeight();
9049           this.$$height = height;
9050         }
9051       }
9052     });
9053
9054   /**
9055    * @ngdoc function
9056    * @name getQualifiedColField
9057    * @methodOf ui.grid.class:GridRow
9058    * @description returns the qualified field name as it exists on scope
9059    * ie: row.entity.fieldA
9060    * @param {GridCol} col column instance
9061    * @returns {string} resulting name that can be evaluated on scope
9062    */
9063     GridRow.prototype.getQualifiedColField = function(col) {
9064       return 'row.' + this.getEntityQualifiedColField(col);
9065     };
9066
9067     /**
9068      * @ngdoc function
9069      * @name getEntityQualifiedColField
9070      * @methodOf ui.grid.class:GridRow
9071      * @description returns the qualified field name minus the row path
9072      * ie: entity.fieldA
9073      * @param {GridCol} col column instance
9074      * @returns {string} resulting name that can be evaluated against a row
9075      */
9076   GridRow.prototype.getEntityQualifiedColField = function(col) {
9077     var base = 'entity';
9078     if ( col.field === uiGridConstants.ENTITY_BINDING ) {
9079       return base;
9080     }
9081     return gridUtil.preEval(base + '.' + col.field);
9082   };
9083   
9084   
9085   /**
9086    * @ngdoc function
9087    * @name setRowInvisible
9088    * @methodOf  ui.grid.class:GridRow
9089    * @description Sets an override on the row that forces it to always
9090    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
9091    * 
9092    * This method can be called from the api, passing in the gridRow we want
9093    * altered.  It should really work by calling gridRow.setRowInvisible, but that's
9094    * not the way I coded it, and too late to change now.  Changed to just call
9095    * the internal function row.setThisRowInvisible().
9096    * 
9097    * @param {GridRow} row the row we want to set to invisible
9098    * 
9099    */
9100   GridRow.prototype.setRowInvisible = function ( row ) {
9101     if (row && row.setThisRowInvisible){
9102       row.setThisRowInvisible( 'user' );
9103     }
9104   };
9105   
9106   
9107   /**
9108    * @ngdoc function
9109    * @name clearRowInvisible
9110    * @methodOf  ui.grid.class:GridRow
9111    * @description Clears an override on the row that forces it to always
9112    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
9113    * 
9114    * This method can be called from the api, passing in the gridRow we want
9115    * altered.  It should really work by calling gridRow.clearRowInvisible, but that's
9116    * not the way I coded it, and too late to change now.  Changed to just call
9117    * the internal function row.clearThisRowInvisible().
9118    * 
9119    * @param {GridRow} row the row we want to clear the invisible flag
9120    * 
9121    */
9122   GridRow.prototype.clearRowInvisible = function ( row ) {
9123     if (row && row.clearThisRowInvisible){
9124       row.clearThisRowInvisible( 'user' );
9125     }
9126   };
9127   
9128   
9129   /**
9130    * @ngdoc function
9131    * @name setThisRowInvisible
9132    * @methodOf  ui.grid.class:GridRow
9133    * @description Sets an override on the row that forces it to always
9134    * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility
9135    *
9136    * @param {string} reason the reason (usually the module) for the row to be invisible.
9137    * E.g. grouping, user, filter
9138    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
9139    */
9140   GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
9141     if ( !this.invisibleReason ){
9142       this.invisibleReason = {};
9143     }
9144     this.invisibleReason[reason] = true;
9145     this.evaluateRowVisibility( fromRowsProcessor);
9146   };
9147
9148
9149   /**
9150    * @ngdoc function
9151    * @name clearRowInvisible
9152    * @methodOf ui.grid.class:GridRow
9153    * @description Clears any override on the row visibility, returning it 
9154    * to normal visibility calculations.  Emits the rowsVisibleChanged
9155    * event
9156    * 
9157    * @param {string} reason the reason (usually the module) for the row to be invisible.
9158    * E.g. grouping, user, filter
9159    * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
9160    */
9161   GridRow.prototype.clearThisRowInvisible = function ( reason, fromRowsProcessor ) {
9162     if (typeof(this.invisibleReason) !== 'undefined' ) {
9163       delete this.invisibleReason[reason];
9164     }
9165     this.evaluateRowVisibility( fromRowsProcessor );
9166   };
9167
9168
9169   /**
9170    * @ngdoc function
9171    * @name evaluateRowVisibility
9172    * @methodOf ui.grid.class:GridRow
9173    * @description Determines whether the row should be visible based on invisibleReason, 
9174    * and if it changes the row visibility, then emits the rowsVisibleChanged event.
9175    * 
9176    * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
9177    * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
9178    * row processor does that already
9179    */
9180   GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
9181     var newVisibility = true;
9182     if ( typeof(this.invisibleReason) !== 'undefined' ){
9183       angular.forEach(this.invisibleReason, function( value, key ){
9184         if ( value ){
9185           newVisibility = false;
9186         }
9187       });
9188     }
9189     
9190     if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
9191       this.visible = newVisibility;
9192       if ( !fromRowProcessor ){
9193         this.grid.queueGridRefresh();
9194         this.grid.api.core.raise.rowsVisibleChanged(this);
9195       }
9196     }
9197   };
9198   
9199
9200   return GridRow;
9201 }]);
9202
9203 })();
9204
9205 (function(){
9206   'use strict';
9207   /**
9208    * @ngdoc object
9209    * @name ui.grid.class:GridRowColumn
9210    * @param {GridRow} row The row for this pair
9211    * @param {GridColumn} column The column for this pair
9212    * @description A row and column pair that represents the intersection of these two entities.
9213    * Must be instantiated as a constructor using the `new` keyword.
9214    */
9215   angular.module('ui.grid')
9216   .factory('GridRowColumn', ['$parse', '$filter',
9217     function GridRowColumnFactory($parse, $filter){
9218       var GridRowColumn = function GridRowColumn(row, col) {
9219         if ( !(this instanceof GridRowColumn)){
9220           throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
9221         }
9222
9223         /**
9224          * @ngdoc object
9225          * @name row
9226          * @propertyOf ui.grid.class:GridRowColumn
9227          * @description {@link ui.grid.class:GridRow }
9228          */
9229         this.row = row;
9230         /**
9231          * @ngdoc object
9232          * @name col
9233          * @propertyOf ui.grid.class:GridRowColumn
9234          * @description {@link ui.grid.class:GridColumn }
9235          */
9236         this.col = col;
9237       };
9238
9239       /**
9240        * @ngdoc function
9241        * @name getIntersectionValueRaw
9242        * @methodOf ui.grid.class:GridRowColumn
9243        * @description Gets the intersection of where the row and column meet.
9244        * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
9245        *          If the column has a cellFilter this will NOT return the filtered value.
9246        */
9247       GridRowColumn.prototype.getIntersectionValueRaw = function(){
9248         var getter = $parse(this.row.getEntityQualifiedColField(this.col));
9249         var context = this.row;
9250         return getter(context);
9251       };
9252       return GridRowColumn;
9253     }
9254   ]);
9255 })();
9256
9257 (function () {
9258   angular.module('ui.grid')
9259     .factory('ScrollEvent', ['gridUtil', function (gridUtil) {
9260
9261       /**
9262        * @ngdoc function
9263        * @name ui.grid.class:ScrollEvent
9264        * @description Model for all scrollEvents
9265        * @param {Grid} grid that owns the scroll event
9266        * @param {GridRenderContainer} sourceRowContainer that owns the scroll event. Can be null
9267        * @param {GridRenderContainer} sourceColContainer that owns the scroll event. Can be null
9268        * @param {string} source the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
9269        */
9270       function ScrollEvent(grid, sourceRowContainer, sourceColContainer, source) {
9271         var self = this;
9272         if (!grid) {
9273           throw new Error("grid argument is required");
9274         }
9275
9276         /**
9277          *  @ngdoc object
9278          *  @name grid
9279          *  @propertyOf  ui.grid.class:ScrollEvent
9280          *  @description A reference back to the grid
9281          */
9282          self.grid = grid;
9283
9284
9285
9286         /**
9287          *  @ngdoc object
9288          *  @name source
9289          *  @propertyOf  ui.grid.class:ScrollEvent
9290          *  @description the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
9291          */
9292         self.source = source;
9293
9294
9295         /**
9296          *  @ngdoc object
9297          *  @name noDelay
9298          *  @propertyOf  ui.grid.class:ScrollEvent
9299          *  @description most scroll events from the mouse or trackpad require delay to operate properly
9300          *  set to false to eliminate delay.  Useful for scroll events that the grid causes, such as scrolling to make a row visible.
9301          */
9302         self.withDelay = true;
9303
9304         self.sourceRowContainer = sourceRowContainer;
9305         self.sourceColContainer = sourceColContainer;
9306
9307         self.newScrollLeft = null;
9308         self.newScrollTop = null;
9309         self.x = null;
9310         self.y = null;
9311
9312         self.verticalScrollLength = -9999999;
9313         self.horizontalScrollLength = -999999;
9314
9315
9316         /**
9317          *  @ngdoc function
9318          *  @name fireThrottledScrollingEvent
9319          *  @methodOf  ui.grid.class:ScrollEvent
9320          *  @description fires a throttled event using grid.api.core.raise.scrollEvent
9321          */
9322         self.fireThrottledScrollingEvent = gridUtil.throttle(function(sourceContainerId) {
9323           self.grid.scrollContainers(sourceContainerId, self);
9324         }, self.grid.options.wheelScrollThrottle, {trailing: true});
9325
9326       }
9327
9328
9329       /**
9330        *  @ngdoc function
9331        *  @name getNewScrollLeft
9332        *  @methodOf  ui.grid.class:ScrollEvent
9333        *  @description returns newScrollLeft property if available; calculates a new value if it isn't
9334        */
9335       ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9336         var self = this;
9337
9338         if (!self.newScrollLeft){
9339           var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
9340
9341           var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
9342
9343           var scrollXPercentage;
9344           if (typeof(self.x.percentage) !== 'undefined' && self.x.percentage !== undefined) {
9345             scrollXPercentage = self.x.percentage;
9346           }
9347           else if (typeof(self.x.pixels) !== 'undefined' && self.x.pixels !== undefined) {
9348             scrollXPercentage = self.x.percentage = (oldScrollLeft + self.x.pixels) / scrollWidth;
9349           }
9350           else {
9351             throw new Error("No percentage or pixel value provided for scroll event X axis");
9352           }
9353
9354           return Math.max(0, scrollXPercentage * scrollWidth);
9355         }
9356
9357         return self.newScrollLeft;
9358       };
9359
9360
9361       /**
9362        *  @ngdoc function
9363        *  @name getNewScrollTop
9364        *  @methodOf  ui.grid.class:ScrollEvent
9365        *  @description returns newScrollTop property if available; calculates a new value if it isn't
9366        */
9367       ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9368         var self = this;
9369
9370
9371         if (!self.newScrollTop){
9372           var scrollLength = rowContainer.getVerticalScrollLength();
9373
9374           var oldScrollTop = viewport[0].scrollTop;
9375
9376           var scrollYPercentage;
9377           if (typeof(self.y.percentage) !== 'undefined' && self.y.percentage !== undefined) {
9378             scrollYPercentage = self.y.percentage;
9379           }
9380           else if (typeof(self.y.pixels) !== 'undefined' && self.y.pixels !== undefined) {
9381             scrollYPercentage = self.y.percentage = (oldScrollTop + self.y.pixels) / scrollLength;
9382           }
9383           else {
9384             throw new Error("No percentage or pixel value provided for scroll event Y axis");
9385           }
9386
9387           return Math.max(0, scrollYPercentage * scrollLength);
9388         }
9389
9390         return self.newScrollTop;
9391       };
9392
9393       ScrollEvent.prototype.atTop = function(scrollTop) {
9394         return (this.y && (this.y.percentage === 0 || this.verticalScrollLength < 0) && scrollTop === 0);
9395       };
9396
9397       ScrollEvent.prototype.atBottom = function(scrollTop) {
9398         return (this.y && (this.y.percentage === 1 || this.verticalScrollLength === 0) && scrollTop > 0);
9399       };
9400
9401       ScrollEvent.prototype.atLeft = function(scrollLeft) {
9402         return (this.x && (this.x.percentage === 0 || this.horizontalScrollLength < 0) && scrollLeft === 0);
9403       };
9404
9405       ScrollEvent.prototype.atRight = function(scrollLeft) {
9406         return (this.x && (this.x.percentage === 1 || this.horizontalScrollLength ===0) && scrollLeft > 0);
9407       };
9408
9409
9410       ScrollEvent.Sources = {
9411         ViewPortScroll: 'ViewPortScroll',
9412         RenderContainerMouseWheel: 'RenderContainerMouseWheel',
9413         RenderContainerTouchMove: 'RenderContainerTouchMove',
9414         Other: 99
9415       };
9416
9417       return ScrollEvent;
9418     }]);
9419
9420
9421
9422 })();
9423
9424 (function () {
9425   'use strict';
9426   /**
9427    *  @ngdoc object
9428    *  @name ui.grid.service:gridClassFactory
9429    *
9430    *  @description factory to return dom specific instances of a grid
9431    *
9432    */
9433   angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
9434     function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
9435
9436       var service = {
9437         /**
9438          * @ngdoc method
9439          * @name createGrid
9440          * @methodOf ui.grid.service:gridClassFactory
9441          * @description Creates a new grid instance. Each instance will have a unique id
9442          * @param {object} options An object map of options to pass into the created grid instance.
9443          * @returns {Grid} grid
9444          */
9445         createGrid : function(options) {
9446           options = (typeof(options) !== 'undefined') ? options : {};
9447           options.id = gridUtil.newId();
9448           var grid = new Grid(options);
9449
9450           // NOTE/TODO: rowTemplate should always be defined...
9451           if (grid.options.rowTemplate) {
9452             var rowTemplateFnPromise = $q.defer();
9453             grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9454             
9455             gridUtil.getTemplate(grid.options.rowTemplate)
9456               .then(
9457                 function (template) {
9458                   var rowTemplateFn = $compile(template);
9459                   rowTemplateFnPromise.resolve(rowTemplateFn);
9460                 },
9461                 function (res) {
9462                   // Todo handle response error here?
9463                   throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9464                 });
9465           }
9466
9467           grid.registerColumnBuilder(service.defaultColumnBuilder);
9468
9469           // Row builder for custom row templates
9470           grid.registerRowBuilder(service.rowTemplateAssigner);
9471
9472           // Reset all rows to visible initially
9473           grid.registerRowsProcessor(function allRowsVisible(rows) {
9474             rows.forEach(function (row) {
9475               row.evaluateRowVisibility( true );
9476             }, 50);
9477
9478             return rows;
9479           });
9480
9481           grid.registerColumnsProcessor(function applyColumnVisibility(columns) {
9482             columns.forEach(function (column) {
9483               column.visible = angular.isDefined(column.colDef.visible) ? column.colDef.visible : true;
9484             });
9485
9486             return columns;
9487           }, 50);
9488
9489           grid.registerRowsProcessor(grid.searchRows, 100);
9490
9491           // Register the default row processor, it sorts rows by selected columns
9492           if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
9493             grid.registerRowsProcessor(grid.options.externalSort, 200);
9494           }
9495           else {
9496             grid.registerRowsProcessor(grid.sortByColumn, 200);
9497           }
9498
9499           return grid;
9500         },
9501
9502         /**
9503          * @ngdoc function
9504          * @name defaultColumnBuilder
9505          * @methodOf ui.grid.service:gridClassFactory
9506          * @description Processes designTime column definitions and applies them to col for the
9507          *              core grid features
9508          * @param {object} colDef reference to column definition
9509          * @param {GridColumn} col reference to gridCol
9510          * @param {object} gridOptions reference to grid options
9511          */
9512         defaultColumnBuilder: function (colDef, col, gridOptions) {
9513
9514           var templateGetPromises = [];
9515
9516           // Abstracts the standard template processing we do for every template type.
9517           var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9518             if ( !colDef[templateType] ){
9519               col[providedType] = defaultTemplate;
9520             } else {
9521               col[providedType] = colDef[templateType];
9522             }
9523  
9524              templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9525                 .then(
9526                 function (template) {
9527                   if ( angular.isFunction(template) ) { template = template(); }
9528                   var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9529                   if ( tooltipType && col[tooltipType] === false ){
9530                     template = template.replace(uiGridConstants.TOOLTIP, '');
9531                   } else if ( tooltipType && col[tooltipType] ){
9532                     template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
9533                   }
9534
9535                   if ( filterType ){
9536                     col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
9537                       return col[filterType] ? "|" + col[filterType] : "";
9538                     });
9539                   } else {
9540                     col[templateType] = template;
9541                   }
9542                 },
9543                 function (res) {
9544                   throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9545                 })
9546             );
9547
9548           };
9549
9550
9551           /**
9552            * @ngdoc property
9553            * @name cellTemplate
9554            * @propertyOf ui.grid.class:GridOptions.columnDef
9555            * @description a custom template for each cell in this column.  The default
9556            * is ui-grid/uiGridCell.  If you are using the cellNav feature, this template
9557            * must contain a div that can receive focus.
9558            *
9559            */
9560           processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9561           col.cellTemplatePromise = templateGetPromises[0];
9562
9563           /**
9564            * @ngdoc property
9565            * @name headerCellTemplate
9566            * @propertyOf ui.grid.class:GridOptions.columnDef
9567            * @description a custom template for the header for this column.  The default
9568            * is ui-grid/uiGridHeaderCell
9569            *
9570            */
9571           processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9572
9573           /**
9574            * @ngdoc property
9575            * @name footerCellTemplate
9576            * @propertyOf ui.grid.class:GridOptions.columnDef
9577            * @description a custom template for the footer for this column.  The default
9578            * is ui-grid/uiGridFooterCell
9579            *
9580            */
9581           processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9582
9583           /**
9584            * @ngdoc property
9585            * @name filterHeaderTemplate
9586            * @propertyOf ui.grid.class:GridOptions.columnDef
9587            * @description a custom template for the filter input.  The default is ui-grid/ui-grid-filter
9588            *
9589            */
9590           processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9591
9592           // Create a promise for the compiled element function
9593           col.compiledElementFnDefer = $q.defer();
9594
9595           return $q.all(templateGetPromises);
9596         },
9597         
9598
9599         rowTemplateAssigner: function rowTemplateAssigner(row) {
9600           var grid = this;
9601
9602           // Row has no template assigned to it
9603           if (!row.rowTemplate) {
9604             // Use the default row template from the grid
9605             row.rowTemplate = grid.options.rowTemplate;
9606
9607             // Use the grid's function for fetching the compiled row template function
9608             row.getRowTemplateFn = grid.getRowTemplateFn;
9609           }
9610           // Row has its own template assigned
9611           else {
9612             // Create a promise for the compiled row template function
9613             var perRowTemplateFnPromise = $q.defer();
9614             row.getRowTemplateFn = perRowTemplateFnPromise.promise;
9615
9616             // Get the row template
9617             gridUtil.getTemplate(row.rowTemplate)
9618               .then(function (template) {
9619                 // Compile the template
9620                 var rowTemplateFn = $compile(template);
9621                 
9622                 // Resolve the compiled template function promise
9623                 perRowTemplateFnPromise.resolve(rowTemplateFn);
9624               },
9625               function (res) {
9626                 // Todo handle response error here?
9627                 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
9628               });
9629           }
9630
9631           return row.getRowTemplateFn;
9632         }
9633       };
9634
9635       //class definitions (moved to separate factories)
9636
9637       return service;
9638     }]);
9639
9640 })();
9641
9642 (function() {
9643
9644 var module = angular.module('ui.grid');
9645
9646 function escapeRegExp(str) {
9647   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
9648 }
9649
9650
9651 /**
9652  *  @ngdoc service
9653  *  @name ui.grid.service:rowSearcher
9654  *
9655  *  @description Service for searching/filtering rows based on column value conditions.
9656  */
9657 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
9658   var defaultCondition = uiGridConstants.filter.CONTAINS;
9659
9660   var rowSearcher = {};
9661
9662   /**
9663    * @ngdoc function
9664    * @name getTerm
9665    * @methodOf ui.grid.service:rowSearcher
9666    * @description Get the term from a filter
9667    * Trims leading and trailing whitespace
9668    * @param {object} filter object to use
9669    * @returns {object} Parsed term
9670    */
9671   rowSearcher.getTerm = function getTerm(filter) {
9672     if (typeof(filter.term) === 'undefined') { return filter.term; }
9673     
9674     var term = filter.term;
9675
9676     // Strip leading and trailing whitespace if the term is a string
9677     if (typeof(term) === 'string') {
9678       term = term.trim();
9679     }
9680
9681     return term;
9682   };
9683
9684   /**
9685    * @ngdoc function
9686    * @name stripTerm
9687    * @methodOf ui.grid.service:rowSearcher
9688    * @description Remove leading and trailing asterisk (*) from the filter's term
9689    * @param {object} filter object to use
9690    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9691    */
9692   rowSearcher.stripTerm = function stripTerm(filter) {
9693     var term = rowSearcher.getTerm(filter);
9694
9695     if (typeof(term) === 'string') {
9696       return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
9697     }
9698     else {
9699       return term;
9700     }
9701   };
9702   
9703
9704   /**
9705    * @ngdoc function
9706    * @name guessCondition
9707    * @methodOf ui.grid.service:rowSearcher
9708    * @description Guess the condition for a filter based on its term
9709    * <br>
9710    * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
9711    * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
9712    * @param {object} filter object to use
9713    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9714    */
9715   rowSearcher.guessCondition = function guessCondition(filter) {
9716     if (typeof(filter.term) === 'undefined' || !filter.term) {
9717       return defaultCondition;
9718     }
9719
9720     var term = rowSearcher.getTerm(filter);
9721     
9722     if (/\*/.test(term)) {
9723       var regexpFlags = '';
9724       if (!filter.flags || !filter.flags.caseSensitive) {
9725         regexpFlags += 'i';
9726       }
9727
9728       var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
9729       return new RegExp('^' + reText + '$', regexpFlags);
9730     }
9731     // Otherwise default to default condition
9732     else {
9733       return defaultCondition;
9734     }
9735   };
9736   
9737   
9738   /**
9739    * @ngdoc function
9740    * @name setupFilters
9741    * @methodOf ui.grid.service:rowSearcher
9742    * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
9743    * do all the parsing and pre-processing and store that data into a new filters object.  The object
9744    * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9745    * 
9746    * We could use a forEach in here, since it's much less performance sensitive, but since we're using 
9747    * for loops everywhere else in this module...
9748    * 
9749    * @param {array} filters the filters from the column (col.filters or [col.filter])
9750    * @returns {array} An array of parsed/preprocessed filters
9751    */
9752   rowSearcher.setupFilters = function setupFilters( filters ){
9753     var newFilters = [];
9754     
9755     var filtersLength = filters.length;
9756     for ( var i = 0; i < filtersLength; i++ ){
9757       var filter = filters[i];
9758       
9759       if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9760         var newFilter = {};
9761         
9762         var regexpFlags = '';
9763         if (!filter.flags || !filter.flags.caseSensitive) {
9764           regexpFlags += 'i';
9765         }
9766     
9767         if ( !gridUtil.isNullOrUndefined(filter.term) ){
9768           // it is possible to have noTerm.
9769           if ( filter.rawTerm ){
9770             newFilter.term = filter.term;
9771           } else {
9772             newFilter.term = rowSearcher.stripTerm(filter);
9773           }
9774         }
9775         newFilter.noTerm = filter.noTerm;
9776         
9777         if ( filter.condition ){
9778           newFilter.condition = filter.condition;
9779         } else {
9780           newFilter.condition = rowSearcher.guessCondition(filter);
9781         }
9782
9783         newFilter.flags = angular.extend( { caseSensitive: false, date: false }, filter.flags );
9784
9785         if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
9786           newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
9787         }
9788         
9789          if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
9790           newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
9791         }
9792
9793         if (newFilter.condition === uiGridConstants.filter.CONTAINS) {
9794           newFilter.containsRE = new RegExp(newFilter.term, regexpFlags);
9795         }
9796
9797         if (newFilter.condition === uiGridConstants.filter.EXACT) {
9798           newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
9799         }
9800         
9801         newFilters.push(newFilter);
9802       }
9803     }
9804     return newFilters;
9805   };
9806   
9807
9808   /**
9809    * @ngdoc function
9810    * @name runColumnFilter
9811    * @methodOf ui.grid.service:rowSearcher
9812    * @description Runs a single pre-parsed filter against a cell, returning true
9813    * if the cell matches that one filter.
9814    * 
9815    * @param {Grid} grid the grid we're working against
9816    * @param {GridRow} row the row we're matching against
9817    * @param {GridCol} column the column that we're working against
9818    * @param {object} filter the specific, preparsed, filter that we want to test
9819    * @returns {boolean} true if we match (row stays visible)
9820    */
9821   rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, filter) {
9822     // Cache typeof condition
9823     var conditionType = typeof(filter.condition);
9824
9825     // Term to search for.
9826     var term = filter.term;
9827
9828     // Get the column value for this row
9829     var value;
9830     if ( column.filterCellFiltered ){
9831       value = grid.getCellDisplayValue(row, column);
9832     } else {
9833       value = grid.getCellValue(row, column);
9834     }
9835
9836
9837     // If the filter's condition is a RegExp, then use it
9838     if (filter.condition instanceof RegExp) {
9839       return filter.condition.test(value);
9840     }
9841
9842     // If the filter's condition is a function, run it
9843     if (conditionType === 'function') {
9844       return filter.condition(term, value, row, column);
9845     }
9846
9847     if (filter.startswithRE) {
9848       return filter.startswithRE.test(value);
9849     }
9850
9851     if (filter.endswithRE) {
9852       return filter.endswithRE.test(value);
9853     }
9854
9855     if (filter.containsRE) {
9856       return filter.containsRE.test(value);
9857     }
9858
9859     if (filter.exactRE) {
9860       return filter.exactRE.test(value);
9861     }
9862
9863     if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
9864       var regex = new RegExp('^' + term + '$');
9865       return !regex.exec(value);
9866     }
9867
9868     if (typeof(value) === 'number' && typeof(term) === 'string' ){
9869       // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
9870       // the same for negative numbers
9871       // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
9872       var tempFloat = parseFloat(term.replace(/\\\./,'.').replace(/\\\-/,'-'));
9873       if (!isNaN(tempFloat)) {
9874         term = tempFloat;
9875       }
9876     }
9877
9878     if (filter.flags.date === true) {
9879       value = new Date(value);
9880       // If the term has a dash in it, it comes through as '\-' -- we need to take out the '\'.
9881       term = new Date(term.replace(/\\/g, ''));
9882     }
9883
9884     if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
9885       return (value > term);
9886     }
9887
9888     if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
9889       return (value >= term);
9890     }
9891
9892     if (filter.condition === uiGridConstants.filter.LESS_THAN) {
9893       return (value < term);
9894     }
9895
9896     if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
9897       return (value <= term);
9898     }
9899
9900     return true;
9901   };
9902
9903
9904   /**
9905    * @ngdoc boolean
9906    * @name useExternalFiltering
9907    * @propertyOf ui.grid.class:GridOptions
9908    * @description False by default. When enabled, this setting suppresses the internal filtering.
9909    * All UI logic will still operate, allowing filter conditions to be set and modified.
9910    * 
9911    * The external filter logic can listen for the `filterChange` event, which fires whenever
9912    * a filter has been adjusted.
9913    */
9914   /**
9915    * @ngdoc function
9916    * @name searchColumn
9917    * @methodOf ui.grid.service:rowSearcher
9918    * @description Process provided filters on provided column against a given row. If the row meets 
9919    * the conditions on all the filters, return true.
9920    * @param {Grid} grid Grid to search in
9921    * @param {GridRow} row Row to search on
9922    * @param {GridCol} column Column with the filters to use
9923    * @param {array} filters array of pre-parsed/preprocessed filters to apply
9924    * @returns {boolean} Whether the column matches or not.
9925    */
9926   rowSearcher.searchColumn = function searchColumn(grid, row, column, filters) {
9927     if (grid.options.useExternalFiltering) {
9928       return true;
9929     }
9930
9931     var filtersLength = filters.length;
9932     for (var i = 0; i < filtersLength; i++) {
9933       var filter = filters[i];
9934
9935       if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){ 
9936         var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9937         if (!ret) {
9938           return false;
9939         }
9940       }
9941     }
9942
9943     return true;
9944   };
9945
9946
9947   /**
9948    * @ngdoc function
9949    * @name search
9950    * @methodOf ui.grid.service:rowSearcher
9951    * @description Run a search across the given rows and columns, marking any rows that don't 
9952    * match the stored col.filters or col.filter as invisible.
9953    * @param {Grid} grid Grid instance to search inside
9954    * @param {Array[GridRow]} rows GridRows to filter
9955    * @param {Array[GridColumn]} columns GridColumns with filters to process
9956    */
9957   rowSearcher.search = function search(grid, rows, columns) {
9958     /*
9959      * Added performance optimisations into this code base, as this logic creates deeply nested
9960      * loops and is therefore very performance sensitive.  In particular, avoiding forEach as
9961      * this impacts some browser optimisers (particularly Chrome), using iterators instead
9962      */
9963
9964     // Don't do anything if we weren't passed any rows
9965     if (!rows) {
9966       return;
9967     }
9968
9969     // don't filter if filtering currently disabled
9970     if (!grid.options.enableFiltering){
9971       return rows;
9972     }
9973
9974     // Build list of filters to apply
9975     var filterData = [];
9976
9977     var colsLength = columns.length;
9978
9979     var hasTerm = function( filters ) {
9980       var hasTerm = false;
9981
9982       filters.forEach( function (filter) {
9983         if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9984           hasTerm = true;
9985         }
9986       });
9987
9988       return hasTerm;
9989     };
9990
9991     for (var i = 0; i < colsLength; i++) {
9992       var col = columns[i];
9993
9994       if (typeof(col.filters) !== 'undefined' && hasTerm(col.filters) ) {
9995         filterData.push( { col: col, filters: rowSearcher.setupFilters(col.filters) } );
9996       }
9997     }
9998
9999     if (filterData.length > 0) {
10000       // define functions outside the loop, performance optimisation
10001       var foreachRow = function(grid, row, col, filters){
10002         if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
10003           row.visible = false;
10004         }
10005       };
10006
10007       var foreachFilterCol = function(grid, filterData){
10008         var rowsLength = rows.length;
10009         for ( var i = 0; i < rowsLength; i++){
10010           foreachRow(grid, rows[i], filterData.col, filterData.filters);  
10011         }
10012       };
10013
10014       // nested loop itself - foreachFilterCol, which in turn calls foreachRow
10015       var filterDataLength = filterData.length;
10016       for ( var j = 0; j < filterDataLength; j++){
10017         foreachFilterCol( grid, filterData[j] );  
10018       }
10019
10020       if (grid.api.core.raise.rowsVisibleChanged) {
10021         grid.api.core.raise.rowsVisibleChanged();
10022       }
10023
10024       // drop any invisible rows
10025       // 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
10026       // rows = rows.filter(function(row){ return row.visible; });
10027
10028     }
10029
10030     return rows;
10031   };
10032
10033   return rowSearcher;
10034 }]);
10035
10036 })();
10037
10038 (function() {
10039
10040 var module = angular.module('ui.grid');
10041
10042 /**
10043  * @ngdoc object
10044  * @name ui.grid.class:rowSorter
10045  * @description rowSorter provides the default sorting mechanisms,
10046  * including guessing column types and applying appropriate sort
10047  * algorithms
10048  *
10049  */
10050
10051 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
10052   var currencyRegexStr =
10053     '(' +
10054     uiGridConstants.CURRENCY_SYMBOLS
10055       .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
10056       .join('|') + // Join all the symbols together with |s
10057     ')?';
10058
10059   // /^[-+]?[£$¤¥]?[\d,.]+%?$/
10060   var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
10061
10062   var rowSorter = {
10063     // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
10064     //   this takes a piece of data from the cell and tries to determine its type and what sorting
10065     //   function to use for it
10066     colSortFnCache: {}
10067   };
10068
10069
10070   /**
10071    * @ngdoc method
10072    * @methodOf ui.grid.class:rowSorter
10073    * @name guessSortFn
10074    * @description Assigns a sort function to use based on the itemType in the column
10075    * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'.  And
10076    * error will be thrown for any other type.
10077    * @returns {function} a sort function that will sort that type
10078    */
10079   rowSorter.guessSortFn = function guessSortFn(itemType) {
10080     switch (itemType) {
10081       case "number":
10082         return rowSorter.sortNumber;
10083       case "numberStr":
10084         return rowSorter.sortNumberStr;
10085       case "boolean":
10086         return rowSorter.sortBool;
10087       case "string":
10088         return rowSorter.sortAlpha;
10089       case "date":
10090         return rowSorter.sortDate;
10091       case "object":
10092         return rowSorter.basicSort;
10093       default:
10094         throw new Error('No sorting function found for type:' + itemType);
10095     }
10096   };
10097
10098
10099   /**
10100    * @ngdoc method
10101    * @methodOf ui.grid.class:rowSorter
10102    * @name handleNulls
10103    * @description Sorts nulls and undefined to the bottom (top when
10104    * descending).  Called by each of the internal sorters before
10105    * attempting to sort.  Note that this method is available on the core api
10106    * via gridApi.core.sortHandleNulls
10107    * @param {object} a sort value a
10108    * @param {object} b sort value b
10109    * @returns {number} null if there were no nulls/undefineds, otherwise returns
10110    * a sort value that should be passed back from the sort function
10111    */
10112   rowSorter.handleNulls = function handleNulls(a, b) {
10113     // We want to allow zero values and false values to be evaluated in the sort function
10114     if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
10115       // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
10116       if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
10117         return 0;
10118       }
10119       else if (!a && a !== 0 && a !== false) {
10120         return 1;
10121       }
10122       else if (!b && b !== 0 && b !== false) {
10123         return -1;
10124       }
10125     }
10126     return null;
10127   };
10128
10129
10130   /**
10131    * @ngdoc method
10132    * @methodOf ui.grid.class:rowSorter
10133    * @name basicSort
10134    * @description Sorts any values that provide the < method, including strings
10135    * or numbers.  Handles nulls and undefined through calling handleNulls
10136    * @param {object} a sort value a
10137    * @param {object} b sort value b
10138    * @returns {number} normal sort function, returns -ve, 0, +ve
10139    */
10140   rowSorter.basicSort = function basicSort(a, b) {
10141     var nulls = rowSorter.handleNulls(a, b);
10142     if ( nulls !== null ){
10143       return nulls;
10144     } else {
10145       if (a === b) {
10146         return 0;
10147       }
10148       if (a < b) {
10149         return -1;
10150       }
10151       return 1;
10152     }
10153   };
10154
10155
10156   /**
10157    * @ngdoc method
10158    * @methodOf ui.grid.class:rowSorter
10159    * @name sortNumber
10160    * @description Sorts numerical values.  Handles nulls and undefined through calling handleNulls
10161    * @param {object} a sort value a
10162    * @param {object} b sort value b
10163    * @returns {number} normal sort function, returns -ve, 0, +ve
10164    */
10165   rowSorter.sortNumber = function sortNumber(a, b) {
10166     var nulls = rowSorter.handleNulls(a, b);
10167     if ( nulls !== null ){
10168       return nulls;
10169     } else {
10170       return a - b;
10171     }
10172   };
10173
10174
10175   /**
10176    * @ngdoc method
10177    * @methodOf ui.grid.class:rowSorter
10178    * @name sortNumberStr
10179    * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
10180    * Handles nulls and undefined through calling handleNulls
10181    * @param {object} a sort value a
10182    * @param {object} b sort value b
10183    * @returns {number} normal sort function, returns -ve, 0, +ve
10184    */
10185   rowSorter.sortNumberStr = function sortNumberStr(a, b) {
10186     var nulls = rowSorter.handleNulls(a, b);
10187     if ( nulls !== null ){
10188       return nulls;
10189     } else {
10190       var numA, // The parsed number form of 'a'
10191           numB, // The parsed number form of 'b'
10192           badA = false,
10193           badB = false;
10194
10195       // Try to parse 'a' to a float
10196       numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
10197
10198       // If 'a' couldn't be parsed to float, flag it as bad
10199       if (isNaN(numA)) {
10200           badA = true;
10201       }
10202
10203       // Try to parse 'b' to a float
10204       numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
10205
10206       // If 'b' couldn't be parsed to float, flag it as bad
10207       if (isNaN(numB)) {
10208           badB = true;
10209       }
10210
10211       // We want bad ones to get pushed to the bottom... which effectively is "greater than"
10212       if (badA && badB) {
10213           return 0;
10214       }
10215
10216       if (badA) {
10217           return 1;
10218       }
10219
10220       if (badB) {
10221           return -1;
10222       }
10223
10224       return numA - numB;
10225     }
10226   };
10227
10228
10229   /**
10230    * @ngdoc method
10231    * @methodOf ui.grid.class:rowSorter
10232    * @name sortAlpha
10233    * @description Sorts string values. Handles nulls and undefined through calling handleNulls
10234    * @param {object} a sort value a
10235    * @param {object} b sort value b
10236    * @returns {number} normal sort function, returns -ve, 0, +ve
10237    */
10238   rowSorter.sortAlpha = function sortAlpha(a, b) {
10239     var nulls = rowSorter.handleNulls(a, b);
10240     if ( nulls !== null ){
10241       return nulls;
10242     } else {
10243       var strA = a.toString().toLowerCase(),
10244           strB = b.toString().toLowerCase();
10245
10246       return strA === strB ? 0 : strA.localeCompare(strB);
10247     }
10248   };
10249
10250
10251   /**
10252    * @ngdoc method
10253    * @methodOf ui.grid.class:rowSorter
10254    * @name sortDate
10255    * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
10256    * Handles date strings by converting to Date object if not already an instance of Date
10257    * @param {object} a sort value a
10258    * @param {object} b sort value b
10259    * @returns {number} normal sort function, returns -ve, 0, +ve
10260    */
10261   rowSorter.sortDate = function sortDate(a, b) {
10262     var nulls = rowSorter.handleNulls(a, b);
10263     if ( nulls !== null ){
10264       return nulls;
10265     } else {
10266       if (!(a instanceof Date)) {
10267         a = new Date(a);
10268       }
10269       if (!(b instanceof Date)){
10270         b = new Date(b);
10271       }
10272       var timeA = a.getTime(),
10273           timeB = b.getTime();
10274
10275       return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
10276     }
10277   };
10278
10279
10280   /**
10281    * @ngdoc method
10282    * @methodOf ui.grid.class:rowSorter
10283    * @name sortBool
10284    * @description Sorts boolean values, true is considered larger than false.
10285    * Handles nulls and undefined through calling handleNulls
10286    * @param {object} a sort value a
10287    * @param {object} b sort value b
10288    * @returns {number} normal sort function, returns -ve, 0, +ve
10289    */
10290   rowSorter.sortBool = function sortBool(a, b) {
10291     var nulls = rowSorter.handleNulls(a, b);
10292     if ( nulls !== null ){
10293       return nulls;
10294     } else {
10295       if (a && b) {
10296         return 0;
10297       }
10298
10299       if (!a && !b) {
10300         return 0;
10301       }
10302       else {
10303         return a ? 1 : -1;
10304       }
10305     }
10306   };
10307
10308
10309   /**
10310    * @ngdoc method
10311    * @methodOf ui.grid.class:rowSorter
10312    * @name getSortFn
10313    * @description Get the sort function for the column.  Looks first in
10314    * rowSorter.colSortFnCache using the column name, failing that it
10315    * looks at col.sortingAlgorithm (and puts it in the cache), failing that
10316    * it guesses the sort algorithm based on the data type.
10317    *
10318    * The cache currently seems a bit pointless, as none of the work we do is
10319    * processor intensive enough to need caching.  Presumably in future we might
10320    * inspect the row data itself to guess the sort function, and in that case
10321    * it would make sense to have a cache, the infrastructure is in place to allow
10322    * that.
10323    *
10324    * @param {Grid} grid the grid to consider
10325    * @param {GridCol} col the column to find a function for
10326    * @param {array} rows an array of grid rows.  Currently unused, but presumably in future
10327    * we might inspect the rows themselves to decide what sort of data might be there
10328    * @returns {function} the sort function chosen for the column
10329    */
10330   rowSorter.getSortFn = function getSortFn(grid, col, rows) {
10331     var sortFn, item;
10332
10333     // See if we already figured out what to use to sort the column and have it in the cache
10334     if (rowSorter.colSortFnCache[col.colDef.name]) {
10335       sortFn = rowSorter.colSortFnCache[col.colDef.name];
10336     }
10337     // If the column has its OWN sorting algorithm, use that
10338     else if (col.sortingAlgorithm !== undefined) {
10339       sortFn = col.sortingAlgorithm;
10340       rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
10341     }
10342     // Always default to sortAlpha when sorting after a cellFilter
10343     else if ( col.sortCellFiltered && col.cellFilter ){
10344       sortFn = rowSorter.sortAlpha;
10345       rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10346     }
10347     // Try and guess what sort function to use
10348     else {
10349       // Guess the sort function
10350       sortFn = rowSorter.guessSortFn(col.colDef.type);
10351
10352       // If we found a sort function, cache it
10353       if (sortFn) {
10354         rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10355       }
10356       else {
10357         // We assign the alpha sort because anything that is null/undefined will never get passed to
10358         // the actual sorting function. It will get caught in our null check and returned to be sorted
10359         // down to the bottom
10360         sortFn = rowSorter.sortAlpha;
10361       }
10362     }
10363
10364     return sortFn;
10365   };
10366
10367
10368
10369   /**
10370    * @ngdoc method
10371    * @methodOf ui.grid.class:rowSorter
10372    * @name prioritySort
10373    * @description Used where multiple columns are present in the sort criteria,
10374    * we determine which column should take precedence in the sort by sorting
10375    * the columns based on their sort.priority
10376    *
10377    * @param {gridColumn} a column a
10378    * @param {gridColumn} b column b
10379    * @returns {number} normal sort function, returns -ve, 0, +ve
10380    */
10381   rowSorter.prioritySort = function (a, b) {
10382     // Both columns have a sort priority
10383     if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
10384       // A is higher priority
10385       if (a.sort.priority < b.sort.priority) {
10386         return -1;
10387       }
10388       // Equal
10389       else if (a.sort.priority === b.sort.priority) {
10390         return 0;
10391       }
10392       // B is higher
10393       else {
10394         return 1;
10395       }
10396     }
10397     // Only A has a priority
10398     else if (a.sort.priority !== undefined) {
10399       return -1;
10400     }
10401     // Only B has a priority
10402     else if (b.sort.priority !== undefined) {
10403       return 1;
10404     }
10405     // Neither has a priority
10406     else {
10407       return 0;
10408     }
10409   };
10410
10411
10412   /**
10413    * @ngdoc object
10414    * @name useExternalSorting
10415    * @propertyOf ui.grid.class:GridOptions
10416    * @description Prevents the internal sorting from executing.  Events will
10417    * still be fired when the sort changes, and the sort information on
10418    * the columns will be updated, allowing an external sorter (for example,
10419    * server sorting) to be implemented.  Defaults to false.
10420    *
10421    */
10422   /**
10423    * @ngdoc method
10424    * @methodOf ui.grid.class:rowSorter
10425    * @name sort
10426    * @description sorts the grid
10427    * @param {Object} grid the grid itself
10428    * @param {array} rows the rows to be sorted
10429    * @param {array} columns the columns in which to look
10430    * for sort criteria
10431    * @returns {array} sorted rows
10432    */
10433   rowSorter.sort = function rowSorterSort(grid, rows, columns) {
10434     // first make sure we are even supposed to do work
10435     if (!rows) {
10436       return;
10437     }
10438
10439     if (grid.options.useExternalSorting){
10440       return rows;
10441     }
10442
10443     // Build the list of columns to sort by
10444     var sortCols = [];
10445     columns.forEach(function (col) {
10446       if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10447         sortCols.push(col);
10448       }
10449     });
10450
10451     // Sort the "sort columns" by their sort priority
10452     sortCols = sortCols.sort(rowSorter.prioritySort);
10453
10454     // Now rows to sort by, maintain original order
10455     if (sortCols.length === 0) {
10456       return rows;
10457     }
10458
10459     // Re-usable variables
10460     var col, direction;
10461
10462     // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10463     var setIndex = function( row, idx ){
10464       row.entity.$$uiGridIndex = idx;
10465     };
10466     rows.forEach(setIndex);
10467
10468     // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
10469     // var d = data.slice(0);
10470     var r = rows.slice(0);
10471
10472     // Now actually sort the data
10473     var rowSortFn = function (rowA, rowB) {
10474       var tem = 0,
10475           idx = 0,
10476           sortFn;
10477
10478       while (tem === 0 && idx < sortCols.length) {
10479         // grab the metadata for the rest of the logic
10480         col = sortCols[idx];
10481         direction = sortCols[idx].sort.direction;
10482
10483         sortFn = rowSorter.getSortFn(grid, col, r);
10484
10485         var propA, propB;
10486
10487         if ( col.sortCellFiltered ){
10488           propA = grid.getCellDisplayValue(rowA, col);
10489           propB = grid.getCellDisplayValue(rowB, col);
10490         } else {
10491           propA = grid.getCellValue(rowA, col);
10492           propB = grid.getCellValue(rowB, col);
10493         }
10494
10495         tem = sortFn(propA, propB, rowA, rowB, direction, col);
10496
10497         idx++;
10498       }
10499
10500       // Chrome doesn't implement a stable sort function.  If our sort returns 0
10501       // (i.e. the items are equal), and we're at the last sort column in the list,
10502       // then return the previous order using our custom
10503       // index variable
10504       if (tem === 0 ) {
10505         return rowA.entity.$$uiGridIndex - rowB.entity.$$uiGridIndex;
10506       }
10507
10508       // Made it this far, we don't have to worry about null & undefined
10509       if (direction === uiGridConstants.ASC) {
10510         return tem;
10511       } else {
10512         return 0 - tem;
10513       }
10514     };
10515
10516     var newRows = rows.sort(rowSortFn);
10517
10518     // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10519     var clearIndex = function( row, idx ){
10520        delete row.entity.$$uiGridIndex;
10521     };
10522     rows.forEach(clearIndex);
10523
10524     return newRows;
10525   };
10526
10527   return rowSorter;
10528 }]);
10529
10530 })();
10531
10532 (function() {
10533
10534 var module = angular.module('ui.grid');
10535
10536 var bindPolyfill;
10537 if (typeof Function.prototype.bind !== "function") {
10538   bindPolyfill = function() {
10539     var slice = Array.prototype.slice;
10540     return function(context) {
10541       var fn = this,
10542         args = slice.call(arguments, 1);
10543       if (args.length) {
10544         return function() {
10545           return arguments.length ? fn.apply(context, args.concat(slice.call(arguments))) : fn.apply(context, args);
10546         };
10547       }
10548       return function() {
10549         return arguments.length ? fn.apply(context, arguments) : fn.call(context);
10550       };
10551     };
10552   };
10553 }
10554
10555 function  getStyles (elem) {
10556   var e = elem;
10557   if (typeof(e.length) !== 'undefined' && e.length) {
10558     e = elem[0];
10559   }
10560
10561   return e.ownerDocument.defaultView.getComputedStyle(e, null);
10562 }
10563
10564 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
10565     // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
10566     // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
10567     rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
10568     cssShow = { position: "absolute", visibility: "hidden", display: "block" };
10569
10570 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
10571   var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
10572           // If we already have the right measurement, avoid augmentation
10573           4 :
10574           // Otherwise initialize for horizontal or vertical properties
10575           name === 'width' ? 1 : 0,
10576
10577           val = 0;
10578
10579   var sides = ['Top', 'Right', 'Bottom', 'Left'];
10580
10581   for ( ; i < 4; i += 2 ) {
10582     var side = sides[i];
10583     // dump('side', side);
10584
10585     // both box models exclude margin, so add it if we want it
10586     if ( extra === 'margin' ) {
10587       var marg = parseFloat(styles[extra + side]);
10588       if (!isNaN(marg)) {
10589         val += marg;
10590       }
10591     }
10592     // dump('val1', val);
10593
10594     if ( isBorderBox ) {
10595       // border-box includes padding, so remove it if we want content
10596       if ( extra === 'content' ) {
10597         var padd = parseFloat(styles['padding' + side]);
10598         if (!isNaN(padd)) {
10599           val -= padd;
10600           // dump('val2', val);
10601         }
10602       }
10603
10604       // at this point, extra isn't border nor margin, so remove border
10605       if ( extra !== 'margin' ) {
10606         var bordermarg = parseFloat(styles['border' + side + 'Width']);
10607         if (!isNaN(bordermarg)) {
10608           val -= bordermarg;
10609           // dump('val3', val);
10610         }
10611       }
10612     }
10613     else {
10614       // at this point, extra isn't content, so add padding
10615       var nocontentPad = parseFloat(styles['padding' + side]);
10616       if (!isNaN(nocontentPad)) {
10617         val += nocontentPad;
10618         // dump('val4', val);
10619       }
10620
10621       // at this point, extra isn't content nor padding, so add border
10622       if ( extra !== 'padding') {
10623         var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
10624         if (!isNaN(nocontentnopad)) {
10625           val += nocontentnopad;
10626           // dump('val5', val);
10627         }
10628       }
10629     }
10630   }
10631
10632   // dump('augVal', val);
10633
10634   return val;
10635 }
10636
10637 function getWidthOrHeight( elem, name, extra ) {
10638   // Start with offset property, which is equivalent to the border-box value
10639   var valueIsBorderBox = true,
10640           val, // = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
10641           styles = getStyles(elem),
10642           isBorderBox = styles['boxSizing'] === 'border-box';
10643
10644   // some non-html elements return undefined for offsetWidth, so check for null/undefined
10645   // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
10646   // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
10647   if ( val <= 0 || val == null ) {
10648     // Fall back to computed then uncomputed css if necessary
10649     val = styles[name];
10650     if ( val < 0 || val == null ) {
10651       val = elem.style[ name ];
10652     }
10653
10654     // Computed unit is not pixels. Stop here and return.
10655     if ( rnumnonpx.test(val) ) {
10656       return val;
10657     }
10658
10659     // we need the check for style in case a browser which returns unreliable values
10660     // for getComputedStyle silently falls back to the reliable elem.style
10661     valueIsBorderBox = isBorderBox &&
10662             ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
10663
10664     // Normalize "", auto, and prepare for extra
10665     val = parseFloat( val ) || 0;
10666   }
10667
10668   // use the active box-sizing model to add/subtract irrelevant styles
10669   var ret = ( val +
10670     augmentWidthOrHeight(
10671       elem,
10672       name,
10673       extra || ( isBorderBox ? "border" : "content" ),
10674       valueIsBorderBox,
10675       styles
10676     )
10677   );
10678
10679   // dump('ret', ret, val);
10680   return ret;
10681 }
10682
10683 function getLineHeight(elm) {
10684   elm = angular.element(elm)[0];
10685   var parent = elm.parentElement;
10686
10687   if (!parent) {
10688     parent = document.getElementsByTagName('body')[0];
10689   }
10690
10691   return parseInt( getStyles(parent).fontSize ) || parseInt( getStyles(elm).fontSize ) || 16;
10692 }
10693
10694 var uid = ['0', '0', '0', '0'];
10695 var uidPrefix = 'uiGrid-';
10696
10697 /**
10698  *  @ngdoc service
10699  *  @name ui.grid.service:GridUtil
10700  *
10701  *  @description Grid utility functions
10702  */
10703 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$interval', '$injector', '$q', '$interpolate', 'uiGridConstants',
10704   function ($log, $window, $document, $http, $templateCache, $timeout, $interval, $injector, $q, $interpolate, uiGridConstants) {
10705   var s = {
10706
10707     augmentWidthOrHeight: augmentWidthOrHeight,
10708
10709     getStyles: getStyles,
10710
10711     /**
10712      * @ngdoc method
10713      * @name createBoundedWrapper
10714      * @methodOf ui.grid.service:GridUtil
10715      *
10716      * @param {object} Object to bind 'this' to
10717      * @param {method} Method to bind
10718      * @returns {Function} The wrapper that performs the binding
10719      *
10720      * @description
10721      * Binds given method to given object.
10722      *
10723      * By means of a wrapper, ensures that ``method`` is always bound to
10724      * ``object`` regardless of its calling environment.
10725      * Iow, inside ``method``, ``this`` always points to ``object``.
10726      *
10727      * See http://alistapart.com/article/getoutbindingsituations
10728      *
10729      */
10730     createBoundedWrapper: function(object, method) {
10731         return function() {
10732             return method.apply(object, arguments);
10733         };
10734     },
10735
10736
10737     /**
10738      * @ngdoc method
10739      * @name readableColumnName
10740      * @methodOf ui.grid.service:GridUtil
10741      *
10742      * @param {string} columnName Column name as a string
10743      * @returns {string} Column name appropriately capitalized and split apart
10744      *
10745        @example
10746        <example module="app">
10747         <file name="app.js">
10748           var app = angular.module('app', ['ui.grid']);
10749
10750           app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
10751             $scope.name = 'firstName';
10752             $scope.columnName = function(name) {
10753               return gridUtil.readableColumnName(name);
10754             };
10755           }]);
10756         </file>
10757         <file name="index.html">
10758           <div ng-controller="MainCtrl">
10759             <strong>Column name:</strong> <input ng-model="name" />
10760             <br>
10761             <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
10762           </div>
10763         </file>
10764       </example>
10765      */
10766     readableColumnName: function (columnName) {
10767       // Convert underscores to spaces
10768       if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
10769
10770       if (typeof(columnName) !== 'string') {
10771         columnName = String(columnName);
10772       }
10773
10774       return columnName.replace(/_+/g, ' ')
10775         // Replace a completely all-capsed word with a first-letter-capitalized version
10776         .replace(/^[A-Z]+$/, function (match) {
10777           return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10778         })
10779         // Capitalize the first letter of words
10780         .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10781           return angular.uppercase(match.charAt(0)) + match.slice(1);
10782         })
10783         // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
10784         // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
10785         // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
10786         .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
10787     },
10788
10789     /**
10790      * @ngdoc method
10791      * @name getColumnsFromData
10792      * @methodOf ui.grid.service:GridUtil
10793      * @description Return a list of column names, given a data set
10794      *
10795      * @param {string} data Data array for grid
10796      * @returns {Object} Column definitions with field accessor and column name
10797      *
10798      * @example
10799        <pre>
10800          var data = [
10801            { firstName: 'Bob', lastName: 'Jones' },
10802            { firstName: 'Frank', lastName: 'Smith' }
10803          ];
10804
10805          var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
10806
10807          columnDefs == [
10808           {
10809             field: 'firstName',
10810             name: 'First Name'
10811           },
10812           {
10813             field: 'lastName',
10814             name: 'Last Name'
10815           }
10816          ];
10817        </pre>
10818      */
10819     getColumnsFromData: function (data, excludeProperties) {
10820       var columnDefs = [];
10821
10822       if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
10823       if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
10824
10825       var item = data[0];
10826
10827       angular.forEach(item,function (prop, propName) {
10828         if ( excludeProperties.indexOf(propName) === -1){
10829           columnDefs.push({
10830             name: propName
10831           });
10832         }
10833       });
10834
10835       return columnDefs;
10836     },
10837
10838     /**
10839      * @ngdoc method
10840      * @name newId
10841      * @methodOf ui.grid.service:GridUtil
10842      * @description Return a unique ID string
10843      *
10844      * @returns {string} Unique string
10845      *
10846      * @example
10847        <pre>
10848         var id = GridUtil.newId();
10849
10850         # 1387305700482;
10851        </pre>
10852      */
10853     newId: (function() {
10854       var seedId = new Date().getTime();
10855       return function() {
10856           return seedId += 1;
10857       };
10858     })(),
10859
10860
10861     /**
10862      * @ngdoc method
10863      * @name getTemplate
10864      * @methodOf ui.grid.service:GridUtil
10865      * @description Get's template from cache / element / url
10866      *
10867      * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10868      *   an jQuery/Angualr element, or a promise that returns the template contents to use.
10869      * @returns {object} a promise resolving to template contents
10870      *
10871      * @example
10872      <pre>
10873      GridUtil.getTemplate(url).then(function (contents) {
10874           alert(contents);
10875         })
10876      </pre>
10877      */
10878     getTemplate: function (template) {
10879       // Try to fetch the template out of the templateCache
10880       if ($templateCache.get(template)) {
10881         return s.postProcessTemplate($templateCache.get(template));
10882       }
10883
10884       // See if the template is itself a promise
10885       if (angular.isFunction(template.then)) {
10886         return template.then(s.postProcessTemplate);
10887       }
10888
10889       // If the template is an element, return the element
10890       try {
10891         if (angular.element(template).length > 0) {
10892           return $q.when(template).then(s.postProcessTemplate);
10893         }
10894       }
10895       catch (err){
10896         //do nothing; not valid html
10897       }
10898
10899       s.logDebug('fetching url', template);
10900
10901       // Default to trying to fetch the template as a url with $http
10902       return $http({ method: 'GET', url: template})
10903         .then(
10904           function (result) {
10905             var templateHtml = result.data.trim();
10906             //put in templateCache for next call
10907             $templateCache.put(template, templateHtml);
10908             return templateHtml;
10909           },
10910           function (err) {
10911             throw new Error("Could not get template " + template + ": " + err);
10912           }
10913         )
10914         .then(s.postProcessTemplate);
10915     },
10916
10917     //
10918     postProcessTemplate: function (template) {
10919       var startSym = $interpolate.startSymbol(),
10920           endSym = $interpolate.endSymbol();
10921
10922       // If either of the interpolation symbols have been changed, we need to alter this template
10923       if (startSym !== '{{' || endSym !== '}}') {
10924         template = template.replace(/\{\{/g, startSym);
10925         template = template.replace(/\}\}/g, endSym);
10926       }
10927
10928       return $q.when(template);
10929     },
10930
10931     /**
10932      * @ngdoc method
10933      * @name guessType
10934      * @methodOf ui.grid.service:GridUtil
10935      * @description guesses the type of an argument
10936      *
10937      * @param {string/number/bool/object} item variable to examine
10938      * @returns {string} one of the following
10939      * - 'string'
10940      * - 'boolean'
10941      * - 'number'
10942      * - 'date'
10943      * - 'object'
10944      */
10945     guessType : function (item) {
10946       var itemType = typeof(item);
10947
10948       // Check for numbers and booleans
10949       switch (itemType) {
10950         case "number":
10951         case "boolean":
10952         case "string":
10953           return itemType;
10954         default:
10955           if (angular.isDate(item)) {
10956             return "date";
10957           }
10958           return "object";
10959       }
10960     },
10961
10962
10963   /**
10964     * @ngdoc method
10965     * @name elementWidth
10966     * @methodOf ui.grid.service:GridUtil
10967     *
10968     * @param {element} element DOM element
10969     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10970     *
10971     * @returns {number} Element width in pixels, accounting for any borders, etc.
10972     */
10973     elementWidth: function (elem) {
10974
10975     },
10976
10977     /**
10978     * @ngdoc method
10979     * @name elementHeight
10980     * @methodOf ui.grid.service:GridUtil
10981     *
10982     * @param {element} element DOM element
10983     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10984     *
10985     * @returns {number} Element height in pixels, accounting for any borders, etc.
10986     */
10987     elementHeight: function (elem) {
10988
10989     },
10990
10991     // Thanks to http://stackoverflow.com/a/13382873/888165
10992     getScrollbarWidth: function() {
10993         var outer = document.createElement("div");
10994         outer.style.visibility = "hidden";
10995         outer.style.width = "100px";
10996         outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
10997
10998         document.body.appendChild(outer);
10999
11000         var widthNoScroll = outer.offsetWidth;
11001         // force scrollbars
11002         outer.style.overflow = "scroll";
11003
11004         // add innerdiv
11005         var inner = document.createElement("div");
11006         inner.style.width = "100%";
11007         outer.appendChild(inner);
11008
11009         var widthWithScroll = inner.offsetWidth;
11010
11011         // remove divs
11012         outer.parentNode.removeChild(outer);
11013
11014         return widthNoScroll - widthWithScroll;
11015     },
11016
11017     swap: function( elem, options, callback, args ) {
11018       var ret, name,
11019               old = {};
11020
11021       // Remember the old values, and insert the new ones
11022       for ( name in options ) {
11023         old[ name ] = elem.style[ name ];
11024         elem.style[ name ] = options[ name ];
11025       }
11026
11027       ret = callback.apply( elem, args || [] );
11028
11029       // Revert the old values
11030       for ( name in options ) {
11031         elem.style[ name ] = old[ name ];
11032       }
11033
11034       return ret;
11035     },
11036
11037     fakeElement: function( elem, options, callback, args ) {
11038       var ret, name,
11039           newElement = angular.element(elem).clone()[0];
11040
11041       for ( name in options ) {
11042         newElement.style[ name ] = options[ name ];
11043       }
11044
11045       angular.element(document.body).append(newElement);
11046
11047       ret = callback.call( newElement, newElement );
11048
11049       angular.element(newElement).remove();
11050
11051       return ret;
11052     },
11053
11054     /**
11055     * @ngdoc method
11056     * @name normalizeWheelEvent
11057     * @methodOf ui.grid.service:GridUtil
11058     *
11059     * @param {event} event A mouse wheel event
11060     *
11061     * @returns {event} A normalized event
11062     *
11063     * @description
11064     * Given an event from this list:
11065     *
11066     * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
11067     *
11068     * "normalize" it
11069     * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
11070     */
11071     normalizeWheelEvent: function (event) {
11072       // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
11073       // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
11074       var lowestDelta, lowestDeltaXY;
11075
11076       var orgEvent   = event || window.event,
11077           args       = [].slice.call(arguments, 1),
11078           delta      = 0,
11079           deltaX     = 0,
11080           deltaY     = 0,
11081           absDelta   = 0,
11082           absDeltaXY = 0,
11083           fn;
11084
11085       // event = $.event.fix(orgEvent);
11086       // event.type = 'mousewheel';
11087
11088       // NOTE: jQuery masks the event and stores it in the event as originalEvent
11089       if (orgEvent.originalEvent) {
11090         orgEvent = orgEvent.originalEvent;
11091       }
11092
11093       // Old school scrollwheel delta
11094       if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
11095       if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
11096
11097       // At a minimum, setup the deltaY to be delta
11098       deltaY = delta;
11099
11100       // Firefox < 17 related to DOMMouseScroll event
11101       if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
11102           deltaY = 0;
11103           deltaX = delta * -1;
11104       }
11105
11106       // New school wheel delta (wheel event)
11107       if ( orgEvent.deltaY ) {
11108           deltaY = orgEvent.deltaY * -1;
11109           delta  = deltaY;
11110       }
11111       if ( orgEvent.deltaX ) {
11112           deltaX = orgEvent.deltaX;
11113           delta  = deltaX * -1;
11114       }
11115
11116       // Webkit
11117       if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
11118       if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
11119
11120       // Look for lowest delta to normalize the delta values
11121       absDelta = Math.abs(delta);
11122       if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
11123       absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
11124       if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
11125
11126       // Get a whole value for the deltas
11127       fn     = delta > 0 ? 'floor' : 'ceil';
11128       delta  = Math[fn](delta  / lowestDelta);
11129       deltaX = Math[fn](deltaX / lowestDeltaXY);
11130       deltaY = Math[fn](deltaY / lowestDeltaXY);
11131
11132       return {
11133         delta: delta,
11134         deltaX: deltaX,
11135         deltaY: deltaY
11136       };
11137     },
11138
11139     // Stolen from Modernizr
11140     // TODO: make this, and everythign that flows from it, robust
11141     //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
11142     isTouchEnabled: function() {
11143       var bool;
11144
11145       if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
11146         bool = true;
11147       }
11148
11149       return bool;
11150     },
11151
11152     isNullOrUndefined: function(obj) {
11153       if (obj === undefined || obj === null) {
11154         return true;
11155       }
11156       return false;
11157     },
11158
11159     endsWith: function(str, suffix) {
11160       if (!str || !suffix || typeof str !== "string") {
11161         return false;
11162       }
11163       return str.indexOf(suffix, str.length - suffix.length) !== -1;
11164     },
11165
11166     arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
11167         var found = false;
11168         angular.forEach(array, function (object) {
11169             if (object[propertyName] === propertyValue) {
11170                 found = true;
11171             }
11172         });
11173         return found;
11174     },
11175
11176     //// Shim requestAnimationFrame
11177     //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
11178     //                       $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
11179     //                       function(fn) {
11180     //                         return $timeout(fn, 10, false);
11181     //                       },
11182
11183     numericAndNullSort: function (a, b) {
11184       if (a === null) { return 1; }
11185       if (b === null) { return -1; }
11186       if (a === null && b === null) { return 0; }
11187       return a - b;
11188     },
11189
11190     // Disable ngAnimate animations on an element
11191     disableAnimations: function (element) {
11192       var $animate;
11193       try {
11194         $animate = $injector.get('$animate');
11195         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
11196         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
11197           $animate.enabled(element, false);
11198         } else {
11199           $animate.enabled(false, element);
11200         }
11201       }
11202       catch (e) {}
11203     },
11204
11205     enableAnimations: function (element) {
11206       var $animate;
11207       try {
11208         $animate = $injector.get('$animate');
11209         // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
11210         if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
11211           $animate.enabled(element, true);
11212         } else {
11213           $animate.enabled(true, element);
11214         }
11215         return $animate;
11216       }
11217       catch (e) {}
11218     },
11219
11220     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11221     nextUid: function nextUid() {
11222       var index = uid.length;
11223       var digit;
11224
11225       while (index) {
11226         index--;
11227         digit = uid[index].charCodeAt(0);
11228         if (digit === 57 /*'9'*/) {
11229           uid[index] = 'A';
11230           return uidPrefix + uid.join('');
11231         }
11232         if (digit === 90  /*'Z'*/) {
11233           uid[index] = '0';
11234         } else {
11235           uid[index] = String.fromCharCode(digit + 1);
11236           return uidPrefix + uid.join('');
11237         }
11238       }
11239       uid.unshift('0');
11240
11241       return uidPrefix + uid.join('');
11242     },
11243
11244     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11245     hashKey: function hashKey(obj) {
11246       var objType = typeof obj,
11247           key;
11248
11249       if (objType === 'object' && obj !== null) {
11250         if (typeof (key = obj.$$hashKey) === 'function') {
11251           // must invoke on object to keep the right this
11252           key = obj.$$hashKey();
11253         }
11254         else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
11255           key = obj.$$hashKey;
11256         }
11257         else if (key === undefined) {
11258           key = obj.$$hashKey = s.nextUid();
11259         }
11260       }
11261       else {
11262         key = obj;
11263       }
11264
11265       return objType + ':' + key;
11266     },
11267
11268     resetUids: function () {
11269       uid = ['0', '0', '0'];
11270     },
11271
11272     /**
11273      * @ngdoc method
11274      * @methodOf ui.grid.service:GridUtil
11275      * @name logError
11276      * @description wraps the $log method, allowing us to choose different
11277      * treatment within ui-grid if we so desired.  At present we only log
11278      * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
11279      * @param {string} logMessage message to be logged to the console
11280      *
11281      */
11282     logError: function( logMessage ){
11283       if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11284         $log.error( logMessage );
11285       }
11286     },
11287
11288     /**
11289      * @ngdoc method
11290      * @methodOf ui.grid.service:GridUtil
11291      * @name logWarn
11292      * @description wraps the $log method, allowing us to choose different
11293      * treatment within ui-grid if we so desired.  At present we only log
11294      * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
11295      * @param {string} logMessage message to be logged to the console
11296      *
11297      */
11298     logWarn: function( logMessage ){
11299       if ( uiGridConstants.LOG_WARN_MESSAGES ){
11300         $log.warn( logMessage );
11301       }
11302     },
11303
11304     /**
11305      * @ngdoc method
11306      * @methodOf ui.grid.service:GridUtil
11307      * @name logDebug
11308      * @description wraps the $log method, allowing us to choose different
11309      * treatment within ui-grid if we so desired.  At present we only log
11310      * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
11311      *
11312      */
11313     logDebug: function() {
11314       if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11315         $log.debug.apply($log, arguments);
11316       }
11317     }
11318
11319   };
11320
11321   /**
11322    * @ngdoc object
11323    * @name focus
11324    * @propertyOf ui.grid.service:GridUtil
11325    * @description Provies a set of methods to set the document focus inside the grid.
11326    * See {@link ui.grid.service:GridUtil.focus} for more information.
11327    */
11328
11329   /**
11330    * @ngdoc object
11331    * @name ui.grid.service:GridUtil.focus
11332    * @description Provies a set of methods to set the document focus inside the grid.
11333    * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
11334    * e.g. click events that need to run before the focus or
11335    * inputs elements that are in a disabled state but are enabled when those events
11336    * are triggered.
11337    */
11338   s.focus = {
11339     queue: [],
11340     //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11341     /**
11342      * @ngdoc method
11343      * @methodOf ui.grid.service:GridUtil.focus
11344      * @name byId
11345      * @description Sets the focus of the document to the given id value.
11346      * If provided with the grid object it will automatically append the grid id.
11347      * This is done to encourage unique dom id's as it allows for multiple grids on a
11348      * page.
11349      * @param {String} id the id of the dom element to set the focus on
11350      * @param {Object=} Grid the grid object for this grid instance. See: {@link ui.grid.class:Grid}
11351      * @param {Number} Grid.id the unique id for this grid. Already set on an initialized grid object.
11352      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11353      * then the promise will fail with the `'canceled'` reason.
11354      */
11355     byId: function (id, Grid) {
11356       this._purgeQueue();
11357       var promise = $timeout(function() {
11358         var elementID = (Grid && Grid.id ? Grid.id + '-' : '') + id;
11359         var element = $window.document.getElementById(elementID);
11360         if (element) {
11361           element.focus();
11362         } else {
11363           s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
11364         }
11365       });
11366       this.queue.push(promise);
11367       return promise;
11368     },
11369
11370     /**
11371      * @ngdoc method
11372      * @methodOf ui.grid.service:GridUtil.focus
11373      * @name byElement
11374      * @description Sets the focus of the document to the given dom element.
11375      * @param {(element|angular.element)} element the DOM element to set the focus on
11376      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11377      * then the promise will fail with the `'canceled'` reason.
11378      */
11379     byElement: function(element){
11380       if (!angular.isElement(element)){
11381         s.logWarn("Trying to focus on an element that isn\'t an element.");
11382         return $q.reject('not-element');
11383       }
11384       element = angular.element(element);
11385       this._purgeQueue();
11386       var promise = $timeout(function(){
11387         if (element){
11388           element[0].focus();
11389         }
11390       });
11391       this.queue.push(promise);
11392       return promise;
11393     },
11394     /**
11395      * @ngdoc method
11396      * @methodOf ui.grid.service:GridUtil.focus
11397      * @name bySelector
11398      * @description Sets the focus of the document to the given dom element.
11399      * @param {(element|angular.element)} parentElement the parent/ancestor of the dom element that you are selecting using the query selector
11400      * @param {String} querySelector finds the dom element using the {@link http://www.w3schools.com/jsref/met_document_queryselector.asp querySelector}
11401      * @param {boolean} [aSync=false] If true then the selector will be querried inside of a timeout. Otherwise the selector will be querried imidately
11402      * then the focus will be called.
11403      * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11404      * then the promise will fail with the `'canceled'` reason.
11405      */
11406     bySelector: function(parentElement, querySelector, aSync){
11407       var self = this;
11408       if (!angular.isElement(parentElement)){
11409         throw new Error("The parent element is not an element.");
11410       }
11411       // Ensure that this is an angular element.
11412       // It is fine if this is already an angular element.
11413       parentElement = angular.element(parentElement);
11414       var focusBySelector = function(){
11415         var element = parentElement[0].querySelector(querySelector);
11416         return self.byElement(element);
11417       };
11418       this._purgeQueue();
11419       if (aSync){ //Do this asynchronysly
11420         var promise = $timeout(focusBySelector);
11421         this.queue.push($timeout(focusBySelector));
11422         return promise;
11423       } else {
11424         return focusBySelector();
11425       }
11426     },
11427     _purgeQueue: function(){
11428       this.queue.forEach(function(element){
11429         $timeout.cancel(element);
11430       });
11431       this.queue = [];
11432     }
11433   };
11434
11435
11436   ['width', 'height'].forEach(function (name) {
11437     var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11438     s['element' + capsName] = function (elem, extra) {
11439       var e = elem;
11440       if (e && typeof(e.length) !== 'undefined' && e.length) {
11441         e = elem[0];
11442       }
11443
11444       if (e && e !== null) {
11445         var styles = getStyles(e);
11446         return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11447                   s.swap(e, cssShow, function() {
11448                     return getWidthOrHeight(e, name, extra );
11449                   }) :
11450                   getWidthOrHeight( e, name, extra );
11451       }
11452       else {
11453         return null;
11454       }
11455     };
11456
11457     s['outerElement' + capsName] = function (elem, margin) {
11458       return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
11459     };
11460   });
11461
11462   // http://stackoverflow.com/a/24107550/888165
11463   s.closestElm = function closestElm(el, selector) {
11464     if (typeof(el.length) !== 'undefined' && el.length) {
11465       el = el[0];
11466     }
11467
11468     var matchesFn;
11469
11470     // find vendor prefix
11471     ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
11472         if (typeof document.body[fn] === 'function') {
11473             matchesFn = fn;
11474             return true;
11475         }
11476         return false;
11477     });
11478
11479     // traverse parents
11480     var parent;
11481     while (el !== null) {
11482       parent = el.parentElement;
11483       if (parent !== null && parent[matchesFn](selector)) {
11484           return parent;
11485       }
11486       el = parent;
11487     }
11488
11489     return null;
11490   };
11491
11492   s.type = function (obj) {
11493     var text = Function.prototype.toString.call(obj.constructor);
11494     return text.match(/function (.*?)\(/)[1];
11495   };
11496
11497   s.getBorderSize = function getBorderSize(elem, borderType) {
11498     if (typeof(elem.length) !== 'undefined' && elem.length) {
11499       elem = elem[0];
11500     }
11501
11502     var styles = getStyles(elem);
11503
11504     // If a specific border is supplied, like 'top', read the 'borderTop' style property
11505     if (borderType) {
11506       borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
11507     }
11508     else {
11509       borderType = 'border';
11510     }
11511
11512     borderType += 'Width';
11513
11514     var val = parseInt(styles[borderType], 10);
11515
11516     if (isNaN(val)) {
11517       return 0;
11518     }
11519     else {
11520       return val;
11521     }
11522   };
11523
11524   // http://stackoverflow.com/a/22948274/888165
11525   // TODO: Opera? Mobile?
11526   s.detectBrowser = function detectBrowser() {
11527     var userAgent = $window.navigator.userAgent;
11528
11529     var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
11530
11531     for (var key in browsers) {
11532       if (browsers[key].test(userAgent)) {
11533         return key;
11534       }
11535     }
11536
11537     return 'unknown';
11538   };
11539
11540   // Borrowed from https://github.com/othree/jquery.rtl-scroll-type
11541   // Determine the scroll "type" this browser is using for RTL
11542   s.rtlScrollType = function rtlScrollType() {
11543     if (rtlScrollType.type) {
11544       return rtlScrollType.type;
11545     }
11546
11547     var definer = angular.element('<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>')[0],
11548         type = 'reverse';
11549
11550     document.body.appendChild(definer);
11551
11552     if (definer.scrollLeft > 0) {
11553       type = 'default';
11554     }
11555     else {
11556       definer.scrollLeft = 1;
11557       if (definer.scrollLeft === 0) {
11558         type = 'negative';
11559       }
11560     }
11561
11562     angular.element(definer).remove();
11563     rtlScrollType.type = type;
11564
11565     return type;
11566   };
11567
11568     /**
11569      * @ngdoc method
11570      * @name normalizeScrollLeft
11571      * @methodOf ui.grid.service:GridUtil
11572      *
11573      * @param {element} element The element to get the `scrollLeft` from.
11574      * @param {grid} grid -  grid used to normalize (uses the rtl property)
11575      *
11576      * @returns {number} A normalized scrollLeft value for the current browser.
11577      *
11578      * @description
11579      * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
11580      */
11581   s.normalizeScrollLeft = function normalizeScrollLeft(element, grid) {
11582     if (typeof(element.length) !== 'undefined' && element.length) {
11583       element = element[0];
11584     }
11585
11586     var scrollLeft = element.scrollLeft;
11587
11588     if (grid.isRTL()) {
11589       switch (s.rtlScrollType()) {
11590         case 'default':
11591           return element.scrollWidth - scrollLeft - element.clientWidth;
11592         case 'negative':
11593           return Math.abs(scrollLeft);
11594         case 'reverse':
11595           return scrollLeft;
11596       }
11597     }
11598
11599     return scrollLeft;
11600   };
11601
11602   /**
11603   * @ngdoc method
11604   * @name denormalizeScrollLeft
11605   * @methodOf ui.grid.service:GridUtil
11606   *
11607   * @param {element} element The element to normalize the `scrollLeft` value for
11608   * @param {number} scrollLeft The `scrollLeft` value to denormalize.
11609   * @param {grid} grid The grid that owns the scroll event.
11610   *
11611   * @returns {number} A normalized scrollLeft value for the current browser.
11612   *
11613   * @description
11614   * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
11615   */
11616   s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft, grid) {
11617     if (typeof(element.length) !== 'undefined' && element.length) {
11618       element = element[0];
11619     }
11620
11621     if (grid.isRTL()) {
11622       switch (s.rtlScrollType()) {
11623         case 'default':
11624           // Get the max scroll for the element
11625           var maxScrollLeft = element.scrollWidth - element.clientWidth;
11626
11627           // Subtract the current scroll amount from the max scroll
11628           return maxScrollLeft - scrollLeft;
11629         case 'negative':
11630           return scrollLeft * -1;
11631         case 'reverse':
11632           return scrollLeft;
11633       }
11634     }
11635
11636     return scrollLeft;
11637   };
11638
11639     /**
11640      * @ngdoc method
11641      * @name preEval
11642      * @methodOf ui.grid.service:GridUtil
11643      *
11644      * @param {string} path Path to evaluate
11645      *
11646      * @returns {string} A path that is normalized.
11647      *
11648      * @description
11649      * Takes a field path and converts it to bracket notation to allow for special characters in path
11650      * @example
11651      * <pre>
11652      * gridUtil.preEval('property') == 'property'
11653      * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
11654      * </pre>
11655      */
11656   s.preEval = function (path) {
11657     var m = uiGridConstants.BRACKET_REGEXP.exec(path);
11658     if (m) {
11659       return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
11660     } else {
11661       path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
11662       var parts = path.split(uiGridConstants.DOT_REGEXP);
11663       var preparsed = [parts.shift()];    // first item must be var notation, thus skip
11664       angular.forEach(parts, function (part) {
11665         preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
11666       });
11667       return preparsed.join('[\'');
11668     }
11669   };
11670
11671   /**
11672    * @ngdoc method
11673    * @name debounce
11674    * @methodOf ui.grid.service:GridUtil
11675    *
11676    * @param {function} func function to debounce
11677    * @param {number} wait milliseconds to delay
11678    * @param {boolean} immediate execute before delay
11679    *
11680    * @returns {function} A function that can be executed as debounced function
11681    *
11682    * @description
11683    * Copied from https://github.com/shahata/angular-debounce
11684    * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
11685    * @example
11686    * <pre>
11687    * var debouncedFunc =  gridUtil.debounce(function(){alert('debounced');}, 500);
11688    * debouncedFunc();
11689    * debouncedFunc();
11690    * debouncedFunc();
11691    * </pre>
11692    */
11693   s.debounce =  function (func, wait, immediate) {
11694     var timeout, args, context, result;
11695     function debounce() {
11696       /* jshint validthis:true */
11697       context = this;
11698       args = arguments;
11699       var later = function () {
11700         timeout = null;
11701         if (!immediate) {
11702           result = func.apply(context, args);
11703         }
11704       };
11705       var callNow = immediate && !timeout;
11706       if (timeout) {
11707         $timeout.cancel(timeout);
11708       }
11709       timeout = $timeout(later, wait, false);
11710       if (callNow) {
11711         result = func.apply(context, args);
11712       }
11713       return result;
11714     }
11715     debounce.cancel = function () {
11716       $timeout.cancel(timeout);
11717       timeout = null;
11718     };
11719     return debounce;
11720   };
11721
11722   /**
11723    * @ngdoc method
11724    * @name throttle
11725    * @methodOf ui.grid.service:GridUtil
11726    *
11727    * @param {function} func function to throttle
11728    * @param {number} wait milliseconds to delay after first trigger
11729    * @param {Object} params to use in throttle.
11730    *
11731    * @returns {function} A function that can be executed as throttled function
11732    *
11733    * @description
11734    * Adapted from debounce function (above)
11735    * Potential keys for Params Object are:
11736    *    trailing (bool) - whether to trigger after throttle time ends if called multiple times
11737    * Updated to use $interval rather than $timeout, as protractor (e2e tests) is able to work with $interval,
11738    * but not with $timeout
11739    *
11740    * Note that when using throttle, you need to use throttle to create a new function upfront, then use the function
11741    * return from that call each time you need to call throttle.  If you call throttle itself repeatedly, the lastCall
11742    * variable will get overwritten and the throttling won't work
11743    *
11744    * @example
11745    * <pre>
11746    * var throttledFunc =  gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11747    * throttledFunc(); //=> logs throttled
11748    * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
11749    * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
11750    * </pre>
11751    */
11752   s.throttle = function(func, wait, options){
11753     options = options || {};
11754     var lastCall = 0, queued = null, context, args;
11755
11756     function runFunc(endDate){
11757       lastCall = +new Date();
11758       func.apply(context, args);
11759       $interval(function(){queued = null; }, 0, 1, false);
11760     }
11761
11762     return function(){
11763       /* jshint validthis:true */
11764       context = this;
11765       args = arguments;
11766       if (queued === null){
11767         var sinceLast = +new Date() - lastCall;
11768         if (sinceLast > wait){
11769           runFunc();
11770         }
11771         else if (options.trailing){
11772           queued = $interval(runFunc, wait - sinceLast, 1, false);
11773         }
11774       }
11775     };
11776   };
11777
11778   s.on = {};
11779   s.off = {};
11780   s._events = {};
11781
11782   s.addOff = function (eventName) {
11783     s.off[eventName] = function (elm, fn) {
11784       var idx = s._events[eventName].indexOf(fn);
11785       if (idx > 0) {
11786         s._events[eventName].removeAt(idx);
11787       }
11788     };
11789   };
11790
11791   var mouseWheeltoBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
11792       nullLowestDeltaTimeout,
11793       lowestDelta;
11794
11795   s.on.mousewheel = function (elm, fn) {
11796     if (!elm || !fn) { return; }
11797
11798     var $elm = angular.element(elm);
11799
11800     // Store the line height and page height for this particular element
11801     $elm.data('mousewheel-line-height', getLineHeight($elm));
11802     $elm.data('mousewheel-page-height', s.elementHeight($elm));
11803     if (!$elm.data('mousewheel-callbacks')) { $elm.data('mousewheel-callbacks', {}); }
11804
11805     var cbs = $elm.data('mousewheel-callbacks');
11806     cbs[fn] = (Function.prototype.bind || bindPolyfill).call(mousewheelHandler, $elm[0], fn);
11807
11808     // Bind all the mousew heel events
11809     for ( var i = mouseWheeltoBind.length; i; ) {
11810       $elm.on(mouseWheeltoBind[--i], cbs[fn]);
11811     }
11812     $elm.on('$destroy', function unbindEvents() {
11813       for ( var i = mouseWheeltoBind.length; i; ) {
11814         $elm.off(mouseWheeltoBind[--i], cbs[fn]);
11815       }
11816     });
11817   };
11818   s.off.mousewheel = function (elm, fn) {
11819     var $elm = angular.element(elm);
11820
11821     var cbs = $elm.data('mousewheel-callbacks');
11822     var handler = cbs[fn];
11823
11824     if (handler) {
11825       for ( var i = mouseWheeltoBind.length; i; ) {
11826         $elm.off(mouseWheeltoBind[--i], handler);
11827       }
11828     }
11829
11830     delete cbs[fn];
11831
11832     if (Object.keys(cbs).length === 0) {
11833       $elm.removeData('mousewheel-line-height');
11834       $elm.removeData('mousewheel-page-height');
11835       $elm.removeData('mousewheel-callbacks');
11836     }
11837   };
11838
11839   function mousewheelHandler(fn, event) {
11840     var $elm = angular.element(this);
11841
11842     var delta      = 0,
11843         deltaX     = 0,
11844         deltaY     = 0,
11845         absDelta   = 0,
11846         offsetX    = 0,
11847         offsetY    = 0;
11848
11849     // jQuery masks events
11850     if (event.originalEvent) { event = event.originalEvent; }
11851
11852     if ( 'detail'      in event ) { deltaY = event.detail * -1;      }
11853     if ( 'wheelDelta'  in event ) { deltaY = event.wheelDelta;       }
11854     if ( 'wheelDeltaY' in event ) { deltaY = event.wheelDeltaY;      }
11855     if ( 'wheelDeltaX' in event ) { deltaX = event.wheelDeltaX * -1; }
11856
11857     // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
11858     if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
11859       deltaX = deltaY * -1;
11860       deltaY = 0;
11861     }
11862
11863     // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
11864     delta = deltaY === 0 ? deltaX : deltaY;
11865
11866     // New school wheel delta (wheel event)
11867     if ( 'deltaY' in event ) {
11868       deltaY = event.deltaY * -1;
11869       delta  = deltaY;
11870     }
11871     if ( 'deltaX' in event ) {
11872       deltaX = event.deltaX;
11873       if ( deltaY === 0 ) { delta  = deltaX * -1; }
11874     }
11875
11876     // No change actually happened, no reason to go any further
11877     if ( deltaY === 0 && deltaX === 0 ) { return; }
11878
11879     // Need to convert lines and pages to pixels if we aren't already in pixels
11880     // There are three delta modes:
11881     //   * deltaMode 0 is by pixels, nothing to do
11882     //   * deltaMode 1 is by lines
11883     //   * deltaMode 2 is by pages
11884     if ( event.deltaMode === 1 ) {
11885         var lineHeight = $elm.data('mousewheel-line-height');
11886         delta  *= lineHeight;
11887         deltaY *= lineHeight;
11888         deltaX *= lineHeight;
11889     }
11890     else if ( event.deltaMode === 2 ) {
11891         var pageHeight = $elm.data('mousewheel-page-height');
11892         delta  *= pageHeight;
11893         deltaY *= pageHeight;
11894         deltaX *= pageHeight;
11895     }
11896
11897     // Store lowest absolute delta to normalize the delta values
11898     absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
11899
11900     if ( !lowestDelta || absDelta < lowestDelta ) {
11901       lowestDelta = absDelta;
11902
11903       // Adjust older deltas if necessary
11904       if ( shouldAdjustOldDeltas(event, absDelta) ) {
11905         lowestDelta /= 40;
11906       }
11907     }
11908
11909     // Get a whole, normalized value for the deltas
11910     delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
11911     deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
11912     deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
11913
11914     // Normalise offsetX and offsetY properties
11915     // if ($elm[0].getBoundingClientRect ) {
11916     //   var boundingRect = $(elm)[0].getBoundingClientRect();
11917     //   offsetX = event.clientX - boundingRect.left;
11918     //   offsetY = event.clientY - boundingRect.top;
11919     // }
11920
11921     // event.deltaX = deltaX;
11922     // event.deltaY = deltaY;
11923     // event.deltaFactor = lowestDelta;
11924
11925     var newEvent = {
11926       originalEvent: event,
11927       deltaX: deltaX,
11928       deltaY: deltaY,
11929       deltaFactor: lowestDelta,
11930       preventDefault: function () { event.preventDefault(); },
11931       stopPropagation: function () { event.stopPropagation(); }
11932     };
11933
11934     // Clearout lowestDelta after sometime to better
11935     // handle multiple device types that give
11936     // a different lowestDelta
11937     // Ex: trackpad = 3 and mouse wheel = 120
11938     if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
11939     nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
11940
11941     fn.call($elm[0], newEvent);
11942   }
11943
11944   function nullLowestDelta() {
11945     lowestDelta = null;
11946   }
11947
11948   function shouldAdjustOldDeltas(orgEvent, absDelta) {
11949     // If this is an older event and the delta is divisable by 120,
11950     // then we are assuming that the browser is treating this as an
11951     // older mouse wheel event and that we should divide the deltas
11952     // by 40 to try and get a more usable deltaFactor.
11953     // Side note, this actually impacts the reported scroll distance
11954     // in older browsers and can cause scrolling to be slower than native.
11955     // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
11956     return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
11957   }
11958
11959   return s;
11960 }]);
11961
11962 // Add 'px' to the end of a number string if it doesn't have it already
11963 module.filter('px', function() {
11964   return function(str) {
11965     if (str.match(/^[\d\.]+$/)) {
11966       return str + 'px';
11967     }
11968     else {
11969       return str;
11970     }
11971   };
11972 });
11973
11974 })();
11975
11976 (function () {
11977   angular.module('ui.grid').config(['$provide', function($provide) {
11978     $provide.decorator('i18nService', ['$delegate', function($delegate) {
11979       var lang = {
11980               aggregate: {
11981                   label: 'položky'
11982               },
11983               groupPanel: {
11984                   description: 'Přesuňte záhlaví zde pro vytvoření skupiny dle sloupce.'
11985               },
11986               search: {
11987                   placeholder: 'Hledat...',
11988                   showingItems: 'Zobrazuji položky:',
11989                   selectedItems: 'Vybrané položky:',
11990                   totalItems: 'Celkem položek:',
11991                   size: 'Velikost strany:',
11992                   first: 'První strana',
11993                   next: 'Další strana',
11994                   previous: 'Předchozí strana',
11995                   last: 'Poslední strana'
11996               },
11997               menu: {
11998                   text: 'Vyberte sloupec:'
11999               },
12000               sort: {
12001                   ascending: 'Seřadit od A-Z',
12002                   descending: 'Seřadit od Z-A',
12003                   remove: 'Odebrat seřazení'
12004               },
12005               column: {
12006                   hide: 'Schovat sloupec'
12007               },
12008               aggregation: {
12009                   count: 'celkem řádků: ',
12010                   sum: 'celkem: ',
12011                   avg: 'avg: ',
12012                   min: 'min.: ',
12013                   max: 'max.: '
12014               },
12015               pinning: {
12016                   pinLeft: 'Zamknout vlevo',
12017                   pinRight: 'Zamknout vpravo',
12018                   unpin: 'Odemknout'
12019               },
12020               gridMenu: {
12021                   columns: 'Sloupce:',
12022                   importerTitle: 'Importovat soubor',
12023                   exporterAllAsCsv: 'Exportovat všechna data do csv',
12024                   exporterVisibleAsCsv: 'Exportovat viditelná data do csv',
12025                   exporterSelectedAsCsv: 'Exportovat vybraná data do csv',
12026                   exporterAllAsPdf: 'Exportovat všechna data do pdf',
12027                   exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
12028                   exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
12029                   clearAllFilters: 'Odstranit všechny filtry'
12030               },
12031               importer: {
12032                   noHeaders: 'Názvy sloupců se nepodařilo získat, obsahuje soubor záhlaví?',
12033                   noObjects: 'Data se nepodařilo zpracovat, obsahuje soubor řádky mimo záhlaví?',
12034                   invalidCsv: 'Soubor nelze zpracovat, jedná se o CSV?',
12035                   invalidJson: 'Soubor nelze zpracovat, je to JSON?',
12036                   jsonNotArray: 'Soubor musí obsahovat json. Ukončuji..'
12037               },
12038               pagination: {
12039                   sizes: 'položek na stránku',
12040                   totalItems: 'položek'
12041               },
12042               grouping: {
12043                   group: 'Seskupit',
12044                   ungroup: 'Odebrat seskupení',
12045                   aggregate_count: 'Agregace: Count',
12046                   aggregate_sum: 'Agregace: Sum',
12047                   aggregate_max: 'Agregace: Max',
12048                   aggregate_min: 'Agregace: Min',
12049                   aggregate_avg: 'Agregace: Avg',
12050                   aggregate_remove: 'Agregace: Odebrat'
12051               }
12052           };
12053
12054           // support varianty of different czech keys.
12055           $delegate.add('cs', lang);
12056           $delegate.add('cz', lang);
12057           $delegate.add('cs-cz', lang);
12058           $delegate.add('cs-CZ', lang);
12059       return $delegate;
12060     }]);
12061   }]);
12062 })();
12063
12064 (function(){
12065   angular.module('ui.grid').config(['$provide', function($provide) {
12066     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12067       $delegate.add('da', {
12068         aggregate:{
12069           label: 'artikler'
12070         },
12071         groupPanel:{
12072           description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
12073         },
12074         search:{
12075           placeholder: 'Søg...',
12076           showingItems: 'Viste rækker:',
12077           selectedItems: 'Valgte rækker:',
12078           totalItems: 'Rækker totalt:',
12079           size: 'Side størrelse:',
12080           first: 'Første side',
12081           next: 'Næste side',
12082           previous: 'Forrige side',
12083           last: 'Sidste side'
12084         },
12085         menu:{
12086           text: 'Vælg kolonner:'
12087         },
12088         sort: {
12089           ascending: 'Sorter stigende',
12090           descending: 'Sorter faldende',
12091           none: 'Sorter ingen',
12092           remove: 'Fjern sortering'
12093         },
12094         column: {
12095           hide: 'Skjul kolonne'
12096         },
12097         aggregation: {
12098           count: 'antal rækker: ',
12099           sum: 'sum: ',
12100           avg: 'gns: ',
12101           min: 'min: ',
12102           max: 'max: '
12103         },
12104         gridMenu: {
12105           columns: 'Kolonner:',
12106           importerTitle: 'Importer fil',
12107           exporterAllAsCsv: 'Eksporter alle data som csv',
12108           exporterVisibleAsCsv: 'Eksporter synlige data som csv',
12109           exporterSelectedAsCsv: 'Eksporter markerede data som csv',
12110           exporterAllAsPdf: 'Eksporter alle data som pdf',
12111           exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
12112           exporterSelectedAsPdf: 'Eksporter markerede data som pdf',
12113           clearAllFilters: 'Clear all filters'
12114         },
12115         importer: {
12116           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12117           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12118           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12119           invalidJson: 'File was unable to be processed, is it valid Json?',
12120           jsonNotArray: 'Imported json file must contain an array, aborting.'
12121         },
12122         pagination: {
12123           aria: {
12124             pageToFirst: 'Gå til første',
12125             pageBack: 'Gå tilbage',
12126             pageSelected: 'Valgte side',
12127             pageForward: 'Gå frem',
12128             pageToLast: 'Gå til sidste'
12129           },
12130           sizes: 'genstande per side',
12131           totalItems: 'genstande',
12132           through: 'gennem',
12133           of: 'af'
12134         }
12135       });
12136       return $delegate;
12137     }]);
12138   }]);
12139 })();
12140
12141 (function () {
12142   angular.module('ui.grid').config(['$provide', function ($provide) {
12143     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12144       $delegate.add('de', {
12145         headerCell: {
12146           aria: {
12147             defaultFilterLabel: 'Filter für Spalte',
12148             removeFilter: 'Filter löschen',
12149             columnMenuButtonLabel: 'Spaltenmenü'
12150           },
12151           priority: 'Priorität:',
12152           filterLabel: "Filter für Spalte: "
12153         },
12154         aggregate: {
12155           label: 'Eintrag'
12156         },
12157         groupPanel: {
12158           description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
12159         },
12160         search: {
12161           placeholder: 'Suche...',
12162           showingItems: 'Zeige Einträge:',
12163           selectedItems: 'Ausgewählte Einträge:',
12164           totalItems: 'Einträge gesamt:',
12165           size: 'Einträge pro Seite:',
12166           first: 'Erste Seite',
12167           next: 'Nächste Seite',
12168           previous: 'Vorherige Seite',
12169           last: 'Letzte Seite'
12170         },
12171         menu: {
12172           text: 'Spalten auswählen:'
12173         },
12174         sort: {
12175           ascending: 'aufsteigend sortieren',
12176           descending: 'absteigend sortieren',
12177           none: 'keine Sortierung',
12178           remove: 'Sortierung zurücksetzen'
12179         },
12180         column: {
12181           hide: 'Spalte ausblenden'
12182         },
12183         aggregation: {
12184           count: 'Zeilen insgesamt: ',
12185           sum: 'gesamt: ',
12186           avg: 'Durchschnitt: ',
12187           min: 'min: ',
12188           max: 'max: '
12189         },
12190         pinning: {
12191             pinLeft: 'Links anheften',
12192             pinRight: 'Rechts anheften',
12193             unpin: 'Lösen'
12194         },
12195         columnMenu: {
12196           close: 'Schließen'
12197         },
12198         gridMenu: {
12199           aria: {
12200             buttonLabel: 'Tabellenmenü'
12201           },
12202           columns: 'Spalten:',
12203           importerTitle: 'Datei importieren',
12204           exporterAllAsCsv: 'Alle Daten als CSV exportieren',
12205           exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
12206           exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
12207           exporterAllAsPdf: 'Alle Daten als PDF exportieren',
12208           exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
12209           exporterSelectedAsPdf: 'markierte Daten als PDF exportieren',
12210           clearAllFilters: 'Alle Filter zurücksetzen'
12211         },
12212         importer: {
12213           noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
12214           noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
12215           invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
12216           invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
12217           jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
12218         },
12219         pagination: {
12220           aria: {
12221             pageToFirst: 'Zum Anfang',
12222             pageBack: 'Seite zurück',
12223             pageSelected: 'Ausgwählte Seite',
12224             pageForward: 'Seite vor',
12225             pageToLast: 'Zum Ende'
12226           },
12227           sizes: 'Einträge pro Seite',
12228           totalItems: 'Einträge',
12229           through: 'bis',
12230           of: 'von'
12231         },
12232         grouping: {
12233             group: 'Gruppieren',
12234             ungroup: 'Gruppierung aufheben',
12235             aggregate_count: 'Agg: Anzahl',
12236             aggregate_sum: 'Agg: Summe',
12237             aggregate_max: 'Agg: Maximum',
12238             aggregate_min: 'Agg: Minimum',
12239             aggregate_avg: 'Agg: Mittelwert',
12240             aggregate_remove: 'Aggregation entfernen'
12241         }
12242       });
12243       return $delegate;
12244     }]);
12245   }]);
12246 })();
12247
12248 (function () {
12249   angular.module('ui.grid').config(['$provide', function($provide) {
12250     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12251       $delegate.add('en', {
12252         headerCell: {
12253           aria: {
12254             defaultFilterLabel: 'Filter for column',
12255             removeFilter: 'Remove Filter',
12256             columnMenuButtonLabel: 'Column Menu'
12257           },
12258           priority: 'Priority:',
12259           filterLabel: "Filter for column: "
12260         },
12261         aggregate: {
12262           label: 'items'
12263         },
12264         groupPanel: {
12265           description: 'Drag a column header here and drop it to group by that column.'
12266         },
12267         search: {
12268           placeholder: 'Search...',
12269           showingItems: 'Showing Items:',
12270           selectedItems: 'Selected Items:',
12271           totalItems: 'Total Items:',
12272           size: 'Page Size:',
12273           first: 'First Page',
12274           next: 'Next Page',
12275           previous: 'Previous Page',
12276           last: 'Last Page'
12277         },
12278         menu: {
12279           text: 'Choose Columns:'
12280         },
12281         sort: {
12282           ascending: 'Sort Ascending',
12283           descending: 'Sort Descending',
12284           none: 'Sort None',
12285           remove: 'Remove Sort'
12286         },
12287         column: {
12288           hide: 'Hide Column'
12289         },
12290         aggregation: {
12291           count: 'total rows: ',
12292           sum: 'total: ',
12293           avg: 'avg: ',
12294           min: 'min: ',
12295           max: 'max: '
12296         },
12297         pinning: {
12298           pinLeft: 'Pin Left',
12299           pinRight: 'Pin Right',
12300           unpin: 'Unpin'
12301         },
12302         columnMenu: {
12303           close: 'Close'
12304         },
12305         gridMenu: {
12306           aria: {
12307             buttonLabel: 'Grid Menu'
12308           },
12309           columns: 'Columns:',
12310           importerTitle: 'Import file',
12311           exporterAllAsCsv: 'Export all data as csv',
12312           exporterVisibleAsCsv: 'Export visible data as csv',
12313           exporterSelectedAsCsv: 'Export selected data as csv',
12314           exporterAllAsPdf: 'Export all data as pdf',
12315           exporterVisibleAsPdf: 'Export visible data as pdf',
12316           exporterSelectedAsPdf: 'Export selected data as pdf',
12317           clearAllFilters: 'Clear all filters'
12318         },
12319         importer: {
12320           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12321           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12322           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12323           invalidJson: 'File was unable to be processed, is it valid Json?',
12324           jsonNotArray: 'Imported json file must contain an array, aborting.'
12325         },
12326         pagination: {
12327           aria: {
12328             pageToFirst: 'Page to first',
12329             pageBack: 'Page back',
12330             pageSelected: 'Selected page',
12331             pageForward: 'Page forward',
12332             pageToLast: 'Page to last'
12333           },
12334           sizes: 'items per page',
12335           totalItems: 'items',
12336           through: 'through',
12337           of: 'of'
12338         },
12339         grouping: {
12340           group: 'Group',
12341           ungroup: 'Ungroup',
12342           aggregate_count: 'Agg: Count',
12343           aggregate_sum: 'Agg: Sum',
12344           aggregate_max: 'Agg: Max',
12345           aggregate_min: 'Agg: Min',
12346           aggregate_avg: 'Agg: Avg',
12347           aggregate_remove: 'Agg: Remove'
12348         },
12349         validate: {
12350           error: 'Error:',
12351           minLength: 'Value should be at least THRESHOLD characters long.',
12352           maxLength: 'Value should be at most THRESHOLD characters long.',
12353           required: 'A value is needed.'
12354         }
12355       });
12356       return $delegate;
12357     }]);
12358   }]);
12359 })();
12360
12361 (function () {
12362   angular.module('ui.grid').config(['$provide', function($provide) {
12363     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12364       $delegate.add('es', {
12365         aggregate: {
12366           label: 'Artículos'
12367         },
12368         groupPanel: {
12369           description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
12370         },
12371         search: {
12372           placeholder: 'Buscar...',
12373           showingItems: 'Artículos Mostrados:',
12374           selectedItems: 'Artículos Seleccionados:',
12375           totalItems: 'Artículos Totales:',
12376           size: 'Tamaño de Página:',
12377           first: 'Primera Página',
12378           next: 'Página Siguiente',
12379           previous: 'Página Anterior',
12380           last: 'Última Página'
12381         },
12382         menu: {
12383           text: 'Elegir columnas:'
12384         },
12385         sort: {
12386           ascending: 'Orden Ascendente',
12387           descending: 'Orden Descendente',
12388           remove: 'Sin Ordenar'
12389         },
12390         column: {
12391           hide: 'Ocultar la columna'
12392         },
12393         aggregation: {
12394           count: 'filas totales: ',
12395           sum: 'total: ',
12396           avg: 'media: ',
12397           min: 'min: ',
12398           max: 'max: '
12399         },
12400         pinning: {
12401           pinLeft: 'Fijar a la Izquierda',
12402           pinRight: 'Fijar a la Derecha',
12403           unpin: 'Quitar Fijación'
12404         },
12405         gridMenu: {
12406           columns: 'Columnas:',
12407           importerTitle: 'Importar archivo',
12408           exporterAllAsCsv: 'Exportar todo como csv',
12409           exporterVisibleAsCsv: 'Exportar vista como csv',
12410           exporterSelectedAsCsv: 'Exportar selección como csv',
12411           exporterAllAsPdf: 'Exportar todo como pdf',
12412           exporterVisibleAsPdf: 'Exportar vista como pdf',
12413           exporterSelectedAsPdf: 'Exportar selección como pdf',
12414           clearAllFilters: 'Limpiar todos los filtros'
12415         },
12416         importer: {
12417           noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
12418           noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
12419           invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
12420           invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
12421           jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
12422         },
12423         pagination: {
12424           sizes: 'registros por página',
12425           totalItems: 'registros',
12426           of: 'de'
12427         },
12428         grouping: {
12429           group: 'Agrupar',
12430           ungroup: 'Desagrupar',
12431           aggregate_count: 'Agr: Cont',
12432           aggregate_sum: 'Agr: Sum',
12433           aggregate_max: 'Agr: Máx',
12434           aggregate_min: 'Agr: Min',
12435           aggregate_avg: 'Agr: Prom',
12436           aggregate_remove: 'Agr: Quitar'
12437         }
12438       });
12439       return $delegate;
12440     }]);
12441 }]);
12442 })();
12443
12444 /**
12445  * Translated by: R. Salarmehr
12446  *                M. Hosseynzade
12447  *                Using Vajje.com online dictionary.
12448  */
12449 (function () {
12450   angular.module('ui.grid').config(['$provide', function ($provide) {
12451     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12452       $delegate.add('fa', {
12453         aggregate: {
12454           label: 'قلم'
12455         },
12456         groupPanel: {
12457           description: 'عنوان یک ستون را بگیر و به گروهی از آن ستون رها کن.'
12458         },
12459         search: {
12460           placeholder: 'جستجو...',
12461           showingItems: 'نمایش اقلام:',
12462           selectedItems: 'قلم\u200cهای انتخاب شده:',
12463           totalItems: 'مجموع اقلام:',
12464           size: 'اندازه\u200cی صفحه:',
12465           first: 'اولین صفحه',
12466           next: 'صفحه\u200cی\u200cبعدی',
12467           previous: 'صفحه\u200cی\u200c قبلی',
12468           last: 'آخرین صفحه'
12469         },
12470         menu: {
12471           text: 'ستون\u200cهای انتخابی:'
12472         },
12473         sort: {
12474           ascending: 'ترتیب صعودی',
12475           descending: 'ترتیب نزولی',
12476           remove: 'حذف مرتب کردن'
12477         },
12478         column: {
12479           hide: 'پنهان\u200cکردن ستون'
12480         },
12481         aggregation: {
12482           count: 'تعداد: ',
12483           sum: 'مجموع: ',
12484           avg: 'میانگین: ',
12485           min: 'کمترین: ',
12486           max: 'بیشترین: '
12487         },
12488         pinning: {
12489           pinLeft: 'پین کردن سمت چپ',
12490           pinRight: 'پین کردن سمت راست',
12491           unpin: 'حذف پین'
12492         },
12493         gridMenu: {
12494           columns: 'ستون\u200cها:',
12495           importerTitle: 'وارد کردن فایل',
12496           exporterAllAsCsv: 'خروجی تمام داده\u200cها در فایل csv',
12497           exporterVisibleAsCsv: 'خروجی داده\u200cهای قابل مشاهده در فایل csv',
12498           exporterSelectedAsCsv: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل csv',
12499           exporterAllAsPdf: 'خروجی تمام داده\u200cها در فایل pdf',
12500           exporterVisibleAsPdf: 'خروجی داده\u200cهای قابل مشاهده در فایل pdf',
12501           exporterSelectedAsPdf: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل pdf',
12502           clearAllFilters: 'پاک کردن تمام فیلتر'
12503         },
12504         importer: {
12505           noHeaders: 'نام ستون قابل استخراج نیست. آیا فایل عنوان دارد؟',
12506           noObjects: 'اشیا قابل استخراج نیستند. آیا به جز عنوان\u200cها در فایل داده وجود دارد؟',
12507           invalidCsv: 'فایل قابل پردازش نیست. آیا فرمت  csv  معتبر است؟',
12508           invalidJson: 'فایل قابل پردازش نیست. آیا فرمت json   معتبر است؟',
12509           jsonNotArray: 'فایل json وارد شده باید حاوی آرایه باشد. عملیات ساقط شد.'
12510         },
12511         pagination: {
12512           sizes: 'اقلام در هر صفحه',
12513           totalItems: 'اقلام',
12514           of: 'از'
12515         },
12516         grouping: {
12517           group: 'گروه\u200cبندی',
12518           ungroup: 'حذف گروه\u200cبندی',
12519           aggregate_count: 'Agg: تعداد',
12520           aggregate_sum: 'Agg: جمع',
12521           aggregate_max: 'Agg: بیشینه',
12522           aggregate_min: 'Agg: کمینه',
12523           aggregate_avg: 'Agg: میانگین',
12524           aggregate_remove: 'Agg: حذف'
12525         }
12526       });
12527       return $delegate;
12528     }]);
12529   }]);
12530 })();
12531
12532 (function () {
12533   angular.module('ui.grid').config(['$provide', function($provide) {
12534     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12535       $delegate.add('fi', {
12536         aggregate: {
12537           label: 'rivit'
12538         },
12539         groupPanel: {
12540           description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
12541         },
12542         search: {
12543           placeholder: 'Hae...',
12544           showingItems: 'Näytetään rivejä:',
12545           selectedItems: 'Valitut rivit:',
12546           totalItems: 'Rivejä yht.:',
12547           size: 'Näytä:',
12548           first: 'Ensimmäinen sivu',
12549           next: 'Seuraava sivu',
12550           previous: 'Edellinen sivu',
12551           last: 'Viimeinen sivu'
12552         },
12553         menu: {
12554           text: 'Valitse sarakkeet:'
12555         },
12556         sort: {
12557           ascending: 'Järjestä nouseva',
12558           descending: 'Järjestä laskeva',
12559           remove: 'Poista järjestys'
12560         },
12561         column: {
12562           hide: 'Piilota sarake'
12563         },
12564         aggregation: {
12565           count: 'Rivejä yht.: ',
12566           sum: 'Summa: ',
12567           avg: 'K.a.: ',
12568           min: 'Min: ',
12569           max: 'Max: '
12570         },
12571         pinning: {
12572          pinLeft: 'Lukitse vasemmalle',
12573           pinRight: 'Lukitse oikealle',
12574           unpin: 'Poista lukitus'
12575         },
12576         gridMenu: {
12577           columns: 'Sarakkeet:',
12578           importerTitle: 'Tuo tiedosto',
12579           exporterAllAsCsv: 'Vie tiedot csv-muodossa',
12580           exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
12581           exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
12582           exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
12583           exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
12584           exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12585           clearAllFilters: 'Puhdista kaikki suodattimet'
12586         },
12587         importer: {
12588           noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
12589           noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
12590           invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
12591           invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
12592           jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
12593         }
12594       });
12595       return $delegate;
12596     }]);
12597   }]);
12598 })();
12599
12600 (function () {
12601   angular.module('ui.grid').config(['$provide', function($provide) {
12602     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12603       $delegate.add('fr', {
12604         headerCell: {
12605           aria: {
12606             defaultFilterLabel: 'Filtre de la colonne',
12607             removeFilter: 'Supprimer le filtre',
12608             columnMenuButtonLabel: 'Menu de la colonne'
12609           },
12610           priority: 'Priorité:',
12611           filterLabel: "Filtre de la colonne: "
12612         },
12613         aggregate: {
12614           label: 'éléments'
12615         },
12616         groupPanel: {
12617           description: 'Faites glisser une en-tête de colonne ici pour créer un groupe de colonnes.'
12618         },
12619         search: {
12620           placeholder: 'Recherche...',
12621           showingItems: 'Affichage des éléments :',
12622           selectedItems: 'Éléments sélectionnés :',
12623           totalItems: 'Nombre total d\'éléments:',
12624           size: 'Taille de page:',
12625           first: 'Première page',
12626           next: 'Page Suivante',
12627           previous: 'Page précédente',
12628           last: 'Dernière page'
12629         },
12630         menu: {
12631           text: 'Choisir des colonnes :'
12632         },
12633         sort: {
12634           ascending: 'Trier par ordre croissant',
12635           descending: 'Trier par ordre décroissant',
12636           none: 'Aucun tri',
12637           remove: 'Enlever le tri'
12638         },
12639         column: {
12640           hide: 'Cacher la colonne'
12641         },
12642         aggregation: {
12643           count: 'lignes totales: ',
12644           sum: 'total: ',
12645           avg: 'moy: ',
12646           min: 'min: ',
12647           max: 'max: '
12648         },
12649         pinning: {
12650           pinLeft: 'Épingler à gauche',
12651           pinRight: 'Épingler à droite',
12652           unpin: 'Détacher'
12653         },
12654         columnMenu: {
12655           close: 'Fermer'
12656         },
12657         gridMenu: {
12658           aria: {
12659             buttonLabel: 'Menu du tableau'
12660           },
12661           columns: 'Colonnes:',
12662           importerTitle: 'Importer un fichier',
12663           exporterAllAsCsv: 'Exporter toutes les données en CSV',
12664           exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
12665           exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
12666           exporterAllAsPdf: 'Exporter toutes les données en PDF',
12667           exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
12668           exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
12669           clearAllFilters: 'Nettoyez tous les filtres'
12670         },
12671         importer: {
12672           noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il une en-tête ?',
12673           noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
12674           invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
12675           invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
12676           jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
12677         },
12678         pagination: {
12679           aria: {
12680             pageToFirst: 'Aller à la première page',
12681             pageBack: 'Page précédente',
12682             pageSelected: 'Page sélectionnée',
12683             pageForward: 'Page suivante',
12684             pageToLast: 'Aller à la dernière page'
12685           },
12686           sizes: 'éléments par page',
12687           totalItems: 'éléments',
12688           through: 'à',
12689           of: 'sur'
12690         },
12691         grouping: {
12692           group: 'Grouper',
12693           ungroup: 'Dégrouper',
12694           aggregate_count: 'Agg: Compter',
12695           aggregate_sum: 'Agg: Somme',
12696           aggregate_max: 'Agg: Max',
12697           aggregate_min: 'Agg: Min',
12698           aggregate_avg: 'Agg: Moy',
12699           aggregate_remove: 'Agg: Retirer'
12700         },
12701         validate: {
12702           error: 'Erreur:',
12703           minLength: 'La valeur doit être supérieure ou égale à THRESHOLD caractères.',
12704           maxLength: 'La valeur doit être inférieure ou égale à THRESHOLD caractères.',
12705           required: 'Une valeur est nécéssaire.'
12706         }
12707       });
12708       return $delegate;
12709     }]);
12710   }]);
12711 })();
12712
12713 (function () {
12714   angular.module('ui.grid').config(['$provide', function ($provide) {
12715     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12716       $delegate.add('he', {
12717         aggregate: {
12718           label: 'items'
12719         },
12720         groupPanel: {
12721           description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
12722         },
12723         search: {
12724           placeholder: 'חפש...',
12725           showingItems: 'מציג:',
12726           selectedItems: 'סה"כ נבחרו:',
12727           totalItems: 'סה"כ רשומות:',
12728           size: 'תוצאות בדף:',
12729           first: 'דף ראשון',
12730           next: 'דף הבא',
12731           previous: 'דף קודם',
12732           last: 'דף אחרון'
12733         },
12734         menu: {
12735           text: 'בחר עמודות:'
12736         },
12737         sort: {
12738           ascending: 'סדר עולה',
12739           descending: 'סדר יורד',
12740           remove: 'בטל'
12741         },
12742         column: {
12743           hide: 'טור הסתר'
12744         },
12745         aggregation: {
12746           count: 'total rows: ',
12747           sum: 'total: ',
12748           avg: 'avg: ',
12749           min: 'min: ',
12750           max: 'max: '
12751         },
12752         gridMenu: {
12753           columns: 'Columns:',
12754           importerTitle: 'Import file',
12755           exporterAllAsCsv: 'Export all data as csv',
12756           exporterVisibleAsCsv: 'Export visible data as csv',
12757           exporterSelectedAsCsv: 'Export selected data as csv',
12758           exporterAllAsPdf: 'Export all data as pdf',
12759           exporterVisibleAsPdf: 'Export visible data as pdf',
12760           exporterSelectedAsPdf: 'Export selected data as pdf',
12761           clearAllFilters: 'Clean all filters'
12762         },
12763         importer: {
12764           noHeaders: 'Column names were unable to be derived, does the file have a header?',
12765           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12766           invalidCsv: 'File was unable to be processed, is it valid CSV?',
12767           invalidJson: 'File was unable to be processed, is it valid Json?',
12768           jsonNotArray: 'Imported json file must contain an array, aborting.'
12769         }
12770       });
12771       return $delegate;
12772     }]);
12773   }]);
12774 })();
12775
12776 (function () {
12777   angular.module('ui.grid').config(['$provide', function($provide) {
12778     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12779       $delegate.add('hy', {
12780         aggregate: {
12781           label: 'տվյալներ'
12782         },
12783         groupPanel: {
12784           description: 'Ըստ սյան խմբավորելու համար քաշեք և գցեք վերնագիրն այստեղ։'
12785         },
12786         search: {
12787           placeholder: 'Փնտրում...',
12788           showingItems: 'Ցուցադրված տվյալներ՝',
12789           selectedItems: 'Ընտրված:',
12790           totalItems: 'Ընդամենը՝',
12791           size: 'Տողերի քանակը էջում՝',
12792           first: 'Առաջին էջ',
12793           next: 'Հաջորդ էջ',
12794           previous: 'Նախորդ էջ',
12795           last: 'Վերջին էջ'
12796         },
12797         menu: {
12798           text: 'Ընտրել սյուները:'
12799         },
12800         sort: {
12801           ascending: 'Աճման կարգով',
12802           descending: 'Նվազման կարգով',
12803           remove: 'Հանել '
12804         },
12805         column: {
12806           hide: 'Թաքցնել սյունը'
12807         },
12808         aggregation: {
12809           count: 'ընդամենը տող՝ ',
12810           sum: 'ընդամենը՝ ',
12811           avg: 'միջին՝ ',
12812           min: 'մին՝ ',
12813           max: 'մաքս՝ '
12814         },
12815         pinning: {
12816           pinLeft: 'Կպցնել ձախ կողմում',
12817           pinRight: 'Կպցնել աջ կողմում',
12818           unpin: 'Արձակել'
12819         },
12820         gridMenu: {
12821           columns: 'Սյուներ:',
12822           importerTitle: 'Ներմուծել ֆայլ',
12823           exporterAllAsCsv: 'Արտահանել ամբողջը CSV',
12824           exporterVisibleAsCsv: 'Արտահանել երևացող տվյալները CSV',
12825           exporterSelectedAsCsv: 'Արտահանել ընտրված տվյալները CSV',
12826           exporterAllAsPdf: 'Արտահանել PDF',
12827           exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
12828           exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
12829           clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
12830         },
12831         importer: {
12832           noHeaders: 'Հնարավոր չեղավ որոշել սյան վերնագրերը։ Արդյո՞ք ֆայլը ունի վերնագրեր։',
12833           noObjects: 'Հնարավոր չեղավ կարդալ տվյալները։ Արդյո՞ք ֆայլում կան տվյալներ։',
12834           invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
12835           invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
12836           jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
12837         }
12838       });
12839       return $delegate;
12840     }]);
12841   }]);
12842 })();
12843
12844 (function () {
12845   angular.module('ui.grid').config(['$provide', function($provide) {
12846     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12847       $delegate.add('it', {
12848         aggregate: {
12849           label: 'elementi'
12850         },
12851         groupPanel: {
12852           description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
12853         },
12854         search: {
12855           placeholder: 'Ricerca...',
12856           showingItems: 'Mostra:',
12857           selectedItems: 'Selezionati:',
12858           totalItems: 'Totali:',
12859           size: 'Tot Pagine:',
12860           first: 'Prima',
12861           next: 'Prossima',
12862           previous: 'Precedente',
12863           last: 'Ultima'
12864         },
12865         menu: {
12866           text: 'Scegli le colonne:'
12867         },
12868         sort: {
12869           ascending: 'Asc.',
12870           descending: 'Desc.',
12871           remove: 'Annulla ordinamento'
12872         },
12873         column: {
12874           hide: 'Nascondi'
12875         },
12876         aggregation: {
12877           count: 'righe totali: ',
12878           sum: 'tot: ',
12879           avg: 'media: ',
12880           min: 'minimo: ',
12881           max: 'massimo: '
12882         },
12883         pinning: {
12884          pinLeft: 'Blocca a sx',
12885           pinRight: 'Blocca a dx',
12886           unpin: 'Blocca in alto'
12887         },
12888         gridMenu: {
12889           columns: 'Colonne:',
12890           importerTitle: 'Importa',
12891           exporterAllAsCsv: 'Esporta tutti i dati in CSV',
12892           exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
12893           exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
12894           exporterAllAsPdf: 'Esporta tutti i dati in PDF',
12895           exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
12896           exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
12897           clearAllFilters: 'Pulire tutti i filtri'
12898         },
12899         importer: {
12900           noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
12901           noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
12902           invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
12903           invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
12904           jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
12905         },
12906         pagination: {
12907           aria: {
12908             pageToFirst: 'Prima',
12909             pageBack: 'Indietro',
12910             pageSelected: 'Pagina selezionata',
12911             pageForward: 'Avanti',
12912             pageToLast: 'Ultima'
12913           },
12914           sizes: 'elementi per pagina',
12915           totalItems: 'elementi',
12916           through: 'a',
12917           of: 'di'
12918         },
12919         grouping: {
12920           group: 'Raggruppa',
12921           ungroup: 'Separa',
12922           aggregate_count: 'Agg: N. Elem.',
12923           aggregate_sum: 'Agg: Somma',
12924           aggregate_max: 'Agg: Massimo',
12925           aggregate_min: 'Agg: Minimo',
12926           aggregate_avg: 'Agg: Media',
12927           aggregate_remove: 'Agg: Rimuovi'
12928         },
12929         validate: {
12930           error: 'Errore:',
12931           minLength: 'Lunghezza minima pari a THRESHOLD caratteri.',
12932           maxLength: 'Lunghezza massima pari a THRESHOLD caratteri.',
12933           required: 'Necessario inserire un valore.'
12934         }
12935       });
12936       return $delegate;
12937     }]);
12938   }]);
12939 })();
12940
12941 (function() {
12942   angular.module('ui.grid').config(['$provide', function($provide) {
12943     $provide.decorator('i18nService', ['$delegate', function($delegate) {
12944       $delegate.add('ja', {
12945         aggregate: {
12946           label: '項目'
12947         },
12948         groupPanel: {
12949           description: 'ここに列ヘッダをドラッグアンドドロップして、その列でグループ化します。'
12950         },
12951         search: {
12952           placeholder: '検索...',
12953           showingItems: '表示中の項目:',
12954           selectedItems: '選択した項目:',
12955           totalItems: '項目の総数:',
12956           size: 'ページサイズ:',
12957           first: '最初のページ',
12958           next: '次のページ',
12959           previous: '前のページ',
12960           last: '前のページ'
12961         },
12962         menu: {
12963           text: '列の選択:'
12964         },
12965         sort: {
12966           ascending: '昇順に並べ替え',
12967           descending: '降順に並べ替え',
12968           remove: '並べ替えの解除'
12969         },
12970         column: {
12971           hide: '列の非表示'
12972         },
12973         aggregation: {
12974           count: '合計行数: ',
12975           sum: '合計: ',
12976           avg: '平均: ',
12977           min: '最小: ',
12978           max: '最大: '
12979         },
12980         pinning: {
12981           pinLeft: '左に固定',
12982           pinRight: '右に固定',
12983           unpin: '固定解除'
12984         },
12985         gridMenu: {
12986           columns: '列:',
12987           importerTitle: 'ファイルのインポート',
12988           exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
12989           exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
12990           exporterSelectedAsCsv: '選択したデータをCSV形式でエクスポート',
12991           exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
12992           exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
12993           exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12994           clearAllFilters: 'すべてのフィルタを清掃してください'
12995         },
12996         importer: {
12997           noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
12998           noObjects: 'オブジェクトを取得できません。ファイルにヘッダ以外のデータが含まれていることを確認してください。',
12999           invalidCsv: 'ファイルを処理できません。ファイルが有効なCSV形式であることを確認してください。',
13000           invalidJson: 'ファイルを処理できません。ファイルが有効なJSON形式であることを確認してください。',
13001           jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
13002         },
13003         pagination: {
13004           aria: {
13005             pageToFirst: '最初のページ',
13006             pageBack: '前のページ',
13007             pageSelected: '現在のページ',
13008             pageForward: '次のページ',
13009             pageToLast: '最後のページ'
13010           },
13011           sizes: '項目/ページ',
13012           totalItems: '項目',
13013           through: 'から',
13014           of: '項目/全'
13015         }
13016       });
13017       return $delegate;
13018     }]);
13019   }]);
13020 })();
13021
13022 (function () {
13023   angular.module('ui.grid').config(['$provide', function($provide) {
13024     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13025       $delegate.add('ko', {
13026         aggregate: {
13027           label: '아이템'
13028         },
13029         groupPanel: {
13030           description: '컬럼으로 그룹핑하기 위해서는 컬럼 헤더를 끌어 떨어뜨려 주세요.'
13031         },
13032         search: {
13033           placeholder: '검색...',
13034           showingItems: '항목 보여주기:',
13035           selectedItems: '선택 항목:',
13036           totalItems: '전체 항목:',
13037           size: '페이지 크기:',
13038           first: '첫번째 페이지',
13039           next: '다음 페이지',
13040           previous: '이전 페이지',
13041           last: '마지막 페이지'
13042         },
13043         menu: {
13044           text: '컬럼을 선택하세요:'
13045         },
13046         sort: {
13047           ascending: '오름차순 정렬',
13048           descending: '내림차순 정렬',
13049           remove: '소팅 제거'
13050         },
13051         column: {
13052           hide: '컬럼 제거'
13053         },
13054         aggregation: {
13055           count: '전체 갯수: ',
13056           sum: '전체: ',
13057           avg: '평균: ',
13058           min: '최소: ',
13059           max: '최대: '
13060         },
13061         pinning: {
13062          pinLeft: '왼쪽 핀',
13063           pinRight: '오른쪽 핀',
13064           unpin: '핀 제거'
13065         },
13066         gridMenu: {
13067           columns: '컬럼:',
13068           importerTitle: '파일 가져오기',
13069           exporterAllAsCsv: 'csv로 모든 데이터 내보내기',
13070           exporterVisibleAsCsv: 'csv로 보이는 데이터 내보내기',
13071           exporterSelectedAsCsv: 'csv로 선택된 데이터 내보내기',
13072           exporterAllAsPdf: 'pdf로 모든 데이터 내보내기',
13073           exporterVisibleAsPdf: 'pdf로 보이는 데이터 내보내기',
13074           exporterSelectedAsPdf: 'pdf로 선택 데이터 내보내기',
13075           clearAllFilters: '모든 필터를 청소'
13076         },
13077         importer: {
13078           noHeaders: '컬럼명이 지정되어 있지 않습니다. 파일에 헤더가 명시되어 있는지 확인해 주세요.',
13079           noObjects: '데이터가 지정되어 있지 않습니다. 데이터가 파일에 있는지 확인해 주세요.',
13080           invalidCsv: '파일을 처리할 수 없습니다. 올바른 csv인지 확인해 주세요.',
13081           invalidJson: '파일을 처리할 수 없습니다. 올바른 json인지 확인해 주세요.',
13082           jsonNotArray: 'json 파일은 배열을 포함해야 합니다.'
13083         },
13084         pagination: {
13085           sizes: '페이지당 항목',
13086           totalItems: '전체 항목'
13087         }
13088       });
13089       return $delegate;
13090     }]);
13091   }]);
13092 })();
13093
13094 (function () {
13095   angular.module('ui.grid').config(['$provide', function($provide) {
13096     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13097       $delegate.add('nl', {
13098         aggregate: {
13099           label: 'items'
13100         },
13101         groupPanel: {
13102           description: 'Sleep hier een kolomnaam heen om op te groeperen.'
13103         },
13104         search: {
13105           placeholder: 'Zoeken...',
13106           showingItems: 'Getoonde items:',
13107           selectedItems: 'Geselecteerde items:',
13108           totalItems: 'Totaal aantal items:',
13109           size: 'Items per pagina:',
13110           first: 'Eerste pagina',
13111           next: 'Volgende pagina',
13112           previous: 'Vorige pagina',
13113           last: 'Laatste pagina'
13114         },
13115         menu: {
13116           text: 'Kies kolommen:'
13117         },
13118         sort: {
13119           ascending: 'Sorteer oplopend',
13120           descending: 'Sorteer aflopend',
13121           remove: 'Verwijder sortering'
13122         },
13123         column: {
13124           hide: 'Verberg kolom'
13125         },
13126         aggregation: {
13127           count: 'Aantal rijen: ',
13128           sum: 'Som: ',
13129           avg: 'Gemiddelde: ',
13130           min: 'Min: ',
13131           max: 'Max: '
13132         },
13133         pinning: {
13134           pinLeft: 'Zet links vast',
13135           pinRight: 'Zet rechts vast',
13136           unpin: 'Maak los'
13137         },
13138         gridMenu: {
13139           columns: 'Kolommen:',
13140           importerTitle: 'Importeer bestand',
13141           exporterAllAsCsv: 'Exporteer alle data als csv',
13142           exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
13143           exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
13144           exporterAllAsPdf: 'Exporteer alle data als pdf',
13145           exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
13146           exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
13147           clearAllFilters: 'Reinig alle filters'
13148         },
13149         importer: {
13150           noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
13151           noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
13152           invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
13153           invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
13154           jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
13155         },
13156         pagination: {
13157             sizes: 'items per pagina',
13158             totalItems: 'items',
13159             of: 'van de'
13160         },
13161         grouping: {
13162             group: 'Groepeer',
13163             ungroup: 'Groepering opheffen',
13164             aggregate_count: 'Agg: Aantal',
13165             aggregate_sum: 'Agg: Som',
13166             aggregate_max: 'Agg: Max',
13167             aggregate_min: 'Agg: Min',
13168             aggregate_avg: 'Agg: Gem',
13169             aggregate_remove: 'Agg: Verwijder'
13170         }
13171       });
13172       return $delegate;
13173     }]);
13174   }]);
13175 })();
13176
13177 (function () {
13178   angular.module('ui.grid').config(['$provide', function($provide) {
13179     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13180       $delegate.add('no', {
13181         headerCell: {
13182           aria: {
13183             defaultFilterLabel: 'Filter for column',
13184             removeFilter: 'Remove Filter',
13185             columnMenuButtonLabel: 'Column Menu'
13186           },
13187           priority: 'Priority:',
13188           filterLabel: "Filter for column: "
13189         },
13190         aggregate: {
13191           label: 'items'
13192         },
13193         groupPanel: {
13194           description: 'Drag a column header here and drop it to group by that column.'
13195         },
13196         search: {
13197           placeholder: 'Search...',
13198           showingItems: 'Showing Items:',
13199           selectedItems: 'Selected Items:',
13200           totalItems: 'Total Items:',
13201           size: 'Page Size:',
13202           first: 'First Page',
13203           next: 'Next Page',
13204           previous: 'Previous Page',
13205           last: 'Last Page'
13206         },
13207         menu: {
13208           text: 'Choose Columns:'
13209         },
13210         sort: {
13211           ascending: 'Sort Ascending',
13212           descending: 'Sort Descending',
13213           none: 'Sort None',
13214           remove: 'Remove Sort'
13215         },
13216         column: {
13217           hide: 'Hide Column'
13218         },
13219         aggregation: {
13220           count: 'total rows: ',
13221           sum: 'total: ',
13222           avg: 'avg: ',
13223           min: 'min: ',
13224           max: 'max: '
13225         },
13226         pinning: {
13227           pinLeft: 'Pin Left',
13228           pinRight: 'Pin Right',
13229           unpin: 'Unpin'
13230         },
13231         columnMenu: {
13232           close: 'Close'
13233         },
13234         gridMenu: {
13235           aria: {
13236             buttonLabel: 'Grid Menu'
13237           },
13238           columns: 'Kolonner:',
13239           importerTitle: 'Importer fil',
13240           exporterAllAsCsv: 'Eksporter alle data som csv',
13241           exporterVisibleAsCsv: 'Eksporter synlige data som csv',
13242           exporterSelectedAsCsv: 'Eksporter utvalgte data som csv',
13243           exporterAllAsPdf: 'Eksporter alle data som pdf',
13244           exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
13245           exporterSelectedAsPdf: 'Eksporter utvalgte data som pdf',
13246           clearAllFilters: 'Clear all filters'
13247         },
13248         importer: {
13249           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13250           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13251           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13252           invalidJson: 'File was unable to be processed, is it valid Json?',
13253           jsonNotArray: 'Imported json file must contain an array, aborting.'
13254         },
13255         pagination: {
13256           aria: {
13257             pageToFirst: 'Page to first',
13258             pageBack: 'Page back',
13259             pageSelected: 'Selected page',
13260             pageForward: 'Page forward',
13261             pageToLast: 'Page to last'
13262           },
13263           sizes: 'items per page',
13264           totalItems: 'items',
13265           through: 'through',
13266           of: 'of'
13267         },
13268         grouping: {
13269           group: 'Group',
13270           ungroup: 'Ungroup',
13271           aggregate_count: 'Agg: Count',
13272           aggregate_sum: 'Agg: Sum',
13273           aggregate_max: 'Agg: Max',
13274           aggregate_min: 'Agg: Min',
13275           aggregate_avg: 'Agg: Avg',
13276           aggregate_remove: 'Agg: Remove'
13277         }
13278       });
13279       return $delegate;
13280     }]);
13281   }]);
13282 })();
13283
13284 (function () {
13285   angular.module('ui.grid').config(['$provide', function($provide) {
13286     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13287       $delegate.add('pl', {
13288         headerCell: {
13289           aria: {
13290             defaultFilterLabel: 'Filtr dla kolumny',
13291             removeFilter: 'Usuń filtr',
13292             columnMenuButtonLabel: 'Menu kolumny'
13293           },
13294           priority: 'Prioritet:',
13295           filterLabel: "Filtr dla kolumny: "
13296         },
13297         aggregate: {
13298           label: 'pozycji'
13299         },
13300         groupPanel: {
13301           description: 'Przeciągnij nagłówek kolumny tutaj, aby pogrupować według niej.'
13302         },
13303         search: {
13304           placeholder: 'Szukaj...',
13305           showingItems: 'Widoczne pozycje:',
13306           selectedItems: 'Zaznaczone pozycje:',
13307           totalItems: 'Wszystkich pozycji:',
13308           size: 'Rozmiar strony:',
13309           first: 'Pierwsza strona',
13310           next: 'Następna strona',
13311           previous: 'Poprzednia strona',
13312           last: 'Ostatnia strona'
13313         },
13314         menu: {
13315           text: 'Wybierz kolumny:'
13316         },
13317         sort: {
13318           ascending: 'Sortuj rosnąco',
13319           descending: 'Sortuj malejąco',
13320           none: 'Brak sortowania',
13321           remove: 'Wyłącz sortowanie'
13322         },
13323         column: {
13324           hide: 'Ukryj kolumne'
13325         },
13326         aggregation: {
13327           count: 'Razem pozycji: ',
13328             sum: 'Razem: ',
13329             avg: 'Średnia: ',
13330             min: 'Min: ',
13331             max: 'Max: '
13332         },
13333         pinning: {
13334           pinLeft: 'Przypnij do lewej',
13335           pinRight: 'Przypnij do prawej',
13336           unpin: 'Odepnij'
13337         },
13338         columnMenu: {
13339           close: 'Zamknij'
13340         },
13341         gridMenu: {
13342           aria: {
13343             buttonLabel: 'Menu Grida'
13344           },
13345           columns: 'Kolumny:',
13346           importerTitle: 'Importuj plik',
13347           exporterAllAsCsv: 'Eksportuj wszystkie dane do csv',
13348           exporterVisibleAsCsv: 'Eksportuj widoczne dane do csv',
13349           exporterSelectedAsCsv: 'Eksportuj zaznaczone dane do csv',
13350           exporterAllAsPdf: 'Eksportuj wszystkie dane do pdf',
13351           exporterVisibleAsPdf: 'Eksportuj widoczne dane do pdf',
13352           exporterSelectedAsPdf: 'Eksportuj zaznaczone dane do pdf',
13353           clearAllFilters: 'Wyczyść filtry'
13354         },
13355         importer: {
13356           noHeaders: 'Nie udało się wczytać nazw kolumn. Czy plik posiada nagłówek?',
13357           noObjects: 'Nie udalo się wczytać pozycji. Czy plik zawiera dane??',
13358           invalidCsv: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik CSV??',
13359           invalidJson: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik Json?',
13360           jsonNotArray: 'Importowany plik json musi zawierać tablicę, importowanie przerwane.'
13361         },
13362         pagination: {
13363           aria: {
13364             pageToFirst: 'Pierwsza strona',
13365             pageBack: 'Poprzednia strona',
13366             pageSelected: 'Wybrana strona',
13367             pageForward: 'Następna strona',
13368             pageToLast: 'Ostatnia strona'
13369           },
13370           sizes: 'pozycji na stronę',
13371           totalItems: 'pozycji',
13372           through: 'do',
13373           of: 'z'
13374         },
13375         grouping: {
13376           group: 'Grupuj',
13377           ungroup: 'Rozgrupuj',
13378           aggregate_count: 'Zbiorczo: Razem',
13379           aggregate_sum: 'Zbiorczo: Suma',
13380           aggregate_max: 'Zbiorczo: Max',
13381           aggregate_min: 'Zbiorczo: Min',
13382           aggregate_avg: 'Zbiorczo: Średnia',
13383           aggregate_remove: 'Zbiorczo: Usuń'
13384         },
13385         validate: {
13386           error: 'Błąd:',
13387           minLength: 'Wartość powinna składać się z co najmniej THRESHOLD znaków.',
13388           maxLength: 'Wartość powinna składać się z przynajmniej THRESHOLD znaków.',
13389           required: 'Wartość jest wymagana.'
13390         }
13391       });
13392       return $delegate;
13393     }]);
13394   }]);
13395 })();
13396
13397 (function () {
13398   angular.module('ui.grid').config(['$provide', function($provide) {
13399     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13400       $delegate.add('pt-br', {
13401         headerCell: {
13402           aria: {
13403             defaultFilterLabel: 'Filtro por coluna',
13404             removeFilter: 'Remover filtro',
13405             columnMenuButtonLabel: 'Menu coluna'
13406           },
13407           priority: 'Prioridade:',
13408           filterLabel: "Filtro por coluna: "
13409         },
13410         aggregate: {
13411           label: 'itens'
13412         },
13413         groupPanel: {
13414           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13415         },
13416         search: {
13417           placeholder: 'Procurar...',
13418           showingItems: 'Mostrando os Itens:',
13419           selectedItems: 'Items Selecionados:',
13420           totalItems: 'Total de Itens:',
13421           size: 'Tamanho da Página:',
13422           first: 'Primeira Página',
13423           next: 'Próxima Página',
13424           previous: 'Página Anterior',
13425           last: 'Última Página'
13426         },
13427         menu: {
13428           text: 'Selecione as colunas:'
13429         },
13430         sort: {
13431           ascending: 'Ordenar Ascendente',
13432           descending: 'Ordenar Descendente',
13433           none: 'Nenhuma Ordem',
13434           remove: 'Remover Ordenação'
13435         },
13436         column: {
13437           hide: 'Esconder coluna'
13438         },
13439         aggregation: {
13440           count: 'total de linhas: ',
13441           sum: 'total: ',
13442           avg: 'med: ',
13443           min: 'min: ',
13444           max: 'max: '
13445         },
13446         pinning: {
13447           pinLeft: 'Fixar Esquerda',
13448           pinRight: 'Fixar Direita',
13449           unpin: 'Desprender'
13450         },
13451         columnMenu: {
13452           close: 'Fechar'
13453         },
13454         gridMenu: {
13455           aria: {
13456             buttonLabel: 'Menu Grid'
13457           },
13458           columns: 'Colunas:',
13459           importerTitle: 'Importar arquivo',
13460           exporterAllAsCsv: 'Exportar todos os dados como csv',
13461           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13462           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13463           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13464           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13465           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13466           clearAllFilters: 'Limpar todos os filtros'
13467         },
13468         importer: {
13469           noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
13470           noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
13471           invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
13472           invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
13473           jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
13474         },
13475         pagination: {
13476           aria: {
13477             pageToFirst: 'Primeira página',
13478             pageBack: 'Página anterior',
13479             pageSelected: 'Página Selecionada',
13480             pageForward: 'Proxima',
13481             pageToLast: 'Anterior'
13482           },
13483           sizes: 'itens por página',
13484           totalItems: 'itens',
13485           through: 'através dos',
13486           of: 'de'
13487         },
13488         grouping: {
13489           group: 'Agrupar',
13490           ungroup: 'Desagrupar',
13491           aggregate_count: 'Agr: Contar',
13492           aggregate_sum: 'Agr: Soma',
13493           aggregate_max: 'Agr: Max',
13494           aggregate_min: 'Agr: Min',
13495           aggregate_avg: 'Agr: Med',
13496           aggregate_remove: 'Agr: Remover'
13497         }
13498       });
13499       return $delegate;
13500     }]);
13501 }]);
13502 })();
13503
13504 (function () {
13505   angular.module('ui.grid').config(['$provide', function($provide) {
13506     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13507       $delegate.add('pt', {
13508         headerCell: {
13509           aria: {
13510             defaultFilterLabel: 'Filtro por coluna',
13511             removeFilter: 'Remover filtro',
13512             columnMenuButtonLabel: 'Menu coluna'
13513           },
13514           priority: 'Prioridade:',
13515           filterLabel: "Filtro por coluna: "
13516         },
13517         aggregate: {
13518           label: 'itens'
13519         },
13520         groupPanel: {
13521           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13522         },
13523         search: {
13524           placeholder: 'Procurar...',
13525           showingItems: 'Mostrando os Itens:',
13526           selectedItems: 'Itens Selecionados:',
13527           totalItems: 'Total de Itens:',
13528           size: 'Tamanho da Página:',
13529           first: 'Primeira Página',
13530           next: 'Próxima Página',
13531           previous: 'Página Anterior',
13532           last: 'Última Página'
13533         },
13534         menu: {
13535           text: 'Selecione as colunas:'
13536         },
13537         sort: {
13538           ascending: 'Ordenar Ascendente',
13539           descending: 'Ordenar Descendente',
13540           none: 'Nenhuma Ordem',
13541           remove: 'Remover Ordenação'
13542         },
13543         column: {
13544           hide: 'Esconder coluna'
13545         },
13546         aggregation: {
13547           count: 'total de linhas: ',
13548           sum: 'total: ',
13549           avg: 'med: ',
13550           min: 'min: ',
13551           max: 'max: '
13552         },
13553         pinning: {
13554           pinLeft: 'Fixar Esquerda',
13555           pinRight: 'Fixar Direita',
13556           unpin: 'Desprender'
13557         },
13558         columnMenu: {
13559           close: 'Fechar'
13560         },
13561         gridMenu: {
13562           aria: {
13563             buttonLabel: 'Menu Grid'
13564           },
13565           columns: 'Colunas:',
13566           importerTitle: 'Importar ficheiro',
13567           exporterAllAsCsv: 'Exportar todos os dados como csv',
13568           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13569           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13570           exporterAllAsPdf: 'Exportar todos os dados como pdf',
13571           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13572           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13573           clearAllFilters: 'Limpar todos os filtros'
13574         },
13575         importer: {
13576           noHeaders: 'Nomes de colunas não puderam ser derivados. O ficheiro tem um cabeçalho?',
13577           noObjects: 'Objetos não puderam ser derivados. Havia dados no ficheiro, além dos cabeçalhos?',
13578           invalidCsv: 'Ficheiro não pode ser processado. É um CSV válido?',
13579           invalidJson: 'Ficheiro não pode ser processado. É um Json válido?',
13580           jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
13581         },
13582         pagination: {
13583           aria: {
13584             pageToFirst: 'Primeira página',
13585             pageBack: 'Página anterior',
13586             pageSelected: 'Página Selecionada',
13587             pageForward: 'Próxima',
13588             pageToLast: 'Anterior'
13589           },
13590           sizes: 'itens por página',
13591           totalItems: 'itens',
13592           through: 'através dos',
13593           of: 'de'
13594         },
13595         grouping: {
13596           group: 'Agrupar',
13597           ungroup: 'Desagrupar',
13598           aggregate_count: 'Agr: Contar',
13599           aggregate_sum: 'Agr: Soma',
13600           aggregate_max: 'Agr: Max',
13601           aggregate_min: 'Agr: Min',
13602           aggregate_avg: 'Agr: Med',
13603           aggregate_remove: 'Agr: Remover'
13604         }
13605       });
13606       return $delegate;
13607     }]);
13608 }]);
13609 })();
13610
13611 (function () {
13612   angular.module('ui.grid').config(['$provide', function($provide) {
13613     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13614       $delegate.add('ro', {
13615         headerCell: {
13616           aria: {
13617             defaultFilterLabel: 'Filtru pentru coloana',
13618             removeFilter: 'Sterge filtru',
13619             columnMenuButtonLabel: 'Column Menu'
13620           },
13621           priority: 'Prioritate:',
13622           filterLabel: "Filtru pentru coloana:"
13623         },
13624         aggregate: {
13625           label: 'Elemente'
13626         },
13627         groupPanel: {
13628           description: 'Trage un cap de coloana aici pentru a grupa elementele dupa coloana respectiva'
13629         },
13630         search: {
13631           placeholder: 'Cauta...',
13632           showingItems: 'Arata elementele:',
13633           selectedItems: 'Elementele selectate:',
13634           totalItems: 'Total elemente:',
13635           size: 'Marime pagina:',
13636           first: 'Prima pagina',
13637           next: 'Pagina urmatoare',
13638           previous: 'Pagina anterioara',
13639           last: 'Ultima pagina'
13640         },
13641         menu: {
13642           text: 'Alege coloane:'
13643         },
13644         sort: {
13645           ascending: 'Ordoneaza crescator',
13646           descending: 'Ordoneaza descrescator',
13647           none: 'Fara ordonare',
13648           remove: 'Sterge ordonarea'
13649         },
13650         column: {
13651           hide: 'Ascunde coloana'
13652         },
13653         aggregation: {
13654           count: 'total linii: ',
13655           sum: 'total: ',
13656           avg: 'medie: ',
13657           min: 'min: ',
13658           max: 'max: '
13659         },
13660         pinning: {
13661           pinLeft: 'Pin la stanga',
13662           pinRight: 'Pin la dreapta',
13663           unpin: 'Sterge pinul'
13664         },
13665         columnMenu: {
13666           close: 'Inchide'
13667         },
13668         gridMenu: {
13669           aria: {
13670             buttonLabel: 'Grid Menu'
13671           },
13672           columns: 'Coloane:',
13673           importerTitle: 'Incarca fisier',
13674           exporterAllAsCsv: 'Exporta toate datele ca csv',
13675           exporterVisibleAsCsv: 'Exporta datele vizibile ca csv',
13676           exporterSelectedAsCsv: 'Exporta datele selectate ca csv',
13677           exporterAllAsPdf: 'Exporta toate datele ca pdf',
13678           exporterVisibleAsPdf: 'Exporta datele vizibile ca pdf',
13679           exporterSelectedAsPdf: 'Exporta datele selectate ca csv pdf',
13680           clearAllFilters: 'Sterge toate filtrele'
13681         },
13682         importer: {
13683           noHeaders: 'Numele coloanelor nu a putut fi incarcat, acest fisier are un header?',
13684           noObjects: 'Datele nu au putut fi incarcate, exista date in fisier in afara numelor de coloane?',
13685           invalidCsv: 'Fisierul nu a putut fi procesat, ati incarcat un CSV valid ?',
13686           invalidJson: 'Fisierul nu a putut fi procesat, ati incarcat un Json valid?',
13687           jsonNotArray: 'Json-ul incarcat trebuie sa contina un array, inchidere.'
13688         },
13689         pagination: {
13690           aria: {
13691             pageToFirst: 'Prima pagina',
13692             pageBack: 'O pagina inapoi',
13693             pageSelected: 'Pagina selectata',
13694             pageForward: 'O pagina inainte',
13695             pageToLast: 'Ultima pagina'
13696           },
13697           sizes: 'Elemente per pagina',
13698           totalItems: 'elemente',
13699           through: 'prin',
13700           of: 'of'
13701         },
13702         grouping: {
13703           group: 'Grupeaza',
13704           ungroup: 'Opreste gruparea',
13705           aggregate_count: 'Agg: Count',
13706           aggregate_sum: 'Agg: Sum',
13707           aggregate_max: 'Agg: Max',
13708           aggregate_min: 'Agg: Min',
13709           aggregate_avg: 'Agg: Avg',
13710           aggregate_remove: 'Agg: Remove'
13711         }
13712       });
13713       return $delegate;
13714     }]);
13715   }]);
13716 })();
13717
13718 (function () {
13719   angular.module('ui.grid').config(['$provide', function($provide) {
13720     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13721       $delegate.add('ru', {
13722         headerCell: {
13723           aria: {
13724             defaultFilterLabel: 'Фильтр столбца',
13725             removeFilter: 'Удалить фильтр',
13726             columnMenuButtonLabel: 'Меню столбца'
13727           },
13728           priority: 'Приоритет:',
13729           filterLabel: "Фильтр столбца: "
13730         },
13731         aggregate: {
13732           label: 'элементы'
13733         },
13734         groupPanel: {
13735           description: 'Для группировки по столбцу перетащите сюда его название.'
13736         },
13737         search: {
13738           placeholder: 'Поиск...',
13739           showingItems: 'Показать элементы:',
13740           selectedItems: 'Выбранные элементы:',
13741           totalItems: 'Всего элементов:',
13742           size: 'Размер страницы:',
13743           first: 'Первая страница',
13744           next: 'Следующая страница',
13745           previous: 'Предыдущая страница',
13746           last: 'Последняя страница'
13747         },
13748         menu: {
13749           text: 'Выбрать столбцы:'
13750         },
13751         sort: {
13752           ascending: 'По возрастанию',
13753           descending: 'По убыванию',
13754           none: 'Без сортировки',
13755           remove: 'Убрать сортировку'
13756         },
13757         column: {
13758           hide: 'Спрятать столбец'
13759         },
13760         aggregation: {
13761           count: 'всего строк: ',
13762           sum: 'итого: ',
13763           avg: 'среднее: ',
13764           min: 'мин: ',
13765           max: 'макс: '
13766         },
13767                                 pinning: {
13768                                         pinLeft: 'Закрепить слева',
13769                                         pinRight: 'Закрепить справа',
13770                                         unpin: 'Открепить'
13771                                 },
13772         columnMenu: {
13773           close: 'Закрыть'
13774         },
13775         gridMenu: {
13776           aria: {
13777             buttonLabel: 'Меню'
13778           },
13779           columns: 'Столбцы:',
13780           importerTitle: 'Импортировать файл',
13781           exporterAllAsCsv: 'Экспортировать всё в CSV',
13782           exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
13783           exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
13784           exporterAllAsPdf: 'Экспортировать всё в PDF',
13785           exporterVisibleAsPdf: 'Экспортировать видимые данные в PDF',
13786           exporterSelectedAsPdf: 'Экспортировать выбранные данные в PDF',
13787           clearAllFilters: 'Очистите все фильтры'
13788         },
13789         importer: {
13790           noHeaders: 'Не удалось получить названия столбцов, есть ли в файле заголовок?',
13791           noObjects: 'Не удалось получить данные, есть ли в файле строки кроме заголовка?',
13792           invalidCsv: 'Не удалось обработать файл, это правильный CSV-файл?',
13793           invalidJson: 'Не удалось обработать файл, это правильный JSON?',
13794           jsonNotArray: 'Импортируемый JSON-файл должен содержать массив, операция отменена.'
13795         },
13796         pagination: {
13797           aria: {
13798             pageToFirst: 'Первая страница',
13799             pageBack: 'Предыдущая страница',
13800             pageSelected: 'Выбранная страница',
13801             pageForward: 'Следующая страница',
13802             pageToLast: 'Последняя страница'
13803           },
13804           sizes: 'строк на страницу',
13805           totalItems: 'строк',
13806           through: 'по',
13807           of: 'из'
13808         },
13809         grouping: {
13810           group: 'Группировать',
13811           ungroup: 'Разгруппировать',
13812           aggregate_count: 'Группировать: Count',
13813           aggregate_sum: 'Для группы: Сумма',
13814           aggregate_max: 'Для группы: Максимум',
13815           aggregate_min: 'Для группы: Минимум',
13816           aggregate_avg: 'Для группы: Среднее',
13817           aggregate_remove: 'Для группы: Пусто'
13818         }
13819       });
13820       return $delegate;
13821     }]);
13822   }]);
13823 })();
13824
13825 (function () {
13826   angular.module('ui.grid').config(['$provide', function($provide) {
13827     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13828       $delegate.add('sk', {
13829         aggregate: {
13830           label: 'items'
13831         },
13832         groupPanel: {
13833           description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13834         },
13835         search: {
13836           placeholder: 'Hľadaj...',
13837           showingItems: 'Zobrazujem položky:',
13838           selectedItems: 'Vybraté položky:',
13839           totalItems: 'Počet položiek:',
13840           size: 'Počet:',
13841           first: 'Prvá strana',
13842           next: 'Ďalšia strana',
13843           previous: 'Predchádzajúca strana',
13844           last: 'Posledná strana'
13845         },
13846         menu: {
13847           text: 'Vyberte stĺpce:'
13848         },
13849         sort: {
13850           ascending: 'Zotriediť vzostupne',
13851           descending: 'Zotriediť zostupne',
13852           remove: 'Vymazať triedenie'
13853         },
13854         aggregation: {
13855           count: 'total rows: ',
13856           sum: 'total: ',
13857           avg: 'avg: ',
13858           min: 'min: ',
13859           max: 'max: '
13860         },
13861         gridMenu: {
13862           columns: 'Columns:',
13863           importerTitle: 'Import file',
13864           exporterAllAsCsv: 'Export all data as csv',
13865           exporterVisibleAsCsv: 'Export visible data as csv',
13866           exporterSelectedAsCsv: 'Export selected data as csv',
13867           exporterAllAsPdf: 'Export all data as pdf',
13868           exporterVisibleAsPdf: 'Export visible data as pdf',
13869           exporterSelectedAsPdf: 'Export selected data as pdf',
13870           clearAllFilters: 'Clear all filters'
13871         },
13872         importer: {
13873           noHeaders: 'Column names were unable to be derived, does the file have a header?',
13874           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13875           invalidCsv: 'File was unable to be processed, is it valid CSV?',
13876           invalidJson: 'File was unable to be processed, is it valid Json?',
13877           jsonNotArray: 'Imported json file must contain an array, aborting.'
13878         }
13879       });
13880       return $delegate;
13881     }]);
13882   }]);
13883 })();
13884
13885 (function () {
13886   angular.module('ui.grid').config(['$provide', function($provide) {
13887     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13888       $delegate.add('sv', {
13889         aggregate: {
13890           label: 'Artiklar'
13891         },
13892         groupPanel: {
13893           description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
13894         },
13895         search: {
13896           placeholder: 'Sök...',
13897           showingItems: 'Visar artiklar:',
13898           selectedItems: 'Valda artiklar:',
13899           totalItems: 'Antal artiklar:',
13900           size: 'Sidstorlek:',
13901           first: 'Första sidan',
13902           next: 'Nästa sida',
13903           previous: 'Föregående sida',
13904           last: 'Sista sidan'
13905         },
13906         menu: {
13907           text: 'Välj kolumner:'
13908         },
13909         sort: {
13910           ascending: 'Sortera stigande',
13911           descending: 'Sortera fallande',
13912           remove: 'Inaktivera sortering'
13913         },
13914         column: {
13915           hide: 'Göm kolumn'
13916         },
13917         aggregation: {
13918           count: 'Antal rader: ',
13919           sum: 'Summa: ',
13920           avg: 'Genomsnitt: ',
13921           min: 'Min: ',
13922           max: 'Max: '
13923         },
13924         pinning: {
13925           pinLeft: 'Fäst vänster',
13926           pinRight: 'Fäst höger',
13927           unpin: 'Lösgör'
13928         },
13929         gridMenu: {
13930           columns: 'Kolumner:',
13931           importerTitle: 'Importera fil',
13932           exporterAllAsCsv: 'Exportera all data som CSV',
13933           exporterVisibleAsCsv: 'Exportera synlig data som CSV',
13934           exporterSelectedAsCsv: 'Exportera markerad data som CSV',
13935           exporterAllAsPdf: 'Exportera all data som PDF',
13936           exporterVisibleAsPdf: 'Exportera synlig data som PDF',
13937           exporterSelectedAsPdf: 'Exportera markerad data som PDF',
13938           clearAllFilters: 'Rengör alla filter'
13939         },
13940         importer: {
13941           noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
13942           noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
13943           invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
13944           invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
13945           jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
13946         },
13947         pagination: {
13948           sizes: 'Artiklar per sida',
13949           totalItems: 'Artiklar'
13950         }
13951       });
13952       return $delegate;
13953     }]);
13954   }]);
13955 })();
13956
13957 (function () {
13958   angular.module('ui.grid').config(['$provide', function($provide) {
13959     $provide.decorator('i18nService', ['$delegate', function($delegate) {
13960       $delegate.add('ta', {
13961         aggregate: {
13962           label: 'உருப்படிகள்'
13963         },
13964         groupPanel: {
13965           description: 'ஒரு பத்தியை குழுவாக அமைக்க அப்பத்தியின் தலைப்பை இங்கே  இழுத்து வரவும் '
13966         },
13967         search: {
13968           placeholder: 'தேடல் ...',
13969           showingItems: 'உருப்படிகளை காண்பித்தல்:',
13970           selectedItems: 'தேர்ந்தெடுக்கப்பட்ட  உருப்படிகள்:',
13971           totalItems: 'மொத்த உருப்படிகள்:',
13972           size: 'பக்க அளவு: ',
13973           first: 'முதல் பக்கம்',
13974           next: 'அடுத்த பக்கம்',
13975           previous: 'முந்தைய பக்கம் ',
13976           last: 'இறுதி பக்கம்'
13977         },
13978         menu: {
13979           text: 'பத்திகளை தேர்ந்தெடு:'
13980         },
13981         sort: {
13982           ascending: 'மேலிருந்து கீழாக',
13983           descending: 'கீழிருந்து மேலாக',
13984           remove: 'வரிசையை நீக்கு'
13985         },
13986         column: {
13987           hide: 'பத்தியை மறைத்து வை '
13988         },
13989         aggregation: {
13990           count: 'மொத்த வரிகள்:',
13991           sum: 'மொத்தம்: ',
13992           avg: 'சராசரி: ',
13993           min: 'குறைந்தபட்ச: ',
13994           max: 'அதிகபட்ச: '
13995         },
13996         pinning: {
13997          pinLeft: 'இடதுபுறமாக தைக்க ',
13998           pinRight: 'வலதுபுறமாக தைக்க',
13999           unpin: 'பிரி'
14000         },
14001         gridMenu: {
14002           columns: 'பத்திகள்:',
14003           importerTitle: 'கோப்பு : படித்தல்',
14004           exporterAllAsCsv: 'எல்லா தரவுகளையும் கோப்பாக்கு: csv',
14005           exporterVisibleAsCsv: 'இருக்கும் தரவுகளை கோப்பாக்கு: csv',
14006           exporterSelectedAsCsv: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: csv',
14007           exporterAllAsPdf: 'எல்லா தரவுகளையும் கோப்பாக்கு: pdf',
14008           exporterVisibleAsPdf: 'இருக்கும் தரவுகளை கோப்பாக்கு: pdf',
14009           exporterSelectedAsPdf: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: pdf',
14010           clearAllFilters: 'Clear all filters'
14011         },
14012         importer: {
14013           noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
14014           noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
14015           invalidCsv:   'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
14016           invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
14017           jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
14018         },
14019         pagination: {
14020           sizes         : 'உருப்படிகள் / பக்கம்',
14021           totalItems    : 'உருப்படிகள் '
14022         },
14023         grouping: {
14024           group : 'குழு',
14025           ungroup : 'பிரி',
14026           aggregate_count       : 'மதிப்பீட்டு : எண்ணு',
14027           aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
14028           aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
14029           aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
14030           aggregate_avg : 'மதிப்பீட்டு : சராசரி',
14031           aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
14032         }
14033       });
14034       return $delegate;
14035     }]);
14036   }]);
14037 })();
14038
14039 (function () {
14040   angular.module('ui.grid').config(['$provide', function($provide) {
14041     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14042       $delegate.add('tr', {
14043         headerCell: {
14044           aria: {
14045             defaultFilterLabel: 'Sütun için filtre',
14046             removeFilter: 'Filtreyi Kaldır',
14047             columnMenuButtonLabel: 'Sütun Menüsü'
14048           },
14049           priority: 'Öncelik:',
14050           filterLabel: "Sütun için filtre: "
14051         },
14052         aggregate: {
14053           label: 'kayıtlar'
14054         },
14055         groupPanel: {
14056           description: 'Sütuna göre gruplamak için sütun başlığını buraya sürükleyin ve bırakın.'
14057         },
14058         search: {
14059           placeholder: 'Arama...',
14060           showingItems: 'Gösterilen Kayıt:',
14061           selectedItems: 'Seçili Kayıt:',
14062           totalItems: 'Toplam Kayıt:',
14063           size: 'Sayfa Boyutu:',
14064           first: 'İlk Sayfa',
14065           next: 'Sonraki Sayfa',
14066           previous: 'Önceki Sayfa',
14067           last: 'Son Sayfa'
14068         },
14069         menu: {
14070           text: 'Sütunları Seç:'
14071         },
14072         sort: {
14073           ascending: 'Artan Sırada Sırala',
14074           descending: 'Azalan Sırada Sırala',
14075           none: 'Sıralama Yapma',
14076           remove: 'Sıralamayı Kaldır'
14077         },
14078         column: {
14079           hide: 'Sütunu Gizle'
14080         },
14081         aggregation: {
14082           count: 'toplam satır: ',
14083           sum: 'toplam: ',
14084           avg: 'ort: ',
14085           min: 'min: ',
14086           max: 'maks: '
14087         },
14088         pinning: {
14089           pinLeft: 'Sola Sabitle',
14090           pinRight: 'Sağa Sabitle',
14091           unpin: 'Sabitlemeyi Kaldır'
14092         },
14093         columnMenu: {
14094           close: 'Kapat'
14095         },
14096         gridMenu: {
14097           aria: {
14098             buttonLabel: 'Tablo Menü'
14099           },
14100           columns: 'Sütunlar:',
14101           importerTitle: 'Dosya içeri aktar',
14102           exporterAllAsCsv: 'Bütün veriyi CSV olarak dışarı aktar',
14103           exporterVisibleAsCsv: 'Görünen veriyi CSV olarak dışarı aktar',
14104           exporterSelectedAsCsv: 'Seçili veriyi CSV olarak dışarı aktar',
14105           exporterAllAsPdf: 'Bütün veriyi PDF olarak dışarı aktar',
14106           exporterVisibleAsPdf: 'Görünen veriyi PDF olarak dışarı aktar',
14107           exporterSelectedAsPdf: 'Seçili veriyi PDF olarak dışarı aktar',
14108           clearAllFilters: 'Bütün filtreleri kaldır'
14109         },
14110         importer: {
14111           noHeaders: 'Sütun isimleri üretilemiyor, dosyanın bir başlığı var mı?',
14112           noObjects: 'Nesneler üretilemiyor, dosyada başlıktan başka bir veri var mı?',
14113           invalidCsv: 'Dosya işlenemedi, geçerli bir CSV dosyası mı?',
14114           invalidJson: 'Dosya işlenemedi, geçerli bir Json dosyası mı?',
14115           jsonNotArray: 'Alınan Json dosyasında bir dizi bulunmalıdır, işlem iptal ediliyor.'
14116         },
14117         pagination: {
14118           aria: {
14119             pageToFirst: 'İlk sayfaya',
14120             pageBack: 'Geri git',
14121             pageSelected: 'Seçili sayfa',
14122             pageForward: 'İleri git',
14123             pageToLast: 'Sona git'
14124           },
14125           sizes: 'Sayfadaki nesne sayısı',
14126           totalItems: 'kayıtlar',
14127           through: '', //note(fsw) : turkish dont have this preposition 
14128           of: '' //note(fsw) : turkish dont have this preposition
14129         },
14130         grouping: {
14131           group: 'Grupla',
14132           ungroup: 'Gruplama',
14133           aggregate_count: 'Yekun: Sayı',
14134           aggregate_sum: 'Yekun: Toplam',
14135           aggregate_max: 'Yekun: Maks',
14136           aggregate_min: 'Yekun: Min',
14137           aggregate_avg: 'Yekun: Ort',
14138           aggregate_remove: 'Yekun: Sil'
14139         }
14140       });
14141       return $delegate;
14142     }]);
14143   }]);
14144 })();
14145 (function () {
14146   angular.module('ui.grid').config(['$provide', function($provide) {
14147     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14148       $delegate.add('ua', {
14149         headerCell: {
14150           aria: {
14151             defaultFilterLabel: 'Фільтр стовпчика',
14152             removeFilter: 'Видалити фільтр',
14153             columnMenuButtonLabel: 'Меню ствпчика'
14154           },
14155           priority: 'Пріоритет:',
14156           filterLabel: "Фільтр стовпчика: "
14157         },
14158         aggregate: {
14159           label: 'елементи'
14160         },
14161         groupPanel: {
14162           description: 'Для групування за стовпчиком перетягніть сюди його назву.'
14163         },
14164         search: {
14165           placeholder: 'Пошук...',
14166           showingItems: 'Показати елементи:',
14167           selectedItems: 'Обрані елементи:',
14168           totalItems: 'Усього елементів:',
14169           size: 'Розмір сторінки:',
14170           first: 'Перша сторінка',
14171           next: 'Наступна сторінка',
14172           previous: 'Попередня сторінка',
14173           last: 'Остання сторінка'
14174         },
14175         menu: {
14176           text: 'Обрати ствпчики:'
14177         },
14178         sort: {
14179           ascending: 'За зростанням',
14180           descending: 'За спаданням',
14181           none: 'Без сортування',
14182           remove: 'Прибрати сортування'
14183         },
14184         column: {
14185           hide: 'Приховати стовпчик'
14186         },
14187         aggregation: {
14188           count: 'усього рядків: ',
14189           sum: 'ітого: ',
14190           avg: 'середнє: ',
14191           min: 'мін: ',
14192           max: 'макс: '
14193         },
14194                                 pinning: {
14195                                         pinLeft: 'Закріпити ліворуч',
14196                                         pinRight: 'Закріпити праворуч',
14197                                         unpin: 'Відкріпити'
14198                                 },
14199         columnMenu: {
14200           close: 'Закрити'
14201         },
14202         gridMenu: {
14203           aria: {
14204             buttonLabel: 'Меню'
14205           },
14206           columns: 'Стовпчики:',
14207           importerTitle: 'Імпортувати файл',
14208           exporterAllAsCsv: 'Експортувати все в CSV',
14209           exporterVisibleAsCsv: 'Експортувати видимі дані в CSV',
14210           exporterSelectedAsCsv: 'Експортувати обрані дані в CSV',
14211           exporterAllAsPdf: 'Експортувати все в PDF',
14212           exporterVisibleAsPdf: 'Експортувати видимі дані в PDF',
14213           exporterSelectedAsPdf: 'Експортувати обрані дані в PDF',
14214           clearAllFilters: 'Очистити всі фільтри'
14215         },
14216         importer: {
14217           noHeaders: 'Не вдалося отримати назви стовпчиків, чи є в файлі заголовок?',
14218           noObjects: 'Не вдалося отримати дані, чи є в файлі рядки окрім заголовка?',
14219           invalidCsv: 'Не вдалося обробити файл, чи це коректний CSV-файл?',
14220           invalidJson: 'Не вдалося обробити файл, чи це коректний JSON?',
14221           jsonNotArray: 'JSON-файл що імпортується повинен містити масив, операцію скасовано.'
14222         },
14223         pagination: {
14224           aria: {
14225             pageToFirst: 'Перша сторінка',
14226             pageBack: 'Попередня сторінка',
14227             pageSelected: 'Обрана сторінка',
14228             pageForward: 'Наступна сторінка',
14229             pageToLast: 'Остання сторінка'
14230           },
14231           sizes: 'рядків на сторінку',
14232           totalItems: 'рядків',
14233           through: 'по',
14234           of: 'з'
14235         },
14236         grouping: {
14237           group: 'Групувати',
14238           ungroup: 'Розгрупувати',
14239           aggregate_count: 'Групувати: Кількість',
14240           aggregate_sum: 'Для групи: Сума',
14241           aggregate_max: 'Для групи: Максимум',
14242           aggregate_min: 'Для групи: Мінімум',
14243           aggregate_avg: 'Для групи: Серднє',
14244           aggregate_remove: 'Для групи: Пусто'
14245         }
14246       });
14247       return $delegate;
14248     }]);
14249   }]);
14250 })();
14251
14252 /**
14253  * @ngdoc overview
14254  * @name ui.grid.i18n
14255  * @description
14256  *
14257  *  # ui.grid.i18n
14258  * This module provides i18n functions to ui.grid and any application that wants to use it
14259
14260  *
14261  * <div doc-module-components="ui.grid.i18n"></div>
14262  */
14263
14264 (function () {
14265   var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
14266   var FILTER_ALIASES = ['t', 'uiTranslate'];
14267
14268   var module = angular.module('ui.grid.i18n');
14269
14270
14271   /**
14272    *  @ngdoc object
14273    *  @name ui.grid.i18n.constant:i18nConstants
14274    *
14275    *  @description constants available in i18n module
14276    */
14277   module.constant('i18nConstants', {
14278     MISSING: '[MISSING]',
14279     UPDATE_EVENT: '$uiI18n',
14280
14281     LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
14282     // default to english
14283     DEFAULT_LANG: 'en'
14284   });
14285
14286 //    module.config(['$provide', function($provide) {
14287 //        $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
14288
14289   /**
14290    *  @ngdoc service
14291    *  @name ui.grid.i18n.service:i18nService
14292    *
14293    *  @description Services for i18n
14294    */
14295   module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
14296     function ($log, i18nConstants, $rootScope) {
14297
14298       var langCache = {
14299         _langs: {},
14300         current: null,
14301         get: function (lang) {
14302           return this._langs[lang.toLowerCase()];
14303         },
14304         add: function (lang, strings) {
14305           var lower = lang.toLowerCase();
14306           if (!this._langs[lower]) {
14307             this._langs[lower] = {};
14308           }
14309           angular.extend(this._langs[lower], strings);
14310         },
14311         getAllLangs: function () {
14312           var langs = [];
14313           if (!this._langs) {
14314             return langs;
14315           }
14316
14317           for (var key in this._langs) {
14318             langs.push(key);
14319           }
14320
14321           return langs;
14322         },
14323         setCurrent: function (lang) {
14324           this.current = lang.toLowerCase();
14325         },
14326         getCurrentLang: function () {
14327           return this.current;
14328         }
14329       };
14330
14331       var service = {
14332
14333         /**
14334          * @ngdoc service
14335          * @name add
14336          * @methodOf ui.grid.i18n.service:i18nService
14337          * @description  Adds the languages and strings to the cache. Decorate this service to
14338          * add more translation strings
14339          * @param {string} lang language to add
14340          * @param {object} stringMaps of strings to add grouped by property names
14341          * @example
14342          * <pre>
14343          *      i18nService.add('en', {
14344          *         aggregate: {
14345          *                 label1: 'items',
14346          *                 label2: 'some more items'
14347          *                 }
14348          *         },
14349          *         groupPanel: {
14350          *              description: 'Drag a column header here and drop it to group by that column.'
14351          *           }
14352          *      }
14353          * </pre>
14354          */
14355         add: function (langs, stringMaps) {
14356           if (typeof(langs) === 'object') {
14357             angular.forEach(langs, function (lang) {
14358               if (lang) {
14359                 langCache.add(lang, stringMaps);
14360               }
14361             });
14362           } else {
14363             langCache.add(langs, stringMaps);
14364           }
14365         },
14366
14367         /**
14368          * @ngdoc service
14369          * @name getAllLangs
14370          * @methodOf ui.grid.i18n.service:i18nService
14371          * @description  return all currently loaded languages
14372          * @returns {array} string
14373          */
14374         getAllLangs: function () {
14375           return langCache.getAllLangs();
14376         },
14377
14378         /**
14379          * @ngdoc service
14380          * @name get
14381          * @methodOf ui.grid.i18n.service:i18nService
14382          * @description  return all currently loaded languages
14383          * @param {string} lang to return.  If not specified, returns current language
14384          * @returns {object} the translation string maps for the language
14385          */
14386         get: function (lang) {
14387           var language = lang ? lang : service.getCurrentLang();
14388           return langCache.get(language);
14389         },
14390
14391         /**
14392          * @ngdoc service
14393          * @name getSafeText
14394          * @methodOf ui.grid.i18n.service:i18nService
14395          * @description  returns the text specified in the path or a Missing text if text is not found
14396          * @param {string} path property path to use for retrieving text from string map
14397          * @param {string} lang to return.  If not specified, returns current language
14398          * @returns {object} the translation for the path
14399          * @example
14400          * <pre>
14401          * i18nService.getSafeText('sort.ascending')
14402          * </pre>
14403          */
14404         getSafeText: function (path, lang) {
14405           var language = lang ? lang : service.getCurrentLang();
14406           var trans = langCache.get(language);
14407
14408           if (!trans) {
14409             return i18nConstants.MISSING;
14410           }
14411
14412           var paths = path.split('.');
14413           var current = trans;
14414
14415           for (var i = 0; i < paths.length; ++i) {
14416             if (current[paths[i]] === undefined || current[paths[i]] === null) {
14417               return i18nConstants.MISSING;
14418             } else {
14419               current = current[paths[i]];
14420             }
14421           }
14422
14423           return current;
14424
14425         },
14426
14427         /**
14428          * @ngdoc service
14429          * @name setCurrentLang
14430          * @methodOf ui.grid.i18n.service:i18nService
14431          * @description sets the current language to use in the application
14432          * $broadcasts the Update_Event on the $rootScope
14433          * @param {string} lang to set
14434          * @example
14435          * <pre>
14436          * i18nService.setCurrentLang('fr');
14437          * </pre>
14438          */
14439
14440         setCurrentLang: function (lang) {
14441           if (lang) {
14442             langCache.setCurrent(lang);
14443             $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
14444           }
14445         },
14446
14447         /**
14448          * @ngdoc service
14449          * @name getCurrentLang
14450          * @methodOf ui.grid.i18n.service:i18nService
14451          * @description returns the current language used in the application
14452          */
14453         getCurrentLang: function () {
14454           var lang = langCache.getCurrentLang();
14455           if (!lang) {
14456             lang = i18nConstants.DEFAULT_LANG;
14457             langCache.setCurrent(lang);
14458           }
14459           return lang;
14460         }
14461
14462       };
14463
14464       return service;
14465
14466     }]);
14467
14468   var localeDirective = function (i18nService, i18nConstants) {
14469     return {
14470       compile: function () {
14471         return {
14472           pre: function ($scope, $elm, $attrs) {
14473             var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
14474             // check for watchable property
14475             var lang = $scope.$eval($attrs[alias]);
14476             if (lang) {
14477               $scope.$watch($attrs[alias], function () {
14478                 i18nService.setCurrentLang(lang);
14479               });
14480             } else if ($attrs.$$observers) {
14481               $attrs.$observe(alias, function () {
14482                 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
14483               });
14484             }
14485           }
14486         };
14487       }
14488     };
14489   };
14490
14491   module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
14492
14493   // directive syntax
14494   var uitDirective = function ($parse, i18nService, i18nConstants) {
14495     return {
14496       restrict: 'EA',
14497       compile: function () {
14498         return {
14499           pre: function ($scope, $elm, $attrs) {
14500             var alias1 = DIRECTIVE_ALIASES[0],
14501               alias2 = DIRECTIVE_ALIASES[1];
14502             var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
14503             var missing = i18nConstants.MISSING + token;
14504             var observer;
14505             if ($attrs.$$observers) {
14506               var prop = $attrs[alias1] ? alias1 : alias2;
14507               observer = $attrs.$observe(prop, function (result) {
14508                 if (result) {
14509                   $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
14510                 }
14511               });
14512             }
14513             var getter = $parse(token);
14514             var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
14515               if (observer) {
14516                 observer($attrs[alias1] || $attrs[alias2]);
14517               } else {
14518                 // set text based on i18n current language
14519                 $elm.html(getter(i18nService.get()) || missing);
14520               }
14521             });
14522             $scope.$on('$destroy', listener);
14523
14524             $elm.html(getter(i18nService.get()) || missing);
14525           }
14526         };
14527       }
14528     };
14529   };
14530
14531   angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
14532     module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
14533   } );
14534
14535   // optional filter syntax
14536   var uitFilter = function ($parse, i18nService, i18nConstants) {
14537     return function (data) {
14538       var getter = $parse(data);
14539       // set text based on i18n current language
14540       return getter(i18nService.get()) || i18nConstants.MISSING + data;
14541     };
14542   };
14543
14544   angular.forEach( FILTER_ALIASES, function ( alias ) {
14545     module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
14546   } );
14547
14548
14549 })();
14550 (function() {
14551   angular.module('ui.grid').config(['$provide', function($provide) {
14552     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14553       $delegate.add('zh-cn', {
14554         headerCell: {
14555           aria: {
14556             defaultFilterLabel: '列过滤器',
14557             removeFilter: '移除过滤器',
14558             columnMenuButtonLabel: '列菜单'
14559           },
14560           priority: '优先级:',
14561           filterLabel: "列过滤器: "
14562         },
14563         aggregate: {
14564           label: '行'
14565         },
14566         groupPanel: {
14567           description: '拖曳表头到此处进行分组'
14568         },
14569         search: {
14570           placeholder: '查找',
14571           showingItems: '已显示行数:',
14572           selectedItems: '已选择行数:',
14573           totalItems: '总行数:',
14574           size: '每页显示行数:',
14575           first: '首页',
14576           next: '下一页',
14577           previous: '上一页',
14578           last: '末页'
14579         },
14580         menu: {
14581           text: '选择列:'
14582         },
14583         sort: {
14584           ascending: '升序',
14585           descending: '降序',
14586           none: '无序',
14587           remove: '取消排序'
14588         },
14589         column: {
14590           hide: '隐藏列'
14591         },
14592         aggregation: {
14593           count: '计数:',
14594           sum: '求和:',
14595           avg: '均值:',
14596           min: '最小值:',
14597           max: '最大值:'
14598         },
14599         pinning: {
14600           pinLeft: '左侧固定',
14601           pinRight: '右侧固定',
14602           unpin: '取消固定'
14603         },
14604         columnMenu: {
14605           close: '关闭'
14606         },
14607         gridMenu: {
14608           aria: {
14609             buttonLabel: '表格菜单'
14610           },
14611           columns: '列:',
14612           importerTitle: '导入文件',
14613           exporterAllAsCsv: '导出全部数据到CSV',
14614           exporterVisibleAsCsv: '导出可见数据到CSV',
14615           exporterSelectedAsCsv: '导出已选数据到CSV',
14616           exporterAllAsPdf: '导出全部数据到PDF',
14617           exporterVisibleAsPdf: '导出可见数据到PDF',
14618           exporterSelectedAsPdf: '导出已选数据到PDF',
14619           clearAllFilters: '清除所有过滤器'
14620         },
14621         importer: {
14622           noHeaders: '无法获取列名,确定文件包含表头?',
14623           noObjects: '无法获取数据,确定文件包含数据?',
14624           invalidCsv: '无法处理文件,确定是合法的CSV文件?',
14625           invalidJson: '无法处理文件,确定是合法的JSON文件?',
14626           jsonNotArray: '导入的文件不是JSON数组!'
14627         },
14628         pagination: {
14629           aria: {
14630             pageToFirst: '第一页',
14631             pageBack: '上一页',
14632             pageSelected: '当前页',
14633             pageForward: '下一页',
14634             pageToLast: '最后一页'
14635           },
14636           sizes: '行每页',
14637           totalItems: '行',
14638           through: '至',
14639           of: '共'
14640         },
14641         grouping: {
14642           group: '分组',
14643           ungroup: '取消分组',
14644           aggregate_count: '合计: 计数',
14645           aggregate_sum: '合计: 求和',
14646           aggregate_max: '合计: 最大',
14647           aggregate_min: '合计: 最小',
14648           aggregate_avg: '合计: 平均',
14649           aggregate_remove: '合计: 移除'
14650         }
14651       });
14652       return $delegate;
14653     }]);
14654   }]);
14655 })();
14656
14657 (function() {
14658   angular.module('ui.grid').config(['$provide', function($provide) {
14659     $provide.decorator('i18nService', ['$delegate', function($delegate) {
14660       $delegate.add('zh-tw', {
14661         aggregate: {
14662           label: '行'
14663         },
14664         groupPanel: {
14665           description: '拖曳表頭到此處進行分組'
14666         },
14667         search: {
14668           placeholder: '查找',
14669           showingItems: '已顯示行數:',
14670           selectedItems: '已選擇行數:',
14671           totalItems: '總行數:',
14672           size: '每頁顯示行數:',
14673           first: '首頁',
14674           next: '下壹頁',
14675           previous: '上壹頁',
14676           last: '末頁'
14677         },
14678         menu: {
14679           text: '選擇列:'
14680         },
14681         sort: {
14682           ascending: '升序',
14683           descending: '降序',
14684           remove: '取消排序'
14685         },
14686         column: {
14687           hide: '隱藏列'
14688         },
14689         aggregation: {
14690           count: '計數:',
14691           sum: '求和:',
14692           avg: '均值:',
14693           min: '最小值:',
14694           max: '最大值:'
14695         },
14696         pinning: {
14697           pinLeft: '左側固定',
14698           pinRight: '右側固定',
14699           unpin: '取消固定'
14700         },
14701         gridMenu: {
14702           columns: '列:',
14703           importerTitle: '導入文件',
14704           exporterAllAsCsv: '導出全部數據到CSV',
14705           exporterVisibleAsCsv: '導出可見數據到CSV',
14706           exporterSelectedAsCsv: '導出已選數據到CSV',
14707           exporterAllAsPdf: '導出全部數據到PDF',
14708           exporterVisibleAsPdf: '導出可見數據到PDF',
14709           exporterSelectedAsPdf: '導出已選數據到PDF',
14710           clearAllFilters: '清除所有过滤器'
14711         },
14712         importer: {
14713           noHeaders: '無法獲取列名,確定文件包含表頭?',
14714           noObjects: '無法獲取數據,確定文件包含數據?',
14715           invalidCsv: '無法處理文件,確定是合法的CSV文件?',
14716           invalidJson: '無法處理文件,確定是合法的JSON文件?',
14717           jsonNotArray: '導入的文件不是JSON數組!'
14718         },
14719         pagination: {
14720           sizes: '行每頁',
14721           totalItems: '行'
14722         }
14723       });
14724       return $delegate;
14725     }]);
14726   }]);
14727 })();
14728
14729 (function() {
14730   'use strict';
14731   /**
14732    *  @ngdoc overview
14733    *  @name ui.grid.autoResize
14734    *
14735    *  @description
14736    *
14737    *  #ui.grid.autoResize
14738    *
14739    *  <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>
14740    *
14741    *  This module provides auto-resizing functionality to UI-Grid.
14742    */
14743   var module = angular.module('ui.grid.autoResize', ['ui.grid']);
14744
14745
14746   module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
14747     return {
14748       require: 'uiGrid',
14749       scope: false,
14750       link: function ($scope, $elm, $attrs, uiGridCtrl) {
14751         var prevGridWidth, prevGridHeight;
14752
14753         function getDimensions() {
14754           prevGridHeight = gridUtil.elementHeight($elm);
14755           prevGridWidth = gridUtil.elementWidth($elm);
14756         }
14757
14758         // Initialize the dimensions
14759         getDimensions();
14760
14761         var resizeTimeoutId;
14762         function startTimeout() {
14763           clearTimeout(resizeTimeoutId);
14764
14765           resizeTimeoutId = setTimeout(function () {
14766             var newGridHeight = gridUtil.elementHeight($elm);
14767             var newGridWidth = gridUtil.elementWidth($elm);
14768
14769             if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
14770               uiGridCtrl.grid.gridHeight = newGridHeight;
14771               uiGridCtrl.grid.gridWidth = newGridWidth;
14772               uiGridCtrl.grid.api.core.raise.gridDimensionChanged(prevGridHeight, prevGridWidth, newGridHeight, newGridWidth);
14773
14774               $scope.$apply(function () {
14775                 uiGridCtrl.grid.refresh()
14776                   .then(function () {
14777                     getDimensions();
14778
14779                     startTimeout();
14780                   });
14781               });
14782             }
14783             else {
14784               startTimeout();
14785             }
14786           }, 250);
14787         }
14788
14789         startTimeout();
14790
14791         $scope.$on('$destroy', function() {
14792           clearTimeout(resizeTimeoutId);
14793         });
14794       }
14795     };
14796   }]);
14797 })();
14798
14799 (function () {
14800   'use strict';
14801
14802   /**
14803    *  @ngdoc overview
14804    *  @name ui.grid.cellNav
14805    *
14806    *  @description
14807
14808       #ui.grid.cellNav
14809
14810       <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>
14811
14812       This module provides cell navigation functionality to UI-Grid.
14813    */
14814   var module = angular.module('ui.grid.cellNav', ['ui.grid']);
14815
14816   /**
14817    *  @ngdoc object
14818    *  @name ui.grid.cellNav.constant:uiGridCellNavConstants
14819    *
14820    *  @description constants available in cellNav
14821    */
14822   module.constant('uiGridCellNavConstants', {
14823     FEATURE_NAME: 'gridCellNav',
14824     CELL_NAV_EVENT: 'cellNav',
14825     direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
14826     EVENT_TYPE: {
14827       KEYDOWN: 0,
14828       CLICK: 1,
14829       CLEAR: 2
14830     }
14831   });
14832
14833
14834   module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
14835     function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
14836       /**
14837        *  @ngdoc object
14838        *  @name ui.grid.cellNav.object:CellNav
14839        *  @description returns a CellNav prototype function
14840        *  @param {object} rowContainer container for rows
14841        *  @param {object} colContainer parent column container
14842        *  @param {object} leftColContainer column container to the left of parent
14843        *  @param {object} rightColContainer column container to the right of parent
14844        */
14845       var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
14846         this.rows = rowContainer.visibleRowCache;
14847         this.columns = colContainer.visibleColumnCache;
14848         this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
14849         this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
14850         this.bodyContainer = rowContainer;
14851       };
14852
14853       /** returns focusable columns of all containers */
14854       UiGridCellNav.prototype.getFocusableCols = function () {
14855         var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
14856
14857         return allColumns.filter(function (col) {
14858           return col.colDef.allowCellFocus;
14859         });
14860       };
14861
14862       /**
14863        *  @ngdoc object
14864        *  @name ui.grid.cellNav.api:GridRow
14865        *
14866        *  @description GridRow settings for cellNav feature, these are available to be
14867        *  set only internally (for example, by other features)
14868        */
14869
14870       /**
14871        *  @ngdoc object
14872        *  @name allowCellFocus
14873        *  @propertyOf  ui.grid.cellNav.api:GridRow
14874        *  @description Enable focus on a cell within this row.  If set to false then no cells
14875        *  in this row can be focused - group header rows as an example would set this to false.
14876        *  <br/>Defaults to true
14877        */
14878       /** returns focusable rows */
14879       UiGridCellNav.prototype.getFocusableRows = function () {
14880         return this.rows.filter(function(row) {
14881           return row.allowCellFocus !== false;
14882         });
14883       };
14884
14885       UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
14886         switch (direction) {
14887           case uiGridCellNavConstants.direction.LEFT:
14888             return this.getRowColLeft(curRow, curCol);
14889           case uiGridCellNavConstants.direction.RIGHT:
14890             return this.getRowColRight(curRow, curCol);
14891           case uiGridCellNavConstants.direction.UP:
14892             return this.getRowColUp(curRow, curCol);
14893           case uiGridCellNavConstants.direction.DOWN:
14894             return this.getRowColDown(curRow, curCol);
14895           case uiGridCellNavConstants.direction.PG_UP:
14896             return this.getRowColPageUp(curRow, curCol);
14897           case uiGridCellNavConstants.direction.PG_DOWN:
14898             return this.getRowColPageDown(curRow, curCol);
14899         }
14900
14901       };
14902
14903       UiGridCellNav.prototype.initializeSelection = function () {
14904         var focusableCols = this.getFocusableCols();
14905         var focusableRows = this.getFocusableRows();
14906         if (focusableCols.length === 0 || focusableRows.length === 0) {
14907           return null;
14908         }
14909
14910         var curRowIndex = 0;
14911         var curColIndex = 0;
14912         return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
14913       };
14914
14915       UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
14916         var focusableCols = this.getFocusableCols();
14917         var focusableRows = this.getFocusableRows();
14918         var curColIndex = focusableCols.indexOf(curCol);
14919         var curRowIndex = focusableRows.indexOf(curRow);
14920
14921         //could not find column in focusable Columns so set it to 1
14922         if (curColIndex === -1) {
14923           curColIndex = 1;
14924         }
14925
14926         var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
14927
14928         //get column to left
14929         if (nextColIndex >= curColIndex) {
14930           // On the first row
14931           // if (curRowIndex === 0 && curColIndex === 0) {
14932           //   return null;
14933           // }
14934           if (curRowIndex === 0) {
14935             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14936           }
14937           else {
14938             //up one row and far right column
14939             return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
14940           }
14941         }
14942         else {
14943           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14944         }
14945       };
14946
14947
14948
14949       UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
14950         var focusableCols = this.getFocusableCols();
14951         var focusableRows = this.getFocusableRows();
14952         var curColIndex = focusableCols.indexOf(curCol);
14953         var curRowIndex = focusableRows.indexOf(curRow);
14954
14955         //could not find column in focusable Columns so set it to 0
14956         if (curColIndex === -1) {
14957           curColIndex = 0;
14958         }
14959         var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
14960
14961         if (nextColIndex <= curColIndex) {
14962           if (curRowIndex === focusableRows.length - 1) {
14963             return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14964           }
14965           else {
14966             //down one row and far left column
14967             return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
14968           }
14969         }
14970         else {
14971           return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14972         }
14973       };
14974
14975       UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
14976         var focusableCols = this.getFocusableCols();
14977         var focusableRows = this.getFocusableRows();
14978         var curColIndex = focusableCols.indexOf(curCol);
14979         var curRowIndex = focusableRows.indexOf(curRow);
14980
14981         //could not find column in focusable Columns so set it to 0
14982         if (curColIndex === -1) {
14983           curColIndex = 0;
14984         }
14985
14986         if (curRowIndex === focusableRows.length - 1) {
14987           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14988         }
14989         else {
14990           //down one row
14991           return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
14992         }
14993       };
14994
14995       UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
14996         var focusableCols = this.getFocusableCols();
14997         var focusableRows = this.getFocusableRows();
14998         var curColIndex = focusableCols.indexOf(curCol);
14999         var curRowIndex = focusableRows.indexOf(curRow);
15000
15001         //could not find column in focusable Columns so set it to 0
15002         if (curColIndex === -1) {
15003           curColIndex = 0;
15004         }
15005
15006         var pageSize = this.bodyContainer.minRowsToRender();
15007         if (curRowIndex >= focusableRows.length - pageSize) {
15008           return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
15009         }
15010         else {
15011           //down one page
15012           return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
15013         }
15014       };
15015
15016       UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
15017         var focusableCols = this.getFocusableCols();
15018         var focusableRows = this.getFocusableRows();
15019         var curColIndex = focusableCols.indexOf(curCol);
15020         var curRowIndex = focusableRows.indexOf(curRow);
15021
15022         //could not find column in focusable Columns so set it to 0
15023         if (curColIndex === -1) {
15024           curColIndex = 0;
15025         }
15026
15027         if (curRowIndex === 0) {
15028           return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
15029         }
15030         else {
15031           //up one row
15032           return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
15033         }
15034       };
15035
15036       UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
15037         var focusableCols = this.getFocusableCols();
15038         var focusableRows = this.getFocusableRows();
15039         var curColIndex = focusableCols.indexOf(curCol);
15040         var curRowIndex = focusableRows.indexOf(curRow);
15041
15042         //could not find column in focusable Columns so set it to 0
15043         if (curColIndex === -1) {
15044           curColIndex = 0;
15045         }
15046
15047         var pageSize = this.bodyContainer.minRowsToRender();
15048         if (curRowIndex - pageSize < 0) {
15049           return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
15050         }
15051         else {
15052           //up one page
15053           return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
15054         }
15055       };
15056       return UiGridCellNav;
15057     }]);
15058
15059   /**
15060    *  @ngdoc service
15061    *  @name ui.grid.cellNav.service:uiGridCellNavService
15062    *
15063    *  @description Services for cell navigation features. If you don't like the key maps we use,
15064    *  or the direction cells navigation, override with a service decorator (see angular docs)
15065    */
15066   module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
15067     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
15068
15069       var service = {
15070
15071         initializeGrid: function (grid) {
15072           grid.registerColumnBuilder(service.cellNavColumnBuilder);
15073
15074
15075           /**
15076            *  @ngdoc object
15077            *  @name ui.grid.cellNav:Grid.cellNav
15078            * @description cellNav properties added to grid class
15079            */
15080           grid.cellNav = {};
15081           grid.cellNav.lastRowCol = null;
15082           grid.cellNav.focusedCells = [];
15083
15084           service.defaultGridOptions(grid.options);
15085
15086           /**
15087            *  @ngdoc object
15088            *  @name ui.grid.cellNav.api:PublicApi
15089            *
15090            *  @description Public Api for cellNav feature
15091            */
15092           var publicApi = {
15093             events: {
15094               cellNav: {
15095                 /**
15096                  * @ngdoc event
15097                  * @name navigate
15098                  * @eventOf  ui.grid.cellNav.api:PublicApi
15099                  * @description raised when the active cell is changed
15100                  * <pre>
15101                  *      gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
15102                  * </pre>
15103                  * @param {object} newRowCol new position
15104                  * @param {object} oldRowCol old position
15105                  */
15106                 navigate: function (newRowCol, oldRowCol) {},
15107                 /**
15108                  * @ngdoc event
15109                  * @name viewPortKeyDown
15110                  * @eventOf  ui.grid.cellNav.api:PublicApi
15111                  * @description  is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
15112                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
15113                  * event whenever you need a keydown event on a cell
15114                  * <br/>
15115                  * @param {object} event keydown event
15116                  * @param {object} rowCol current rowCol position
15117                  */
15118                 viewPortKeyDown: function (event, rowCol) {},
15119
15120                 /**
15121                  * @ngdoc event
15122                  * @name viewPortKeyPress
15123                  * @eventOf  ui.grid.cellNav.api:PublicApi
15124                  * @description  is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
15125                  * due to the difficulties of setting focus on a cell that is not visible in the viewport.  Use this
15126                  * event whenever you need a keypress event on a cell
15127                  * <br/>
15128                  * @param {object} event keypress event
15129                  * @param {object} rowCol current rowCol position
15130                  */
15131                 viewPortKeyPress: function (event, rowCol) {}
15132               }
15133             },
15134             methods: {
15135               cellNav: {
15136                 /**
15137                  * @ngdoc function
15138                  * @name scrollToFocus
15139                  * @methodOf  ui.grid.cellNav.api:PublicApi
15140                  * @description brings the specified row and column into view, and sets focus
15141                  * to that cell
15142                  * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
15143                  * @param {object} colDef to make visible and set focus
15144                  * @returns {promise} a promise that is resolved after any scrolling is finished
15145                  */
15146                 scrollToFocus: function (rowEntity, colDef) {
15147                   return service.scrollToFocus(grid, rowEntity, colDef);
15148                 },
15149
15150                 /**
15151                  * @ngdoc function
15152                  * @name getFocusedCell
15153                  * @methodOf  ui.grid.cellNav.api:PublicApi
15154                  * @description returns the current (or last if Grid does not have focus) focused row and column
15155                  * <br> value is null if no selection has occurred
15156                  */
15157                 getFocusedCell: function () {
15158                   return grid.cellNav.lastRowCol;
15159                 },
15160
15161                 /**
15162                  * @ngdoc function
15163                  * @name getCurrentSelection
15164                  * @methodOf  ui.grid.cellNav.api:PublicApi
15165                  * @description returns an array containing the current selection
15166                  * <br> array is empty if no selection has occurred
15167                  */
15168                 getCurrentSelection: function () {
15169                   return grid.cellNav.focusedCells;
15170                 },
15171
15172                 /**
15173                  * @ngdoc function
15174                  * @name rowColSelectIndex
15175                  * @methodOf  ui.grid.cellNav.api:PublicApi
15176                  * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
15177                  * isn't selected
15178                  * @param {object} rowCol the rowCol to evaluate
15179                  */
15180                 rowColSelectIndex: function (rowCol) {
15181                   //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
15182                   var index = -1;
15183                   for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
15184                     if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
15185                       grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
15186                       index = i;
15187                       break;
15188                     }
15189                   }
15190                   return index;
15191                 }
15192               }
15193             }
15194           };
15195
15196           grid.api.registerEventsFromObject(publicApi.events);
15197
15198           grid.api.registerMethodsFromObject(publicApi.methods);
15199
15200         },
15201
15202         defaultGridOptions: function (gridOptions) {
15203           /**
15204            *  @ngdoc object
15205            *  @name ui.grid.cellNav.api:GridOptions
15206            *
15207            *  @description GridOptions for cellNav feature, these are available to be
15208            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15209            */
15210
15211           /**
15212            *  @ngdoc object
15213            *  @name modifierKeysToMultiSelectCells
15214            *  @propertyOf  ui.grid.cellNav.api:GridOptions
15215            *  @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
15216            *  <br/>Defaults to false
15217            */
15218           gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
15219           
15220           /**
15221            *  @ngdoc array
15222            *  @name keyDownOverrides
15223            *  @propertyOf  ui.grid.cellNav.api:GridOptions
15224            *  @description An array of event objects to override on keydown. If an event is overridden, the viewPortKeyDown event will
15225            *               be raised with the overridden events, allowing custom keydown behavior.
15226            *  <br/>Defaults to []
15227            */
15228           gridOptions.keyDownOverrides = gridOptions.keyDownOverrides || [];
15229
15230         },
15231
15232         /**
15233          * @ngdoc service
15234          * @name decorateRenderContainers
15235          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15236          * @description  decorates grid renderContainers with cellNav functions
15237          */
15238         decorateRenderContainers: function (grid) {
15239
15240           var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
15241           var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
15242
15243           if (leftContainer !== null) {
15244             grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
15245           }
15246           if (rightContainer !== null) {
15247             grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
15248           }
15249
15250           grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
15251         },
15252
15253         /**
15254          * @ngdoc service
15255          * @name getDirection
15256          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15257          * @description  determines which direction to for a given keyDown event
15258          * @returns {uiGridCellNavConstants.direction} direction
15259          */
15260         getDirection: function (evt) {
15261           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
15262             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
15263             return uiGridCellNavConstants.direction.LEFT;
15264           }
15265           if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
15266             evt.keyCode === uiGridConstants.keymap.TAB) {
15267             return uiGridCellNavConstants.direction.RIGHT;
15268           }
15269
15270           if (evt.keyCode === uiGridConstants.keymap.UP ||
15271             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
15272             return uiGridCellNavConstants.direction.UP;
15273           }
15274
15275           if (evt.keyCode === uiGridConstants.keymap.PG_UP){
15276             return uiGridCellNavConstants.direction.PG_UP;
15277           }
15278
15279           if (evt.keyCode === uiGridConstants.keymap.DOWN ||
15280             evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
15281             return uiGridCellNavConstants.direction.DOWN;
15282           }
15283
15284           if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
15285             return uiGridCellNavConstants.direction.PG_DOWN;
15286           }
15287
15288           return null;
15289         },
15290
15291         /**
15292          * @ngdoc service
15293          * @name cellNavColumnBuilder
15294          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15295          * @description columnBuilder function that adds cell navigation properties to grid column
15296          * @returns {promise} promise that will load any needed templates when resolved
15297          */
15298         cellNavColumnBuilder: function (colDef, col, gridOptions) {
15299           var promises = [];
15300
15301           /**
15302            *  @ngdoc object
15303            *  @name ui.grid.cellNav.api:ColumnDef
15304            *
15305            *  @description Column Definitions for cellNav feature, these are available to be
15306            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15307            */
15308
15309           /**
15310            *  @ngdoc object
15311            *  @name allowCellFocus
15312            *  @propertyOf  ui.grid.cellNav.api:ColumnDef
15313            *  @description Enable focus on a cell within this column.
15314            *  <br/>Defaults to true
15315            */
15316           colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
15317
15318           return $q.all(promises);
15319         },
15320
15321         /**
15322          * @ngdoc method
15323          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15324          * @name scrollToFocus
15325          * @description Scroll the grid such that the specified
15326          * row and column is in view, and set focus to the cell in that row and column
15327          * @param {Grid} grid the grid you'd like to act upon, usually available
15328          * from gridApi.grid
15329          * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
15330          * @param {object} colDef to make visible and set focus to
15331          * @returns {promise} a promise that is resolved after any scrolling is finished
15332          */
15333         scrollToFocus: function (grid, rowEntity, colDef) {
15334           var gridRow = null, gridCol = null;
15335
15336           if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
15337             gridRow = grid.getRow(rowEntity);
15338           }
15339
15340           if (typeof(colDef) !== 'undefined' && colDef !== null) {
15341             gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
15342           }
15343           return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
15344             var rowCol = { row: gridRow, col: gridCol };
15345
15346             // Broadcast the navigation
15347             if (gridRow !== null && gridCol !== null) {
15348               grid.cellNav.broadcastCellNav(rowCol);
15349             }
15350           });
15351
15352
15353
15354         },
15355
15356
15357         /**
15358          * @ngdoc method
15359          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15360          * @name getLeftWidth
15361          * @description Get the current drawn width of the columns in the
15362          * grid up to the numbered column, and add an apportionment for the
15363          * column that we're on.  So if we are on column 0, we want to scroll
15364          * 0% (i.e. exclude this column from calc).  If we're on the last column
15365          * we want to scroll to 100% (i.e. include this column in the calc). So
15366          * we include (thisColIndex / totalNumberCols) % of this column width
15367          * @param {Grid} grid the grid you'd like to act upon, usually available
15368          * from gridApi.grid
15369          * @param {gridCol} upToCol the column to total up to and including
15370          */
15371         getLeftWidth: function (grid, upToCol) {
15372           var width = 0;
15373
15374           if (!upToCol) {
15375             return width;
15376           }
15377
15378           var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
15379
15380           // total column widths up-to but not including the passed in column
15381           grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
15382             if ( index < lastIndex ){
15383               width += col.drawnWidth;
15384             }
15385           });
15386
15387           // pro-rata the final column based on % of total columns.
15388           var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
15389           width += upToCol.drawnWidth * percentage;
15390
15391           return width;
15392         }
15393       };
15394
15395       return service;
15396     }]);
15397
15398   /**
15399    *  @ngdoc directive
15400    *  @name ui.grid.cellNav.directive:uiCellNav
15401    *  @element div
15402    *  @restrict EA
15403    *
15404    *  @description Adds cell navigation features to the grid columns
15405    *
15406    *  @example
15407    <example module="app">
15408    <file name="app.js">
15409    var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
15410
15411    app.controller('MainCtrl', ['$scope', function ($scope) {
15412       $scope.data = [
15413         { name: 'Bob', title: 'CEO' },
15414             { name: 'Frank', title: 'Lowly Developer' }
15415       ];
15416
15417       $scope.columnDefs = [
15418         {name: 'name'},
15419         {name: 'title'}
15420       ];
15421     }]);
15422    </file>
15423    <file name="index.html">
15424    <div ng-controller="MainCtrl">
15425    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
15426    </div>
15427    </file>
15428    </example>
15429    */
15430   module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
15431     function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
15432       return {
15433         replace: true,
15434         priority: -150,
15435         require: '^uiGrid',
15436         scope: false,
15437         controller: function () {},
15438         compile: function () {
15439           return {
15440             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15441               var _scope = $scope;
15442
15443               var grid = uiGridCtrl.grid;
15444               uiGridCellNavService.initializeGrid(grid);
15445
15446               uiGridCtrl.cellNav = {};
15447
15448               //Ensure that the object has all of the methods we expect it to
15449               uiGridCtrl.cellNav.makeRowCol = function (obj) {
15450                 if (!(obj instanceof GridRowColumn)) {
15451                   obj = new GridRowColumn(obj.row, obj.col);
15452                 }
15453                 return obj;
15454               };
15455
15456               uiGridCtrl.cellNav.getActiveCell = function () {
15457                 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
15458                 if (elms.length > 0){
15459                   return elms[0];
15460                 }
15461
15462                 return undefined;
15463               };
15464
15465               uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
15466                 modifierDown = !(modifierDown === undefined || !modifierDown);
15467
15468                 newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
15469
15470                 uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
15471                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
15472               };
15473
15474               uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
15475                 grid.cellNav.focusedCells = [];
15476                 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
15477               };
15478
15479               uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
15480                 modifierDown = !(modifierDown === undefined || !modifierDown);
15481
15482                 rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
15483
15484                 var row = rowCol.row,
15485                   col = rowCol.col;
15486
15487                 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
15488
15489                 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
15490                   var newRowCol = new GridRowColumn(row, col);
15491
15492                   if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col){
15493                     grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol, originEvt);
15494                     grid.cellNav.lastRowCol = newRowCol;
15495                   }
15496                   if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
15497                     grid.cellNav.focusedCells.push(rowCol);
15498                   } else {
15499                     grid.cellNav.focusedCells = [rowCol];
15500                   }
15501                 } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
15502                   rowColSelectIndex >= 0) {
15503
15504                   grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15505                 }
15506               };
15507
15508               uiGridCtrl.cellNav.handleKeyDown = function (evt) {
15509                 var direction = uiGridCellNavService.getDirection(evt);
15510                 if (direction === null) {
15511                   return null;
15512                 }
15513
15514                 var containerId = 'body';
15515                 if (evt.uiGridTargetRenderContainerId) {
15516                   containerId = evt.uiGridTargetRenderContainerId;
15517                 }
15518
15519                 // Get the last-focused row+col combo
15520                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15521                 if (lastRowCol) {
15522                   // Figure out which new row+combo we're navigating to
15523                   var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
15524                   var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
15525                   var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
15526                   // Shift+tab on top-left cell should exit cellnav on render container
15527                   if (
15528                     // Navigating left
15529                     direction === uiGridCellNavConstants.direction.LEFT &&
15530                     // New col is last col (i.e. wrap around)
15531                     rowCol.col === focusableCols[focusableCols.length - 1] &&
15532                     // Staying on same row, which means we're at first row
15533                     rowCol.row === lastRowCol.row &&
15534                     evt.keyCode === uiGridConstants.keymap.TAB &&
15535                     evt.shiftKey
15536                   ) {
15537                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15538                     uiGridCtrl.cellNav.clearFocus();
15539                     return true;
15540                   }
15541                   // Tab on bottom-right cell should exit cellnav on render container
15542                   else if (
15543                     direction === uiGridCellNavConstants.direction.RIGHT &&
15544                     // New col is first col (i.e. wrap around)
15545                     rowCol.col === focusableCols[0] &&
15546                     // Staying on same row, which means we're at first row
15547                     rowCol.row === lastRowCol.row &&
15548                     evt.keyCode === uiGridConstants.keymap.TAB &&
15549                     !evt.shiftKey
15550                   ) {
15551                     grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15552                     uiGridCtrl.cellNav.clearFocus();
15553                     return true;
15554                   }
15555
15556                   // Scroll to the new cell, if it's not completely visible within the render container's viewport
15557                   grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
15558                     uiGridCtrl.cellNav.broadcastCellNav(rowCol, null, evt);
15559                   });
15560
15561
15562                   evt.stopPropagation();
15563                   evt.preventDefault();
15564
15565                   return false;
15566                 }
15567               };
15568             },
15569             post: function ($scope, $elm, $attrs, uiGridCtrl) {
15570               var _scope = $scope;
15571               var grid = uiGridCtrl.grid;
15572
15573               function addAriaLiveRegion(){
15574                 // Thanks to google docs for the inspiration behind how to do this
15575                 // XXX: Why is this entire mess nessasary?
15576                 // Because browsers take a lot of coercing to get them to read out live regions
15577                 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
15578                 var ariaNotifierDomElt = '<div ' +
15579                                            'id="' + grid.id +'-aria-speakable" ' +
15580                                            'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
15581                                            'aria-live="assertive" ' +
15582                                            'role="region" ' +
15583                                            'aria-atomic="true" ' +
15584                                            'aria-hidden="false" ' +
15585                                            'aria-relevant="additions" ' +
15586                                            '>' +
15587                                            '&nbsp;' +
15588                                          '</div>';
15589
15590                 var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
15591                 $elm.prepend(ariaNotifier);
15592                 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
15593                   /*
15594                    * If the cell nav event was because of a focus event then we don't want to
15595                    * change the notifier text.
15596                    * Reasoning: Voice Over fires a focus events when moving arround the grid.
15597                    * If the screen reader is handing the grid nav properly then we don't need to
15598                    * use the alert to notify the user of the movement.
15599                    * In all other cases we do want a notification event.
15600                    */
15601                   if (originEvt && originEvt.type === 'focus'){return;}
15602
15603                   function setNotifyText(text){
15604                     if (text === ariaNotifier.text()){return;}
15605                     ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
15606                     /*
15607                      * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
15608                      */
15609                     ariaNotifier[0].innerHTML = "";
15610                     ariaNotifier[0].style.visibility = 'hidden';
15611                     ariaNotifier[0].style.visibility = 'visible';
15612                     if (text !== ''){
15613                       ariaNotifier[0].style.clip = 'auto';
15614                       /*
15615                        * The space after the text is something that google docs does.
15616                        */
15617                       ariaNotifier[0].appendChild(document.createTextNode(text + " "));
15618                       ariaNotifier[0].style.visibility = 'hidden';
15619                       ariaNotifier[0].style.visibility = 'visible';
15620                     }
15621                   }
15622
15623                   var values = [];
15624                   var currentSelection = grid.api.cellNav.getCurrentSelection();
15625                   for (var i = 0; i < currentSelection.length; i++) {
15626                     values.push(grid.getCellDisplayValue(currentSelection[i].row, currentSelection[i].col));
15627                   }
15628                   var cellText = values.toString();
15629                   setNotifyText(cellText);
15630
15631                 });
15632               }
15633               addAriaLiveRegion();
15634             }
15635           };
15636         }
15637       };
15638     }]);
15639
15640   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
15641     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
15642       return {
15643         replace: true,
15644         priority: -99999, //this needs to run very last
15645         require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
15646         scope: false,
15647         compile: function () {
15648           return {
15649             post: function ($scope, $elm, $attrs, controllers) {
15650               var uiGridCtrl = controllers[0],
15651                  renderContainerCtrl = controllers[1],
15652                  uiGridCellnavCtrl = controllers[2];
15653
15654               // Skip attaching cell-nav specific logic if the directive is not attached above us
15655               if (!uiGridCtrl.grid.api.cellNav) { return; }
15656
15657               var containerId = renderContainerCtrl.containerId;
15658
15659               var grid = uiGridCtrl.grid;
15660
15661               //run each time a render container is created
15662               uiGridCellNavService.decorateRenderContainers(grid);
15663
15664               // focusser only created for body
15665               if (containerId !== 'body') {
15666                 return;
15667               }
15668
15669
15670
15671               if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
15672                 $elm.attr('aria-multiselectable', true);
15673               } else {
15674                 $elm.attr('aria-multiselectable', false);
15675               }
15676
15677               //add an element with no dimensions that can be used to set focus and capture keystrokes
15678               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);
15679               $elm.append(focuser);
15680
15681               focuser.on('focus', function (evt) {
15682                 evt.uiGridTargetRenderContainerId = containerId;
15683                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15684                 if (rowCol === null) {
15685                   rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
15686                   if (rowCol.row && rowCol.col) {
15687                     uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15688                   }
15689                 }
15690               });
15691
15692               uiGridCellnavCtrl.setAriaActivedescendant = function(id){
15693                 $elm.attr('aria-activedescendant', id);
15694               };
15695
15696               uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
15697                 if ($elm.attr('aria-activedescendant') === id){
15698                   $elm.attr('aria-activedescendant', '');
15699                 }
15700               };
15701
15702
15703               uiGridCtrl.focus = function () {
15704                 gridUtil.focus.byElement(focuser[0]);
15705                 //allow for first time grid focus
15706               };
15707
15708               var viewPortKeyDownWasRaisedForRowCol = null;
15709               // Bind to keydown events in the render container
15710               focuser.on('keydown', function (evt) {
15711                 evt.uiGridTargetRenderContainerId = containerId;
15712                 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15713                 var raiseViewPortKeyDown = uiGridCtrl.grid.options.keyDownOverrides.some(function (override) {
15714                     return Object.keys(override).every( function (property) {
15715                         return override[property] === evt[property];
15716                     });
15717                 });
15718                 var result = raiseViewPortKeyDown ? null : uiGridCtrl.cellNav.handleKeyDown(evt);
15719                 if (result === null) {
15720                   uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
15721                   viewPortKeyDownWasRaisedForRowCol = rowCol;
15722                 }
15723               });
15724               //Bind to keypress events in the render container
15725               //keypress events are needed by edit function so the key press
15726               //that initiated an edit is not lost
15727               //must fire the event in a timeout so the editor can
15728               //initialize and subscribe to the event on another event loop
15729               focuser.on('keypress', function (evt) {
15730                 if (viewPortKeyDownWasRaisedForRowCol) {
15731                   $timeout(function () {
15732                     uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
15733                   },4);
15734
15735                   viewPortKeyDownWasRaisedForRowCol = null;
15736                 }
15737               });
15738
15739               $scope.$on('$destroy', function(){
15740                 //Remove all event handlers associated with this focuser.
15741                 focuser.off();
15742               });
15743
15744             }
15745           };
15746         }
15747       };
15748     }]);
15749
15750   module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
15751     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
15752       return {
15753         replace: true,
15754         priority: -99999, //this needs to run very last
15755         require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
15756         scope: false,
15757         compile: function () {
15758           return {
15759             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15760             },
15761             post: function ($scope, $elm, $attrs, controllers) {
15762               var uiGridCtrl = controllers[0],
15763                 renderContainerCtrl = controllers[1];
15764
15765               // Skip attaching cell-nav specific logic if the directive is not attached above us
15766               if (!uiGridCtrl.grid.api.cellNav) { return; }
15767
15768               var containerId = renderContainerCtrl.containerId;
15769               //no need to process for other containers
15770               if (containerId !== 'body') {
15771                 return;
15772               }
15773
15774               var grid = uiGridCtrl.grid;
15775
15776               grid.api.core.on.scrollBegin($scope, function (args) {
15777
15778                 // Skip if there's no currently-focused cell
15779                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15780                 if (lastRowCol === null) {
15781                   return;
15782                 }
15783
15784                 //if not in my container, move on
15785                 //todo: worry about horiz scroll
15786                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15787                   return;
15788                 }
15789
15790                 uiGridCtrl.cellNav.clearFocus();
15791
15792               });
15793
15794               grid.api.core.on.scrollEnd($scope, function (args) {
15795                 // Skip if there's no currently-focused cell
15796                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15797                 if (lastRowCol === null) {
15798                   return;
15799                 }
15800
15801                 //if not in my container, move on
15802                 //todo: worry about horiz scroll
15803                 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15804                   return;
15805                 }
15806
15807                 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
15808
15809               });
15810
15811               grid.api.cellNav.on.navigate($scope, function () {
15812                 //focus again because it can be lost
15813                  uiGridCtrl.focus();
15814               });
15815
15816             }
15817           };
15818         }
15819       };
15820     }]);
15821
15822   /**
15823    *  @ngdoc directive
15824    *  @name ui.grid.cellNav.directive:uiGridCell
15825    *  @element div
15826    *  @restrict A
15827    *  @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
15828    */
15829   module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
15830     function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
15831       return {
15832         priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
15833         restrict: 'A',
15834         require: ['^uiGrid', '?^uiGridCellnav'],
15835         scope: false,
15836         link: function ($scope, $elm, $attrs, controllers) {
15837           var uiGridCtrl = controllers[0],
15838               uiGridCellnavCtrl = controllers[1];
15839           // Skip attaching cell-nav specific logic if the directive is not attached above us
15840           if (!uiGridCtrl.grid.api.cellNav) { return; }
15841
15842           if (!$scope.col.colDef.allowCellFocus) {
15843             return;
15844           }
15845
15846           //Convinience local variables
15847           var grid = uiGridCtrl.grid;
15848           $scope.focused = false;
15849
15850           // Make this cell focusable but only with javascript/a mouse click
15851           $elm.attr('tabindex', -1);
15852
15853           // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
15854           $elm.find('div').on('click', function (evt) {
15855             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
15856
15857             evt.stopPropagation();
15858             $scope.$apply();
15859           });
15860
15861
15862           /*
15863            * XXX Hack for screen readers.
15864            * This allows the grid to focus using only the screen reader cursor.
15865            * Since the focus event doesn't include key press information we can't use it
15866            * as our primary source of the event.
15867            */
15868           $elm.on('mousedown', preventMouseDown);
15869
15870           //turn on and off for edit events
15871           if (uiGridCtrl.grid.api.edit) {
15872             uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
15873               $elm.off('mousedown', preventMouseDown);
15874             });
15875
15876             uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
15877               $elm.on('mousedown', preventMouseDown);
15878             });
15879
15880             uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
15881               $elm.on('mousedown', preventMouseDown);
15882             });
15883           }
15884
15885           // In case we created a new row, and we are the new created row by ngRepeat
15886           // then this cell content might have been selected previously
15887           refreshCellFocus();
15888
15889           function preventMouseDown(evt) {
15890             //Prevents the foucus event from firing if the click event is already going to fire.
15891             //If both events fire it will cause bouncing behavior.
15892             evt.preventDefault();
15893           }
15894
15895           //You can only focus on elements with a tabindex value
15896           $elm.on('focus', function (evt) {
15897             uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
15898             evt.stopPropagation();
15899             $scope.$apply();
15900           });
15901
15902           // This event is fired for all cells.  If the cell matches, then focus is set
15903           $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, refreshCellFocus);
15904
15905           // Refresh cell focus when a new row id added to the grid
15906           var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
15907             // Clear the focus if it's set to avoid the wrong cell getting focused during
15908             // a short period of time (from now until $timeout function executed)
15909             clearFocus();
15910
15911             $timeout(refreshCellFocus);
15912           }, [uiGridConstants.dataChange.ROW]);
15913
15914           function refreshCellFocus() {
15915             var isFocused = grid.cellNav.focusedCells.some(function (focusedRowCol, index) {
15916               return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
15917             });
15918             if (isFocused) {
15919               setFocused();
15920             } else {
15921               clearFocus();
15922             }
15923           }
15924
15925           function setFocused() {
15926             if (!$scope.focused){
15927               var div = $elm.find('div');
15928               div.addClass('ui-grid-cell-focus');
15929               $elm.attr('aria-selected', true);
15930               uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
15931               $scope.focused = true;
15932             }
15933           }
15934
15935           function clearFocus() {
15936             if ($scope.focused){
15937               var div = $elm.find('div');
15938               div.removeClass('ui-grid-cell-focus');
15939               $elm.attr('aria-selected', false);
15940               uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
15941               $scope.focused = false;
15942             }
15943           }
15944
15945           $scope.$on('$destroy', function () {
15946             dataChangeDereg();
15947
15948             //.off withouth paramaters removes all handlers
15949             $elm.find('div').off();
15950             $elm.off();
15951           });
15952         }
15953       };
15954     }]);
15955
15956 })();
15957
15958 (function () {
15959   'use strict';
15960
15961   /**
15962    * @ngdoc overview
15963    * @name ui.grid.edit
15964    * @description
15965    *
15966    * # ui.grid.edit
15967    *
15968    * <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>
15969    *
15970    * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
15971    * a keyboard.
15972    * <br/>
15973    * <br/>
15974    * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
15975    * user to key data and then tab, arrow, or enter to the cells beside or below.
15976    *
15977    * <div doc-module-components="ui.grid.edit"></div>
15978    */
15979
15980   var module = angular.module('ui.grid.edit', ['ui.grid']);
15981
15982   /**
15983    *  @ngdoc object
15984    *  @name ui.grid.edit.constant:uiGridEditConstants
15985    *
15986    *  @description constants available in edit module
15987    */
15988   module.constant('uiGridEditConstants', {
15989     EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
15990     //must be lowercase because template bulder converts to lower
15991     EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
15992     events: {
15993       BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
15994       END_CELL_EDIT: 'uiGridEventEndCellEdit',
15995       CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
15996     }
15997   });
15998
15999   /**
16000    *  @ngdoc service
16001    *  @name ui.grid.edit.service:uiGridEditService
16002    *
16003    *  @description Services for editing features
16004    */
16005   module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
16006     function ($q, uiGridConstants, gridUtil) {
16007
16008       var service = {
16009
16010         initializeGrid: function (grid) {
16011
16012           service.defaultGridOptions(grid.options);
16013
16014           grid.registerColumnBuilder(service.editColumnBuilder);
16015           grid.edit = {};
16016
16017           /**
16018            *  @ngdoc object
16019            *  @name ui.grid.edit.api:PublicApi
16020            *
16021            *  @description Public Api for edit feature
16022            */
16023           var publicApi = {
16024             events: {
16025               edit: {
16026                 /**
16027                  * @ngdoc event
16028                  * @name afterCellEdit
16029                  * @eventOf  ui.grid.edit.api:PublicApi
16030                  * @description raised when cell editing is complete
16031                  * <pre>
16032                  *      gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
16033                  * </pre>
16034                  * @param {object} rowEntity the options.data element that was edited
16035                  * @param {object} colDef the column that was edited
16036                  * @param {object} newValue new value
16037                  * @param {object} oldValue old value
16038                  */
16039                 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
16040                 },
16041                 /**
16042                  * @ngdoc event
16043                  * @name beginCellEdit
16044                  * @eventOf  ui.grid.edit.api:PublicApi
16045                  * @description raised when cell editing starts on a cell
16046                  * <pre>
16047                  *      gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
16048                  * </pre>
16049                  * @param {object} rowEntity the options.data element that was edited
16050                  * @param {object} colDef the column that was edited
16051                  * @param {object} triggerEvent the event that triggered the edit.  Useful to prevent losing keystrokes on some
16052                  *                 complex editors
16053                  */
16054                 beginCellEdit: function (rowEntity, colDef, triggerEvent) {
16055                 },
16056                 /**
16057                  * @ngdoc event
16058                  * @name cancelCellEdit
16059                  * @eventOf  ui.grid.edit.api:PublicApi
16060                  * @description raised when cell editing is cancelled on a cell
16061                  * <pre>
16062                  *      gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
16063                  * </pre>
16064                  * @param {object} rowEntity the options.data element that was edited
16065                  * @param {object} colDef the column that was edited
16066                  */
16067                 cancelCellEdit: function (rowEntity, colDef) {
16068                 }
16069               }
16070             },
16071             methods: {
16072               edit: { }
16073             }
16074           };
16075
16076           grid.api.registerEventsFromObject(publicApi.events);
16077           //grid.api.registerMethodsFromObject(publicApi.methods);
16078
16079         },
16080
16081         defaultGridOptions: function (gridOptions) {
16082
16083           /**
16084            *  @ngdoc object
16085            *  @name ui.grid.edit.api:GridOptions
16086            *
16087            *  @description Options for configuring the edit feature, these are available to be
16088            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16089            */
16090
16091           /**
16092            *  @ngdoc object
16093            *  @name enableCellEdit
16094            *  @propertyOf  ui.grid.edit.api:GridOptions
16095            *  @description If defined, sets the default value for the editable flag on each individual colDefs
16096            *  if their individual enableCellEdit configuration is not defined. Defaults to undefined.
16097            */
16098
16099           /**
16100            *  @ngdoc object
16101            *  @name cellEditableCondition
16102            *  @propertyOf  ui.grid.edit.api:GridOptions
16103            *  @description If specified, either a value or function to be used by all columns before editing.
16104            *  If falsy, then editing of cell is not allowed.
16105            *  @example
16106            *  <pre>
16107            *  function($scope){
16108            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16109            *    return true;
16110            *  }
16111            *  </pre>
16112            */
16113           gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
16114
16115           /**
16116            *  @ngdoc object
16117            *  @name editableCellTemplate
16118            *  @propertyOf  ui.grid.edit.api:GridOptions
16119            *  @description If specified, cellTemplate to use as the editor for all columns.
16120            *  <br/> defaults to 'ui-grid/cellTextEditor'
16121            */
16122
16123           /**
16124            *  @ngdoc object
16125            *  @name enableCellEditOnFocus
16126            *  @propertyOf  ui.grid.edit.api:GridOptions
16127            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
16128            *  <br/>_requires cellNav feature and the edit feature to be enabled_
16129            */
16130             //enableCellEditOnFocus can only be used if cellnav module is used
16131           gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
16132         },
16133
16134         /**
16135          * @ngdoc service
16136          * @name editColumnBuilder
16137          * @methodOf ui.grid.edit.service:uiGridEditService
16138          * @description columnBuilder function that adds edit properties to grid column
16139          * @returns {promise} promise that will load any needed templates when resolved
16140          */
16141         editColumnBuilder: function (colDef, col, gridOptions) {
16142
16143           var promises = [];
16144
16145           /**
16146            *  @ngdoc object
16147            *  @name ui.grid.edit.api:ColumnDef
16148            *
16149            *  @description Column Definition for edit feature, these are available to be
16150            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
16151            */
16152
16153           /**
16154            *  @ngdoc object
16155            *  @name enableCellEdit
16156            *  @propertyOf  ui.grid.edit.api:ColumnDef
16157            *  @description enable editing on column
16158            */
16159           colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
16160             (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
16161
16162           /**
16163            *  @ngdoc object
16164            *  @name cellEditableCondition
16165            *  @propertyOf  ui.grid.edit.api:ColumnDef
16166            *  @description If specified, either a value or function evaluated before editing cell.  If falsy, then editing of cell is not allowed.
16167            *  @example
16168            *  <pre>
16169            *  function($scope){
16170            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16171            *    return true;
16172            *  }
16173            *  </pre>
16174            */
16175           colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition :  colDef.cellEditableCondition;
16176
16177           /**
16178            *  @ngdoc object
16179            *  @name editableCellTemplate
16180            *  @propertyOf  ui.grid.edit.api:ColumnDef
16181            *  @description cell template to be used when editing this column. Can be Url or text template
16182            *  <br/>Defaults to gridOptions.editableCellTemplate
16183            */
16184           if (colDef.enableCellEdit) {
16185             colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
16186
16187             promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
16188               .then(
16189               function (template) {
16190                 col.editableCellTemplate = template;
16191               },
16192               function (res) {
16193                 // Todo handle response error here?
16194                 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
16195               }));
16196           }
16197
16198           /**
16199            *  @ngdoc object
16200            *  @name enableCellEditOnFocus
16201            *  @propertyOf  ui.grid.edit.api:ColumnDef
16202            *  @requires ui.grid.cellNav
16203            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
16204            *  <br>_requires both the cellNav feature and the edit feature to be enabled_
16205            */
16206             //enableCellEditOnFocus can only be used if cellnav module is used
16207           colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
16208
16209
16210           /**
16211            *  @ngdoc string
16212            *  @name editModelField
16213            *  @propertyOf  ui.grid.edit.api:ColumnDef
16214            *  @description a bindable string value that is used when binding to edit controls instead of colDef.field
16215            *  <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}.  The
16216            *  grid should display state.name in the cell and sort/filter based on the state.name property but the editor
16217            *  requires the full state object.
16218            *  <br/>colDef.field = 'state.name'
16219            *  <br/>colDef.editModelField = 'state'
16220            */
16221           //colDef.editModelField
16222
16223           return $q.all(promises);
16224         },
16225
16226         /**
16227          * @ngdoc service
16228          * @name isStartEditKey
16229          * @methodOf ui.grid.edit.service:uiGridEditService
16230          * @description  Determines if a keypress should start editing.  Decorate this service to override with your
16231          * own key events.  See service decorator in angular docs.
16232          * @param {Event} evt keydown event
16233          * @returns {boolean} true if an edit should start
16234          */
16235         isStartEditKey: function (evt) {
16236           if (evt.metaKey ||
16237               evt.keyCode === uiGridConstants.keymap.ESC ||
16238               evt.keyCode === uiGridConstants.keymap.SHIFT ||
16239               evt.keyCode === uiGridConstants.keymap.CTRL ||
16240               evt.keyCode === uiGridConstants.keymap.ALT ||
16241               evt.keyCode === uiGridConstants.keymap.WIN ||
16242               evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
16243
16244              evt.keyCode === uiGridConstants.keymap.LEFT ||
16245             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
16246
16247             evt.keyCode === uiGridConstants.keymap.RIGHT ||
16248             evt.keyCode === uiGridConstants.keymap.TAB ||
16249
16250             evt.keyCode === uiGridConstants.keymap.UP ||
16251             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
16252
16253             evt.keyCode === uiGridConstants.keymap.DOWN ||
16254             evt.keyCode === uiGridConstants.keymap.ENTER) {
16255             return false;
16256
16257           }
16258           return true;
16259         }
16260
16261
16262       };
16263
16264       return service;
16265
16266     }]);
16267
16268   /**
16269    *  @ngdoc directive
16270    *  @name ui.grid.edit.directive:uiGridEdit
16271    *  @element div
16272    *  @restrict A
16273    *
16274    *  @description Adds editing features to the ui-grid directive.
16275    *
16276    *  @example
16277    <example module="app">
16278    <file name="app.js">
16279    var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
16280
16281    app.controller('MainCtrl', ['$scope', function ($scope) {
16282       $scope.data = [
16283         { name: 'Bob', title: 'CEO' },
16284             { name: 'Frank', title: 'Lowly Developer' }
16285       ];
16286
16287       $scope.columnDefs = [
16288         {name: 'name', enableCellEdit: true},
16289         {name: 'title', enableCellEdit: true}
16290       ];
16291     }]);
16292    </file>
16293    <file name="index.html">
16294    <div ng-controller="MainCtrl">
16295    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
16296    </div>
16297    </file>
16298    </example>
16299    */
16300   module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
16301     return {
16302       replace: true,
16303       priority: 0,
16304       require: '^uiGrid',
16305       scope: false,
16306       compile: function () {
16307         return {
16308           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16309             uiGridEditService.initializeGrid(uiGridCtrl.grid);
16310           },
16311           post: function ($scope, $elm, $attrs, uiGridCtrl) {
16312           }
16313         };
16314       }
16315     };
16316   }]);
16317
16318   /**
16319    *  @ngdoc directive
16320    *  @name ui.grid.edit.directive:uiGridRenderContainer
16321    *  @element div
16322    *  @restrict A
16323    *
16324    *  @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
16325    *
16326    */
16327   module.directive('uiGridViewport', [ 'uiGridEditConstants',
16328     function ( uiGridEditConstants) {
16329       return {
16330         replace: true,
16331         priority: -99998, //run before cellNav
16332         require: ['^uiGrid', '^uiGridRenderContainer'],
16333         scope: false,
16334         compile: function () {
16335           return {
16336             post: function ($scope, $elm, $attrs, controllers) {
16337               var uiGridCtrl = controllers[0];
16338
16339               // Skip attaching if edit and cellNav is not enabled
16340               if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
16341
16342               var containerId =  controllers[1].containerId;
16343               //no need to process for other containers
16344               if (containerId !== 'body') {
16345                 return;
16346               }
16347
16348               //refocus on the grid
16349               $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16350                 uiGridCtrl.focus();
16351               });
16352               $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16353                 uiGridCtrl.focus();
16354               });
16355
16356             }
16357           };
16358         }
16359       };
16360     }]);
16361
16362   /**
16363    *  @ngdoc directive
16364    *  @name ui.grid.edit.directive:uiGridCell
16365    *  @element div
16366    *  @restrict A
16367    *
16368    *  @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
16369    *  Editing Actions.
16370    *
16371    *  Binds edit start events to the uiGridCell element.  When the events fire, the gridCell element is appended
16372    *  with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
16373    *
16374    *  The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
16375    *  and do the initial steps needed to edit the cell (setfocus on input element, etc).
16376    *
16377    *  When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
16378    *  it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
16379    *
16380    *  If editableCellTemplate recognizes that the editing has been cancelled (esc key)
16381    *  it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event.  The original value
16382    *  will be set back on the model by the uiGridCell directive.
16383    *
16384    *  Events that invoke editing:
16385    *    - dblclick
16386    *    - F2 keydown (when using cell selection)
16387    *
16388    *  Events that end editing:
16389    *    - Dependent on the specific editableCellTemplate
16390    *    - Standards should be blur and enter keydown
16391    *
16392    *  Events that cancel editing:
16393    *    - Dependent on the specific editableCellTemplate
16394    *    - Standards should be Esc keydown
16395    *
16396    *  Grid Events that end editing:
16397    *    - uiGridConstants.events.GRID_SCROLL
16398    *
16399    */
16400
16401   /**
16402    *  @ngdoc object
16403    *  @name ui.grid.edit.api:GridRow
16404    *
16405    *  @description GridRow options for edit feature, these are available to be
16406    *  set internally only, by other features
16407    */
16408
16409   /**
16410    *  @ngdoc object
16411    *  @name enableCellEdit
16412    *  @propertyOf  ui.grid.edit.api:GridRow
16413    *  @description enable editing on row, grouping for example might disable editing on group header rows
16414    */
16415
16416   module.directive('uiGridCell',
16417     ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
16418       function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
16419         var touchstartTimeout = 500;
16420         if ($injector.has('uiGridCellNavService')) {
16421           var uiGridCellNavService = $injector.get('uiGridCellNavService');
16422         }
16423
16424         return {
16425           priority: -100, // run after default uiGridCell directive
16426           restrict: 'A',
16427           scope: false,
16428           require: '?^uiGrid',
16429           link: function ($scope, $elm, $attrs, uiGridCtrl) {
16430             var html;
16431             var origCellValue;
16432             var inEdit = false;
16433             var cellModel;
16434             var cancelTouchstartTimeout;
16435
16436             var editCellScope;
16437
16438             if (!$scope.col.colDef.enableCellEdit) {
16439               return;
16440             }
16441
16442             var cellNavNavigateDereg = function() {};
16443             var viewPortKeyDownDereg = function() {};
16444
16445
16446             var setEditable = function() {
16447               if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
16448                 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
16449                   registerBeginEditEvents();
16450                 }
16451               } else {
16452                 if ($scope.beginEditEventsWired) {
16453                   cancelBeginEditEvents();
16454                 }
16455               }
16456             };
16457
16458             setEditable();
16459
16460             var rowWatchDereg = $scope.$watch('row', function (n, o) {
16461               if (n !== o) {
16462                 setEditable();
16463               }
16464             });
16465
16466
16467             $scope.$on('$destroy', function destroyEvents() {
16468               rowWatchDereg();
16469               // unbind all jquery events in order to avoid memory leaks
16470               $elm.off();
16471             });
16472
16473             function registerBeginEditEvents() {
16474               $elm.on('dblclick', beginEdit);
16475
16476               // Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
16477               $elm.on('touchstart', touchStart);
16478
16479               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16480
16481                 viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
16482                   if (rowCol === null) {
16483                     return;
16484                   }
16485
16486                   if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
16487                     //important to do this before scrollToIfNecessary
16488                     beginEditKeyDown(evt);
16489                   }
16490                 });
16491
16492                 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol, evt) {
16493                   if ($scope.col.colDef.enableCellEditOnFocus) {
16494                     // Don't begin edit if the cell hasn't changed
16495                     if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
16496                       newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
16497                       $timeout(function () {
16498                         beginEdit(evt);
16499                       });
16500                     }
16501                   }
16502                 });
16503               }
16504
16505               $scope.beginEditEventsWired = true;
16506
16507             }
16508
16509             function touchStart(event) {
16510               // jQuery masks events
16511               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
16512                 event = event.originalEvent;
16513               }
16514
16515               // Bind touchend handler
16516               $elm.on('touchend', touchEnd);
16517
16518               // Start a timeout
16519               cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
16520
16521               // Timeout's done! Start the edit
16522               cancelTouchstartTimeout.then(function () {
16523                 // Use setTimeout to start the edit because beginEdit expects to be outside of $digest
16524                 setTimeout(beginEdit, 0);
16525
16526                 // Undbind the touchend handler, we don't need it anymore
16527                 $elm.off('touchend', touchEnd);
16528               });
16529             }
16530
16531             // Cancel any touchstart timeout
16532             function touchEnd(event) {
16533               $timeout.cancel(cancelTouchstartTimeout);
16534               $elm.off('touchend', touchEnd);
16535             }
16536
16537             function cancelBeginEditEvents() {
16538               $elm.off('dblclick', beginEdit);
16539               $elm.off('keydown', beginEditKeyDown);
16540               $elm.off('touchstart', touchStart);
16541               cellNavNavigateDereg();
16542               viewPortKeyDownDereg();
16543               $scope.beginEditEventsWired = false;
16544             }
16545
16546             function beginEditKeyDown(evt) {
16547               if (uiGridEditService.isStartEditKey(evt)) {
16548                 beginEdit(evt);
16549               }
16550             }
16551
16552             function shouldEdit(col, row) {
16553               return !row.isSaving &&
16554                 ( angular.isFunction(col.colDef.cellEditableCondition) ?
16555                     col.colDef.cellEditableCondition($scope) :
16556                     col.colDef.cellEditableCondition );
16557             }
16558
16559
16560             function beginEdit(triggerEvent) {
16561               //we need to scroll the cell into focus before invoking the editor
16562               $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
16563                 .then(function () {
16564                   beginEditAfterScroll(triggerEvent);
16565                 });
16566             }
16567
16568             /**
16569              *  @ngdoc property
16570              *  @name editDropdownOptionsArray
16571              *  @propertyOf ui.grid.edit.api:ColumnDef
16572              *  @description an array of values in the format
16573              *  [ {id: xxx, value: xxx} ], which is populated
16574              *  into the edit dropdown
16575              *
16576              */
16577             /**
16578              *  @ngdoc property
16579              *  @name editDropdownIdLabel
16580              *  @propertyOf ui.grid.edit.api:ColumnDef
16581              *  @description the label for the "id" field
16582              *  in the editDropdownOptionsArray.  Defaults
16583              *  to 'id'
16584              *  @example
16585              *  <pre>
16586              *    $scope.gridOptions = {
16587              *      columnDefs: [
16588              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16589              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16590              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16591              *      ],
16592              *  </pre>
16593              *
16594              */
16595             /**
16596              *  @ngdoc property
16597              *  @name editDropdownRowEntityOptionsArrayPath
16598              *  @propertyOf ui.grid.edit.api:ColumnDef
16599              *  @description a path to a property on row.entity containing an
16600              *  array of values in the format
16601              *  [ {id: xxx, value: xxx} ], which will be used to populate
16602              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16603              *  the backing row entity.
16604              *  If this property is set then editDropdownOptionsArray will be ignored.
16605              *  @example
16606              *  <pre>
16607              *    $scope.gridOptions = {
16608              *      columnDefs: [
16609              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16610              *          editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
16611              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16612              *      ],
16613              *  </pre>
16614              *
16615              */
16616             /**
16617              *  @ngdoc service
16618              *  @name editDropdownOptionsFunction
16619              *  @methodOf ui.grid.edit.api:ColumnDef
16620              *  @description a function returning an array of values in the format
16621              *  [ {id: xxx, value: xxx} ], which will be used to populate
16622              *  the edit dropdown.  This can be used when the dropdown values are dependent on
16623              *  the backing row entity with some kind of algorithm.
16624              *  If this property is set then both editDropdownOptionsArray and
16625              *  editDropdownRowEntityOptionsArrayPath will be ignored.
16626              *  @param {object} rowEntity the options.data element that the returned array refers to
16627              *  @param {object} colDef the column that implements this dropdown
16628              *  @returns {object} an array of values in the format
16629              *  [ {id: xxx, value: xxx} ] used to populate the edit dropdown
16630              *  @example
16631              *  <pre>
16632              *    $scope.gridOptions = {
16633              *      columnDefs: [
16634              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16635              *          editDropdownOptionsFunction: function(rowEntity, colDef) {
16636              *            if (rowEntity.foo === 'bar') {
16637              *              return [{id: 'bar1', value: 'BAR 1'},
16638              *                      {id: 'bar2', value: 'BAR 2'},
16639              *                      {id: 'bar3', value: 'BAR 3'}];
16640              *            } else {
16641              *              return [{id: 'foo1', value: 'FOO 1'},
16642              *                      {id: 'foo2', value: 'FOO 2'}];
16643              *            }
16644              *          },
16645              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16646              *      ],
16647              *  </pre>
16648              *
16649              */
16650             /**
16651              *  @ngdoc property
16652              *  @name editDropdownValueLabel
16653              *  @propertyOf ui.grid.edit.api:ColumnDef
16654              *  @description the label for the "value" field
16655              *  in the editDropdownOptionsArray.  Defaults
16656              *  to 'value'
16657              *  @example
16658              *  <pre>
16659              *    $scope.gridOptions = {
16660              *      columnDefs: [
16661              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16662              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16663              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16664              *      ],
16665              *  </pre>
16666              *
16667              */
16668             /**
16669              *  @ngdoc property
16670              *  @name editDropdownFilter
16671              *  @propertyOf ui.grid.edit.api:ColumnDef
16672              *  @description A filter that you would like to apply to the values in the options list
16673              *  of the dropdown.  For example if you were using angular-translate you might set this
16674              *  to `'translate'`
16675              *  @example
16676              *  <pre>
16677              *    $scope.gridOptions = {
16678              *      columnDefs: [
16679              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16680              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16681              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
16682              *      ],
16683              *  </pre>
16684              *
16685              */
16686             function beginEditAfterScroll(triggerEvent) {
16687               // If we are already editing, then just skip this so we don't try editing twice...
16688               if (inEdit) {
16689                 return;
16690               }
16691
16692               if (!shouldEdit($scope.col, $scope.row)) {
16693                 return;
16694               }
16695
16696               var modelField = $scope.row.getQualifiedColField($scope.col);
16697               if ($scope.col.colDef.editModelField) {
16698                 modelField = gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField);
16699               }
16700
16701               cellModel = $parse(modelField);
16702
16703               //get original value from the cell
16704               origCellValue = cellModel($scope);
16705
16706               html = $scope.col.editableCellTemplate;
16707               html = html.replace(uiGridConstants.MODEL_COL_FIELD, modelField);
16708               html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
16709
16710               var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
16711               html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
16712
16713               var inputType = 'text';
16714               switch ($scope.col.colDef.type){
16715                 case 'boolean':
16716                   inputType = 'checkbox';
16717                   break;
16718                 case 'number':
16719                   inputType = 'number';
16720                   break;
16721                 case 'date':
16722                   inputType = 'date';
16723                   break;
16724               }
16725               html = html.replace('INPUT_TYPE', inputType);
16726
16727               // In order to fill dropdown options we use:
16728               // - A function/promise or
16729               // - An array inside of row entity if no function exists or
16730               // - A single array for the whole column if none of the previous exists.
16731               var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
16732               if (editDropdownOptionsFunction) {
16733                 $q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
16734                         .then(function(result) {
16735                   $scope.editDropdownOptionsArray = result;
16736                 });
16737               } else {
16738                 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
16739                 if (editDropdownRowEntityOptionsArrayPath) {
16740                   $scope.editDropdownOptionsArray =  resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
16741                 }
16742                 else {
16743                   $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
16744                 }
16745               }
16746               $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
16747               $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
16748
16749               var cellElement;
16750               var createEditor = function(){
16751                 inEdit = true;
16752                 cancelBeginEditEvents();
16753                 var cellElement = angular.element(html);
16754                 $elm.append(cellElement);
16755                 editCellScope = $scope.$new();
16756                 $compile(cellElement)(editCellScope);
16757                 var gridCellContentsEl = angular.element($elm.children()[0]);
16758                 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
16759               };
16760               if (!$rootScope.$$phase) {
16761                 $scope.$apply(createEditor);
16762               } else {
16763                 createEditor();
16764               }
16765
16766               //stop editing when grid is scrolled
16767               var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
16768                 if ($scope.grid.disableScrolling) {
16769                   return;
16770                 }
16771                 endEdit();
16772                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16773                 deregOnGridScroll();
16774                 deregOnEndCellEdit();
16775                 deregOnCancelCellEdit();
16776               });
16777
16778               //end editing
16779               var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16780                 endEdit();
16781                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16782                 deregOnEndCellEdit();
16783                 deregOnGridScroll();
16784                 deregOnCancelCellEdit();
16785               });
16786
16787               //cancel editing
16788               var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16789                 cancelEdit();
16790                 deregOnCancelCellEdit();
16791                 deregOnGridScroll();
16792                 deregOnEndCellEdit();
16793               });
16794
16795               $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
16796               $timeout(function () {
16797                 //execute in a timeout to give any complex editor templates a cycle to completely render
16798                 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
16799               });
16800             }
16801
16802             function endEdit() {
16803               $scope.grid.disableScrolling = false;
16804               if (!inEdit) {
16805                 return;
16806               }
16807
16808               //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
16809               //back to grid here. The focus call needs to be before the $destroy and removal of the control,
16810               //otherwise ng-model-options of UpdateOn: 'blur' will not work.
16811               if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16812                 uiGridCtrl.focus();
16813               }
16814
16815               var gridCellContentsEl = angular.element($elm.children()[0]);
16816               //remove edit element
16817               editCellScope.$destroy();
16818               var children = $elm.children();
16819               for (var i = 1; i < children.length; i++) {
16820                 angular.element(children[i]).remove();
16821               }
16822               gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
16823               inEdit = false;
16824               registerBeginEditEvents();
16825               $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
16826             }
16827
16828             function cancelEdit() {
16829               $scope.grid.disableScrolling = false;
16830               if (!inEdit) {
16831                 return;
16832               }
16833               cellModel.assign($scope, origCellValue);
16834               $scope.$apply();
16835
16836               $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
16837               endEdit();
16838             }
16839
16840             // resolves a string path against the given object
16841             // shamelessly borrowed from
16842             // http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
16843             function resolveObjectFromPath(object, path) {
16844               path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
16845               path = path.replace(/^\./, '');           // strip a leading dot
16846               var a = path.split('.');
16847               while (a.length) {
16848                   var n = a.shift();
16849                   if (n in object) {
16850                       object = object[n];
16851                   } else {
16852                       return;
16853                   }
16854               }
16855               return object;
16856             }
16857
16858           }
16859         };
16860       }]);
16861
16862   /**
16863    *  @ngdoc directive
16864    *  @name ui.grid.edit.directive:uiGridEditor
16865    *  @element div
16866    *  @restrict A
16867    *
16868    *  @description input editor directive for editable fields.
16869    *  Provides EndEdit and CancelEdit events
16870    *
16871    *  Events that end editing:
16872    *     blur and enter keydown
16873    *
16874    *  Events that cancel editing:
16875    *    - Esc keydown
16876    *
16877    */
16878   module.directive('uiGridEditor',
16879     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
16880       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
16881         return {
16882           scope: true,
16883           require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
16884           compile: function () {
16885             return {
16886               pre: function ($scope, $elm, $attrs) {
16887
16888               },
16889               post: function ($scope, $elm, $attrs, controllers) {
16890                 var uiGridCtrl, renderContainerCtrl, ngModel;
16891                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16892                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16893                 if (controllers[2]) { ngModel = controllers[2]; }
16894
16895                 //set focus at start of edit
16896                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
16897                   $timeout(function () {
16898                     $elm[0].focus();
16899                     //only select text if it is not being replaced below in the cellNav viewPortKeyPress
16900                     if ($elm[0].select && ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav))) {
16901                       $elm[0].select();
16902                     }
16903                     else {
16904                       //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
16905                       //fields should not allow setSelectionRange.  We ignore the error for those browsers
16906                       //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
16907                       try {
16908                         $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
16909                       }
16910                       catch (ex) {
16911                         //ignore
16912                       }
16913                     }
16914                   });
16915
16916                   //set the keystroke that started the edit event
16917                   //we must do this because the BeginEdit is done in a different event loop than the intitial
16918                   //keydown event
16919                   //fire this event for the keypress that is received
16920                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16921                     var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
16922                       if (uiGridEditService.isStartEditKey(evt)) {
16923                         ngModel.$setViewValue(String.fromCharCode( typeof evt.which === 'number' ? evt.which : evt.keyCode), evt);
16924                         ngModel.$render();
16925                       }
16926                       viewPortKeyDownUnregister();
16927                     });
16928                   }
16929
16930                   // macOS will blur the checkbox when clicked in Safari and Firefox,
16931                   // to get around this, we disable the blur handler on mousedown,
16932                   // and then focus the checkbox and re-enable the blur handler after $timeout
16933                   $elm.on('mousedown', function(evt) {
16934                     if ($elm[0].type === 'checkbox') {
16935                       $elm.off('blur', $scope.stopEdit);
16936                       $timeout(function() {
16937                         $elm.focus();
16938                         $elm.on('blur', $scope.stopEdit);
16939                       });
16940                     }
16941                   });
16942
16943                   $elm.on('blur', $scope.stopEdit);
16944                 });
16945
16946
16947                 $scope.deepEdit = false;
16948
16949                 $scope.stopEdit = function (evt) {
16950                   if ($scope.inputForm && !$scope.inputForm.$valid) {
16951                     evt.stopPropagation();
16952                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16953                   }
16954                   else {
16955                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16956                   }
16957                   $scope.deepEdit = false;
16958                 };
16959
16960
16961                 $elm.on('click', function (evt) {
16962                   if ($elm[0].type !== 'checkbox') {
16963                     $scope.deepEdit = true;
16964                     $timeout(function () {
16965                       $scope.grid.disableScrolling = true;
16966                     });
16967                   }
16968                 });
16969
16970                 $elm.on('keydown', function (evt) {
16971                   switch (evt.keyCode) {
16972                     case uiGridConstants.keymap.ESC:
16973                       evt.stopPropagation();
16974                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16975                       break;
16976                   }
16977
16978                   if ($scope.deepEdit &&
16979                     (evt.keyCode === uiGridConstants.keymap.LEFT ||
16980                      evt.keyCode === uiGridConstants.keymap.RIGHT ||
16981                      evt.keyCode === uiGridConstants.keymap.UP ||
16982                      evt.keyCode === uiGridConstants.keymap.DOWN)) {
16983                     evt.stopPropagation();
16984                   }
16985                   // Pass the keydown event off to the cellNav service, if it exists
16986                   else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16987                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16988                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16989                       $scope.stopEdit(evt);
16990                     }
16991                   }
16992                   else {
16993                     //handle enter and tab for editing not using cellNav
16994                     switch (evt.keyCode) {
16995                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16996                       case uiGridConstants.keymap.TAB:
16997                         evt.stopPropagation();
16998                         evt.preventDefault();
16999                         $scope.stopEdit(evt);
17000                         break;
17001                     }
17002                   }
17003
17004                   return true;
17005                 });
17006
17007                 $scope.$on('$destroy', function unbindEvents() {
17008                   // unbind all jquery events in order to avoid memory leaks
17009                   $elm.off();
17010                 });
17011               }
17012             };
17013           }
17014         };
17015       }]);
17016
17017   /**
17018    *  @ngdoc directive
17019    *  @name ui.grid.edit.directive:input
17020    *  @element input
17021    *  @restrict E
17022    *
17023    *  @description directive to provide binding between input[date] value and ng-model for angular 1.2
17024    *  It is similar to input[date] directive of angular 1.3
17025    *
17026    *  Supported date format for input is 'yyyy-MM-dd'
17027    *  The directive will set the $valid property of input element and the enclosing form to false if
17028    *  model is invalid date or value of input is entered wrong.
17029    *
17030    */
17031     module.directive('uiGridEditor', ['$filter', function ($filter) {
17032       function parseDateString(dateString) {
17033         if (typeof(dateString) === 'undefined' || dateString === '') {
17034           return null;
17035         }
17036         var parts = dateString.split('-');
17037         if (parts.length !== 3) {
17038           return null;
17039         }
17040         var year = parseInt(parts[0], 10);
17041         var month = parseInt(parts[1], 10);
17042         var day = parseInt(parts[2], 10);
17043
17044         if (month < 1 || year < 1 || day < 1) {
17045           return null;
17046         }
17047         return new Date(year, (month - 1), day);
17048       }
17049       return {
17050         priority: -100, // run after default uiGridEditor directive
17051         require: '?ngModel',
17052         link: function (scope, element, attrs, ngModel) {
17053
17054           if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
17055
17056             ngModel.$formatters.push(function (modelValue) {
17057               ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
17058               return $filter('date')(modelValue, 'yyyy-MM-dd');
17059             });
17060
17061             ngModel.$parsers.push(function (viewValue) {
17062               if (viewValue && viewValue.length > 0) {
17063                 var dateValue = parseDateString(viewValue);
17064                 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
17065                 return dateValue;
17066               }
17067               else {
17068                 ngModel.$setValidity(null, true);
17069                 return null;
17070               }
17071             });
17072           }
17073         }
17074       };
17075     }]);
17076
17077
17078   /**
17079    *  @ngdoc directive
17080    *  @name ui.grid.edit.directive:uiGridEditDropdown
17081    *  @element div
17082    *  @restrict A
17083    *
17084    *  @description dropdown editor for editable fields.
17085    *  Provides EndEdit and CancelEdit events
17086    *
17087    *  Events that end editing:
17088    *     blur and enter keydown, and any left/right nav
17089    *
17090    *  Events that cancel editing:
17091    *    - Esc keydown
17092    *
17093    */
17094   module.directive('uiGridEditDropdown',
17095     ['uiGridConstants', 'uiGridEditConstants', '$timeout',
17096       function (uiGridConstants, uiGridEditConstants, $timeout) {
17097         return {
17098           require: ['?^uiGrid', '?^uiGridRenderContainer'],
17099           scope: true,
17100           compile: function () {
17101             return {
17102               pre: function ($scope, $elm, $attrs) {
17103
17104               },
17105               post: function ($scope, $elm, $attrs, controllers) {
17106                 var uiGridCtrl = controllers[0];
17107                 var renderContainerCtrl = controllers[1];
17108
17109                 //set focus at start of edit
17110                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
17111                   $timeout(function(){
17112                     $elm[0].focus();
17113                   });
17114
17115                   $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
17116                   $elm.on('blur', function (evt) {
17117                     $scope.stopEdit(evt);
17118                   });
17119                 });
17120
17121
17122                 $scope.stopEdit = function (evt) {
17123                   // no need to validate a dropdown - invalid values shouldn't be
17124                   // available in the list
17125                   $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17126                 };
17127
17128                 $elm.on('keydown', function (evt) {
17129                   switch (evt.keyCode) {
17130                     case uiGridConstants.keymap.ESC:
17131                       evt.stopPropagation();
17132                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
17133                       break;
17134                   }
17135                   if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
17136                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
17137                     if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
17138                       $scope.stopEdit(evt);
17139                     }
17140                   }
17141                   else {
17142                     //handle enter and tab for editing not using cellNav
17143                     switch (evt.keyCode) {
17144                       case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
17145                       case uiGridConstants.keymap.TAB:
17146                         evt.stopPropagation();
17147                         evt.preventDefault();
17148                         $scope.stopEdit(evt);
17149                         break;
17150                     }
17151                   }
17152                   return true;
17153                 });
17154
17155                 $scope.$on('$destroy', function unbindEvents() {
17156                   // unbind jquery events to prevent memory leaks
17157                   $elm.off();
17158                 });
17159               }
17160             };
17161           }
17162         };
17163       }]);
17164
17165   /**
17166    *  @ngdoc directive
17167    *  @name ui.grid.edit.directive:uiGridEditFileChooser
17168    *  @element div
17169    *  @restrict A
17170    *
17171    *  @description input editor directive for editable fields.
17172    *  Provides EndEdit and CancelEdit events
17173    *
17174    *  Events that end editing:
17175    *     blur and enter keydown
17176    *
17177    *  Events that cancel editing:
17178    *    - Esc keydown
17179    *
17180    */
17181   module.directive('uiGridEditFileChooser',
17182     ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
17183       function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
17184         return {
17185           scope: true,
17186           require: ['?^uiGrid', '?^uiGridRenderContainer'],
17187           compile: function () {
17188             return {
17189               pre: function ($scope, $elm, $attrs) {
17190
17191               },
17192               post: function ($scope, $elm, $attrs, controllers) {
17193                 var uiGridCtrl, renderContainerCtrl;
17194                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
17195                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
17196                 var grid = uiGridCtrl.grid;
17197
17198                 var handleFileSelect = function( event ){
17199                   var target = event.srcElement || event.target;
17200
17201                   if (target && target.files && target.files.length > 0) {
17202                     /**
17203                      *  @ngdoc property
17204                      *  @name editFileChooserCallback
17205                      *  @propertyOf  ui.grid.edit.api:ColumnDef
17206                      *  @description A function that should be called when any files have been chosen
17207                      *  by the user.  You should use this to process the files appropriately for your
17208                      *  application.
17209                      *
17210                      *  It passes the gridCol, the gridRow (from which you can get gridRow.entity),
17211                      *  and the files.  The files are in the format as returned from the file chooser,
17212                      *  an array of files, with each having useful information such as:
17213                      *  - `files[0].lastModifiedDate`
17214                      *  - `files[0].name`
17215                      *  - `files[0].size`  (appears to be in bytes)
17216                      *  - `files[0].type`  (MIME type by the looks)
17217                      *
17218                      *  Typically you would do something with these files - most commonly you would
17219                      *  use the filename or read the file itself in.  The example function does both.
17220                      *
17221                      *  @example
17222                      *  <pre>
17223                      *  editFileChooserCallBack: function(gridRow, gridCol, files ){
17224                      *    // ignore all but the first file, it can only choose one anyway
17225                      *    // set the filename into this column
17226                      *    gridRow.entity.filename = file[0].name;
17227                      *
17228                      *    // read the file and set it into a hidden column, which we may do stuff with later
17229                      *    var setFile = function(fileContent){
17230                      *      gridRow.entity.file = fileContent.currentTarget.result;
17231                      *    };
17232                      *    var reader = new FileReader();
17233                      *    reader.onload = setFile;
17234                      *    reader.readAsText( files[0] );
17235                      *  }
17236                      *  </pre>
17237                      */
17238                     if ( typeof($scope.col.colDef.editFileChooserCallback) === 'function' ) {
17239                       $scope.col.colDef.editFileChooserCallback($scope.row, $scope.col, target.files);
17240                     } else {
17241                       gridUtil.logError('You need to set colDef.editFileChooserCallback to use the file chooser');
17242                     }
17243
17244                     target.form.reset();
17245                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17246                   } else {
17247                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
17248                   }
17249                 };
17250
17251                 $elm[0].addEventListener('change', handleFileSelect, false);
17252
17253                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
17254                   $elm[0].focus();
17255                   $elm[0].select();
17256
17257                   $elm.on('blur', function (evt) {
17258                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17259                   });
17260                 });
17261
17262                 $scope.$on('$destroy', function unbindEvents() {
17263                   // unbind jquery events to prevent memory leaks
17264                   $elm.off();
17265                   $elm[0].removeEventListener('change', handleFileSelect, false);
17266                 });
17267               }
17268             };
17269           }
17270         };
17271       }]);
17272 })();
17273
17274 (function () {
17275   'use strict';
17276
17277   /**
17278    * @ngdoc overview
17279    * @name ui.grid.emptyBaseLayer
17280    * @description
17281    *
17282    * # ui.grid.emptyBaseLayer
17283    *
17284    * <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>
17285    *
17286    * This module provides the ability to have the background of the ui-grid be empty rows, this would be displayed in the case were
17287    * the grid height is greater then the amount of rows displayed.
17288    *
17289    * <div doc-module-components="ui.grid.emptyBaseLayer"></div>
17290    */
17291   var module = angular.module('ui.grid.emptyBaseLayer', ['ui.grid']);
17292
17293
17294   /**
17295    *  @ngdoc service
17296    *  @name ui.grid.emptyBaseLayer.service:uiGridBaseLayerService
17297    *
17298    *  @description Services for the empty base layer grid
17299    */
17300   module.service('uiGridBaseLayerService', ['gridUtil', '$compile', function (gridUtil, $compile) {
17301     var service = {
17302       initializeGrid: function (grid, disableEmptyBaseLayer) {
17303
17304         /**
17305          *  @ngdoc object
17306          *  @name ui.grid.emptyBaseLayer.api:GridOptions
17307          *
17308          *  @description GridOptions for emptyBaseLayer feature, these are available to be
17309          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17310          */
17311         grid.baseLayer = {
17312           emptyRows: []
17313         };
17314
17315         /**
17316          *  @ngdoc object
17317          *  @name enableEmptyGridBaseLayer
17318          *  @propertyOf  ui.grid.emptyBaseLayer.api:GridOptions
17319          *  @description Enable empty base layer, which shows empty rows as background on the entire grid
17320          *  <br/>Defaults to true, if the directive is used.
17321          *  <br/>Set to false either by setting this attribute or passing false to the directive.
17322          */
17323         //default option to true unless it was explicitly set to false
17324         if (grid.options.enableEmptyGridBaseLayer !== false) {
17325           grid.options.enableEmptyGridBaseLayer = !disableEmptyBaseLayer;
17326         }
17327       },
17328
17329       setNumberOfEmptyRows: function(viewportHeight, grid) {
17330         var rowHeight = grid.options.rowHeight,
17331           rows = Math.ceil(viewportHeight / rowHeight);
17332         if (rows > 0) {
17333           grid.baseLayer.emptyRows = [];
17334           for (var i = 0; i < rows; i++) {
17335             grid.baseLayer.emptyRows.push({});
17336           }
17337         }
17338       }
17339     };
17340     return service;
17341   }]);
17342
17343   /**
17344    *  @ngdoc object
17345    *  @name ui.grid.emptyBaseLayer.directive:uiGridEmptyBaseLayer
17346    *  @description Shows empty rows in the background of the ui-grid, these span
17347    *  the full height of the ui-grid, so that there won't be blank space below the shown rows.
17348    *  @example
17349    *  <pre>
17350    *  <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer></div>
17351    *  </pre>
17352    *  Or you can enable/disable it dynamically by passing in true or false. It doesn't
17353    *  the value, so it would only be set on initial render.
17354    *  <pre>
17355    *  <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer="false"></div>
17356    *  </pre>
17357    */
17358   module.directive('uiGridEmptyBaseLayer', ['gridUtil', 'uiGridBaseLayerService',
17359       '$parse',
17360     function (gridUtil, uiGridBaseLayerService, $parse) {
17361       return {
17362         require: '^uiGrid',
17363         scope: false,
17364         compile: function ($elm, $attrs) {
17365           return {
17366             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17367               var disableEmptyBaseLayer = $parse($attrs.uiGridEmptyBaseLayer)($scope) === false;
17368               uiGridBaseLayerService.initializeGrid(uiGridCtrl.grid, disableEmptyBaseLayer);
17369             },
17370             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17371               if (!uiGridCtrl.grid.options.enableEmptyGridBaseLayer) {
17372                 return;
17373               }
17374
17375               var renderBodyContainer = uiGridCtrl.grid.renderContainers.body,
17376                 viewportHeight = renderBodyContainer.getViewportHeight();
17377
17378               function heightHasChanged() {
17379                 var newViewPortHeight = renderBodyContainer.getViewportHeight();
17380
17381                 if (newViewPortHeight !== viewportHeight) {
17382                   viewportHeight = newViewPortHeight;
17383                   return true;
17384                 }
17385                 return false;
17386               }
17387
17388               function getEmptyBaseLayerCss(viewportHeight) {
17389                 // Set ui-grid-empty-base-layer height
17390                 return '.grid' + uiGridCtrl.grid.id +
17391                   ' .ui-grid-render-container ' +
17392                   '.ui-grid-empty-base-layer-container.ui-grid-canvas ' +
17393                   '{ height: ' + viewportHeight + 'px; }';
17394               }
17395
17396               uiGridCtrl.grid.registerStyleComputation({
17397                 func: function() {
17398                   if (heightHasChanged()) {
17399                     uiGridBaseLayerService.setNumberOfEmptyRows(viewportHeight, uiGridCtrl.grid);
17400                   }
17401                   return getEmptyBaseLayerCss(viewportHeight);
17402                 }
17403               });
17404             }
17405           };
17406         }
17407       };
17408     }]);
17409
17410   /**
17411    *  @ngdoc directive
17412    *  @name ui.grid.emptyBaseLayer.directive:uiGridViewport
17413    *  @description stacks on the uiGridViewport directive to append the empty grid base layer html elements to the
17414    *  default gridRow template
17415    */
17416   module.directive('uiGridViewport',
17417     ['$compile', 'gridUtil', '$templateCache',
17418       function ($compile, gridUtil, $templateCache) {
17419         return {
17420           priority: -200,
17421           scope: false,
17422           compile: function ($elm, $attrs) {
17423             var emptyBaseLayerContainer = $templateCache.get('ui-grid/emptyBaseLayerContainer');
17424             $elm.prepend(emptyBaseLayerContainer);
17425             return {
17426               pre: function ($scope, $elm, $attrs, controllers) {
17427               },
17428               post: function ($scope, $elm, $attrs, controllers) {
17429               }
17430             };
17431           }
17432         };
17433       }]);
17434
17435 })();
17436
17437 (function () {
17438   'use strict';
17439
17440   /**
17441    * @ngdoc overview
17442    * @name ui.grid.expandable
17443    * @description
17444    *
17445    * # ui.grid.expandable
17446    *
17447    * <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>
17448    *
17449    * This module provides the ability to create subgrids with the ability to expand a row
17450    * to show the subgrid.
17451    *
17452    * <div doc-module-components="ui.grid.expandable"></div>
17453    */
17454   var module = angular.module('ui.grid.expandable', ['ui.grid']);
17455
17456   /**
17457    *  @ngdoc service
17458    *  @name ui.grid.expandable.service:uiGridExpandableService
17459    *
17460    *  @description Services for the expandable grid
17461    */
17462   module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
17463     var service = {
17464       initializeGrid: function (grid) {
17465
17466         grid.expandable = {};
17467         grid.expandable.expandedAll = false;
17468
17469         /**
17470          *  @ngdoc object
17471          *  @name enableExpandable
17472          *  @propertyOf  ui.grid.expandable.api:GridOptions
17473          *  @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
17474          *  within your application, or in specific modes on _this_ grid. Defaults to true.
17475          *  @example
17476          *  <pre>
17477          *    $scope.gridOptions = {
17478          *      enableExpandable: false
17479          *    }
17480          *  </pre>
17481          */
17482         grid.options.enableExpandable = grid.options.enableExpandable !== false;
17483
17484         /**
17485          *  @ngdoc object
17486          *  @name expandableRowHeight
17487          *  @propertyOf  ui.grid.expandable.api:GridOptions
17488          *  @description Height in pixels of the expanded subgrid.  Defaults to
17489          *  150
17490          *  @example
17491          *  <pre>
17492          *    $scope.gridOptions = {
17493          *      expandableRowHeight: 150
17494          *    }
17495          *  </pre>
17496          */
17497         grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
17498
17499         /**
17500          *  @ngdoc object
17501          *  @name
17502          *  @propertyOf  ui.grid.expandable.api:GridOptions
17503          *  @description Width in pixels of the expandable column. Defaults to 40
17504          *  @example
17505          *  <pre>
17506          *    $scope.gridOptions = {
17507          *      expandableRowHeaderWidth: 40
17508          *    }
17509          *  </pre>
17510          */
17511         grid.options.expandableRowHeaderWidth = grid.options.expandableRowHeaderWidth || 40;
17512
17513         /**
17514          *  @ngdoc object
17515          *  @name expandableRowTemplate
17516          *  @propertyOf  ui.grid.expandable.api:GridOptions
17517          *  @description Mandatory. The template for your expanded row
17518          *  @example
17519          *  <pre>
17520          *    $scope.gridOptions = {
17521          *      expandableRowTemplate: 'expandableRowTemplate.html'
17522          *    }
17523          *  </pre>
17524          */
17525         if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
17526           gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
17527           grid.options.enableExpandable = false;
17528         }
17529
17530         /**
17531          *  @ngdoc object
17532          *  @name ui.grid.expandable.api:PublicApi
17533          *
17534          *  @description Public Api for expandable feature
17535          */
17536         /**
17537          *  @ngdoc object
17538          *  @name ui.grid.expandable.api:GridRow
17539          *
17540          *  @description Additional properties added to GridRow when using the expandable module
17541          */
17542         /**
17543          *  @ngdoc object
17544          *  @name ui.grid.expandable.api:GridOptions
17545          *
17546          *  @description Options for configuring the expandable feature, these are available to be
17547          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17548          */
17549         var publicApi = {
17550           events: {
17551             expandable: {
17552               /**
17553                * @ngdoc event
17554                * @name rowExpandedStateChanged
17555                * @eventOf  ui.grid.expandable.api:PublicApi
17556                * @description raised when row expanded or collapsed
17557                * <pre>
17558                *      gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
17559                * </pre>
17560                * @param {GridRow} row the row that was expanded
17561                */
17562               rowExpandedBeforeStateChanged: function(scope,row){
17563               },
17564               rowExpandedStateChanged: function (scope, row) {
17565               }
17566             }
17567           },
17568
17569           methods: {
17570             expandable: {
17571               /**
17572                * @ngdoc method
17573                * @name toggleRowExpansion
17574                * @methodOf  ui.grid.expandable.api:PublicApi
17575                * @description Toggle a specific row
17576                * <pre>
17577                *      gridApi.expandable.toggleRowExpansion(rowEntity);
17578                * </pre>
17579                * @param {object} rowEntity the data entity for the row you want to expand
17580                */
17581               toggleRowExpansion: function (rowEntity) {
17582                 var row = grid.getRow(rowEntity);
17583                 if (row !== null) {
17584                   service.toggleRowExpansion(grid, row);
17585                 }
17586               },
17587
17588               /**
17589                * @ngdoc method
17590                * @name expandAllRows
17591                * @methodOf  ui.grid.expandable.api:PublicApi
17592                * @description Expand all subgrids.
17593                * <pre>
17594                *      gridApi.expandable.expandAllRows();
17595                * </pre>
17596                */
17597               expandAllRows: function() {
17598                 service.expandAllRows(grid);
17599               },
17600
17601               /**
17602                * @ngdoc method
17603                * @name collapseAllRows
17604                * @methodOf  ui.grid.expandable.api:PublicApi
17605                * @description Collapse all subgrids.
17606                * <pre>
17607                *      gridApi.expandable.collapseAllRows();
17608                * </pre>
17609                */
17610               collapseAllRows: function() {
17611                 service.collapseAllRows(grid);
17612               },
17613
17614               /**
17615                * @ngdoc method
17616                * @name toggleAllRows
17617                * @methodOf  ui.grid.expandable.api:PublicApi
17618                * @description Toggle all subgrids.
17619                * <pre>
17620                *      gridApi.expandable.toggleAllRows();
17621                * </pre>
17622                */
17623               toggleAllRows: function() {
17624                 service.toggleAllRows(grid);
17625               },
17626               /**
17627                * @ngdoc function
17628                * @name expandRow
17629                * @methodOf  ui.grid.expandable.api:PublicApi
17630                * @description Expand the data row
17631                * @param {object} rowEntity gridOptions.data[] array instance
17632                */
17633               expandRow: function (rowEntity) {
17634                 var row = grid.getRow(rowEntity);
17635                 if (row !== null && !row.isExpanded) {
17636                   service.toggleRowExpansion(grid, row);
17637                 }
17638               },
17639               /**
17640                * @ngdoc function
17641                * @name collapseRow
17642                * @methodOf  ui.grid.expandable.api:PublicApi
17643                * @description Collapse the data row
17644                * @param {object} rowEntity gridOptions.data[] array instance
17645                */
17646               collapseRow: function (rowEntity) {
17647                 var row = grid.getRow(rowEntity);
17648                 if (row !== null && row.isExpanded) {
17649                   service.toggleRowExpansion(grid, row);
17650                 }
17651               },
17652               /**
17653                * @ngdoc function
17654                * @name getExpandedRows
17655                * @methodOf  ui.grid.expandable.api:PublicApi
17656                * @description returns all expandedRow's entity references
17657                */
17658               getExpandedRows: function () {
17659                 return service.getExpandedRows(grid).map(function (gridRow) {
17660                   return gridRow.entity;
17661                 });
17662               }
17663             }
17664           }
17665         };
17666         grid.api.registerEventsFromObject(publicApi.events);
17667         grid.api.registerMethodsFromObject(publicApi.methods);
17668       },
17669
17670       toggleRowExpansion: function (grid, row) {
17671         // trigger the "before change" event. Can change row height dynamically this way.
17672         grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
17673         /**
17674          *  @ngdoc object
17675          *  @name isExpanded
17676          *  @propertyOf  ui.grid.expandable.api:GridRow
17677          *  @description Whether or not the row is currently expanded.
17678          *  @example
17679          *  <pre>
17680          *    $scope.api.expandable.on.rowExpandedStateChanged($scope, function (row) {
17681          *      if (row.isExpanded) {
17682          *        //...
17683          *      }
17684          *    });
17685          *  </pre>
17686          */
17687         row.isExpanded = !row.isExpanded;
17688         if (angular.isUndefined(row.expandedRowHeight)){
17689           row.expandedRowHeight = grid.options.expandableRowHeight;
17690         }
17691
17692         if (row.isExpanded) {
17693           row.height = row.grid.options.rowHeight + row.expandedRowHeight;
17694         }
17695         else {
17696           row.height = row.grid.options.rowHeight;
17697           grid.expandable.expandedAll = false;
17698         }
17699         grid.api.expandable.raise.rowExpandedStateChanged(row);
17700       },
17701
17702       expandAllRows: function(grid, $scope) {
17703         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
17704           if (!row.isExpanded) {
17705             service.toggleRowExpansion(grid, row);
17706           }
17707         });
17708         grid.expandable.expandedAll = true;
17709         grid.queueGridRefresh();
17710       },
17711
17712       collapseAllRows: function(grid) {
17713         grid.renderContainers.body.visibleRowCache.forEach( function(row) {
17714           if (row.isExpanded) {
17715             service.toggleRowExpansion(grid, row);
17716           }
17717         });
17718         grid.expandable.expandedAll = false;
17719         grid.queueGridRefresh();
17720       },
17721
17722       toggleAllRows: function(grid) {
17723         if (grid.expandable.expandedAll) {
17724           service.collapseAllRows(grid);
17725         }
17726         else {
17727           service.expandAllRows(grid);
17728         }
17729       },
17730
17731       getExpandedRows: function (grid) {
17732         return grid.rows.filter(function (row) {
17733           return row.isExpanded;
17734         });
17735       }
17736     };
17737     return service;
17738   }]);
17739
17740   /**
17741    *  @ngdoc object
17742    *  @name enableExpandableRowHeader
17743    *  @propertyOf  ui.grid.expandable.api:GridOptions
17744    *  @description Show a rowHeader to provide the expandable buttons.  If set to false then implies
17745    *  you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
17746    *  @example
17747    *  <pre>
17748    *    $scope.gridOptions = {
17749    *      enableExpandableRowHeader: false
17750    *    }
17751    *  </pre>
17752    */
17753   module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
17754     function (uiGridExpandableService, $templateCache) {
17755       return {
17756         replace: true,
17757         priority: 0,
17758         require: '^uiGrid',
17759         scope: false,
17760         compile: function () {
17761           return {
17762             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17763               uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
17764
17765               if (!uiGridCtrl.grid.options.enableExpandable) {
17766                 return;
17767               }
17768
17769               if (uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
17770                 var expandableRowHeaderColDef = {
17771                   name: 'expandableButtons',
17772                   displayName: '',
17773                   exporterSuppressExport: true,
17774                   enableColumnResizing: false,
17775                   enableColumnMenu: false,
17776                   width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
17777                 };
17778                 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
17779                 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
17780                 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef, -90);
17781               }
17782
17783             },
17784             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17785             }
17786           };
17787         }
17788       };
17789     }]);
17790
17791   /**
17792    *  @ngdoc directive
17793    *  @name ui.grid.expandable.directive:uiGrid
17794    *  @description stacks on the uiGrid directive to register child grid with parent row when child is created
17795    */
17796   module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
17797     function (uiGridExpandableService, $templateCache) {
17798       return {
17799         replace: true,
17800         priority: 599,
17801         require: '^uiGrid',
17802         scope: false,
17803         compile: function () {
17804           return {
17805             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17806
17807               uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
17808                 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
17809                 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
17810
17811                   /**
17812                    *  @ngdoc directive
17813                    *  @name ui.grid.expandable.class:Grid
17814                    *  @description Additional Grid properties added by expandable module
17815                    */
17816
17817                   /**
17818                    *  @ngdoc object
17819                    *  @name parentRow
17820                    *  @propertyOf ui.grid.expandable.class:Grid
17821                    *  @description reference to the expanded parent row that owns this grid
17822                    */
17823                   uiGridCtrl.grid.parentRow = $scope.row;
17824
17825                   //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
17826                  // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
17827                  //   uiGridCtrl.grid.parentRow = newHeight;
17828                  // });
17829                 }
17830
17831               });
17832             },
17833             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17834
17835             }
17836           };
17837         }
17838       };
17839     }]);
17840
17841   /**
17842    *  @ngdoc directive
17843    *  @name ui.grid.expandable.directive:uiGridExpandableRow
17844    *  @description directive to render the expandable row template
17845    */
17846   module.directive('uiGridExpandableRow',
17847   ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
17848     function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
17849
17850       return {
17851         replace: false,
17852         priority: 0,
17853         scope: false,
17854
17855         compile: function () {
17856           return {
17857             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17858               gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
17859                 function (template) {
17860                   if ($scope.grid.options.expandableRowScope) {
17861                     /**
17862                      *  @ngdoc object
17863                      *  @name expandableRowScope
17864                      *  @propertyOf  ui.grid.expandable.api:GridOptions
17865                      *  @description  Variables of object expandableScope will be available in the scope of the expanded subgrid
17866                      *  @example
17867                      *  <pre>
17868                      *    $scope.gridOptions = {
17869                      *      expandableRowScope: expandableScope
17870                      *    }
17871                      *  </pre>
17872                      */
17873                     var expandableRowScope = $scope.grid.options.expandableRowScope;
17874                     for (var property in expandableRowScope) {
17875                       if (expandableRowScope.hasOwnProperty(property)) {
17876                         $scope[property] = expandableRowScope[property];
17877                       }
17878                     }
17879                   }
17880                   var expandedRowElement = angular.element(template);
17881                   $elm.append(expandedRowElement);
17882                   expandedRowElement = $compile(expandedRowElement)($scope);
17883                   $scope.row.expandedRendered = true;
17884               });
17885             },
17886
17887             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17888               $scope.$on('$destroy', function() {
17889                 $scope.row.expandedRendered = false;
17890               });
17891             }
17892           };
17893         }
17894       };
17895     }]);
17896
17897   /**
17898    *  @ngdoc directive
17899    *  @name ui.grid.expandable.directive:uiGridRow
17900    *  @description stacks on the uiGridRow directive to add support for expandable rows
17901    */
17902   module.directive('uiGridRow',
17903     ['$compile', 'gridUtil', '$templateCache',
17904       function ($compile, gridUtil, $templateCache) {
17905         return {
17906           priority: -200,
17907           scope: false,
17908           compile: function ($elm, $attrs) {
17909             return {
17910               pre: function ($scope, $elm, $attrs, controllers) {
17911
17912                 if (!$scope.grid.options.enableExpandable) {
17913                   return;
17914                 }
17915
17916                 $scope.expandableRow = {};
17917
17918                 $scope.expandableRow.shouldRenderExpand = function () {
17919                   var ret = $scope.colContainer.name === 'body' &&  $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
17920                   return ret;
17921                 };
17922
17923                 $scope.expandableRow.shouldRenderFiller = function () {
17924                   var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
17925                   return ret;
17926                 };
17927
17928  /*
17929   * Commented out @PaulL1.  This has no purpose that I can see, and causes #2964.  If this code needs to be reinstated for some
17930   * reason it needs to use drawnWidth, not width, and needs to check column visibility.  It should really use render container
17931   * visible column cache also instead of checking column.renderContainer.
17932                   function updateRowContainerWidth() {
17933                       var grid = $scope.grid;
17934                       var colWidth = 0;
17935                       grid.columns.forEach( function (column) {
17936                           if (column.renderContainer === 'left') {
17937                             colWidth += column.width;
17938                           }
17939                       });
17940                       colWidth = Math.floor(colWidth);
17941                       return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
17942                           ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
17943                           ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
17944                   }
17945
17946                   if ($scope.colContainer.name === 'left') {
17947                       $scope.grid.registerStyleComputation({
17948                           priority: 15,
17949                           func: updateRowContainerWidth
17950                       });
17951                   }*/
17952
17953               },
17954               post: function ($scope, $elm, $attrs, controllers) {
17955               }
17956             };
17957           }
17958         };
17959       }]);
17960
17961   /**
17962    *  @ngdoc directive
17963    *  @name ui.grid.expandable.directive:uiGridViewport
17964    *  @description stacks on the uiGridViewport directive to append the expandable row html elements to the
17965    *  default gridRow template
17966    */
17967   module.directive('uiGridViewport',
17968     ['$compile', 'gridUtil', '$templateCache',
17969       function ($compile, gridUtil, $templateCache) {
17970         return {
17971           priority: -200,
17972           scope: false,
17973           compile: function ($elm, $attrs) {
17974
17975              //todo: this adds ng-if watchers to each row even if the grid is not using expandable directive
17976              //      or options.enableExpandable == false
17977              //      The alternative is to compile the template and append to each row in a uiGridRow directive
17978
17979             var rowRepeatDiv = angular.element($elm.children().children()[0]);
17980             var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
17981             var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
17982             rowRepeatDiv.append(expandedRowElement);
17983             rowRepeatDiv.append(expandedRowFillerElement);
17984             return {
17985               pre: function ($scope, $elm, $attrs, controllers) {
17986               },
17987               post: function ($scope, $elm, $attrs, controllers) {
17988               }
17989             };
17990           }
17991         };
17992       }]);
17993
17994 })();
17995
17996 /* global console */
17997
17998 (function () {
17999   'use strict';
18000
18001   /**
18002    * @ngdoc overview
18003    * @name ui.grid.exporter
18004    * @description
18005    *
18006    * # ui.grid.exporter
18007    *
18008    * <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>
18009    *
18010    * This module provides the ability to export data from the grid.
18011    *
18012    * Data can be exported in a range of formats, and all data, visible
18013    * data, or selected rows can be exported, with all columns or visible
18014    * columns.
18015    *
18016    * No UI is provided, the caller should provide their own UI/buttons
18017    * as appropriate, or enable the gridMenu
18018    *
18019    * <br/>
18020    * <br/>
18021    *
18022    * <div doc-module-components="ui.grid.exporter"></div>
18023    */
18024
18025   var module = angular.module('ui.grid.exporter', ['ui.grid']);
18026
18027   /**
18028    *  @ngdoc object
18029    *  @name ui.grid.exporter.constant:uiGridExporterConstants
18030    *
18031    *  @description constants available in exporter module
18032    */
18033   /**
18034    * @ngdoc property
18035    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18036    * @name ALL
18037    * @description export all data, including data not visible.  Can
18038    * be set for either rowTypes or colTypes
18039    */
18040   /**
18041    * @ngdoc property
18042    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18043    * @name VISIBLE
18044    * @description export only visible data, including data not visible.  Can
18045    * be set for either rowTypes or colTypes
18046    */
18047   /**
18048    * @ngdoc property
18049    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18050    * @name SELECTED
18051    * @description export all data, including data not visible.  Can
18052    * be set only for rowTypes, selection of only some columns is
18053    * not supported
18054    */
18055   module.constant('uiGridExporterConstants', {
18056     featureName: 'exporter',
18057     ALL: 'all',
18058     VISIBLE: 'visible',
18059     SELECTED: 'selected',
18060     CSV_CONTENT: 'CSV_CONTENT',
18061     BUTTON_LABEL: 'BUTTON_LABEL',
18062     FILE_NAME: 'FILE_NAME'
18063   });
18064
18065   /**
18066    *  @ngdoc service
18067    *  @name ui.grid.exporter.service:uiGridExporterService
18068    *
18069    *  @description Services for exporter feature
18070    */
18071   module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
18072     function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
18073
18074       var service = {
18075
18076         delay: 100,
18077
18078         initializeGrid: function (grid) {
18079
18080           //add feature namespace and any properties to grid for needed state
18081           grid.exporter = {};
18082           this.defaultGridOptions(grid.options);
18083
18084           /**
18085            *  @ngdoc object
18086            *  @name ui.grid.exporter.api:PublicApi
18087            *
18088            *  @description Public Api for exporter feature
18089            */
18090           var publicApi = {
18091             events: {
18092               exporter: {
18093               }
18094             },
18095             methods: {
18096               exporter: {
18097                 /**
18098                  * @ngdoc function
18099                  * @name csvExport
18100                  * @methodOf  ui.grid.exporter.api:PublicApi
18101                  * @description Exports rows from the grid in csv format,
18102                  * the data exported is selected based on the provided options
18103                  * @param {string} rowTypes which rows to export, valid values are
18104                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18105                  * uiGridExporterConstants.SELECTED
18106                  * @param {string} colTypes which columns to export, valid values are
18107                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
18108                  */
18109                 csvExport: function (rowTypes, colTypes) {
18110                   service.csvExport(grid, rowTypes, colTypes);
18111                 },
18112                 /**
18113                  * @ngdoc function
18114                  * @name pdfExport
18115                  * @methodOf  ui.grid.exporter.api:PublicApi
18116                  * @description Exports rows from the grid in pdf format,
18117                  * the data exported is selected based on the provided options
18118                  * Note that this function has a dependency on pdfMake, all
18119                  * going well this has been installed for you.
18120                  * The resulting pdf opens in a new browser window.
18121                  * @param {string} rowTypes which rows to export, valid values are
18122                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18123                  * uiGridExporterConstants.SELECTED
18124                  * @param {string} colTypes which columns to export, valid values are
18125                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
18126                  */
18127                 pdfExport: function (rowTypes, colTypes) {
18128                   service.pdfExport(grid, rowTypes, colTypes);
18129                 }
18130               }
18131             }
18132           };
18133
18134           grid.api.registerEventsFromObject(publicApi.events);
18135
18136           grid.api.registerMethodsFromObject(publicApi.methods);
18137
18138           if (grid.api.core.addToGridMenu){
18139             service.addToMenu( grid );
18140           } else {
18141             // order of registration is not guaranteed, register in a little while
18142             $interval( function() {
18143               if (grid.api.core.addToGridMenu){
18144                 service.addToMenu( grid );
18145               }
18146             }, this.delay, 1);
18147           }
18148
18149         },
18150
18151         defaultGridOptions: function (gridOptions) {
18152           //default option to true unless it was explicitly set to false
18153           /**
18154            * @ngdoc object
18155            * @name ui.grid.exporter.api:GridOptions
18156            *
18157            * @description GridOptions for exporter feature, these are available to be
18158            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18159            */
18160           /**
18161            * @ngdoc object
18162            * @name ui.grid.exporter.api:ColumnDef
18163            * @description ColumnDef settings for exporter
18164            */
18165           /**
18166            * @ngdoc object
18167            * @name exporterSuppressMenu
18168            * @propertyOf  ui.grid.exporter.api:GridOptions
18169            * @description Don't show the export menu button, implying the user
18170            * will roll their own UI for calling the exporter
18171            * <br/>Defaults to false
18172            */
18173           gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
18174           /**
18175            * @ngdoc object
18176            * @name exporterMenuLabel
18177            * @propertyOf  ui.grid.exporter.api:GridOptions
18178            * @description The text to show on the exporter menu button
18179            * link
18180            * <br/>Defaults to 'Export'
18181            */
18182           gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
18183           /**
18184            * @ngdoc object
18185            * @name exporterSuppressColumns
18186            * @propertyOf  ui.grid.exporter.api:GridOptions
18187            * @description Columns that should not be exported.  The selectionRowHeader is already automatically
18188            * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
18189            * output then add it in this list.  You should provide an array of column names.
18190            * <br/>Defaults to: []
18191            * <pre>
18192            *   gridOptions.exporterSuppressColumns = [ 'buttons' ];
18193            * </pre>
18194            */
18195           gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
18196           /**
18197            * @ngdoc object
18198            * @name exporterCsvColumnSeparator
18199            * @propertyOf  ui.grid.exporter.api:GridOptions
18200            * @description The character to use as column separator
18201            * link
18202            * <br/>Defaults to ','
18203            */
18204           gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
18205           /**
18206            * @ngdoc object
18207            * @name exporterCsvFilename
18208            * @propertyOf  ui.grid.exporter.api:GridOptions
18209            * @description The default filename to use when saving the downloaded csv.
18210            * This will only work in some browsers.
18211            * <br/>Defaults to 'download.csv'
18212            */
18213           gridOptions.exporterCsvFilename = gridOptions.exporterCsvFilename ? gridOptions.exporterCsvFilename : 'download.csv';
18214           /**
18215            * @ngdoc object
18216            * @name exporterPdfFilename
18217            * @propertyOf  ui.grid.exporter.api:GridOptions
18218            * @description The default filename to use when saving the downloaded pdf, only used in IE (other browsers open pdfs in a new window)
18219            * <br/>Defaults to 'download.pdf'
18220            */
18221           gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
18222           /**
18223            * @ngdoc object
18224            * @name exporterOlderExcelCompatibility
18225            * @propertyOf  ui.grid.exporter.api:GridOptions
18226            * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
18227            * through as ï»¿ in the first column header.  Setting this option to false will suppress this, at the
18228            * expense of proper utf-16 handling in applications that do recognise the BOM
18229            * <br/>Defaults to false
18230            */
18231           gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
18232           /**
18233            * @ngdoc object
18234            * @name exporterIsExcelCompatible
18235            * @propertyOf  ui.grid.exporter.api:GridOptions
18236            * @description Separator header, used to set a custom column separator in a csv file, only works on MS Excel.
18237            * Used it on other programs will make csv content display unproperly. Setting this option to false won't add this header.
18238            * <br/>Defaults to false
18239            */
18240           gridOptions.exporterIsExcelCompatible = gridOptions.exporterIsExcelCompatible === true;
18241           /**
18242            * @ngdoc object
18243            * @name exporterMenuItemOrder
18244            * @propertyOf  ui.grid.exporter.api:GridOptions
18245            * @description An option to determine the starting point for the menu items created by the exporter
18246            * <br/>Defaults to 200
18247            */
18248           gridOptions.exporterMenuItemOrder = gridOptions.exporterMenuItemOrder ? gridOptions.exporterMenuItemOrder : 200;
18249           /**
18250            * @ngdoc object
18251            * @name exporterPdfDefaultStyle
18252            * @propertyOf  ui.grid.exporter.api:GridOptions
18253            * @description The default style in pdfMake format
18254            * <br/>Defaults to:
18255            * <pre>
18256            *   {
18257            *     fontSize: 11
18258            *   }
18259            * </pre>
18260            */
18261           gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
18262           /**
18263            * @ngdoc object
18264            * @name exporterPdfTableStyle
18265            * @propertyOf  ui.grid.exporter.api:GridOptions
18266            * @description The table style in pdfMake format
18267            * <br/>Defaults to:
18268            * <pre>
18269            *   {
18270            *     margin: [0, 5, 0, 15]
18271            *   }
18272            * </pre>
18273            */
18274           gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
18275           /**
18276            * @ngdoc object
18277            * @name exporterPdfTableHeaderStyle
18278            * @propertyOf  ui.grid.exporter.api:GridOptions
18279            * @description The tableHeader style in pdfMake format
18280            * <br/>Defaults to:
18281            * <pre>
18282            *   {
18283            *     bold: true,
18284            *     fontSize: 12,
18285            *     color: 'black'
18286            *   }
18287            * </pre>
18288            */
18289           gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
18290           /**
18291            * @ngdoc object
18292            * @name exporterPdfHeader
18293            * @propertyOf  ui.grid.exporter.api:GridOptions
18294            * @description The header section for pdf exports.  Can be
18295            * simple text:
18296            * <pre>
18297            *   gridOptions.exporterPdfHeader = 'My Header';
18298            * </pre>
18299            * Can be a more complex object in pdfMake format:
18300            * <pre>
18301            *   gridOptions.exporterPdfHeader = {
18302            *     columns: [
18303            *       'Left part',
18304            *       { text: 'Right part', alignment: 'right' }
18305            *     ]
18306            *   };
18307            * </pre>
18308            * Or can be a function, allowing page numbers and the like
18309            * <pre>
18310            *   gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
18311            * </pre>
18312            */
18313           gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
18314           /**
18315            * @ngdoc object
18316            * @name exporterPdfFooter
18317            * @propertyOf  ui.grid.exporter.api:GridOptions
18318            * @description The header section for pdf exports.  Can be
18319            * simple text:
18320            * <pre>
18321            *   gridOptions.exporterPdfFooter = 'My Footer';
18322            * </pre>
18323            * Can be a more complex object in pdfMake format:
18324            * <pre>
18325            *   gridOptions.exporterPdfFooter = {
18326            *     columns: [
18327            *       'Left part',
18328            *       { text: 'Right part', alignment: 'right' }
18329            *     ]
18330            *   };
18331            * </pre>
18332            * Or can be a function, allowing page numbers and the like
18333            * <pre>
18334            *   gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
18335            * </pre>
18336            */
18337           gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
18338           /**
18339            * @ngdoc object
18340            * @name exporterPdfOrientation
18341            * @propertyOf  ui.grid.exporter.api:GridOptions
18342            * @description The orientation, should be a valid pdfMake value,
18343            * 'landscape' or 'portrait'
18344            * <br/>Defaults to landscape
18345            */
18346           gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
18347           /**
18348            * @ngdoc object
18349            * @name exporterPdfPageSize
18350            * @propertyOf  ui.grid.exporter.api:GridOptions
18351            * @description The orientation, should be a valid pdfMake
18352            * paper size, usually 'A4' or 'LETTER'
18353            * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
18354            * <br/>Defaults to A4
18355            */
18356           gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
18357           /**
18358            * @ngdoc object
18359            * @name exporterPdfMaxGridWidth
18360            * @propertyOf  ui.grid.exporter.api:GridOptions
18361            * @description The maxium grid width - the current grid width
18362            * will be scaled to match this, with any fixed width columns
18363            * being adjusted accordingly.
18364            * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
18365            */
18366           gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
18367           /**
18368            * @ngdoc object
18369            * @name exporterPdfTableLayout
18370            * @propertyOf  ui.grid.exporter.api:GridOptions
18371            * @description A tableLayout in pdfMake format,
18372            * controls gridlines and the like.  We use the default
18373            * layout usually.
18374            * <br/>Defaults to null, which means no layout
18375            */
18376
18377           /**
18378            * @ngdoc object
18379            * @name exporterMenuAllData
18380            * @porpertyOf  ui.grid.exporter.api:GridOptions
18381            * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
18382            */
18383           gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
18384
18385           /**
18386            * @ngdoc object
18387            * @name exporterMenuVisibleData
18388            * @porpertyOf  ui.grid.exporter.api:GridOptions
18389            * @description Add export visible data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
18390            */
18391           gridOptions.exporterMenuVisibleData = gridOptions.exporterMenuVisibleData !== undefined ? gridOptions.exporterMenuVisibleData : true;
18392
18393           /**
18394            * @ngdoc object
18395            * @name exporterMenuSelectedData
18396            * @porpertyOf  ui.grid.exporter.api:GridOptions
18397            * @description Add export selected data as cvs/pdf menu items to the ui-grid grid menu, if it's present.  Defaults to true.
18398            */
18399           gridOptions.exporterMenuSelectedData = gridOptions.exporterMenuSelectedData !== undefined ? gridOptions.exporterMenuSelectedData : true;
18400
18401           /**
18402            * @ngdoc object
18403            * @name exporterMenuCsv
18404            * @propertyOf  ui.grid.exporter.api:GridOptions
18405            * @description Add csv export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
18406            */
18407           gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
18408
18409           /**
18410            * @ngdoc object
18411            * @name exporterMenuPdf
18412            * @propertyOf  ui.grid.exporter.api:GridOptions
18413            * @description Add pdf export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
18414            */
18415           gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
18416
18417           /**
18418            * @ngdoc object
18419            * @name exporterPdfCustomFormatter
18420            * @propertyOf  ui.grid.exporter.api:GridOptions
18421            * @description A custom callback routine that changes the pdf document, adding any
18422            * custom styling or content that is supported by pdfMake.  Takes in the complete docDefinition, and
18423            * must return an updated docDefinition ready for pdfMake.
18424            * @example
18425            * In this example we add a style to the style array, so that we can use it in our
18426            * footer definition.
18427            * <pre>
18428            *   gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
18429            *     docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
18430            *     return docDefinition;
18431            *   }
18432            *
18433            *   gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
18434            * </pre>
18435            */
18436           gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
18437
18438           /**
18439            * @ngdoc object
18440            * @name exporterHeaderFilterUseName
18441            * @propertyOf  ui.grid.exporter.api:GridOptions
18442            * @description Defaults to false, which leads to `displayName` being passed into the headerFilter.
18443            * If set to true, then will pass `name` instead.
18444            *
18445            *
18446            * @example
18447            * <pre>
18448            *   gridOptions.exporterHeaderFilterUseName = true;
18449            * </pre>
18450            */
18451           gridOptions.exporterHeaderFilterUseName = gridOptions.exporterHeaderFilterUseName === true;
18452
18453           /**
18454            * @ngdoc object
18455            * @name exporterHeaderFilter
18456            * @propertyOf  ui.grid.exporter.api:GridOptions
18457            * @description A function to apply to the header displayNames before exporting.  Useful for internationalisation,
18458            * for example if you were using angular-translate you'd set this to `$translate.instant`.  Note that this
18459            * call must be synchronous, it cannot be a call that returns a promise.
18460            *
18461            * Behaviour can be changed to pass in `name` instead of `displayName` through use of `exporterHeaderFilterUseName: true`.
18462            *
18463            * @example
18464            * <pre>
18465            *   gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
18466            * </pre>
18467            * OR
18468            * <pre>
18469            *   gridOptions.exporterHeaderFilter = $translate.instant;
18470            * </pre>
18471            */
18472
18473           /**
18474            * @ngdoc function
18475            * @name exporterFieldCallback
18476            * @propertyOf  ui.grid.exporter.api:GridOptions
18477            * @description A function to call for each field before exporting it.  Allows
18478            * massaging of raw data into a display format, for example if you have applied
18479            * filters to convert codes into decodes, or you require
18480            * a specific date format in the exported content.
18481            *
18482            * The method is called once for each field exported, and provides the grid, the
18483            * gridCol and the GridRow for you to use as context in massaging the data.
18484            *
18485            * @param {Grid} grid provides the grid in case you have need of it
18486            * @param {GridRow} row the row from which the data comes
18487            * @param {GridCol} col the column from which the data comes
18488            * @param {object} value the value for your massaging
18489            * @returns {object} you must return the massaged value ready for exporting
18490            *
18491            * @example
18492            * <pre>
18493            *   gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
18494            *     if ( col.name === 'status' ){
18495            *       value = decodeStatus( value );
18496            *     }
18497            *     return value;
18498            *   }
18499            * </pre>
18500            */
18501           gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
18502
18503           /**
18504            * @ngdoc function
18505            * @name exporterAllDataFn
18506            * @propertyOf  ui.grid.exporter.api:GridOptions
18507            * @description This promise is needed when exporting all rows,
18508            * and the data need to be provided by server side. Default is null.
18509            * @returns {Promise} a promise to load all data from server
18510            *
18511            * @example
18512            * <pre>
18513            *   gridOptions.exporterAllDataFn = function () {
18514            *     return $http.get('/data/100.json')
18515            *   }
18516            * </pre>
18517            */
18518           gridOptions.exporterAllDataFn = gridOptions.exporterAllDataFn ? gridOptions.exporterAllDataFn : null;
18519
18520           /**
18521            * @ngdoc function
18522            * @name exporterAllDataPromise
18523            * @propertyOf  ui.grid.exporter.api:GridOptions
18524            * @description DEPRECATED - exporterAllDataFn used to be
18525            * called this, but it wasn't a promise, it was a function that returned
18526            * a promise.  Deprecated, but supported for backward compatibility, use
18527            * exporterAllDataFn instead.
18528            * @returns {Promise} a promise to load all data from server
18529            *
18530            * @example
18531            * <pre>
18532            *   gridOptions.exporterAllDataFn = function () {
18533            *     return $http.get('/data/100.json')
18534            *   }
18535            * </pre>
18536            */
18537           if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
18538             gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
18539           }
18540         },
18541
18542
18543         /**
18544          * @ngdoc function
18545          * @name addToMenu
18546          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18547          * @description Adds export items to the grid menu,
18548          * allowing the user to select export options
18549          * @param {Grid} grid the grid from which data should be exported
18550          */
18551         addToMenu: function ( grid ) {
18552           grid.api.core.addToGridMenu( grid, [
18553             {
18554               title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
18555               action: function ($event) {
18556                 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
18557               },
18558               shown: function() {
18559                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
18560               },
18561               order: grid.options.exporterMenuItemOrder
18562             },
18563             {
18564               title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
18565               action: function ($event) {
18566                 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
18567               },
18568               shown: function() {
18569                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuVisibleData;
18570               },
18571               order: grid.options.exporterMenuItemOrder + 1
18572             },
18573             {
18574               title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
18575               action: function ($event) {
18576                 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
18577               },
18578               shown: function() {
18579                 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuSelectedData &&
18580                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
18581               },
18582               order: grid.options.exporterMenuItemOrder + 2
18583             },
18584             {
18585               title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
18586               action: function ($event) {
18587                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
18588               },
18589               shown: function() {
18590                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
18591               },
18592               order: grid.options.exporterMenuItemOrder + 3
18593             },
18594             {
18595               title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
18596               action: function ($event) {
18597                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
18598               },
18599               shown: function() {
18600                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuVisibleData;
18601               },
18602               order: grid.options.exporterMenuItemOrder + 4
18603             },
18604             {
18605               title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
18606               action: function ($event) {
18607                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
18608               },
18609               shown: function() {
18610                 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuSelectedData &&
18611                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
18612               },
18613               order: grid.options.exporterMenuItemOrder + 5
18614             }
18615           ]);
18616         },
18617
18618
18619         /**
18620          * @ngdoc function
18621          * @name csvExport
18622          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18623          * @description Exports rows from the grid in csv format,
18624          * the data exported is selected based on the provided options
18625          * @param {Grid} grid the grid from which data should be exported
18626          * @param {string} rowTypes which rows to export, valid values are
18627          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18628          * uiGridExporterConstants.SELECTED
18629          * @param {string} colTypes which columns to export, valid values are
18630          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18631          * uiGridExporterConstants.SELECTED
18632          */
18633         csvExport: function (grid, rowTypes, colTypes) {
18634           var self = this;
18635           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
18636             var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
18637             var exportData = self.getData(grid, rowTypes, colTypes);
18638             var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
18639
18640             self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterCsvColumnSeparator, grid.options.exporterOlderExcelCompatibility, grid.options.exporterIsExcelCompatible);
18641           });
18642         },
18643
18644         /**
18645          * @ngdoc function
18646          * @name loadAllDataIfNeeded
18647          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18648          * @description When using server side pagination, use exporterAllDataFn to
18649          * load all data before continuing processing.
18650          * When using client side pagination, return a resolved promise so processing
18651          * continues immediately
18652          * @param {Grid} grid the grid from which data should be exported
18653          * @param {string} rowTypes which rows to export, valid values are
18654          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18655          * uiGridExporterConstants.SELECTED
18656          * @param {string} colTypes which columns to export, valid values are
18657          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18658          * uiGridExporterConstants.SELECTED
18659          */
18660         loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
18661           if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
18662             return grid.options.exporterAllDataFn()
18663               .then(function() {
18664                 grid.modifyRows(grid.options.data);
18665               });
18666           } else {
18667             var deferred = $q.defer();
18668             deferred.resolve();
18669             return deferred.promise;
18670           }
18671         },
18672
18673         /**
18674          * @ngdoc property
18675          * @propertyOf ui.grid.exporter.api:ColumnDef
18676          * @name exporterSuppressExport
18677          * @description Suppresses export for this column.  Used by selection and expandable.
18678          */
18679
18680         /**
18681          * @ngdoc function
18682          * @name getColumnHeaders
18683          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18684          * @description Gets the column headers from the grid to use
18685          * as a title row for the exported file, all headers have
18686          * headerCellFilters applied as appropriate.
18687          *
18688          * Column headers are an array of objects, each object has
18689          * name, displayName, width and align attributes.  Only name is
18690          * used for csv, all attributes are used for pdf.
18691          *
18692          * @param {Grid} grid the grid from which data should be exported
18693          * @param {string} colTypes which columns to export, valid values are
18694          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18695          * uiGridExporterConstants.SELECTED
18696          */
18697         getColumnHeaders: function (grid, colTypes) {
18698           var headers = [];
18699           var columns;
18700
18701           if ( colTypes === uiGridExporterConstants.ALL ){
18702             columns = grid.columns;
18703           } else {
18704             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18705             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18706             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18707
18708             columns = leftColumns.concat(bodyColumns,rightColumns);
18709           }
18710
18711           columns.forEach( function( gridCol, index ) {
18712             if ( gridCol.colDef.exporterSuppressExport !== true &&
18713                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
18714               headers.push({
18715                 name: gridCol.field,
18716                 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
18717                 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
18718                 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
18719               });
18720             }
18721           });
18722
18723           return headers;
18724         },
18725
18726
18727         /**
18728          * @ngdoc property
18729          * @propertyOf ui.grid.exporter.api:ColumnDef
18730          * @name exporterPdfAlign
18731          * @description the alignment you'd like for this specific column when
18732          * exported into a pdf.  Can be 'left', 'right', 'center' or any other
18733          * valid pdfMake alignment option.
18734          */
18735
18736
18737         /**
18738          * @ngdoc object
18739          * @name ui.grid.exporter.api:GridRow
18740          * @description GridRow settings for exporter
18741          */
18742         /**
18743          * @ngdoc object
18744          * @name exporterEnableExporting
18745          * @propertyOf  ui.grid.exporter.api:GridRow
18746          * @description If set to false, then don't export this row, notwithstanding visible or
18747          * other settings
18748          * <br/>Defaults to true
18749          */
18750
18751         /**
18752          * @ngdoc function
18753          * @name getData
18754          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18755          * @description Gets data from the grid based on the provided options,
18756          * all cells have cellFilters applied as appropriate.  Any rows marked
18757          * `exporterEnableExporting: false` will not be exported
18758          * @param {Grid} grid the grid from which data should be exported
18759          * @param {string} rowTypes which rows to export, valid values are
18760          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18761          * uiGridExporterConstants.SELECTED
18762          * @param {string} colTypes which columns to export, valid values are
18763          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18764          * uiGridExporterConstants.SELECTED
18765          * @param {boolean} applyCellFilters whether or not to get the display value or the raw value of the data
18766          */
18767         getData: function (grid, rowTypes, colTypes, applyCellFilters) {
18768           var data = [];
18769           var rows;
18770           var columns;
18771
18772           switch ( rowTypes ) {
18773             case uiGridExporterConstants.ALL:
18774               rows = grid.rows;
18775               break;
18776             case uiGridExporterConstants.VISIBLE:
18777               rows = grid.getVisibleRows();
18778               break;
18779             case uiGridExporterConstants.SELECTED:
18780               if ( grid.api.selection ){
18781                 rows = grid.api.selection.getSelectedGridRows();
18782               } else {
18783                 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
18784               }
18785               break;
18786           }
18787
18788           if ( colTypes === uiGridExporterConstants.ALL ){
18789             columns = grid.columns;
18790           } else {
18791             var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18792             var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18793             var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18794
18795             columns = leftColumns.concat(bodyColumns,rightColumns);
18796           }
18797
18798           rows.forEach( function( row, index ) {
18799
18800             if (row.exporterEnableExporting !== false) {
18801               var extractedRow = [];
18802
18803
18804               columns.forEach( function( gridCol, index ) {
18805               if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
18806                    gridCol.colDef.exporterSuppressExport !== true &&
18807                    grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
18808                   var cellValue = applyCellFilters ? grid.getCellDisplayValue( row, gridCol ) : grid.getCellValue( row, gridCol );
18809                   var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, cellValue ) };
18810                   if ( gridCol.colDef.exporterPdfAlign ) {
18811                     extractedField.alignment = gridCol.colDef.exporterPdfAlign;
18812                   }
18813                   extractedRow.push(extractedField);
18814                 }
18815               });
18816
18817               data.push(extractedRow);
18818             }
18819           });
18820
18821           return data;
18822         },
18823
18824
18825         /**
18826          * @ngdoc function
18827          * @name formatAsCsv
18828          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18829          * @description Formats the column headers and data as a CSV,
18830          * and sends that data to the user
18831          * @param {array} exportColumnHeaders an array of column headers,
18832          * where each header is an object with name, width and maybe alignment
18833          * @param {array} exportData an array of rows, where each row is
18834          * an array of column data
18835          * @param {string} separator a string that represents the separator to be used in the csv file
18836          * @returns {string} csv the formatted csv as a string
18837          */
18838         formatAsCsv: function (exportColumnHeaders, exportData, separator) {
18839           var self = this;
18840
18841           var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
18842
18843           var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
18844
18845           csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
18846
18847           return csv;
18848         },
18849
18850         /**
18851          * @ngdoc function
18852          * @name formatRowAsCsv
18853          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18854          * @description Renders a single field as a csv field, including
18855          * quotes around the value
18856          * @param {exporterService} exporter pass in exporter
18857          * @param {array} row the row to be turned into a csv string
18858          * @returns {string} a csv-ified version of the row
18859          */
18860         formatRowAsCsv: function (exporter, separator) {
18861           return function (row) {
18862             return row.map(exporter.formatFieldAsCsv).join(separator);
18863           };
18864         },
18865
18866         /**
18867          * @ngdoc function
18868          * @name formatFieldAsCsv
18869          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18870          * @description Renders a single field as a csv field, including
18871          * quotes around the value
18872          * @param {field} field the field to be turned into a csv string,
18873          * may be of any type
18874          * @returns {string} a csv-ified version of the field
18875          */
18876         formatFieldAsCsv: function (field) {
18877           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18878             return '';
18879           }
18880           if (typeof(field.value) === 'number') {
18881             return field.value;
18882           }
18883           if (typeof(field.value) === 'boolean') {
18884             return (field.value ? 'TRUE' : 'FALSE') ;
18885           }
18886           if (typeof(field.value) === 'string') {
18887             return '"' + field.value.replace(/"/g,'""') + '"';
18888           }
18889
18890           return JSON.stringify(field.value);
18891         },
18892
18893
18894         /**
18895          * @ngdoc function
18896          * @name isIE
18897          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18898          * @description Checks whether current browser is IE and returns it's version if it is
18899         */
18900         isIE: function () {
18901           var match = navigator.userAgent.search(/(?:Edge|MSIE|Trident\/.*; rv:)/);
18902           var isIE = false;
18903
18904           if (match !== -1) {
18905             isIE = true;
18906           }
18907
18908           return isIE;
18909         },
18910
18911
18912         /**
18913          * @ngdoc function
18914          * @name downloadFile
18915          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18916          * @description Triggers download of a csv file.  Logic provided
18917          * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
18918          * @param {string} fileName the filename we'd like our file to be
18919          * given
18920          * @param {string} csvContent the csv content that we'd like to
18921          * download as a file
18922          * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
18923           * @param {boolean} exporterIsExcelCompatible whether or not we add separator header ('sep=X')
18924          */
18925         downloadFile: function (fileName, csvContent, columnSeparator, exporterOlderExcelCompatibility, exporterIsExcelCompatible) {
18926           var D = document;
18927           var a = D.createElement('a');
18928           var strMimeType = 'application/octet-stream;charset=utf-8';
18929           var rawFile;
18930           var ieVersion = this.isIE();
18931
18932           if (exporterIsExcelCompatible) {
18933               csvContent = 'sep=' + columnSeparator + '\r\n' + csvContent;
18934           }
18935
18936           // IE10+
18937           if (navigator.msSaveBlob) {
18938             return navigator.msSaveOrOpenBlob(
18939               new Blob(
18940                 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18941                 { type: strMimeType } ),
18942               fileName
18943             );
18944           }
18945
18946           if (ieVersion) {
18947             var frame = D.createElement('iframe');
18948             document.body.appendChild(frame);
18949
18950             frame.contentWindow.document.open('text/html', 'replace');
18951             frame.contentWindow.document.write(csvContent);
18952             frame.contentWindow.document.close();
18953             frame.contentWindow.focus();
18954             frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18955
18956             document.body.removeChild(frame);
18957             return true;
18958           }
18959
18960           //html5 A[download]
18961           if ('download' in a) {
18962             var blob = new Blob(
18963               [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18964               { type: strMimeType }
18965             );
18966             rawFile = URL.createObjectURL(blob);
18967             a.setAttribute('download', fileName);
18968           } else {
18969             rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
18970             a.setAttribute('target', '_blank');
18971           }
18972
18973           a.href = rawFile;
18974           a.setAttribute('style', 'display:none;');
18975           D.body.appendChild(a);
18976           setTimeout(function() {
18977             if (a.click) {
18978               a.click();
18979               // Workaround for Safari 5
18980             } else if (document.createEvent) {
18981               var eventObj = document.createEvent('MouseEvents');
18982               eventObj.initEvent('click', true, true);
18983               a.dispatchEvent(eventObj);
18984             }
18985             D.body.removeChild(a);
18986
18987           }, this.delay);
18988         },
18989
18990         /**
18991          * @ngdoc function
18992          * @name pdfExport
18993          * @methodOf  ui.grid.exporter.service:uiGridExporterService
18994          * @description Exports rows from the grid in pdf format,
18995          * the data exported is selected based on the provided options.
18996          * Note that this function has a dependency on pdfMake, which must
18997          * be installed.  The resulting pdf opens in a new
18998          * browser window.
18999          * @param {Grid} grid the grid from which data should be exported
19000          * @param {string} rowTypes which rows to export, valid values are
19001          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
19002          * uiGridExporterConstants.SELECTED
19003          * @param {string} colTypes which columns to export, valid values are
19004          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
19005          * uiGridExporterConstants.SELECTED
19006          */
19007         pdfExport: function (grid, rowTypes, colTypes) {
19008           var self = this;
19009           this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
19010             var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
19011             var exportData = self.getData(grid, rowTypes, colTypes);
19012             var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
19013
19014             if (self.isIE() || navigator.appVersion.indexOf("Edge") !== -1) {
19015               self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
19016             } else {
19017               pdfMake.createPdf(docDefinition).open();
19018             }
19019           });
19020         },
19021
19022
19023         /**
19024          * @ngdoc function
19025          * @name downloadPdf
19026          * @methodOf  ui.grid.exporter.service:uiGridExporterService
19027          * @description Generates and retrieves the pdf as a blob, then downloads
19028          * it as a file.  Only used in IE, in all other browsers we use the native
19029          * pdfMake.open function to just open the PDF
19030          * @param {string} fileName the filename to give to the pdf, can be set
19031          * through exporterPdfFilename
19032          * @param {object} docDefinition a pdf docDefinition that we can generate
19033          * and get a blob from
19034          */
19035         downloadPDF: function (fileName, docDefinition) {
19036           var D = document;
19037           var a = D.createElement('a');
19038           var strMimeType = 'application/octet-stream;charset=utf-8';
19039           var rawFile;
19040           var ieVersion;
19041
19042           ieVersion = this.isIE(); // This is now a boolean value
19043           var doc = pdfMake.createPdf(docDefinition);
19044           var blob;
19045
19046           doc.getBuffer( function (buffer) {
19047             blob = new Blob([buffer]);
19048
19049             // IE10+
19050             if (navigator.msSaveBlob) {
19051               return navigator.msSaveBlob(
19052                 blob, fileName
19053               );
19054             }
19055
19056             // Previously:  && ieVersion < 10
19057             // ieVersion now returns a boolean for the
19058             // sake of sanity. We just check `msSaveBlob` first.
19059             if (ieVersion) {
19060               var frame = D.createElement('iframe');
19061               document.body.appendChild(frame);
19062
19063               frame.contentWindow.document.open("text/html", "replace");
19064               frame.contentWindow.document.write(blob);
19065               frame.contentWindow.document.close();
19066               frame.contentWindow.focus();
19067               frame.contentWindow.document.execCommand('SaveAs', true, fileName);
19068
19069               document.body.removeChild(frame);
19070               return true;
19071             }
19072           });
19073         },
19074
19075
19076         /**
19077          * @ngdoc function
19078          * @name renderAsPdf
19079          * @methodOf  ui.grid.exporter.service:uiGridExporterService
19080          * @description Renders the data into a pdf, and opens that pdf.
19081          *
19082          * @param {Grid} grid the grid from which data should be exported
19083          * @param {array} exportColumnHeaders an array of column headers,
19084          * where each header is an object with name, width and maybe alignment
19085          * @param {array} exportData an array of rows, where each row is
19086          * an array of column data
19087          * @returns {object} a pdfMake format document definition, ready
19088          * for generation
19089          */
19090         prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
19091           var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
19092
19093           var headerColumns = exportColumnHeaders.map( function( header ) {
19094             return { text: header.displayName, style: 'tableHeader' };
19095           });
19096
19097           var stringData = exportData.map(this.formatRowAsPdf(this));
19098
19099           var allData = [headerColumns].concat(stringData);
19100
19101           var docDefinition = {
19102             pageOrientation: grid.options.exporterPdfOrientation,
19103             pageSize: grid.options.exporterPdfPageSize,
19104             content: [{
19105               style: 'tableStyle',
19106               table: {
19107                 headerRows: 1,
19108                 widths: headerWidths,
19109                 body: allData
19110               }
19111             }],
19112             styles: {
19113               tableStyle: grid.options.exporterPdfTableStyle,
19114               tableHeader: grid.options.exporterPdfTableHeaderStyle
19115             },
19116             defaultStyle: grid.options.exporterPdfDefaultStyle
19117           };
19118
19119           if ( grid.options.exporterPdfLayout ){
19120             docDefinition.layout = grid.options.exporterPdfLayout;
19121           }
19122
19123           if ( grid.options.exporterPdfHeader ){
19124             docDefinition.header = grid.options.exporterPdfHeader;
19125           }
19126
19127           if ( grid.options.exporterPdfFooter ){
19128             docDefinition.footer = grid.options.exporterPdfFooter;
19129           }
19130
19131           if ( grid.options.exporterPdfCustomFormatter ){
19132             docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
19133           }
19134           return docDefinition;
19135
19136         },
19137
19138
19139         /**
19140          * @ngdoc function
19141          * @name calculatePdfHeaderWidths
19142          * @methodOf  ui.grid.exporter.service:uiGridExporterService
19143          * @description Determines the column widths base on the
19144          * widths we got from the grid.  If the column is drawn
19145          * then we have a drawnWidth.  If the column is not visible
19146          * then we have '*', 'x%' or a width.  When columns are
19147          * not visible they don't contribute to the overall gridWidth,
19148          * so we need to adjust to allow for extra columns
19149          *
19150          * Our basic heuristic is to take the current gridWidth, plus
19151          * numeric columns and call this the base gridwidth.
19152          *
19153          * To that we add 100 for any '*' column, and x% of the base gridWidth
19154          * for any column that is a %
19155          *
19156          * @param {Grid} grid the grid from which data should be exported
19157          * @param {array} exportHeaders array of header information
19158          * @returns {object} an array of header widths
19159          */
19160         calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
19161           var baseGridWidth = 0;
19162           exportHeaders.forEach( function(value){
19163             if (typeof(value.width) === 'number'){
19164               baseGridWidth += value.width;
19165             }
19166           });
19167
19168           var extraColumns = 0;
19169           exportHeaders.forEach( function(value){
19170             if (value.width === '*'){
19171               extraColumns += 100;
19172             }
19173             if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
19174               var percent = parseInt(value.width.match(/(\d)*%/)[0]);
19175
19176               value.width = baseGridWidth * percent / 100;
19177               extraColumns += value.width;
19178             }
19179           });
19180
19181           var gridWidth = baseGridWidth + extraColumns;
19182
19183           return exportHeaders.map(function( header ) {
19184             return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
19185           });
19186
19187         },
19188
19189         /**
19190          * @ngdoc function
19191          * @name formatRowAsPdf
19192          * @methodOf  ui.grid.exporter.service:uiGridExporterService
19193          * @description Renders a row in a format consumable by PDF,
19194          * mainly meaning casting everything to a string
19195          * @param {exporterService} exporter pass in exporter
19196          * @param {array} row the row to be turned into a csv string
19197          * @returns {string} a csv-ified version of the row
19198          */
19199         formatRowAsPdf: function ( exporter ) {
19200           return function( row ) {
19201             return row.map(exporter.formatFieldAsPdfString);
19202           };
19203         },
19204
19205
19206         /**
19207          * @ngdoc function
19208          * @name formatFieldAsCsv
19209          * @methodOf  ui.grid.exporter.service:uiGridExporterService
19210          * @description Renders a single field as a pdf-able field, which
19211          * is different from a csv field only in that strings don't have quotes
19212          * around them
19213          * @param {field} field the field to be turned into a pdf string,
19214          * may be of any type
19215          * @returns {string} a string-ified version of the field
19216          */
19217         formatFieldAsPdfString: function (field) {
19218           var returnVal;
19219           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
19220             returnVal = '';
19221           } else if (typeof(field.value) === 'number') {
19222             returnVal = field.value.toString();
19223           } else if (typeof(field.value) === 'boolean') {
19224             returnVal = (field.value ? 'TRUE' : 'FALSE') ;
19225           } else if (typeof(field.value) === 'string') {
19226             returnVal = field.value.replace(/"/g,'""');
19227           } else {
19228             returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
19229           }
19230
19231           if (field.alignment && typeof(field.alignment) === 'string' ){
19232             returnVal = { text: returnVal, alignment: field.alignment };
19233           }
19234
19235           return returnVal;
19236         }
19237       };
19238
19239       return service;
19240
19241     }
19242   ]);
19243
19244   /**
19245    *  @ngdoc directive
19246    *  @name ui.grid.exporter.directive:uiGridExporter
19247    *  @element div
19248    *  @restrict A
19249    *
19250    *  @description Adds exporter features to grid
19251    *
19252    *  @example
19253    <example module="app">
19254    <file name="app.js">
19255    var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
19256
19257    app.controller('MainCtrl', ['$scope', function ($scope) {
19258       $scope.data = [
19259         { name: 'Bob', title: 'CEO' },
19260             { name: 'Frank', title: 'Lowly Developer' }
19261       ];
19262
19263       $scope.gridOptions = {
19264         enableGridMenu: true,
19265         exporterMenuCsv: false,
19266         columnDefs: [
19267           {name: 'name', enableCellEdit: true},
19268           {name: 'title', enableCellEdit: true}
19269         ],
19270         data: $scope.data
19271       };
19272     }]);
19273    </file>
19274    <file name="index.html">
19275    <div ng-controller="MainCtrl">
19276    <div ui-grid="gridOptions" ui-grid-exporter></div>
19277    </div>
19278    </file>
19279    </example>
19280    */
19281   module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
19282     function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
19283       return {
19284         replace: true,
19285         priority: 0,
19286         require: '^uiGrid',
19287         scope: false,
19288         link: function ($scope, $elm, $attrs, uiGridCtrl) {
19289           uiGridExporterService.initializeGrid(uiGridCtrl.grid);
19290           uiGridCtrl.grid.exporter.$scope = $scope;
19291         }
19292       };
19293     }
19294   ]);
19295 })();
19296
19297 (function () {
19298   'use strict';
19299
19300   /**
19301    * @ngdoc overview
19302    * @name ui.grid.grouping
19303    * @description
19304    *
19305    * # ui.grid.grouping
19306    *
19307    * <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>
19308    *
19309    * This module provides grouping of rows based on the data in them, similar
19310    * in concept to excel grouping.  You can group multiple columns, resulting in
19311    * nested grouping.
19312    *
19313    * In concept this feature is similar to sorting + grid footer/aggregation, it
19314    * sorts the data based on the grouped columns, then creates group rows that
19315    * reflect a break in the data.  Each of those group rows can have aggregations for
19316    * the data within that group.
19317    *
19318    * This feature leverages treeBase to provide the tree functionality itself,
19319    * the key thing this feature does therefore is to set treeLevels on the rows
19320    * and insert the group headers.
19321    *
19322    * Design information:
19323    * -------------------
19324    *
19325    * Each column will get new menu items - group by, and aggregate by.  Group by
19326    * will cause this column to be sorted (if not already), and will move this column
19327    * to the front of the sorted columns (i.e. grouped columns take precedence over
19328    * sorted columns).  It will respect the sort order already set if there is one,
19329    * and it will allow the sorting logic to change that sort order, it just forces
19330    * the column to the front of the sorting.  You can group by multiple columns, the
19331    * logic will add this column to the sorting after any already grouped columns.
19332    *
19333    * Once a grouping is defined, grouping logic is added to the rowsProcessors.  This
19334    * will process the rows, identifying a break in the data value, and inserting a grouping row.
19335    * Grouping rows have specific attributes on them:
19336    *
19337    *  - internalRow = true: tells us that this isn't a real row, so we can ignore it
19338    *    from any processing that it looking at core data rows.  This is used by the core
19339    *    logic (or will be one day), as it's not grouping specific
19340    *  - groupHeader = true: tells us this is a groupHeader.  This is used by the grouping logic
19341    *    to know if this is a groupHeader row or not
19342    *
19343    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
19344    * row order or filtering or anything like that is changed.  In order to avoid the row instantiation
19345    * time, and to preserve state across invocations, we hold a cache of the rows that we created
19346    * last time, and we use them again this time if we can.
19347    *
19348    * By default rows are collapsed, which means all data rows have their visible property
19349    * set to false, and only level 0 group rows are set to visible.
19350    *
19351    * <br/>
19352    * <br/>
19353    *
19354    * <div doc-module-components="ui.grid.grouping"></div>
19355    */
19356
19357   var module = angular.module('ui.grid.grouping', ['ui.grid', 'ui.grid.treeBase']);
19358
19359   /**
19360    *  @ngdoc object
19361    *  @name ui.grid.grouping.constant:uiGridGroupingConstants
19362    *
19363    *  @description constants available in grouping module, this includes
19364    *  all the constants declared in the treeBase module (these are manually copied
19365    *  as there isn't an easy way to include constants in another constants file, and
19366    *  we don't want to make users include treeBase)
19367    *
19368    */
19369   module.constant('uiGridGroupingConstants', {
19370     featureName: "grouping",
19371     rowHeaderColName: 'treeBaseRowHeaderCol',
19372     EXPANDED: 'expanded',
19373     COLLAPSED: 'collapsed',
19374     aggregation: {
19375       COUNT: 'count',
19376       SUM: 'sum',
19377       MAX: 'max',
19378       MIN: 'min',
19379       AVG: 'avg'
19380     }
19381   });
19382
19383   /**
19384    *  @ngdoc service
19385    *  @name ui.grid.grouping.service:uiGridGroupingService
19386    *
19387    *  @description Services for grouping features
19388    */
19389   module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
19390   function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
19391
19392     var service = {
19393
19394       initializeGrid: function (grid, $scope) {
19395         uiGridTreeBaseService.initializeGrid( grid, $scope );
19396
19397         //add feature namespace and any properties to grid for needed
19398         /**
19399          *  @ngdoc object
19400          *  @name ui.grid.grouping.grid:grouping
19401          *
19402          *  @description Grid properties and functions added for grouping
19403          */
19404         grid.grouping = {};
19405
19406         /**
19407          *  @ngdoc property
19408          *  @propertyOf ui.grid.grouping.grid:grouping
19409          *  @name groupHeaderCache
19410          *
19411          *  @description Cache that holds the group header rows we created last time, we'll
19412          *  reuse these next time, not least because they hold our expanded states.
19413          *
19414          *  We need to take care with these that they don't become a memory leak, we
19415          *  create a new cache each time using the values from the old cache.  This works
19416          *  so long as we're creating group rows for invisible rows as well.
19417          *
19418          *  The cache is a nested hash, indexed on the value we grouped by.  So if we
19419          *  grouped by gender then age, we'd maybe have something like:
19420          *  ```
19421          *    {
19422          *      male: {
19423          *        row: <pointer to the old row>,
19424          *        children: {
19425          *          22: { row: <pointer to the old row> },
19426          *          31: { row: <pointer to the old row> }
19427          *      },
19428          *      female: {
19429          *        row: <pointer to the old row>,
19430          *        children: {
19431          *          28: { row: <pointer to the old row> },
19432          *          55: { row: <pointer to the old row> }
19433          *      }
19434          *    }
19435          *  ```
19436          *
19437          *  We create new rows for any missing rows, this means that they come in as collapsed.
19438          *
19439          */
19440         grid.grouping.groupHeaderCache = {};
19441
19442         service.defaultGridOptions(grid.options);
19443
19444         grid.registerRowsProcessor(service.groupRows, 400);
19445
19446         grid.registerColumnBuilder( service.groupingColumnBuilder);
19447
19448         grid.registerColumnsProcessor(service.groupingColumnProcessor, 400);
19449
19450         /**
19451          *  @ngdoc object
19452          *  @name ui.grid.grouping.api:PublicApi
19453          *
19454          *  @description Public Api for grouping feature
19455          */
19456         var publicApi = {
19457           events: {
19458             grouping: {
19459               /**
19460                * @ngdoc event
19461                * @eventOf ui.grid.grouping.api:PublicApi
19462                * @name aggregationChanged
19463                * @description raised whenever aggregation is changed, added or removed from a column
19464                *
19465                * <pre>
19466                *      gridApi.grouping.on.aggregationChanged(scope,function(col){})
19467                * </pre>
19468                * @param {gridCol} col the column which on which aggregation changed. The aggregation
19469                * type is available as `col.treeAggregation.type`
19470                */
19471               aggregationChanged: {},
19472               /**
19473                * @ngdoc event
19474                * @eventOf ui.grid.grouping.api:PublicApi
19475                * @name groupingChanged
19476                * @description raised whenever the grouped columns changes
19477                *
19478                * <pre>
19479                *      gridApi.grouping.on.groupingChanged(scope,function(col){})
19480                * </pre>
19481                * @param {gridCol} col the column which on which grouping changed. The new grouping is
19482                * available as `col.grouping`
19483                */
19484               groupingChanged: {}
19485             }
19486           },
19487           methods: {
19488             grouping: {
19489               /**
19490                * @ngdoc function
19491                * @name getGrouping
19492                * @methodOf  ui.grid.grouping.api:PublicApi
19493                * @description Get the grouping configuration for this grid,
19494                * used by the saveState feature.  Adds expandedState to the information
19495                * provided by the internal getGrouping, and removes any aggregations that have a source
19496                * of grouping (i.e. will be automatically reapplied when we regroup the column)
19497                * Returned grouping is an object
19498                *   `{ grouping: groupArray, treeAggregations: aggregateArray, expandedState: hash }`
19499                * where grouping contains an array of objects:
19500                *   `{ field: column.field, colName: column.name, groupPriority: column.grouping.groupPriority }`
19501                * and aggregations contains an array of objects:
19502                *   `{ field: column.field, colName: column.name, aggregation: column.grouping.aggregation }`
19503                * and expandedState is a hash of the currently expanded nodes
19504                *
19505                * The groupArray will be sorted by groupPriority.
19506                *
19507                * @param {boolean} getExpanded whether or not to return the expanded state
19508                * @returns {object} grouping configuration
19509                */
19510               getGrouping: function ( getExpanded ) {
19511                 var grouping = service.getGrouping(grid);
19512
19513                 grouping.grouping.forEach( function( group ) {
19514                   group.colName = group.col.name;
19515                   delete group.col;
19516                 });
19517
19518                 grouping.aggregations.forEach( function( aggregation ) {
19519                   aggregation.colName = aggregation.col.name;
19520                   delete aggregation.col;
19521                 });
19522
19523                 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
19524                   return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
19525                 });
19526
19527                 if ( getExpanded ){
19528                   grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
19529                 }
19530
19531                 return grouping;
19532               },
19533
19534               /**
19535                * @ngdoc function
19536                * @name setGrouping
19537                * @methodOf  ui.grid.grouping.api:PublicApi
19538                * @description Set the grouping configuration for this grid,
19539                * used by the saveState feature, but can also be used by any
19540                * user to specify a combined grouping and aggregation configuration
19541                * @param {object} config the config you want to apply, in the format
19542                * provided out by getGrouping
19543                */
19544               setGrouping: function ( config ) {
19545                 service.setGrouping(grid, config);
19546               },
19547
19548               /**
19549                * @ngdoc function
19550                * @name groupColumn
19551                * @methodOf  ui.grid.grouping.api:PublicApi
19552                * @description Adds this column to the existing grouping, at the end of the priority order.
19553                * If the column doesn't have a sort, adds one, by default ASC
19554                *
19555                * This column will move to the left of any non-group columns, the
19556                * move is handled in a columnProcessor, so gets called as part of refresh
19557                *
19558                * @param {string} columnName the name of the column we want to group
19559                */
19560               groupColumn: function( columnName ) {
19561                 var column = grid.getColumn(columnName);
19562                 service.groupColumn(grid, column);
19563               },
19564
19565               /**
19566                * @ngdoc function
19567                * @name ungroupColumn
19568                * @methodOf  ui.grid.grouping.api:PublicApi
19569                * @description Removes the groupPriority from this column.  If the
19570                * column was previously aggregated the aggregation will come back.
19571                * The sort will remain.
19572                *
19573                * This column will move to the right of any other group columns, the
19574                * move is handled in a columnProcessor, so gets called as part of refresh
19575                *
19576                * @param {string} columnName the name of the column we want to ungroup
19577                */
19578               ungroupColumn: function( columnName ) {
19579                 var column = grid.getColumn(columnName);
19580                 service.ungroupColumn(grid, column);
19581               },
19582
19583               /**
19584                * @ngdoc function
19585                * @name clearGrouping
19586                * @methodOf  ui.grid.grouping.api:PublicApi
19587                * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
19588                * as we don't know whether that sorting was added by grouping or was there beforehand
19589                *
19590                */
19591               clearGrouping: function() {
19592                 service.clearGrouping(grid);
19593               },
19594
19595               /**
19596                * @ngdoc function
19597                * @name aggregateColumn
19598                * @methodOf  ui.grid.grouping.api:PublicApi
19599                * @description Sets the aggregation type on a column, if the
19600                * column is currently grouped then it removes the grouping first.
19601                * If the aggregationDef is null then will result in the aggregation
19602                * being removed
19603                *
19604                * @param {string} columnName the column we want to aggregate
19605                * @param {string} or {function} aggregationDef one of the recognised types
19606                * from uiGridGroupingConstants or a custom aggregation function.
19607                * @param {string} aggregationLabel (optional) The label to use for this aggregation.
19608                */
19609               aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
19610                 var column = grid.getColumn(columnName);
19611                 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
19612               }
19613
19614             }
19615           }
19616         };
19617
19618         grid.api.registerEventsFromObject(publicApi.events);
19619
19620         grid.api.registerMethodsFromObject(publicApi.methods);
19621
19622         grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
19623
19624       },
19625
19626       defaultGridOptions: function (gridOptions) {
19627         //default option to true unless it was explicitly set to false
19628         /**
19629          *  @ngdoc object
19630          *  @name ui.grid.grouping.api:GridOptions
19631          *
19632          *  @description GridOptions for grouping feature, these are available to be
19633          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
19634          */
19635
19636         /**
19637          *  @ngdoc object
19638          *  @name enableGrouping
19639          *  @propertyOf  ui.grid.grouping.api:GridOptions
19640          *  @description Enable row grouping for entire grid.
19641          *  <br/>Defaults to true
19642          */
19643         gridOptions.enableGrouping = gridOptions.enableGrouping !== false;
19644
19645         /**
19646          *  @ngdoc object
19647          *  @name groupingShowCounts
19648          *  @propertyOf  ui.grid.grouping.api:GridOptions
19649          *  @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
19650          *  sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
19651          *  to break, since the group header rows will always be a string with groupingShowCounts enabled.
19652          *  <br/>Defaults to true except on columns of types 'date' and 'object'
19653          */
19654         gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
19655
19656         /**
19657          *  @ngdoc object
19658          *  @name groupingNullLabel
19659          *  @propertyOf  ui.grid.grouping.api:GridOptions
19660          *  @description The string to use for the grouping header row label on rows which contain a null or undefined value in the grouped column.
19661          *  <br/>Defaults to "Null"
19662          */
19663         gridOptions.groupingNullLabel = typeof(gridOptions.groupingNullLabel) === 'undefined' ? 'Null' : gridOptions.groupingNullLabel;
19664
19665         /**
19666          *  @ngdoc object
19667          *  @name enableGroupHeaderSelection
19668          *  @propertyOf  ui.grid.grouping.api:GridOptions
19669          *  @description Allows group header rows to be selected.
19670          *  <br/>Defaults to false
19671          */
19672         gridOptions.enableGroupHeaderSelection = gridOptions.enableGroupHeaderSelection === true;
19673       },
19674
19675
19676       /**
19677        * @ngdoc function
19678        * @name groupingColumnBuilder
19679        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19680        * @description Sets the grouping defaults based on the columnDefs
19681        *
19682        * @param {object} colDef columnDef we're basing on
19683        * @param {GridCol} col the column we're to update
19684        * @param {object} gridOptions the options we should use
19685        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
19686        */
19687       groupingColumnBuilder: function (colDef, col, gridOptions) {
19688         /**
19689          *  @ngdoc object
19690          *  @name ui.grid.grouping.api:ColumnDef
19691          *
19692          *  @description ColumnDef for grouping feature, these are available to be
19693          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
19694          */
19695
19696         /**
19697          *  @ngdoc object
19698          *  @name enableGrouping
19699          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19700          *  @description Enable grouping on this column
19701          *  <br/>Defaults to true.
19702          */
19703         if (colDef.enableGrouping === false){
19704           return;
19705         }
19706
19707         /**
19708          *  @ngdoc object
19709          *  @name grouping
19710          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19711          *  @description Set the grouping for a column.  Format is:
19712          *  ```
19713          *    {
19714          *      groupPriority: <number, starts at 0, if less than 0 or undefined then we're aggregating in this column>
19715          *    }
19716          *  ```
19717          *
19718          *  **Note that aggregation used to be included in grouping, but is now separately set on the column via treeAggregation
19719          *  setting in treeBase**
19720          *
19721          *  We group in the priority order given, this will also put these columns to the high order of the sort irrespective
19722          *  of the sort priority given them.  If there is no sort defined then we sort ascending, if there is a sort defined then
19723          *  we use that sort.
19724          *
19725          *  If the groupPriority is undefined or less than 0, then we expect to be aggregating, and we look at the
19726          *  aggregation types to determine what sort of aggregation we can do.  Values are in the constants file, but
19727          *  include SUM, COUNT, MAX, MIN
19728          *
19729          *  groupPriorities should generally be sequential, if they're not then the next time getGrouping is called
19730          *  we'll renumber them to be sequential.
19731          *  <br/>Defaults to undefined.
19732          */
19733
19734         if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
19735           col.grouping = angular.copy(colDef.grouping);
19736           if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
19737             col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19738             col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19739           }
19740         } else if (typeof(col.grouping) === 'undefined'){
19741           col.grouping = {};
19742         }
19743
19744         if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
19745           col.suppressRemoveSort = true;
19746         }
19747
19748         var groupColumn = {
19749           name: 'ui.grid.grouping.group',
19750           title: i18nService.get().grouping.group,
19751           icon: 'ui-grid-icon-indent-right',
19752           shown: function () {
19753             return typeof(this.context.col.grouping) === 'undefined' ||
19754                    typeof(this.context.col.grouping.groupPriority) === 'undefined' ||
19755                    this.context.col.grouping.groupPriority < 0;
19756           },
19757           action: function () {
19758             service.groupColumn( this.context.col.grid, this.context.col );
19759           }
19760         };
19761
19762         var ungroupColumn = {
19763           name: 'ui.grid.grouping.ungroup',
19764           title: i18nService.get().grouping.ungroup,
19765           icon: 'ui-grid-icon-indent-left',
19766           shown: function () {
19767             return typeof(this.context.col.grouping) !== 'undefined' &&
19768                    typeof(this.context.col.grouping.groupPriority) !== 'undefined' &&
19769                    this.context.col.grouping.groupPriority >= 0;
19770           },
19771           action: function () {
19772             service.ungroupColumn( this.context.col.grid, this.context.col );
19773           }
19774         };
19775
19776         var aggregateRemove = {
19777           name: 'ui.grid.grouping.aggregateRemove',
19778           title: i18nService.get().grouping.aggregate_remove,
19779           shown: function () {
19780             return typeof(this.context.col.treeAggregationFn) !== 'undefined';
19781           },
19782           action: function () {
19783             service.aggregateColumn( this.context.col.grid, this.context.col, null);
19784           }
19785         };
19786
19787         // generic adder for the aggregation menus, which follow a pattern
19788         var addAggregationMenu = function(type, title){
19789           title = title || i18nService.get().grouping['aggregate_' + type] || type;
19790           var menuItem = {
19791             name: 'ui.grid.grouping.aggregate' + type,
19792             title: title,
19793             shown: function () {
19794               return typeof(this.context.col.treeAggregation) === 'undefined' ||
19795                      typeof(this.context.col.treeAggregation.type) === 'undefined' ||
19796                      this.context.col.treeAggregation.type !== type;
19797             },
19798             action: function () {
19799               service.aggregateColumn( this.context.col.grid, this.context.col, type);
19800             }
19801           };
19802
19803           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregate' + type)) {
19804             col.menuItems.push(menuItem);
19805           }
19806         };
19807
19808         /**
19809          *  @ngdoc object
19810          *  @name groupingShowGroupingMenu
19811          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19812          *  @description Show the grouping (group and ungroup items) menu on this column
19813          *  <br/>Defaults to true.
19814          */
19815         if ( col.colDef.groupingShowGroupingMenu !== false ){
19816           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
19817             col.menuItems.push(groupColumn);
19818           }
19819
19820           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.ungroup')) {
19821             col.menuItems.push(ungroupColumn);
19822           }
19823         }
19824
19825
19826         /**
19827          *  @ngdoc object
19828          *  @name groupingShowAggregationMenu
19829          *  @propertyOf  ui.grid.grouping.api:ColumnDef
19830          *  @description Show the aggregation menu on this column
19831          *  <br/>Defaults to true.
19832          */
19833         if ( col.colDef.groupingShowAggregationMenu !== false ){
19834           angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
19835             addAggregationMenu(name);
19836           });
19837           angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
19838             addAggregationMenu(name, aggregationDef.menuTitle);
19839           });
19840
19841           if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregateRemove')) {
19842             col.menuItems.push(aggregateRemove);
19843           }
19844         }
19845       },
19846
19847
19848
19849
19850       /**
19851        * @ngdoc function
19852        * @name groupingColumnProcessor
19853        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19854        * @description Moves the columns around based on which are grouped
19855        *
19856        * @param {array} columns the columns to consider rendering
19857        * @param {array} rows the grid rows, which we don't use but are passed to us
19858        * @returns {array} updated columns array
19859        */
19860       groupingColumnProcessor: function( columns, rows ) {
19861         var grid = this;
19862
19863         columns = service.moveGroupColumns(this, columns, rows);
19864         return columns;
19865       },
19866
19867       /**
19868        * @ngdoc function
19869        * @name groupedFinalizerFn
19870        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19871        * @description Used on group columns to display the rendered value and optionally
19872        * display the count of rows.
19873        *
19874        * @param {aggregation} the aggregation entity for a grouped column
19875        */
19876       groupedFinalizerFn: function( aggregation ){
19877         var col = this;
19878
19879         if ( typeof(aggregation.groupVal) !== 'undefined') {
19880           aggregation.rendered = aggregation.groupVal;
19881           if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' && col.colDef.type !== 'object' ){
19882             aggregation.rendered += (' (' + aggregation.value + ')');
19883           }
19884         } else {
19885           aggregation.rendered = null;
19886         }
19887       },
19888
19889       /**
19890        * @ngdoc function
19891        * @name moveGroupColumns
19892        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19893        * @description Moves the column order so that the grouped columns are lined up
19894        * to the left (well, unless you're RTL, then it's the right).  By doing this in
19895        * the columnsProcessor, we make it transient - when the column is ungrouped it'll
19896        * go back to where it was.
19897        *
19898        * Does nothing if the option `moveGroupColumns` is set to false.
19899        *
19900        * @param {Grid} grid grid object
19901        * @param {array} columns the columns that we should process/move
19902        * @param {array} rows the grid rows
19903        * @returns {array} updated columns
19904        */
19905       moveGroupColumns: function( grid, columns, rows ){
19906         if ( grid.options.moveGroupColumns === false){
19907           return columns;
19908         }
19909
19910         columns.forEach( function(column, index){
19911           // position used to make stable sort in moveGroupColumns
19912           column.groupingPosition = index;
19913         });
19914
19915         columns.sort(function(a, b){
19916           var a_group, b_group;
19917           if (a.isRowHeader){
19918             a_group = a.headerPriority;
19919           }
19920           else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
19921             a_group = null;
19922           } else {
19923             a_group = a.grouping.groupPriority;
19924           }
19925
19926           if (b.isRowHeader){
19927             b_group = b.headerPriority;
19928           }
19929           else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
19930             b_group = null;
19931           } else {
19932             b_group = b.grouping.groupPriority;
19933           }
19934
19935           // groups get sorted to the top
19936           if ( a_group !== null && b_group === null) { return -1; }
19937           if ( b_group !== null && a_group === null) { return 1; }
19938           if ( a_group !== null && b_group !== null) {return a_group - b_group; }
19939
19940           return a.groupingPosition - b.groupingPosition;
19941         });
19942
19943         columns.forEach( function(column, index) {
19944           delete column.groupingPosition;
19945         });
19946
19947         return columns;
19948       },
19949
19950
19951       /**
19952        * @ngdoc function
19953        * @name groupColumn
19954        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19955        * @description Adds this column to the existing grouping, at the end of the priority order.
19956        * If the column doesn't have a sort, adds one, by default ASC
19957        *
19958        * This column will move to the left of any non-group columns, the
19959        * move is handled in a columnProcessor, so gets called as part of refresh
19960        *
19961        * @param {Grid} grid grid object
19962        * @param {GridCol} column the column we want to group
19963        */
19964       groupColumn: function( grid, column){
19965         if ( typeof(column.grouping) === 'undefined' ){
19966           column.grouping = {};
19967         }
19968
19969         // set the group priority to the next number in the hierarchy
19970         var existingGrouping = service.getGrouping( grid );
19971         column.grouping.groupPriority = existingGrouping.grouping.length;
19972
19973         // save sort in order to restore it when column is ungrouped
19974         column.previousSort = angular.copy(column.sort);
19975
19976         // add sort if not present
19977         if ( !column.sort ){
19978           column.sort = { direction: uiGridConstants.ASC };
19979         } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
19980           column.sort.direction = uiGridConstants.ASC;
19981         }
19982
19983         column.treeAggregation = { type: uiGridGroupingConstants.aggregation.COUNT, source: 'grouping' };
19984         column.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19985         column.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19986
19987         grid.api.grouping.raise.groupingChanged(column);
19988         // This indirectly calls service.tidyPriorities( grid );
19989         grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
19990
19991         grid.queueGridRefresh();
19992       },
19993
19994
19995        /**
19996        * @ngdoc function
19997        * @name ungroupColumn
19998        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
19999        * @description Removes the groupPriority from this column.  If the
20000        * column was previously aggregated the aggregation will come back.
20001        * The sort will remain.
20002        *
20003        * This column will move to the right of any other group columns, the
20004        * move is handled in a columnProcessor, so gets called as part of refresh
20005        *
20006        * @param {Grid} grid grid object
20007        * @param {GridCol} column the column we want to ungroup
20008        */
20009       ungroupColumn: function( grid, column){
20010         if ( typeof(column.grouping) === 'undefined' ){
20011           return;
20012         }
20013
20014         delete column.grouping.groupPriority;
20015         delete column.treeAggregation;
20016         delete column.customTreeAggregationFinalizer;
20017
20018         if (column.previousSort) {
20019           column.sort = column.previousSort;
20020           delete column.previousSort;
20021         }
20022
20023         service.tidyPriorities( grid );
20024
20025         grid.api.grouping.raise.groupingChanged(column);
20026         grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
20027
20028         grid.queueGridRefresh();
20029       },
20030
20031       /**
20032        * @ngdoc function
20033        * @name aggregateColumn
20034        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20035        * @description Sets the aggregation type on a column, if the
20036        * column is currently grouped then it removes the grouping first.
20037        *
20038        * @param {Grid} grid grid object
20039        * @param {GridCol} column the column we want to aggregate
20040        * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
20041        */
20042       aggregateColumn: function( grid, column, aggregationType){
20043
20044         if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20045           service.ungroupColumn( grid, column );
20046         }
20047
20048         var aggregationDef = {};
20049         if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
20050           aggregationDef = grid.options.treeCustomAggregations[aggregationType];
20051         } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
20052           aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
20053         }
20054
20055         column.treeAggregation = { type: aggregationType, label:  i18nService.get().aggregation[aggregationDef.label] || aggregationDef.label };
20056         column.treeAggregationFn = aggregationDef.aggregationFn;
20057         column.treeAggregationFinalizerFn = aggregationDef.finalizerFn;
20058
20059         grid.api.grouping.raise.aggregationChanged(column);
20060
20061         grid.queueGridRefresh();
20062       },
20063
20064
20065       /**
20066        * @ngdoc function
20067        * @name setGrouping
20068        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20069        * @description Set the grouping based on a config object, used by the save state feature
20070        * (more specifically, by the restore function in that feature )
20071        *
20072        * @param {Grid} grid grid object
20073        * @param {object} config the config we want to set, same format as that returned by getGrouping
20074        */
20075       setGrouping: function ( grid, config ){
20076         if ( typeof(config) === 'undefined' ){
20077           return;
20078         }
20079
20080         // first remove any existing grouping
20081         service.clearGrouping(grid);
20082
20083         if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
20084           config.grouping.forEach( function( group ) {
20085             var col = grid.getColumn(group.colName);
20086
20087             if ( col ) {
20088               service.groupColumn( grid, col );
20089             }
20090           });
20091         }
20092
20093         if ( config.aggregations && config.aggregations.length ){
20094           config.aggregations.forEach( function( aggregation ) {
20095             var col = grid.getColumn(aggregation.colName);
20096
20097             if ( col ) {
20098               service.aggregateColumn( grid, col, aggregation.aggregation.type );
20099             }
20100           });
20101         }
20102
20103         if ( config.rowExpandedStates ){
20104           service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
20105         }
20106       },
20107
20108
20109       /**
20110        * @ngdoc function
20111        * @name clearGrouping
20112        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20113        * @description Clear any grouped columns and any aggregations.  Doesn't remove sorting,
20114        * as we don't know whether that sorting was added by grouping or was there beforehand
20115        *
20116        * @param {Grid} grid grid object
20117        */
20118       clearGrouping: function( grid ) {
20119         var currentGrouping = service.getGrouping(grid);
20120
20121         if ( currentGrouping.grouping.length > 0 ){
20122           currentGrouping.grouping.forEach( function( group ) {
20123             if (!group.col){
20124               // should have a group.colName if there's no col
20125               group.col = grid.getColumn(group.colName);
20126             }
20127             service.ungroupColumn(grid, group.col);
20128           });
20129         }
20130
20131         if ( currentGrouping.aggregations.length > 0 ){
20132           currentGrouping.aggregations.forEach( function( aggregation ){
20133             if (!aggregation.col){
20134               // should have a group.colName if there's no col
20135               aggregation.col = grid.getColumn(aggregation.colName);
20136             }
20137             service.aggregateColumn(grid, aggregation.col, null);
20138           });
20139         }
20140       },
20141
20142
20143       /**
20144        * @ngdoc function
20145        * @name tidyPriorities
20146        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20147        * @description Renumbers groupPriority and sortPriority such that
20148        * groupPriority is contiguous, and sortPriority either matches
20149        * groupPriority (for group columns), and otherwise is contiguous and
20150        * higher than groupPriority.
20151        *
20152        * @param {Grid} grid grid object
20153        */
20154       tidyPriorities: function( grid ){
20155         // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
20156         if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
20157           grid = this.grid;
20158         }
20159
20160         var groupArray = [];
20161         var sortArray = [];
20162
20163         grid.columns.forEach( function(column, index){
20164           if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20165             groupArray.push(column);
20166           } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
20167             sortArray.push(column);
20168           }
20169         });
20170
20171         groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
20172         groupArray.forEach( function(column, index){
20173           column.grouping.groupPriority = index;
20174           column.suppressRemoveSort = true;
20175           if ( typeof(column.sort) === 'undefined'){
20176             column.sort = {};
20177           }
20178           column.sort.priority = index;
20179         });
20180
20181         var i = groupArray.length;
20182         sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
20183         sortArray.forEach( function(column, index){
20184           column.sort.priority = i;
20185           column.suppressRemoveSort = column.colDef.suppressRemoveSort;
20186           i++;
20187         });
20188       },
20189
20190
20191       /**
20192        * @ngdoc function
20193        * @name groupRows
20194        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20195        * @description The rowProcessor that creates the groupHeaders (i.e. does
20196        * the actual grouping).
20197        *
20198        * Assumes it is always called after the sorting processor, guaranteed by the priority setting
20199        *
20200        * Processes all the rows in order, inserting a groupHeader row whenever there is a change
20201        * in value of a grouped row, based on the sortAlgorithm used for the column.  The group header row
20202        * is looked up in the groupHeaderCache, and used from there if there is one. The entity is reset
20203        * to {} if one is found.
20204        *
20205        * As it processes it maintains a `processingState` array. This records, for each level of grouping we're
20206        * working with, the following information:
20207        * ```
20208        *   {
20209        *     fieldName: name,
20210        *     col: col,
20211        *     initialised: boolean,
20212        *     currentValue: value,
20213        *     currentRow: gridRow,
20214        *   }
20215        * ```
20216        * We look for changes in the currentValue at any of the levels.  Where we find a change we:
20217        *
20218        * - create a new groupHeader row in the array
20219        *
20220        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
20221        * @returns {array} the updated rows, including our new group rows
20222        */
20223       groupRows: function( renderableRows ) {
20224         if (renderableRows.length === 0){
20225           return renderableRows;
20226         }
20227
20228         var grid = this;
20229         grid.grouping.oldGroupingHeaderCache = grid.grouping.groupingHeaderCache || {};
20230         grid.grouping.groupingHeaderCache = {};
20231
20232         var processingState = service.initialiseProcessingState( grid );
20233
20234         // processes each of the fields we are grouping by, checks if the value has changed and inserts a groupHeader
20235         // Broken out as shouldn't create functions in a loop.
20236         var updateProcessingState = function( groupFieldState, stateIndex ) {
20237           var fieldValue = grid.getCellValue(row, groupFieldState.col);
20238
20239           // look for change of value - and insert a header
20240           if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
20241             service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
20242             i++;
20243           }
20244         };
20245
20246         // use a for loop because it's tolerant of the array length changing whilst we go - we can
20247         // manipulate the iterator when we insert groupHeader rows
20248         for (var i = 0; i < renderableRows.length; i++ ){
20249           var row = renderableRows[i];
20250
20251           if ( row.visible ){
20252             processingState.forEach( updateProcessingState );
20253           }
20254         }
20255
20256         delete grid.grouping.oldGroupingHeaderCache;
20257         return renderableRows;
20258       },
20259
20260
20261       /**
20262        * @ngdoc function
20263        * @name initialiseProcessingState
20264        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20265        * @description Creates the processing state array that is used
20266        * for groupRows.
20267        *
20268        * @param {Grid} grid grid object
20269        * @returns {array} an array in the format described in the groupRows method,
20270        * initialised with blank values
20271        */
20272       initialiseProcessingState: function( grid ){
20273         var processingState = [];
20274         var columnSettings = service.getGrouping( grid );
20275
20276         columnSettings.grouping.forEach( function( groupItem, index){
20277           processingState.push({
20278             fieldName: groupItem.field,
20279             col: groupItem.col,
20280             initialised: false,
20281             currentValue: null,
20282             currentRow: null
20283           });
20284         });
20285
20286         return processingState;
20287       },
20288
20289
20290       /**
20291        * @ngdoc function
20292        * @name getGrouping
20293        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20294        * @description Get the grouping settings from the columns.  As a side effect
20295        * this always renumbers the grouping starting at 0
20296        * @param {Grid} grid grid object
20297        * @returns {array} an array of the group fields, in order of priority
20298        */
20299       getGrouping: function( grid ){
20300         var groupArray = [];
20301         var aggregateArray = [];
20302
20303         // get all the grouping
20304         grid.columns.forEach( function(column, columnIndex){
20305           if ( column.grouping ){
20306             if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20307               groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
20308             }
20309           }
20310           if ( column.treeAggregation && column.treeAggregation.type ){
20311             aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
20312           }
20313         });
20314
20315         // sort grouping into priority order
20316         groupArray.sort( function(a, b){
20317           return a.groupPriority - b.groupPriority;
20318         });
20319
20320         // renumber the priority in case it was somewhat messed up, then remove the grouping reference
20321         groupArray.forEach( function( group, index) {
20322           group.grouping.groupPriority = index;
20323           group.groupPriority = index;
20324           delete group.grouping;
20325         });
20326
20327         return { grouping: groupArray, aggregations: aggregateArray };
20328       },
20329
20330
20331       /**
20332        * @ngdoc function
20333        * @name insertGroupHeader
20334        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20335        * @description Create a group header row, and link it to the various configuration
20336        * items that we use.
20337        *
20338        * Look for the row in the oldGroupingHeaderCache, write the row into the new groupingHeaderCache.
20339        *
20340        * @param {Grid} grid grid object
20341        * @param {array} renderableRows the rows that we are processing
20342        * @param {number} rowIndex the row we were up to processing
20343        * @param {array} processingState the current processing state
20344        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header -
20345        * i.e. the column that we want to create a header for
20346        */
20347       insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
20348         // set the value that caused the end of a group into the header row and the processing state
20349         var fieldName = processingState[stateIndex].fieldName;
20350         var col = processingState[stateIndex].col;
20351
20352         var newValue = grid.getCellValue(renderableRows[rowIndex], col);
20353         var newDisplayValue = newValue;
20354         if ( typeof(newValue) === 'undefined' || newValue === null ) {
20355           newDisplayValue = grid.options.groupingNullLabel;
20356         }
20357
20358         var getKeyAsValueForCacheMap = function(key) {
20359           if (angular.isObject(key)) {
20360               return JSON.stringify(key);
20361           } else {
20362               return key;
20363           }
20364         };
20365
20366         var cacheItem = grid.grouping.oldGroupingHeaderCache;
20367         for ( var i = 0; i < stateIndex; i++ ){
20368           if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)] ){
20369             cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
20370           }
20371         }
20372
20373         var headerRow;
20374         if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(newValue)]){
20375           headerRow = cacheItem[getKeyAsValueForCacheMap(newValue)].row;
20376           headerRow.entity = {};
20377         } else {
20378           headerRow = new GridRow( {}, null, grid );
20379           gridClassFactory.rowTemplateAssigner.call(grid, headerRow);
20380         }
20381
20382         headerRow.entity['$$' + processingState[stateIndex].col.uid] = { groupVal: newDisplayValue };
20383         headerRow.treeLevel = stateIndex;
20384         headerRow.groupHeader = true;
20385         headerRow.internalRow = true;
20386         headerRow.enableCellEdit = false;
20387         headerRow.enableSelection = grid.options.enableGroupHeaderSelection;
20388         processingState[stateIndex].initialised = true;
20389         processingState[stateIndex].currentValue = newValue;
20390         processingState[stateIndex].currentRow = headerRow;
20391
20392         // set all processing states below this one to not be initialised - change of this state
20393         // means all those need to start again
20394         service.finaliseProcessingState( processingState, stateIndex + 1);
20395
20396         // insert our new header row
20397         renderableRows.splice(rowIndex, 0, headerRow);
20398
20399         // add our new header row to the cache
20400         cacheItem = grid.grouping.groupingHeaderCache;
20401         for ( i = 0; i < stateIndex; i++ ){
20402           cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
20403         }
20404         cacheItem[getKeyAsValueForCacheMap(newValue)] = { row: headerRow, children: {} };
20405       },
20406
20407
20408       /**
20409        * @ngdoc function
20410        * @name finaliseProcessingState
20411        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20412        * @description Set all processing states lower than the one that had a break in value to
20413        * no longer be initialised.  Render the counts into the entity ready for display.
20414        *
20415        * @param {Grid} grid grid object
20416        * @param {array} processingState the current processing state
20417        * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
20418        * processing states after this need to be finalised
20419        */
20420       finaliseProcessingState: function( processingState, stateIndex ){
20421         for ( var i = stateIndex; i < processingState.length; i++){
20422           processingState[i].initialised = false;
20423           processingState[i].currentRow = null;
20424           processingState[i].currentValue = null;
20425         }
20426       },
20427
20428
20429       /**
20430        * @ngdoc function
20431        * @name getRowExpandedStates
20432        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20433        * @description Extract the groupHeaderCache hash, pulling out only the states.
20434        *
20435        * The example below shows a grid that is grouped by gender then age
20436        *
20437        * <pre>
20438        *   {
20439        *     male: {
20440        *       state: 'expanded',
20441        *       children: {
20442        *         22: { state: 'expanded' },
20443        *         30: { state: 'collapsed' }
20444        *       }
20445        *     },
20446        *     female: {
20447        *       state: 'expanded',
20448        *       children: {
20449        *         28: { state: 'expanded' },
20450        *         55: { state: 'collapsed' }
20451        *       }
20452        *     }
20453        *   }
20454        * </pre>
20455        *
20456        * @param {Grid} grid grid object
20457        * @returns {hash} the expanded states as a hash
20458        */
20459       getRowExpandedStates: function(treeChildren){
20460         if ( typeof(treeChildren) === 'undefined' ){
20461           return {};
20462         }
20463
20464         var newChildren = {};
20465
20466         angular.forEach( treeChildren, function( value, key ){
20467           newChildren[key] = { state: value.row.treeNode.state };
20468           if ( value.children ){
20469             newChildren[key].children = service.getRowExpandedStates( value.children );
20470           } else {
20471             newChildren[key].children = {};
20472           }
20473         });
20474
20475         return newChildren;
20476       },
20477
20478
20479       /**
20480        * @ngdoc function
20481        * @name applyRowExpandedStates
20482        * @methodOf  ui.grid.grouping.service:uiGridGroupingService
20483        * @description Take a hash in the format as created by getRowExpandedStates,
20484        * and apply it to the grid.grouping.groupHeaderCache.
20485        *
20486        * Takes a treeSubset, and applies to a treeSubset - so can be called
20487        * recursively.
20488        *
20489        * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
20490        * the children of that hash
20491        * @returns {hash} expandedStates can be the full expanded states, or children
20492        * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
20493        */
20494       applyRowExpandedStates: function( currentNode, expandedStates ){
20495         if ( typeof(expandedStates) === 'undefined' ){
20496           return;
20497         }
20498
20499         angular.forEach(expandedStates, function( value, key ) {
20500           if ( currentNode[key] ){
20501             currentNode[key].row.treeNode.state = value.state;
20502
20503             if (value.children && currentNode[key].children){
20504               service.applyRowExpandedStates( currentNode[key].children, value.children );
20505             }
20506           }
20507         });
20508       }
20509
20510
20511     };
20512
20513     return service;
20514
20515   }]);
20516
20517
20518   /**
20519    *  @ngdoc directive
20520    *  @name ui.grid.grouping.directive:uiGridGrouping
20521    *  @element div
20522    *  @restrict A
20523    *
20524    *  @description Adds grouping features to grid
20525    *
20526    *  @example
20527    <example module="app">
20528    <file name="app.js">
20529    var app = angular.module('app', ['ui.grid', 'ui.grid.grouping']);
20530
20531    app.controller('MainCtrl', ['$scope', function ($scope) {
20532       $scope.data = [
20533         { name: 'Bob', title: 'CEO' },
20534             { name: 'Frank', title: 'Lowly Developer' }
20535       ];
20536
20537       $scope.columnDefs = [
20538         {name: 'name', enableCellEdit: true},
20539         {name: 'title', enableCellEdit: true}
20540       ];
20541
20542       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
20543     }]);
20544    </file>
20545    <file name="index.html">
20546    <div ng-controller="MainCtrl">
20547    <div ui-grid="gridOptions" ui-grid-grouping></div>
20548    </div>
20549    </file>
20550    </example>
20551    */
20552   module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
20553   function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
20554     return {
20555       replace: true,
20556       priority: 0,
20557       require: '^uiGrid',
20558       scope: false,
20559       compile: function () {
20560         return {
20561           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
20562             if (uiGridCtrl.grid.options.enableGrouping !== false){
20563               uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
20564             }
20565           },
20566           post: function ($scope, $elm, $attrs, uiGridCtrl) {
20567           }
20568         };
20569       }
20570     };
20571   }]);
20572
20573 })();
20574
20575 (function () {
20576   'use strict';
20577
20578   /**
20579    * @ngdoc overview
20580    * @name ui.grid.importer
20581    * @description
20582    *
20583    * # ui.grid.importer
20584    *
20585    * <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>
20586    *
20587    * This module provides the ability to import data into the grid. It
20588    * uses the column defs to work out which data belongs in which column,
20589    * and creates entities from a configured class (typically a $resource).
20590    *
20591    * If the rowEdit feature is enabled, it also calls save on those newly
20592    * created objects, and then displays any errors in the imported data.
20593    *
20594    * Currently the importer imports only CSV and json files, although provision has been
20595    * made to process other file formats, and these can be added over time.
20596    *
20597    * For json files, the properties within each object in the json must match the column names
20598    * (to put it another way, the importer doesn't process the json, it just copies the objects
20599    * within the json into a new instance of the specified object type)
20600    *
20601    * For CSV import, the default column identification relies on each column in the
20602    * header row matching a column.name or column.displayName. Optionally, a column identification
20603    * callback can be used.  This allows matching using other attributes, which is particularly
20604    * useful if your application has internationalised column headings (i.e. the headings that
20605    * the user sees don't match the column names).
20606    *
20607    * The importer makes use of the grid menu as the UI for requesting an
20608    * import.
20609    *
20610    * <div ui-grid-importer></div>
20611    */
20612
20613   var module = angular.module('ui.grid.importer', ['ui.grid']);
20614
20615   /**
20616    *  @ngdoc object
20617    *  @name ui.grid.importer.constant:uiGridImporterConstants
20618    *
20619    *  @description constants available in importer module
20620    */
20621
20622   module.constant('uiGridImporterConstants', {
20623     featureName: 'importer'
20624   });
20625
20626   /**
20627    *  @ngdoc service
20628    *  @name ui.grid.importer.service:uiGridImporterService
20629    *
20630    *  @description Services for importer feature
20631    */
20632   module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
20633     function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
20634
20635       var service = {
20636
20637         initializeGrid: function ($scope, grid) {
20638
20639           //add feature namespace and any properties to grid for needed state
20640           grid.importer = {
20641             $scope: $scope
20642           };
20643
20644           this.defaultGridOptions(grid.options);
20645
20646           /**
20647            *  @ngdoc object
20648            *  @name ui.grid.importer.api:PublicApi
20649            *
20650            *  @description Public Api for importer feature
20651            */
20652           var publicApi = {
20653             events: {
20654               importer: {
20655               }
20656             },
20657             methods: {
20658               importer: {
20659                 /**
20660                  * @ngdoc function
20661                  * @name importFile
20662                  * @methodOf  ui.grid.importer.api:PublicApi
20663                  * @description Imports a file into the grid using the file object
20664                  * provided.  Bypasses the grid menu
20665                  * @param {File} fileObject the file we want to import, as a javascript
20666                  * File object
20667                  */
20668                 importFile: function ( fileObject ) {
20669                   service.importThisFile( grid, fileObject );
20670                 }
20671               }
20672             }
20673           };
20674
20675           grid.api.registerEventsFromObject(publicApi.events);
20676
20677           grid.api.registerMethodsFromObject(publicApi.methods);
20678
20679           if ( grid.options.enableImporter && grid.options.importerShowMenu ){
20680             if ( grid.api.core.addToGridMenu ){
20681               service.addToMenu( grid );
20682             } else {
20683               // order of registration is not guaranteed, register in a little while
20684               $interval( function() {
20685                 if (grid.api.core.addToGridMenu){
20686                   service.addToMenu( grid );
20687                 }
20688               }, 100, 1);
20689             }
20690           }
20691         },
20692
20693
20694         defaultGridOptions: function (gridOptions) {
20695           //default option to true unless it was explicitly set to false
20696           /**
20697            * @ngdoc object
20698            * @name ui.grid.importer.api:GridOptions
20699            *
20700            * @description GridOptions for importer feature, these are available to be
20701            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20702            */
20703
20704           /**
20705            * @ngdoc property
20706            * @propertyOf ui.grid.importer.api:GridOptions
20707            * @name enableImporter
20708            * @description Whether or not importer is enabled.  Automatically set
20709            * to false if the user's browser does not support the required fileApi.
20710            * Otherwise defaults to true.
20711            *
20712            */
20713           if (gridOptions.enableImporter  || gridOptions.enableImporter === undefined) {
20714             if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
20715               gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
20716               gridOptions.enableImporter = false;
20717             } else {
20718               gridOptions.enableImporter = true;
20719             }
20720           } else {
20721             gridOptions.enableImporter = false;
20722           }
20723
20724           /**
20725            * @ngdoc method
20726            * @name importerProcessHeaders
20727            * @methodOf ui.grid.importer.api:GridOptions
20728            * @description A callback function that will process headers using custom
20729            * logic.  Set this callback function if the headers that your user will provide in their
20730            * import file don't necessarily match the grid header or field names.  This might commonly
20731            * occur where your application is internationalised, and therefore the field names
20732            * that the user recognises are in a different language than the field names that
20733            * ui-grid knows about.
20734            *
20735            * Defaults to the internal `processHeaders` method, which seeks to match using both
20736            * displayName and column.name.  Any non-matching columns are discarded.
20737            *
20738            * Your callback routine should respond by processing the header array, and returning an array
20739            * of matching column names.  A null value in any given position means "don't import this column"
20740            *
20741            * <pre>
20742            *      gridOptions.importerProcessHeaders: function( headerArray ) {
20743            *        var myHeaderColumns = [];
20744            *        var thisCol;
20745            *        headerArray.forEach( function( value, index ) {
20746            *          thisCol = mySpecialLookupFunction( value );
20747            *          myHeaderColumns.push( thisCol.name );
20748            *        });
20749            *
20750            *        return myHeaderCols;
20751            *      })
20752            * </pre>
20753            * @param {Grid} grid the grid we're importing into
20754            * @param {array} headerArray an array of the text from the first row of the csv file,
20755            * which you need to match to column.names
20756            * @returns {array} array of matching column names, in the same order as the headerArray
20757            *
20758            */
20759           gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
20760
20761           /**
20762            * @ngdoc method
20763            * @name importerHeaderFilter
20764            * @methodOf ui.grid.importer.api:GridOptions
20765            * @description A callback function that will filter (usually translate) a single
20766            * header.  Used when you want to match the passed in column names to the column
20767            * displayName after the header filter.
20768            *
20769            * Your callback routine needs to return the filtered header value.
20770            * <pre>
20771            *      gridOptions.importerHeaderFilter: function( displayName ) {
20772            *        return $translate.instant( displayName );
20773            *      })
20774            * </pre>
20775            *
20776            * or:
20777            * <pre>
20778            *      gridOptions.importerHeaderFilter: $translate.instant
20779            * </pre>
20780            * @param {string} displayName the displayName that we'd like to translate
20781            * @returns {string} the translated name
20782            *
20783            */
20784           gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
20785
20786           /**
20787            * @ngdoc method
20788            * @name importerErrorCallback
20789            * @methodOf ui.grid.importer.api:GridOptions
20790            * @description A callback function that provides custom error handling, rather
20791            * than the standard grid behaviour of an alert box and a console message.  You
20792            * might use this to internationalise the console log messages, or to write to a
20793            * custom logging routine that returned errors to the server.
20794            *
20795            * <pre>
20796            *      gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
20797            *        myUserDisplayRoutine( errorKey );
20798            *        myLoggingRoutine( consoleMessage, context );
20799            *      })
20800            * </pre>
20801            * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
20802            * in some way
20803            * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
20804            * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
20805            * @param {string} consoleMessage the English console message that importer would have written
20806            * @param {object} context the context data that importer would have appended to that console message,
20807            * often the file content itself or the element that is in error
20808            *
20809            */
20810           if ( !gridOptions.importerErrorCallback ||  typeof(gridOptions.importerErrorCallback) !== 'function' ){
20811             delete gridOptions.importerErrorCallback;
20812           }
20813
20814           /**
20815            * @ngdoc method
20816            * @name importerDataAddCallback
20817            * @methodOf ui.grid.importer.api:GridOptions
20818            * @description A mandatory callback function that adds data to the source data array.  The grid
20819            * generally doesn't add rows to the source data array, it is tidier to handle this through a user
20820            * callback.
20821            *
20822            * <pre>
20823            *      gridOptions.importerDataAddCallback: function( grid, newObjects ) {
20824            *        $scope.myData = $scope.myData.concat( newObjects );
20825            *      })
20826            * </pre>
20827            * @param {Grid} grid the grid we're importing into, may be useful in some way
20828            * @param {array} newObjects an array of new objects that you should add to your data
20829            *
20830            */
20831           if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
20832             gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
20833             gridOptions.enableImporter = false;
20834           }
20835
20836           /**
20837            * @ngdoc object
20838            * @name importerNewObject
20839            * @propertyOf  ui.grid.importer.api:GridOptions
20840            * @description An object on which we call `new` to create each new row before inserting it into
20841            * the data array.  Typically this would be a $resource entity, which means that if you're using
20842            * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
20843            *
20844            * Defaults to a vanilla javascript object
20845            *
20846            * @example
20847            * <pre>
20848            *   gridOptions.importerNewObject = MyRes;
20849            * </pre>
20850            *
20851            */
20852
20853           /**
20854            * @ngdoc property
20855            * @propertyOf ui.grid.importer.api:GridOptions
20856            * @name importerShowMenu
20857            * @description Whether or not to show an item in the grid menu.  Defaults to true.
20858            *
20859            */
20860           gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
20861
20862           /**
20863            * @ngdoc method
20864            * @methodOf ui.grid.importer.api:GridOptions
20865            * @name importerObjectCallback
20866            * @description A callback that massages the data for each object.  For example,
20867            * you might have data stored as a code value, but display the decode.  This callback
20868            * can be used to change the decoded value back into a code.  Defaults to doing nothing.
20869            * @param {Grid} grid in case you need it
20870            * @param {object} newObject the new object as importer has created it, modify it
20871            * then return the modified version
20872            * @returns {object} the modified object
20873            * @example
20874            * <pre>
20875            *   gridOptions.importerObjectCallback = function ( grid, newObject ) {
20876            *     switch newObject.status {
20877            *       case 'Active':
20878            *         newObject.status = 1;
20879            *         break;
20880            *       case 'Inactive':
20881            *         newObject.status = 2;
20882            *         break;
20883            *     }
20884            *     return newObject;
20885            *   };
20886            * </pre>
20887            */
20888           gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
20889         },
20890
20891
20892         /**
20893          * @ngdoc function
20894          * @name addToMenu
20895          * @methodOf  ui.grid.importer.service:uiGridImporterService
20896          * @description Adds import menu item to the grid menu,
20897          * allowing the user to request import of a file
20898          * @param {Grid} grid the grid into which data should be imported
20899          */
20900         addToMenu: function ( grid ) {
20901           grid.api.core.addToGridMenu( grid, [
20902             {
20903               title: i18nService.getSafeText('gridMenu.importerTitle'),
20904               order: 150
20905             },
20906             {
20907               templateUrl: 'ui-grid/importerMenuItemContainer',
20908               action: function ($event) {
20909                 this.grid.api.importer.importAFile( grid );
20910               },
20911               order: 151
20912             }
20913           ]);
20914         },
20915
20916
20917         /**
20918          * @ngdoc function
20919          * @name importThisFile
20920          * @methodOf ui.grid.importer.service:uiGridImporterService
20921          * @description Imports the provided file into the grid using the file object
20922          * provided.  Bypasses the grid menu
20923          * @param {Grid} grid the grid we're importing into
20924          * @param {File} fileObject the file we want to import, as returned from the File
20925          * javascript object
20926          */
20927         importThisFile: function ( grid, fileObject ) {
20928           if (!fileObject){
20929             gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
20930             return;
20931           }
20932
20933           var reader = new FileReader();
20934
20935           switch ( fileObject.type ){
20936             case 'application/json':
20937               reader.onload = service.importJsonClosure( grid );
20938               break;
20939             default:
20940               reader.onload = service.importCsvClosure( grid );
20941               break;
20942           }
20943
20944           reader.readAsText( fileObject );
20945         },
20946
20947
20948         /**
20949          * @ngdoc function
20950          * @name importJson
20951          * @methodOf ui.grid.importer.service:uiGridImporterService
20952          * @description Creates a function that imports a json file into the grid.
20953          * The json data is imported into new objects of type `gridOptions.importerNewObject`,
20954          * and if the rowEdit feature is enabled the rows are marked as dirty
20955          * @param {Grid} grid the grid we want to import into
20956          * @param {FileObject} importFile the file that we want to import, as
20957          * a FileObject
20958          */
20959         importJsonClosure: function( grid ) {
20960           return function( importFile ){
20961             var newObjects = [];
20962             var newObject;
20963
20964             var importArray = service.parseJson( grid, importFile );
20965             if (importArray === null){
20966               return;
20967             }
20968             importArray.forEach(  function( value, index ) {
20969               newObject = service.newObject( grid );
20970               angular.extend( newObject, value );
20971               newObject = grid.options.importerObjectCallback( grid, newObject );
20972               newObjects.push( newObject );
20973             });
20974
20975             service.addObjects( grid, newObjects );
20976
20977           };
20978         },
20979
20980
20981         /**
20982          * @ngdoc function
20983          * @name parseJson
20984          * @methodOf ui.grid.importer.service:uiGridImporterService
20985          * @description Parses a json file, returns the parsed data.
20986          * Displays an error if file doesn't parse
20987          * @param {Grid} grid the grid that we want to import into
20988          * @param {FileObject} importFile the file that we want to import, as
20989          * a FileObject
20990          * @returns {array} array of objects from the imported json
20991          */
20992         parseJson: function( grid, importFile ){
20993           var loadedObjects;
20994           try {
20995             loadedObjects = JSON.parse( importFile.target.result );
20996           } catch (e) {
20997             service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
20998             return;
20999           }
21000
21001           if ( !Array.isArray( loadedObjects ) ){
21002             service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
21003             return [];
21004           } else {
21005             return loadedObjects;
21006           }
21007         },
21008
21009
21010
21011         /**
21012          * @ngdoc function
21013          * @name importCsvClosure
21014          * @methodOf ui.grid.importer.service:uiGridImporterService
21015          * @description Creates a function that imports a csv file into the grid
21016          * (allowing it to be used in the reader.onload event)
21017          * @param {Grid} grid the grid that we want to import into
21018          * @param {FileObject} importFile the file that we want to import, as
21019          * a file object
21020          */
21021         importCsvClosure: function( grid ) {
21022           return function( importFile ){
21023             var importArray = service.parseCsv( importFile );
21024             if ( !importArray || importArray.length < 1 ){
21025               service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
21026               return;
21027             }
21028
21029             var newObjects = service.createCsvObjects( grid, importArray );
21030             if ( !newObjects || newObjects.length === 0 ){
21031               service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
21032               return;
21033             }
21034
21035             service.addObjects( grid, newObjects );
21036           };
21037         },
21038
21039
21040         /**
21041          * @ngdoc function
21042          * @name parseCsv
21043          * @methodOf ui.grid.importer.service:uiGridImporterService
21044          * @description Parses a csv file into an array of arrays, with the first
21045          * array being the headers, and the remaining arrays being the data.
21046          * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
21047          * which is noted as being under the MIT license.  The code is modified to pass the jscs yoda condition
21048          * checker
21049          * @param {FileObject} importFile the file that we want to import, as a
21050          * file object
21051          */
21052         parseCsv: function( importFile ) {
21053           var csv = importFile.target.result;
21054
21055           // use the CSV-JS library to parse
21056           return CSV.parse(csv);
21057         },
21058
21059
21060         /**
21061          * @ngdoc function
21062          * @name createCsvObjects
21063          * @methodOf ui.grid.importer.service:uiGridImporterService
21064          * @description Converts an array of arrays (representing the csv file)
21065          * into a set of objects.  Uses the provided `gridOptions.importerNewObject`
21066          * to create the objects, and maps the header row into the individual columns
21067          * using either `gridOptions.importerProcessHeaders`, or by using a native method
21068          * of matching to either the displayName, column name or column field of
21069          * the columns in the column defs.  The resulting objects will have attributes
21070          * that are named based on the column.field or column.name, in that order.
21071          * @param {Grid} grid the grid that we want to import into
21072          * @param {Array} importArray the data that we want to import, as an array
21073          */
21074         createCsvObjects: function( grid, importArray ){
21075           // pull off header row and turn into headers
21076           var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
21077           if ( !headerMapping || headerMapping.length === 0 ){
21078             service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
21079             return [];
21080           }
21081
21082           var newObjects = [];
21083           var newObject;
21084           importArray.forEach( function( row, index ) {
21085             newObject = service.newObject( grid );
21086             if ( row !== null ){
21087               row.forEach( function( field, index ){
21088                 if ( headerMapping[index] !== null ){
21089                   newObject[ headerMapping[index] ] = field;
21090                 }
21091               });
21092             }
21093             newObject = grid.options.importerObjectCallback( grid, newObject );
21094             newObjects.push( newObject );
21095           });
21096
21097           return newObjects;
21098         },
21099
21100
21101         /**
21102          * @ngdoc function
21103          * @name processHeaders
21104          * @methodOf ui.grid.importer.service:uiGridImporterService
21105          * @description Determines the columns that the header row from
21106          * a csv (or other) file represents.
21107          * @param {Grid} grid the grid we're importing into
21108          * @param {array} headerRow the header row that we wish to match against
21109          * the column definitions
21110          * @returns {array} an array of the attribute names that should be used
21111          * for that column, based on matching the headers or creating the headers
21112          *
21113          */
21114         processHeaders: function( grid, headerRow ) {
21115           var headers = [];
21116           if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
21117             // we are going to create new columnDefs for all these columns, so just remove
21118             // spaces from the names to create fields
21119             headerRow.forEach( function( value, index ) {
21120               headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
21121             });
21122             return headers;
21123           } else {
21124             var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
21125             headerRow.forEach(  function( value, index ) {
21126               if ( lookupHash[value] ) {
21127                 headers.push( lookupHash[value] );
21128               } else if ( lookupHash[ value.toLowerCase() ] ) {
21129                 headers.push( lookupHash[ value.toLowerCase() ] );
21130               } else {
21131                 headers.push( null );
21132               }
21133             });
21134             return headers;
21135           }
21136         },
21137
21138
21139         /**
21140          * @name flattenColumnDefs
21141          * @methodOf ui.grid.importer.service:uiGridImporterService
21142          * @description Runs through the column defs and creates a hash of
21143          * the displayName, name and field, and of each of those values forced to lower case,
21144          * with each pointing to the field or name
21145          * (whichever is present).  Used to lookup column headers and decide what
21146          * attribute name to give to the resulting field.
21147          * @param {Grid} grid the grid we're importing into
21148          * @param {array} columnDefs the columnDefs that we should flatten
21149          * @returns {hash} the flattened version of the column def information, allowing
21150          * us to look up a value by `flattenedHash[ headerValue ]`
21151          */
21152         flattenColumnDefs: function( grid, columnDefs ){
21153           var flattenedHash = {};
21154           columnDefs.forEach(  function( columnDef, index) {
21155             if ( columnDef.name ){
21156               flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
21157               flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
21158             }
21159
21160             if ( columnDef.field ){
21161               flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
21162               flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
21163             }
21164
21165             if ( columnDef.displayName ){
21166               flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
21167               flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
21168             }
21169
21170             if ( columnDef.displayName && grid.options.importerHeaderFilter ){
21171               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
21172               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
21173             }
21174           });
21175
21176           return flattenedHash;
21177         },
21178
21179
21180         /**
21181          * @ngdoc function
21182          * @name addObjects
21183          * @methodOf ui.grid.importer.service:uiGridImporterService
21184          * @description Inserts our new objects into the grid data, and
21185          * sets the rows to dirty if the rowEdit feature is being used
21186          *
21187          * Does this by registering a watch on dataChanges, which essentially
21188          * is waiting on the result of the grid data watch, and downstream processing.
21189          *
21190          * When the callback is called, it deregisters itself - we don't want to run
21191          * again next time data is added.
21192          *
21193          * If we never get called, we deregister on destroy.
21194          *
21195          * @param {Grid} grid the grid we're importing into
21196          * @param {array} newObjects the objects we want to insert into the grid data
21197          * @returns {object} the new object
21198          */
21199         addObjects: function( grid, newObjects, $scope ){
21200           if ( grid.api.rowEdit ){
21201             var dataChangeDereg = grid.registerDataChangeCallback( function() {
21202               grid.api.rowEdit.setRowsDirty( newObjects );
21203               dataChangeDereg();
21204             }, [uiGridConstants.dataChange.ROW] );
21205
21206             grid.importer.$scope.$on( '$destroy', dataChangeDereg );
21207           }
21208
21209           grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
21210
21211         },
21212
21213
21214         /**
21215          * @ngdoc function
21216          * @name newObject
21217          * @methodOf ui.grid.importer.service:uiGridImporterService
21218          * @description Makes a new object based on `gridOptions.importerNewObject`,
21219          * or based on an empty object if not present
21220          * @param {Grid} grid the grid we're importing into
21221          * @returns {object} the new object
21222          */
21223         newObject: function( grid ){
21224           if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
21225             return new grid.options.importerNewObject();
21226           } else {
21227             return {};
21228           }
21229         },
21230
21231
21232         /**
21233          * @ngdoc function
21234          * @name alertError
21235          * @methodOf ui.grid.importer.service:uiGridImporterService
21236          * @description Provides an internationalised user alert for the failure,
21237          * and logs a console message including diagnostic content.
21238          * Optionally, if the the `gridOptions.importerErrorCallback` routine
21239          * is defined, then calls that instead, allowing user specified error routines
21240          * @param {Grid} grid the grid we're importing into
21241          * @param {array} headerRow the header row that we wish to match against
21242          * the column definitions
21243          */
21244         alertError: function( grid, alertI18nToken, consoleMessage, context ){
21245           if ( grid.options.importerErrorCallback ){
21246             grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
21247           } else {
21248             $window.alert(i18nService.getSafeText( alertI18nToken ));
21249             gridUtil.logError(consoleMessage + context );
21250           }
21251         }
21252       };
21253
21254       return service;
21255
21256     }
21257   ]);
21258
21259   /**
21260    *  @ngdoc directive
21261    *  @name ui.grid.importer.directive:uiGridImporter
21262    *  @element div
21263    *  @restrict A
21264    *
21265    *  @description Adds importer features to grid
21266    *
21267    */
21268   module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
21269     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
21270       return {
21271         replace: true,
21272         priority: 0,
21273         require: '^uiGrid',
21274         scope: false,
21275         link: function ($scope, $elm, $attrs, uiGridCtrl) {
21276           uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
21277         }
21278       };
21279     }
21280   ]);
21281
21282   /**
21283    *  @ngdoc directive
21284    *  @name ui.grid.importer.directive:uiGridImporterMenuItem
21285    *  @element div
21286    *  @restrict A
21287    *
21288    *  @description Handles the processing from the importer menu item - once a file is
21289    *  selected
21290    *
21291    */
21292   module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
21293     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
21294       return {
21295         replace: true,
21296         priority: 0,
21297         require: '^uiGrid',
21298         scope: false,
21299         templateUrl: 'ui-grid/importerMenuItem',
21300         link: function ($scope, $elm, $attrs, uiGridCtrl) {
21301           var handleFileSelect = function( event ){
21302             var target = event.srcElement || event.target;
21303
21304             if (target && target.files && target.files.length === 1) {
21305               var fileObject = target.files[0];
21306               uiGridImporterService.importThisFile( grid, fileObject );
21307               target.form.reset();
21308             }
21309           };
21310
21311           var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
21312           var grid = uiGridCtrl.grid;
21313
21314           if ( fileChooser.length !== 1 ){
21315             gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
21316           } else {
21317             fileChooser[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google
21318           }
21319         }
21320       };
21321     }
21322   ]);
21323 })();
21324
21325 (function() {
21326   'use strict';
21327   /**
21328    *  @ngdoc overview
21329    *  @name ui.grid.infiniteScroll
21330    *
21331    *  @description
21332    *
21333    * #ui.grid.infiniteScroll
21334    *
21335    * <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>
21336    *
21337    * This module provides infinite scroll functionality to ui-grid
21338    *
21339    */
21340   var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
21341   /**
21342    *  @ngdoc service
21343    *  @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21344    *
21345    *  @description Service for infinite scroll features
21346    */
21347   module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
21348
21349     var service = {
21350
21351       /**
21352        * @ngdoc function
21353        * @name initializeGrid
21354        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21355        * @description This method register events and methods into grid public API
21356        */
21357
21358       initializeGrid: function(grid, $scope) {
21359         service.defaultGridOptions(grid.options);
21360
21361         if (!grid.options.enableInfiniteScroll){
21362           return;
21363         }
21364
21365         grid.infiniteScroll = { dataLoading: false };
21366         service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
21367           grid.api.core.on.scrollEnd($scope, service.handleScroll);
21368
21369         /**
21370          *  @ngdoc object
21371          *  @name ui.grid.infiniteScroll.api:PublicAPI
21372          *
21373          *  @description Public API for infinite scroll feature
21374          */
21375         var publicApi = {
21376           events: {
21377             infiniteScroll: {
21378
21379               /**
21380                * @ngdoc event
21381                * @name needLoadMoreData
21382                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
21383                * @description This event fires when scroll reaches bottom percentage of grid
21384                * and needs to load data
21385                */
21386
21387               needLoadMoreData: function ($scope, fn) {
21388               },
21389
21390               /**
21391                * @ngdoc event
21392                * @name needLoadMoreDataTop
21393                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
21394                * @description This event fires when scroll reaches top percentage of grid
21395                * and needs to load data
21396                */
21397
21398               needLoadMoreDataTop: function ($scope, fn) {
21399               }
21400             }
21401           },
21402           methods: {
21403             infiniteScroll: {
21404
21405               /**
21406                * @ngdoc function
21407                * @name dataLoaded
21408                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21409                * @description Call this function when you have loaded the additional data
21410                * requested.  You should set scrollUp and scrollDown to indicate
21411                * whether there are still more pages in each direction.
21412                *
21413                * If you call dataLoaded without first calling `saveScrollPercentage` then we will
21414                * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
21415                * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
21416                * on variable speed internet connections.  Using `saveScrollPercentage` as demonstrated in the tutorial
21417                * should give a smoother scrolling experience for users.
21418                *
21419                * See infinite_scroll tutorial for example of usage
21420                * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
21421                * any more infinite scroll events upward
21422                * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
21423                * fire any more infinite scroll events downward
21424                * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted.  If you're
21425                * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
21426                */
21427               dataLoaded: function( scrollUp, scrollDown ) {
21428                 service.setScrollDirections(grid, scrollUp, scrollDown);
21429
21430                 var promise = service.adjustScroll(grid).then(function() {
21431                   grid.infiniteScroll.dataLoading = false;
21432                 });
21433
21434                 return promise;
21435               },
21436
21437               /**
21438                * @ngdoc function
21439                * @name resetScroll
21440                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21441                * @description Call this function when you have taken some action that makes the current
21442                * scroll position invalid.  For example, if you're using external sorting and you've resorted
21443                * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
21444                * you've reused an existing grid for a new data set
21445                *
21446                * You must tell us whether there is data upwards or downwards after the reset
21447                *
21448                * @param {boolean} scrollUp flag that there are pages upwards, fire
21449                * infinite scroll events upward
21450                * @param {boolean} scrollDown flag that there are pages downwards, so
21451                * fire infinite scroll events downward
21452                */
21453               resetScroll: function( scrollUp, scrollDown ) {
21454                 service.setScrollDirections( grid, scrollUp, scrollDown);
21455
21456                 service.adjustInfiniteScrollPosition(grid, 0);
21457               },
21458
21459
21460               /**
21461                * @ngdoc function
21462                * @name saveScrollPercentage
21463                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21464                * @description Saves the scroll percentage and number of visible rows before you adjust the data,
21465                * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
21466                */
21467               saveScrollPercentage: function() {
21468                 grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
21469                 grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
21470               },
21471
21472
21473               /**
21474                * @ngdoc function
21475                * @name dataRemovedTop
21476                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21477                * @description Adjusts the scroll position after you've removed data at the top
21478                * @param {boolean} scrollUp flag that there are pages upwards, fire
21479                * infinite scroll events upward
21480                * @param {boolean} scrollDown flag that there are pages downwards, so
21481                * fire infinite scroll events downward
21482                */
21483               dataRemovedTop: function( scrollUp, scrollDown ) {
21484                 service.dataRemovedTop( grid, scrollUp, scrollDown );
21485               },
21486
21487               /**
21488                * @ngdoc function
21489                * @name dataRemovedBottom
21490                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21491                * @description Adjusts the scroll position after you've removed data at the bottom
21492                * @param {boolean} scrollUp flag that there are pages upwards, fire
21493                * infinite scroll events upward
21494                * @param {boolean} scrollDown flag that there are pages downwards, so
21495                * fire infinite scroll events downward
21496                */
21497               dataRemovedBottom: function( scrollUp, scrollDown ) {
21498                 service.dataRemovedBottom( grid, scrollUp, scrollDown );
21499               },
21500
21501               /**
21502                * @ngdoc function
21503                * @name setScrollDirections
21504                * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21505                * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
21506                * and also sets the grid.suppressParentScroll
21507                * @param {boolean} scrollUp whether there are pages available up - defaults to false
21508                * @param {boolean} scrollDown whether there are pages available down - defaults to true
21509                */
21510               setScrollDirections:  function ( scrollUp, scrollDown ) {
21511                 service.setScrollDirections( grid, scrollUp, scrollDown );
21512               }
21513
21514             }
21515           }
21516         };
21517         grid.api.registerEventsFromObject(publicApi.events);
21518         grid.api.registerMethodsFromObject(publicApi.methods);
21519       },
21520
21521
21522       defaultGridOptions: function (gridOptions) {
21523         //default option to true unless it was explicitly set to false
21524         /**
21525          *  @ngdoc object
21526          *  @name ui.grid.infiniteScroll.api:GridOptions
21527          *
21528          *  @description GridOptions for infinite scroll feature, these are available to be
21529          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21530          */
21531
21532         /**
21533          *  @ngdoc object
21534          *  @name enableInfiniteScroll
21535          *  @propertyOf  ui.grid.infiniteScroll.api:GridOptions
21536          *  @description Enable infinite scrolling for this grid
21537          *  <br/>Defaults to true
21538          */
21539         gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
21540
21541         /**
21542          * @ngdoc property
21543          * @name infiniteScrollRowsFromEnd
21544          * @propertyOf ui.grid.class:GridOptions
21545          * @description This setting controls how close to the end of the dataset a user gets before
21546          * more data is requested by the infinite scroll, whether scrolling up or down.  This allows you to
21547          * 'prefetch' rows before the user actually runs out of scrolling.
21548          *
21549          * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
21550          * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
21551          * preserve that scroll position
21552          *
21553          * <br> Defaults to 20
21554          */
21555         gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
21556
21557         /**
21558          * @ngdoc property
21559          * @name infiniteScrollUp
21560          * @propertyOf ui.grid.class:GridOptions
21561          * @description Whether you allow infinite scroll up, implying that the first page of data
21562          * you have displayed is in the middle of your data set.  If set to true then we trigger the
21563          * needMoreDataTop event when the user hits the top of the scrollbar.
21564          * <br> Defaults to false
21565          */
21566         gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
21567
21568         /**
21569          * @ngdoc property
21570          * @name infiniteScrollDown
21571          * @propertyOf ui.grid.class:GridOptions
21572          * @description Whether you allow infinite scroll down, implying that the first page of data
21573          * you have displayed is in the middle of your data set.  If set to true then we trigger the
21574          * needMoreData event when the user hits the bottom of the scrollbar.
21575          * <br> Defaults to true
21576          */
21577         gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
21578       },
21579
21580
21581       /**
21582        * @ngdoc function
21583        * @name setScrollDirections
21584        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21585        * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
21586        * and also sets the grid.suppressParentScroll
21587        * @param {grid} grid the grid we're operating on
21588        * @param {boolean} scrollUp whether there are pages available up - defaults to false
21589        * @param {boolean} scrollDown whether there are pages available down - defaults to true
21590        */
21591       setScrollDirections:  function ( grid, scrollUp, scrollDown ) {
21592         grid.infiniteScroll.scrollUp = ( scrollUp === true );
21593         grid.suppressParentScrollUp = ( scrollUp === true );
21594
21595         grid.infiniteScroll.scrollDown = ( scrollDown !== false);
21596         grid.suppressParentScrollDown = ( scrollDown !== false);
21597       },
21598
21599
21600       /**
21601        * @ngdoc function
21602        * @name handleScroll
21603        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21604        * @description Called whenever the grid scrolls, determines whether the scroll should
21605        * trigger an infinite scroll request for more data
21606        * @param {object} args the args from the event
21607        */
21608       handleScroll:  function (args) {
21609         // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
21610         if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
21611           return;
21612         }
21613
21614         if (args.y) {
21615
21616           // If the user is scrolling very quickly all the way to the top/bottom, the scroll handler can get confused
21617           // about the direction. First we check if they've gone all the way, and data always is loaded in this case.
21618           if (args.y.percentage === 0) {
21619             args.grid.scrollDirection = uiGridConstants.scrollDirection.UP;
21620             service.loadData(args.grid);
21621           } else if (args.y.percentage === 1) {
21622             args.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN;
21623             service.loadData(args.grid);
21624           } else { // Scroll position is somewhere in between top/bottom, so determine whether it's far enough to load more data.
21625             var percentage;
21626             var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
21627             if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
21628               percentage = args.y.percentage;
21629               if (percentage <= targetPercentage){
21630                 service.loadData(args.grid);
21631               }
21632             } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
21633               percentage = 1 - args.y.percentage;
21634               if (percentage <= targetPercentage){
21635                 service.loadData(args.grid);
21636               }
21637             }
21638           }
21639         }
21640       },
21641
21642
21643       /**
21644        * @ngdoc function
21645        * @name loadData
21646        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21647        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
21648        * and whether there are more pages upwards or downwards.  It also stores the number of rows that we had previously,
21649        * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
21650        * @param {Grid} grid the grid we're working on
21651        */
21652       loadData: function (grid) {
21653         // save number of currently visible rows to calculate new scroll position later - we know that we want
21654         // to be at approximately the row we're currently at
21655         grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
21656         grid.infiniteScroll.direction = grid.scrollDirection;
21657         delete grid.infiniteScroll.prevScrollTop;
21658
21659         if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
21660           grid.infiniteScroll.dataLoading = true;
21661           grid.api.infiniteScroll.raise.needLoadMoreDataTop();
21662         } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
21663           grid.infiniteScroll.dataLoading = true;
21664           grid.api.infiniteScroll.raise.needLoadMoreData();
21665         }
21666       },
21667
21668
21669       /**
21670        * @ngdoc function
21671        * @name adjustScroll
21672        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21673        * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
21674        * addition and to make things look clean.
21675        *
21676        * If we're scrolling up we scroll to the first row of the old data set -
21677        * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
21678        * 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
21679        * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
21680        * the data comes back.
21681        *
21682        * Neither of these are good assumptions, but making this a smoother experience really requires
21683        * 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
21684        * 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
21685        * somewhere else in the mean-time, in which case they'll get a jump back to the new data.  Anyway, this will do for
21686        * now, until someone wants to do better.
21687        * @param {Grid} grid the grid we're working on
21688        * @returns {promise} a promise that is resolved when scrolling has finished
21689        */
21690       adjustScroll: function(grid){
21691         var promise = $q.defer();
21692         $timeout(function () {
21693           var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
21694
21695           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
21696           rowHeight = grid.options.rowHeight;
21697
21698           if ( grid.infiniteScroll.direction === undefined ){
21699             // called from initialize, tweak our scroll up a little
21700             service.adjustInfiniteScrollPosition(grid, 0);
21701           }
21702
21703           newVisibleRows = grid.getVisibleRowCount();
21704
21705           // in case not enough data is loaded to enable scroller - load more data
21706           var canvasHeight = rowHeight * newVisibleRows;
21707           if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
21708             grid.api.infiniteScroll.raise.needLoadMoreData();
21709           }
21710
21711           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
21712             oldTop = grid.infiniteScroll.prevScrollTop || 0;
21713             newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
21714             service.adjustInfiniteScrollPosition(grid, newTop);
21715             $timeout( function() {
21716               promise.resolve();
21717             });
21718           }
21719
21720           if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
21721             newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
21722             service.adjustInfiniteScrollPosition(grid, newTop);
21723             $timeout( function() {
21724               promise.resolve();
21725             });
21726           }
21727         }, 0);
21728
21729         return promise.promise;
21730       },
21731
21732
21733       /**
21734        * @ngdoc function
21735        * @name adjustInfiniteScrollPosition
21736        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21737        * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
21738        * @param {Grid} grid the grid we're working on
21739        * @param {number} scrollTop the position through the grid that we want to scroll to
21740        */
21741       adjustInfiniteScrollPosition: function (grid, scrollTop) {
21742         var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
21743           visibleRows = grid.getVisibleRowCount(),
21744           viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
21745           rowHeight = grid.options.rowHeight,
21746           scrollHeight = visibleRows*rowHeight-viewportHeight;
21747
21748         //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
21749         if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
21750           // using pixels results in a relative scroll, hence we have to use percentage
21751           scrollEvent.y = {percentage: 1/scrollHeight};
21752         }
21753         else {
21754           scrollEvent.y = {percentage: scrollTop/scrollHeight};
21755         }
21756         grid.scrollContainers('', scrollEvent);
21757       },
21758
21759
21760       /**
21761        * @ngdoc function
21762        * @name dataRemovedTop
21763        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21764        * @description Adjusts the scroll position after you've removed data at the top. You should
21765        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
21766        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
21767        * before you start removing data
21768        * @param {Grid} grid the grid we're working on
21769        * @param {boolean} scrollUp flag that there are pages upwards, fire
21770        * infinite scroll events upward
21771        * @param {boolean} scrollDown flag that there are pages downwards, so
21772        * fire infinite scroll events downward
21773        */
21774       dataRemovedTop: function( grid, scrollUp, scrollDown ) {
21775         var newVisibleRows, oldTop, newTop, rowHeight;
21776         service.setScrollDirections( grid, scrollUp, scrollDown );
21777
21778         newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
21779         oldTop = grid.infiniteScroll.prevScrollTop;
21780         rowHeight = grid.options.rowHeight;
21781
21782         // since we removed from the top, our new scroll row will be the old scroll row less the number
21783         // of rows removed
21784         newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
21785
21786         service.adjustInfiniteScrollPosition( grid, newTop );
21787       },
21788
21789       /**
21790        * @ngdoc function
21791        * @name dataRemovedBottom
21792        * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21793        * @description Adjusts the scroll position after you've removed data at the bottom.  You should
21794        * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
21795        * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
21796        * before you start removing data
21797        * @param {Grid} grid the grid we're working on
21798        * @param {boolean} scrollUp flag that there are pages upwards, fire
21799        * infinite scroll events upward
21800        * @param {boolean} scrollDown flag that there are pages downwards, so
21801        * fire infinite scroll events downward
21802        */
21803       dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
21804         var newTop;
21805         service.setScrollDirections( grid, scrollUp, scrollDown );
21806
21807         newTop = grid.infiniteScroll.prevScrollTop;
21808
21809         service.adjustInfiniteScrollPosition( grid, newTop );
21810       }
21811     };
21812     return service;
21813   }]);
21814   /**
21815    *  @ngdoc directive
21816    *  @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
21817    *  @element div
21818    *  @restrict A
21819    *
21820    *  @description Adds infinite scroll features to grid
21821    *
21822    *  @example
21823    <example module="app">
21824    <file name="app.js">
21825    var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
21826
21827    app.controller('MainCtrl', ['$scope', function ($scope) {
21828       $scope.data = [
21829         { name: 'Alex', car: 'Toyota' },
21830             { name: 'Sam', car: 'Lexus' }
21831       ];
21832
21833       $scope.columnDefs = [
21834         {name: 'name'},
21835         {name: 'car'}
21836       ];
21837     }]);
21838    </file>
21839    <file name="index.html">
21840    <div ng-controller="MainCtrl">
21841    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
21842    </div>
21843    </file>
21844    </example>
21845    */
21846
21847   module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
21848     function (uiGridInfiniteScrollService) {
21849       return {
21850         priority: -200,
21851         scope: false,
21852         require: '^uiGrid',
21853         compile: function($scope, $elm, $attr){
21854           return {
21855             pre: function($scope, $elm, $attr, uiGridCtrl) {
21856               uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
21857             },
21858             post: function($scope, $elm, $attr) {
21859             }
21860           };
21861         }
21862       };
21863     }]);
21864
21865 })();
21866
21867 (function () {
21868   'use strict';
21869
21870   /**
21871    * @ngdoc overview
21872    * @name ui.grid.moveColumns
21873    * @description
21874    *
21875    * # ui.grid.moveColumns
21876    *
21877    * <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>
21878    *
21879    * This module provides column moving capability to ui.grid. It enables to change the position of columns.
21880    * <div doc-module-components="ui.grid.moveColumns"></div>
21881    */
21882   var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
21883
21884   /**
21885    *  @ngdoc service
21886    *  @name ui.grid.moveColumns.service:uiGridMoveColumnService
21887    *  @description Service for column moving feature.
21888    */
21889   module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
21890
21891     var service = {
21892       initializeGrid: function (grid) {
21893         var self = this;
21894         this.registerPublicApi(grid);
21895         this.defaultGridOptions(grid.options);
21896         grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
21897         grid.registerColumnBuilder(self.movableColumnBuilder);
21898         grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
21899       },
21900       registerPublicApi: function (grid) {
21901         var self = this;
21902         /**
21903          *  @ngdoc object
21904          *  @name ui.grid.moveColumns.api:PublicApi
21905          *  @description Public Api for column moving feature.
21906          */
21907         var publicApi = {
21908           events: {
21909             /**
21910              * @ngdoc event
21911              * @name columnPositionChanged
21912              * @eventOf  ui.grid.moveColumns.api:PublicApi
21913              * @description raised when column is moved
21914              * <pre>
21915              *      gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
21916              * </pre>
21917              * @param {object} colDef the column that was moved
21918              * @param {integer} originalPosition of the column
21919              * @param {integer} finalPosition of the column
21920              */
21921             colMovable: {
21922               columnPositionChanged: function (colDef, originalPosition, newPosition) {
21923               }
21924             }
21925           },
21926           methods: {
21927             /**
21928              * @ngdoc method
21929              * @name moveColumn
21930              * @methodOf  ui.grid.moveColumns.api:PublicApi
21931              * @description Method can be used to change column position.
21932              * <pre>
21933              *      gridApi.colMovable.moveColumn(oldPosition, newPosition)
21934              * </pre>
21935              * @param {integer} originalPosition of the column
21936              * @param {integer} finalPosition of the column
21937              */
21938             colMovable: {
21939               moveColumn: function (originalPosition, finalPosition) {
21940                 var columns = grid.columns;
21941                 if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
21942                   gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
21943                   return;
21944                 }
21945                 var nonMovableColumns = 0;
21946                 for (var i = 0; i < columns.length; i++) {
21947                   if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
21948                     nonMovableColumns++;
21949                   }
21950                 }
21951                 if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
21952                   gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
21953                   return;
21954                 }
21955                 var findPositionForRenderIndex = function (index) {
21956                   var position = index;
21957                   for (var i = 0; i <= position; i++) {
21958                     if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
21959                       position++;
21960                     }
21961                   }
21962                   return position;
21963                 };
21964                 self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
21965               }
21966             }
21967           }
21968         };
21969         grid.api.registerEventsFromObject(publicApi.events);
21970         grid.api.registerMethodsFromObject(publicApi.methods);
21971       },
21972       defaultGridOptions: function (gridOptions) {
21973         /**
21974          *  @ngdoc object
21975          *  @name ui.grid.moveColumns.api:GridOptions
21976          *
21977          *  @description Options for configuring the move column feature, these are available to be
21978          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21979          */
21980         /**
21981          *  @ngdoc object
21982          *  @name enableColumnMoving
21983          *  @propertyOf  ui.grid.moveColumns.api:GridOptions
21984          *  @description If defined, sets the default value for the colMovable flag on each individual colDefs
21985          *  if their individual enableColumnMoving configuration is not defined. Defaults to true.
21986          */
21987         gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
21988       },
21989       movableColumnBuilder: function (colDef, col, gridOptions) {
21990         var promises = [];
21991         /**
21992          *  @ngdoc object
21993          *  @name ui.grid.moveColumns.api:ColumnDef
21994          *
21995          *  @description Column Definition for move column feature, these are available to be
21996          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
21997          */
21998         /**
21999          *  @ngdoc object
22000          *  @name enableColumnMoving
22001          *  @propertyOf  ui.grid.moveColumns.api:ColumnDef
22002          *  @description Enable column moving for the column.
22003          */
22004         colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
22005           : colDef.enableColumnMoving;
22006         return $q.all(promises);
22007       },
22008       /**
22009        * @ngdoc method
22010        * @name updateColumnCache
22011        * @methodOf  ui.grid.moveColumns
22012        * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
22013        */
22014       updateColumnCache: function(grid){
22015         grid.moveColumns.orderCache = grid.getOnlyDataColumns();
22016       },
22017       /**
22018        * @ngdoc method
22019        * @name verifyColumnOrder
22020        * @methodOf  ui.grid.moveColumns
22021        * @description dataChangeCallback which uses the cached column order to restore the column order
22022        * when it is reset by altering the columnDefs array.
22023        */
22024       verifyColumnOrder: function(grid){
22025         var headerRowOffset = grid.rowHeaderColumns.length;
22026         var newIndex;
22027
22028         angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
22029           newIndex = grid.columns.indexOf(cacheCol);
22030           if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
22031             var column = grid.columns.splice(newIndex, 1)[0];
22032             grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
22033           }
22034         });
22035       },
22036       redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
22037         var columns = grid.columns;
22038
22039         if (originalPosition === newPosition) {
22040           return;
22041         }
22042
22043         //check columns in between move-range to make sure they are visible columns
22044         var pos = (originalPosition < newPosition) ? originalPosition + 1 : originalPosition - 1;
22045         var i0 = Math.min(pos, newPosition);
22046         for (i0; i0 <= Math.max(pos, newPosition); i0++) {
22047           if (columns[i0].visible) {
22048             break;
22049           }
22050         }
22051         if (i0 > Math.max(pos, newPosition)) {
22052           //no visible column found, column did not visibly move
22053           return;
22054         }
22055
22056         var originalColumn = columns[originalPosition];
22057         if (originalColumn.colDef.enableColumnMoving) {
22058           if (originalPosition > newPosition) {
22059             for (var i1 = originalPosition; i1 > newPosition; i1--) {
22060               columns[i1] = columns[i1 - 1];
22061             }
22062           }
22063           else if (newPosition > originalPosition) {
22064             for (var i2 = originalPosition; i2 < newPosition; i2++) {
22065               columns[i2] = columns[i2 + 1];
22066             }
22067           }
22068           columns[newPosition] = originalColumn;
22069           service.updateColumnCache(grid);
22070           grid.queueGridRefresh();
22071           $timeout(function () {
22072             grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
22073             grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
22074           });
22075         }
22076       }
22077     };
22078     return service;
22079   }]);
22080
22081   /**
22082    *  @ngdoc directive
22083    *  @name ui.grid.moveColumns.directive:uiGridMoveColumns
22084    *  @element div
22085    *  @restrict A
22086    *  @description Adds column moving features to the ui-grid directive.
22087    *  @example
22088    <example module="app">
22089    <file name="app.js">
22090    var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
22091    app.controller('MainCtrl', ['$scope', function ($scope) {
22092         $scope.data = [
22093           { name: 'Bob', title: 'CEO', age: 45 },
22094           { name: 'Frank', title: 'Lowly Developer', age: 25 },
22095           { name: 'Jenny', title: 'Highly Developer', age: 35 }
22096         ];
22097         $scope.columnDefs = [
22098           {name: 'name'},
22099           {name: 'title'},
22100           {name: 'age'}
22101         ];
22102       }]);
22103    </file>
22104    <file name="main.css">
22105    .grid {
22106       width: 100%;
22107       height: 150px;
22108     }
22109    </file>
22110    <file name="index.html">
22111    <div ng-controller="MainCtrl">
22112    <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
22113    </div>
22114    </file>
22115    </example>
22116    */
22117   module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
22118     return {
22119       replace: true,
22120       priority: 0,
22121       require: '^uiGrid',
22122       scope: false,
22123       compile: function () {
22124         return {
22125           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22126             uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
22127           },
22128           post: function ($scope, $elm, $attrs, uiGridCtrl) {
22129           }
22130         };
22131       }
22132     };
22133   }]);
22134
22135   /**
22136    *  @ngdoc directive
22137    *  @name ui.grid.moveColumns.directive:uiGridHeaderCell
22138    *  @element div
22139    *  @restrict A
22140    *
22141    *  @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
22142    *
22143    *  On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
22144    *  In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
22145    *  On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
22146    *
22147    *  Events that invoke cloning of header cell:
22148    *    - mousedown
22149    *
22150    *  Events that invoke movement of cloned header cell:
22151    *    - mousemove
22152    *
22153    *  Events that invoke repositioning of column:
22154    *    - mouseup
22155    */
22156   module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
22157     function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
22158       return {
22159         priority: -10,
22160         require: '^uiGrid',
22161         compile: function () {
22162           return {
22163             post: function ($scope, $elm, $attrs, uiGridCtrl) {
22164
22165               if ($scope.col.colDef.enableColumnMoving) {
22166
22167                 /*
22168                  * Our general approach to column move is that we listen to a touchstart or mousedown
22169                  * event over the column header.  When we hear one, then we wait for a move of the same type
22170                  * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
22171                  * a mousemove (i.e. a drag) before we decide that there's a move underway.  If there's never a move,
22172                  * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
22173                  *
22174                  */
22175                 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
22176
22177                 var gridLeft;
22178                 var previousMouseX;
22179                 var totalMouseMovement;
22180                 var rightMoveLimit;
22181                 var elmCloned = false;
22182                 var movingElm;
22183                 var reducedWidth;
22184                 var moveOccurred = false;
22185
22186                 var downFn = function( event ){
22187                   //Setting some variables required for calculations.
22188                   gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
22189                   if ( $scope.grid.hasLeftContainer() ){
22190                     gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
22191                   }
22192
22193                   previousMouseX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
22194                   totalMouseMovement = 0;
22195                   rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
22196
22197                   if ( event.type === 'mousedown' ){
22198                     $document.on('mousemove', moveFn);
22199                     $document.on('mouseup', upFn);
22200                   } else if ( event.type === 'touchstart' ){
22201                     $document.on('touchmove', moveFn);
22202                     $document.on('touchend', upFn);
22203                   }
22204                 };
22205
22206                 var moveFn = function( event ) {
22207                   var pageX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
22208                   var changeValue = pageX - previousMouseX;
22209                   if ( changeValue === 0 ){ return; }
22210                   //Disable text selection in Chrome during column move
22211                   document.onselectstart = function() { return false; };
22212
22213                   moveOccurred = true;
22214
22215                   if (!elmCloned) {
22216                     cloneElement();
22217                   }
22218                   else if (elmCloned) {
22219                     moveElement(changeValue);
22220                     previousMouseX = pageX;
22221                   }
22222                 };
22223
22224                 var upFn = function( event ){
22225                   //Re-enable text selection after column move
22226                   document.onselectstart = null;
22227
22228                   //Remove the cloned element on mouse up.
22229                   if (movingElm) {
22230                     movingElm.remove();
22231                     elmCloned = false;
22232                   }
22233
22234                   offAllEvents();
22235                   onDownEvents();
22236
22237                   if (!moveOccurred){
22238                     return;
22239                   }
22240
22241                   var columns = $scope.grid.columns;
22242                   var columnIndex = 0;
22243                   for (var i = 0; i < columns.length; i++) {
22244                     if (columns[i].colDef.name !== $scope.col.colDef.name) {
22245                       columnIndex++;
22246                     }
22247                     else {
22248                       break;
22249                     }
22250                   }
22251
22252                   var targetIndex;
22253
22254                   //Case where column should be moved to a position on its left
22255                   if (totalMouseMovement < 0) {
22256                     var totalColumnsLeftWidth = 0;
22257                     var il;
22258                     if ( $scope.grid.isRTL() ){
22259                       for (il = columnIndex + 1; il < columns.length; il++) {
22260                         if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22261                           totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22262                           if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
22263                             uiGridMoveColumnService.redrawColumnAtPosition
22264                             ($scope.grid, columnIndex, il - 1);
22265                             break;
22266                           }
22267                         }
22268                       }
22269                     }
22270                     else {
22271                       for (il = columnIndex - 1; il >= 0; il--) {
22272                         if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22273                           totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22274                           if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
22275                             uiGridMoveColumnService.redrawColumnAtPosition
22276                             ($scope.grid, columnIndex, il + 1);
22277                             break;
22278                           }
22279                         }
22280                       }
22281                     }
22282
22283                     //Case where column should be moved to beginning (or end in RTL) of the grid.
22284                     if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
22285                       targetIndex = 0;
22286                       if ( $scope.grid.isRTL() ){
22287                         targetIndex = columns.length - 1;
22288                       }
22289                       uiGridMoveColumnService.redrawColumnAtPosition
22290                       ($scope.grid, columnIndex, targetIndex);
22291                     }
22292                   }
22293
22294                   //Case where column should be moved to a position on its right
22295                   else if (totalMouseMovement > 0) {
22296                     var totalColumnsRightWidth = 0;
22297                     var ir;
22298                     if ( $scope.grid.isRTL() ){
22299                       for (ir = columnIndex - 1; ir > 0; ir--) {
22300                         if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
22301                           totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
22302                           if (totalColumnsRightWidth > totalMouseMovement) {
22303                             uiGridMoveColumnService.redrawColumnAtPosition
22304                             ($scope.grid, columnIndex, ir);
22305                             break;
22306                           }
22307                         }
22308                       }
22309                     }
22310                     else {
22311                       for (ir = columnIndex + 1; ir < columns.length; ir++) {
22312                         if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
22313                           totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
22314                           if (totalColumnsRightWidth > totalMouseMovement) {
22315                             uiGridMoveColumnService.redrawColumnAtPosition
22316                             ($scope.grid, columnIndex, ir - 1);
22317                             break;
22318                           }
22319                         }
22320                       }
22321                     }
22322
22323
22324                     //Case where column should be moved to end (or beginning in RTL) of the grid.
22325                     if (totalColumnsRightWidth < totalMouseMovement) {
22326                       targetIndex = columns.length - 1;
22327                       if ( $scope.grid.isRTL() ){
22328                         targetIndex = 0;
22329                       }
22330                       uiGridMoveColumnService.redrawColumnAtPosition
22331                       ($scope.grid, columnIndex, targetIndex);
22332                     }
22333                   }
22334
22335
22336
22337                 };
22338
22339                 var onDownEvents = function(){
22340                   $contentsElm.on('touchstart', downFn);
22341                   $contentsElm.on('mousedown', downFn);
22342                 };
22343
22344                 var offAllEvents = function() {
22345                   $contentsElm.off('touchstart', downFn);
22346                   $contentsElm.off('mousedown', downFn);
22347
22348                   $document.off('mousemove', moveFn);
22349                   $document.off('touchmove', moveFn);
22350
22351                   $document.off('mouseup', upFn);
22352                   $document.off('touchend', upFn);
22353                 };
22354
22355                 onDownEvents();
22356
22357
22358                 var cloneElement = function () {
22359                   elmCloned = true;
22360
22361                   //Cloning header cell and appending to current header cell.
22362                   movingElm = $elm.clone();
22363                   $elm.parent().append(movingElm);
22364
22365                   //Left of cloned element should be aligned to original header cell.
22366                   movingElm.addClass('movingColumn');
22367                   var movingElementStyles = {};
22368                   movingElementStyles.left = $elm[0].offsetLeft + 'px';
22369                   var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
22370                   var elmRight = $elm[0].getBoundingClientRect().right;
22371                   if (elmRight > gridRight) {
22372                     reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
22373                     movingElementStyles.width = reducedWidth + 'px';
22374                   }
22375                   movingElm.css(movingElementStyles);
22376                 };
22377
22378                 var moveElement = function (changeValue) {
22379                   //Calculate total column width
22380                   var columns = $scope.grid.columns;
22381                   var totalColumnWidth = 0;
22382                   for (var i = 0; i < columns.length; i++) {
22383                     if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
22384                       totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
22385                     }
22386                   }
22387
22388                   //Calculate new position of left of column
22389                   var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
22390                   var currentElmRight = movingElm[0].getBoundingClientRect().right;
22391                   var newElementLeft;
22392
22393                   newElementLeft = currentElmLeft - gridLeft + changeValue;
22394                   newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
22395
22396                   //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
22397                   if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
22398                     movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft +
22399                     (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
22400                   }
22401                   else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
22402                     changeValue *= 8;
22403                     var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
22404                     scrollEvent.x = {pixels: changeValue};
22405                     scrollEvent.grid.scrollContainers('',scrollEvent);
22406                   }
22407
22408                   //Calculate total width of columns on the left of the moving column and the mouse movement
22409                   var totalColumnsLeftWidth = 0;
22410                   for (var il = 0; il < columns.length; il++) {
22411                     if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22412                       if (columns[il].colDef.name !== $scope.col.colDef.name) {
22413                         totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22414                       }
22415                       else {
22416                         break;
22417                       }
22418                     }
22419                   }
22420                   if ($scope.newScrollLeft === undefined) {
22421                     totalMouseMovement += changeValue;
22422                   }
22423                   else {
22424                     totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
22425                   }
22426
22427                   //Increase width of moving column, in case the rightmost column was moved and its width was
22428                   //decreased because of overflow
22429                   if (reducedWidth < $scope.col.drawnWidth) {
22430                     reducedWidth += Math.abs(changeValue);
22431                     movingElm.css({'width': reducedWidth + 'px'});
22432                   }
22433                 };
22434
22435                 $scope.$on('$destroy', offAllEvents);
22436               }
22437             }
22438           };
22439         }
22440       };
22441     }]);
22442 })();
22443
22444 (function() {
22445   'use strict';
22446
22447   /**
22448    * @ngdoc overview
22449    * @name ui.grid.pagination
22450    *
22451    * @description
22452    *
22453    * # ui.grid.pagination
22454    *
22455    * <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>
22456    *
22457    * This module provides pagination support to ui-grid
22458    */
22459   var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);
22460
22461   /**
22462    * @ngdoc service
22463    * @name ui.grid.pagination.service:uiGridPaginationService
22464    *
22465    * @description Service for the pagination feature
22466    */
22467   module.service('uiGridPaginationService', ['gridUtil',
22468     function (gridUtil) {
22469       var service = {
22470         /**
22471          * @ngdoc method
22472          * @name initializeGrid
22473          * @methodOf ui.grid.pagination.service:uiGridPaginationService
22474          * @description Attaches the service to a certain grid
22475          * @param {Grid} grid The grid we want to work with
22476          */
22477         initializeGrid: function (grid) {
22478           service.defaultGridOptions(grid.options);
22479
22480           /**
22481           * @ngdoc object
22482           * @name ui.grid.pagination.api:PublicAPI
22483           *
22484           * @description Public API for the pagination feature
22485           */
22486           var publicApi = {
22487             events: {
22488               pagination: {
22489               /**
22490                * @ngdoc event
22491                * @name paginationChanged
22492                * @eventOf ui.grid.pagination.api:PublicAPI
22493                * @description This event fires when the pageSize or currentPage changes
22494                * @param {int} currentPage requested page number
22495                * @param {int} pageSize requested page size
22496                */
22497                 paginationChanged: function (currentPage, pageSize) { }
22498               }
22499             },
22500             methods: {
22501               pagination: {
22502                 /**
22503                  * @ngdoc method
22504                  * @name getPage
22505                  * @methodOf ui.grid.pagination.api:PublicAPI
22506                  * @description Returns the number of the current page
22507                  */
22508                 getPage: function () {
22509                   return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
22510                 },
22511                 /**
22512                  * @ngdoc method
22513                  * @name getFirstRowIndex
22514                  * @methodOf ui.grid.pagination.api:PublicAPI
22515                  * @description Returns the index of the first row of the current page.
22516                  */
22517                 getFirstRowIndex: function () {
22518                   if (grid.options.useCustomPagination) {
22519                     return grid.options.paginationPageSizes.reduce(function(result, size, index) {
22520                       return index < grid.options.paginationCurrentPage - 1 ? result + size : result;
22521                     }, 0);
22522                   }
22523                   return ((grid.options.paginationCurrentPage - 1) * grid.options.paginationPageSize);
22524                 },
22525                 /**
22526                  * @ngdoc method
22527                  * @name getLastRowIndex
22528                  * @methodOf ui.grid.pagination.api:PublicAPI
22529                  * @description Returns the index of the last row of the current page.
22530                  */
22531                 getLastRowIndex: function () {
22532                   if (grid.options.useCustomPagination) {
22533                     return publicApi.methods.pagination.getFirstRowIndex() + grid.options.paginationPageSizes[grid.options.paginationCurrentPage - 1];
22534                   }
22535                   return Math.min(grid.options.paginationCurrentPage * grid.options.paginationPageSize, grid.options.totalItems);
22536                 },
22537                 /**
22538                  * @ngdoc method
22539                  * @name getTotalPages
22540                  * @methodOf ui.grid.pagination.api:PublicAPI
22541                  * @description Returns the total number of pages
22542                  */
22543                 getTotalPages: function () {
22544                   if (!grid.options.enablePagination) {
22545                     return null;
22546                   }
22547
22548                   if (grid.options.useCustomPagination) {
22549                     return grid.options.paginationPageSizes.length;
22550                   }
22551
22552                   return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
22553                 },
22554                 /**
22555                  * @ngdoc method
22556                  * @name nextPage
22557                  * @methodOf ui.grid.pagination.api:PublicAPI
22558                  * @description Moves to the next page, if possible
22559                  */
22560                 nextPage: function () {
22561                   if (!grid.options.enablePagination) {
22562                     return;
22563                   }
22564
22565                   if (grid.options.totalItems > 0) {
22566                     grid.options.paginationCurrentPage = Math.min(
22567                       grid.options.paginationCurrentPage + 1,
22568                       publicApi.methods.pagination.getTotalPages()
22569                     );
22570                   } else {
22571                     grid.options.paginationCurrentPage++;
22572                   }
22573                 },
22574                 /**
22575                  * @ngdoc method
22576                  * @name previousPage
22577                  * @methodOf ui.grid.pagination.api:PublicAPI
22578                  * @description Moves to the previous page, if we're not on the first page
22579                  */
22580                 previousPage: function () {
22581                   if (!grid.options.enablePagination) {
22582                     return;
22583                   }
22584
22585                   grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
22586                 },
22587                 /**
22588                  * @ngdoc method
22589                  * @name seek
22590                  * @methodOf ui.grid.pagination.api:PublicAPI
22591                  * @description Moves to the requested page
22592                  * @param {int} page The number of the page that should be displayed
22593                  */
22594                 seek: function (page) {
22595                   if (!grid.options.enablePagination) {
22596                     return;
22597                   }
22598                   if (!angular.isNumber(page) || page < 1) {
22599                     throw 'Invalid page number: ' + page;
22600                   }
22601
22602                   grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
22603                 }
22604               }
22605             }
22606           };
22607
22608           grid.api.registerEventsFromObject(publicApi.events);
22609           grid.api.registerMethodsFromObject(publicApi.methods);
22610
22611           var processPagination = function( renderableRows ){
22612             if (grid.options.useExternalPagination || !grid.options.enablePagination) {
22613               return renderableRows;
22614             }
22615             //client side pagination
22616             var pageSize = parseInt(grid.options.paginationPageSize, 10);
22617             var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
22618
22619             var visibleRows = renderableRows.filter(function (row) { return row.visible; });
22620             grid.options.totalItems = visibleRows.length;
22621
22622             var firstRow = publicApi.methods.pagination.getFirstRowIndex();
22623             var lastRow  = publicApi.methods.pagination.getLastRowIndex();
22624
22625             if (firstRow > visibleRows.length) {
22626               currentPage = grid.options.paginationCurrentPage = 1;
22627               firstRow = (currentPage - 1) * pageSize;
22628             }
22629             return visibleRows.slice(firstRow, lastRow);
22630           };
22631
22632           grid.registerRowsProcessor(processPagination, 900 );
22633
22634         },
22635         defaultGridOptions: function (gridOptions) {
22636           /**
22637            * @ngdoc object
22638            * @name ui.grid.pagination.api:GridOptions
22639            *
22640            * @description GridOptions for the pagination feature, these are available to be
22641            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22642            */
22643
22644           /**
22645            * @ngdoc property
22646            * @name enablePagination
22647            * @propertyOf ui.grid.pagination.api:GridOptions
22648            * @description Enables pagination.  Defaults to true.
22649            */
22650           gridOptions.enablePagination = gridOptions.enablePagination !== false;
22651           /**
22652            * @ngdoc property
22653            * @name enablePaginationControls
22654            * @propertyOf ui.grid.pagination.api:GridOptions
22655            * @description Enables the paginator at the bottom of the grid. Turn this off if you want to implement your
22656            *              own controls outside the grid.
22657            */
22658           gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
22659           /**
22660            * @ngdoc property
22661            * @name useExternalPagination
22662            * @propertyOf ui.grid.pagination.api:GridOptions
22663            * @description Disables client side pagination. When true, handle the paginationChanged event and set data
22664            *              and totalItems.  Defaults to `false`
22665            */
22666           gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
22667
22668           /**
22669            * @ngdoc property
22670            * @name useCustomPagination
22671            * @propertyOf ui.grid.pagination.api:GridOptions
22672            * @description Disables client-side pagination. When true, handle the `paginationChanged` event and set `data`,
22673            *              `firstRowIndex`, `lastRowIndex`, and `totalItems`.  Defaults to `false`.
22674            */
22675           gridOptions.useCustomPagination = gridOptions.useCustomPagination === true;
22676
22677           /**
22678            * @ngdoc property
22679            * @name totalItems
22680            * @propertyOf ui.grid.pagination.api:GridOptions
22681            * @description Total number of items, set automatically when using client side pagination, but needs set by user
22682            *              for server side pagination
22683            */
22684           if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
22685             gridOptions.totalItems = 0;
22686           }
22687           /**
22688            * @ngdoc property
22689            * @name paginationPageSizes
22690            * @propertyOf ui.grid.pagination.api:GridOptions
22691            * @description Array of page sizes, defaults to `[250, 500, 1000]`
22692            */
22693           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
22694             gridOptions.paginationPageSizes = [250, 500, 1000];
22695           }
22696           /**
22697            * @ngdoc property
22698            * @name paginationPageSize
22699            * @propertyOf ui.grid.pagination.api:GridOptions
22700            * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
22701            */
22702           if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
22703             if (gridOptions.paginationPageSizes.length > 0) {
22704               gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
22705             } else {
22706               gridOptions.paginationPageSize = 0;
22707             }
22708           }
22709           /**
22710            * @ngdoc property
22711            * @name paginationCurrentPage
22712            * @propertyOf ui.grid.pagination.api:GridOptions
22713            * @description Current page number, defaults to 1
22714            */
22715           if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
22716             gridOptions.paginationCurrentPage = 1;
22717           }
22718
22719           /**
22720            * @ngdoc property
22721            * @name paginationTemplate
22722            * @propertyOf ui.grid.pagination.api:GridOptions
22723            * @description A custom template for the pager, defaults to `ui-grid/pagination`
22724            */
22725           if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
22726             gridOptions.paginationTemplate = 'ui-grid/pagination';
22727           }
22728         },
22729         /**
22730          * @ngdoc method
22731          * @methodOf ui.grid.pagination.service:uiGridPaginationService
22732          * @name uiGridPaginationService
22733          * @description  Raises paginationChanged and calls refresh for client side pagination
22734          * @param {Grid} grid the grid for which the pagination changed
22735          * @param {int} currentPage requested page number
22736          * @param {int} pageSize requested page size
22737          */
22738         onPaginationChanged: function (grid, currentPage, pageSize) {
22739             grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
22740             if (!grid.options.useExternalPagination) {
22741               grid.queueGridRefresh(); //client side pagination
22742             }
22743         }
22744       };
22745
22746       return service;
22747     }
22748   ]);
22749   /**
22750    *  @ngdoc directive
22751    *  @name ui.grid.pagination.directive:uiGridPagination
22752    *  @element div
22753    *  @restrict A
22754    *
22755    *  @description Adds pagination features to grid
22756    *  @example
22757    <example module="app">
22758    <file name="app.js">
22759    var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);
22760
22761    app.controller('MainCtrl', ['$scope', function ($scope) {
22762       $scope.data = [
22763         { name: 'Alex', car: 'Toyota' },
22764         { name: 'Sam', car: 'Lexus' },
22765         { name: 'Joe', car: 'Dodge' },
22766         { name: 'Bob', car: 'Buick' },
22767         { name: 'Cindy', car: 'Ford' },
22768         { name: 'Brian', car: 'Audi' },
22769         { name: 'Malcom', car: 'Mercedes Benz' },
22770         { name: 'Dave', car: 'Ford' },
22771         { name: 'Stacey', car: 'Audi' },
22772         { name: 'Amy', car: 'Acura' },
22773         { name: 'Scott', car: 'Toyota' },
22774         { name: 'Ryan', car: 'BMW' },
22775       ];
22776
22777       $scope.gridOptions = {
22778         data: 'data',
22779         paginationPageSizes: [5, 10, 25],
22780         paginationPageSize: 5,
22781         columnDefs: [
22782           {name: 'name'},
22783           {name: 'car'}
22784         ]
22785        }
22786     }]);
22787    </file>
22788    <file name="index.html">
22789    <div ng-controller="MainCtrl">
22790    <div ui-grid="gridOptions" ui-grid-pagination></div>
22791    </div>
22792    </file>
22793    </example>
22794    */
22795   module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
22796     function (gridUtil, uiGridPaginationService) {
22797       return {
22798         priority: -200,
22799         scope: false,
22800         require: 'uiGrid',
22801         link: {
22802           pre: function ($scope, $elm, $attr, uiGridCtrl) {
22803             uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
22804
22805             gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
22806               .then(function (contents) {
22807                 var template = angular.element(contents);
22808                 $elm.append(template);
22809                 uiGridCtrl.innerCompile(template);
22810               });
22811           }
22812         }
22813       };
22814     }
22815   ]);
22816
22817   /**
22818    *  @ngdoc directive
22819    *  @name ui.grid.pagination.directive:uiGridPager
22820    *  @element div
22821    *
22822    *  @description Panel for handling pagination
22823    */
22824   module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
22825     function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
22826       return {
22827         priority: -200,
22828         scope: true,
22829         require: '^uiGrid',
22830         link: function ($scope, $elm, $attr, uiGridCtrl) {
22831           var defaultFocusElementSelector = '.ui-grid-pager-control-input';
22832           $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
22833
22834           $scope.paginationApi = uiGridCtrl.grid.api.pagination;
22835           $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
22836           $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
22837           $scope.paginationOf = i18nService.getSafeText('pagination.of');
22838           $scope.paginationThrough = i18nService.getSafeText('pagination.through');
22839
22840           var options = uiGridCtrl.grid.options;
22841
22842           uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
22843             adjustment.height = adjustment.height - gridUtil.elementHeight($elm, "padding");
22844             return adjustment;
22845           });
22846
22847           var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
22848             if (!grid.options.useExternalPagination) {
22849               grid.options.totalItems = grid.rows.length;
22850             }
22851           }, [uiGridConstants.dataChange.ROW]);
22852
22853           $scope.$on('$destroy', dataChangeDereg);
22854
22855           var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
22856               if (newValues === oldValues || oldValues === undefined) {
22857                 return;
22858               }
22859
22860               if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
22861                 options.paginationCurrentPage = 1;
22862                 return;
22863               }
22864
22865               if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
22866                 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
22867                 return;
22868               }
22869
22870               uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
22871             }
22872           );
22873
22874           $scope.$on('$destroy', function() {
22875             deregP();
22876           });
22877
22878           $scope.cantPageForward = function () {
22879             if ($scope.paginationApi.getTotalPages()) {
22880               return $scope.cantPageToLast();
22881             } else {
22882               return options.data.length < 1;
22883             }
22884           };
22885
22886           $scope.cantPageToLast = function () {
22887             var totalPages = $scope.paginationApi.getTotalPages();
22888             return !totalPages || options.paginationCurrentPage >= totalPages;
22889           };
22890
22891           $scope.cantPageBackward = function () {
22892             return options.paginationCurrentPage <= 1;
22893           };
22894
22895           var focusToInputIf = function(condition){
22896             if (condition){
22897               gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
22898             }
22899           };
22900
22901           //Takes care of setting focus to the middle element when focus is lost
22902           $scope.pageFirstPageClick = function () {
22903             $scope.paginationApi.seek(1);
22904             focusToInputIf($scope.cantPageBackward());
22905           };
22906
22907           $scope.pagePreviousPageClick = function () {
22908             $scope.paginationApi.previousPage();
22909             focusToInputIf($scope.cantPageBackward());
22910           };
22911
22912           $scope.pageNextPageClick = function () {
22913             $scope.paginationApi.nextPage();
22914             focusToInputIf($scope.cantPageForward());
22915           };
22916
22917           $scope.pageLastPageClick = function () {
22918             $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
22919             focusToInputIf($scope.cantPageToLast());
22920           };
22921
22922         }
22923       };
22924     }
22925   ]);
22926 })();
22927
22928 (function () {
22929   'use strict';
22930
22931   /**
22932    * @ngdoc overview
22933    * @name ui.grid.pinning
22934    * @description
22935    *
22936    * # ui.grid.pinning
22937    *
22938    * <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>
22939    *
22940    * This module provides column pinning to the end user via menu options in the column header
22941    *
22942    * <div doc-module-components="ui.grid.pinning"></div>
22943    */
22944
22945   var module = angular.module('ui.grid.pinning', ['ui.grid']);
22946
22947   module.constant('uiGridPinningConstants', {
22948     container: {
22949       LEFT: 'left',
22950       RIGHT: 'right',
22951       NONE: ''
22952     }
22953   });
22954
22955   module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) {
22956     var service = {
22957
22958       initializeGrid: function (grid) {
22959         service.defaultGridOptions(grid.options);
22960
22961         // Register a column builder to add new menu items for pinning left and right
22962         grid.registerColumnBuilder(service.pinningColumnBuilder);
22963
22964         /**
22965          *  @ngdoc object
22966          *  @name ui.grid.pinning.api:PublicApi
22967          *
22968          *  @description Public Api for pinning feature
22969          */
22970         var publicApi = {
22971           events: {
22972             pinning: {
22973               /**
22974                * @ngdoc event
22975                * @name columnPin
22976                * @eventOf ui.grid.pinning.api:PublicApi
22977                * @description raised when column pin state has changed
22978                * <pre>
22979                *   gridApi.pinning.on.columnPinned(scope, function(colDef){})
22980                * </pre>
22981                * @param {object} colDef the column that was changed
22982                * @param {string} container the render container the column is in ('left', 'right', '')
22983                */
22984               columnPinned: function(colDef, container) {
22985               }
22986             }
22987           },
22988           methods: {
22989             pinning: {
22990               /**
22991                * @ngdoc function
22992                * @name pinColumn
22993                * @methodOf ui.grid.pinning.api:PublicApi
22994                * @description pin column left, right, or none
22995                * <pre>
22996                *   gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
22997                * </pre>
22998                * @param {gridColumn} col the column being pinned
22999                * @param {string} container one of the recognised types
23000                * from uiGridPinningConstants
23001                */
23002               pinColumn: function(col, container) {
23003                 service.pinColumn(grid, col, container);
23004               }
23005             }
23006           }
23007         };
23008
23009         grid.api.registerEventsFromObject(publicApi.events);
23010         grid.api.registerMethodsFromObject(publicApi.methods);
23011       },
23012
23013       defaultGridOptions: function (gridOptions) {
23014         //default option to true unless it was explicitly set to false
23015         /**
23016          *  @ngdoc object
23017          *  @name ui.grid.pinning.api:GridOptions
23018          *
23019          *  @description GridOptions for pinning feature, these are available to be
23020            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23021          */
23022
23023         /**
23024          *  @ngdoc object
23025          *  @name enablePinning
23026          *  @propertyOf  ui.grid.pinning.api:GridOptions
23027          *  @description Enable pinning for the entire grid.
23028          *  <br/>Defaults to true
23029          */
23030         gridOptions.enablePinning = gridOptions.enablePinning !== false;
23031
23032       },
23033
23034       pinningColumnBuilder: function (colDef, col, gridOptions) {
23035         //default to true unless gridOptions or colDef is explicitly false
23036
23037         /**
23038          *  @ngdoc object
23039          *  @name ui.grid.pinning.api:ColumnDef
23040          *
23041          *  @description ColumnDef for pinning feature, these are available to be
23042          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
23043          */
23044
23045         /**
23046          *  @ngdoc object
23047          *  @name enablePinning
23048          *  @propertyOf  ui.grid.pinning.api:ColumnDef
23049          *  @description Enable pinning for the individual column.
23050          *  <br/>Defaults to true
23051          */
23052         colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
23053
23054
23055         /**
23056          *  @ngdoc object
23057          *  @name pinnedLeft
23058          *  @propertyOf  ui.grid.pinning.api:ColumnDef
23059          *  @description Column is pinned left when grid is rendered
23060          *  <br/>Defaults to false
23061          */
23062
23063         /**
23064          *  @ngdoc object
23065          *  @name pinnedRight
23066          *  @propertyOf  ui.grid.pinning.api:ColumnDef
23067          *  @description Column is pinned right when grid is rendered
23068          *  <br/>Defaults to false
23069          */
23070         if (colDef.pinnedLeft) {
23071           col.renderContainer = 'left';
23072           col.grid.createLeftContainer();
23073         }
23074         else if (colDef.pinnedRight) {
23075           col.renderContainer = 'right';
23076           col.grid.createRightContainer();
23077         }
23078
23079         if (!colDef.enablePinning) {
23080           return;
23081         }
23082
23083         var pinColumnLeftAction = {
23084           name: 'ui.grid.pinning.pinLeft',
23085           title: i18nService.get().pinning.pinLeft,
23086           icon: 'ui-grid-icon-left-open',
23087           shown: function () {
23088             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
23089           },
23090           action: function () {
23091             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT);
23092           }
23093         };
23094
23095         var pinColumnRightAction = {
23096           name: 'ui.grid.pinning.pinRight',
23097           title: i18nService.get().pinning.pinRight,
23098           icon: 'ui-grid-icon-right-open',
23099           shown: function () {
23100             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
23101           },
23102           action: function () {
23103             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT);
23104           }
23105         };
23106
23107         var removePinAction = {
23108           name: 'ui.grid.pinning.unpin',
23109           title: i18nService.get().pinning.unpin,
23110           icon: 'ui-grid-icon-cancel',
23111           shown: function () {
23112             return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
23113           },
23114           action: function () {
23115             service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.NONE);
23116           }
23117         };
23118
23119         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
23120           col.menuItems.push(pinColumnLeftAction);
23121         }
23122         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
23123           col.menuItems.push(pinColumnRightAction);
23124         }
23125         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
23126           col.menuItems.push(removePinAction);
23127         }
23128       },
23129
23130       pinColumn: function(grid, col, container) {
23131         if (container === uiGridPinningConstants.container.NONE) {
23132           col.renderContainer = null;
23133           col.colDef.pinnedLeft = col.colDef.pinnedRight = false;
23134         }
23135         else {
23136           col.renderContainer = container;
23137           if (container === uiGridPinningConstants.container.LEFT) {
23138             grid.createLeftContainer();
23139           }
23140           else if (container === uiGridPinningConstants.container.RIGHT) {
23141             grid.createRightContainer();
23142           }
23143         }
23144
23145         grid.refresh()
23146         .then(function() {
23147           grid.api.pinning.raise.columnPinned( col.colDef, container );
23148         });
23149       }
23150     };
23151
23152     return service;
23153   }]);
23154
23155   module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
23156     function (gridUtil, uiGridPinningService) {
23157       return {
23158         require: 'uiGrid',
23159         scope: false,
23160         compile: function () {
23161           return {
23162             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23163               uiGridPinningService.initializeGrid(uiGridCtrl.grid);
23164             },
23165             post: function ($scope, $elm, $attrs, uiGridCtrl) {
23166             }
23167           };
23168         }
23169       };
23170     }]);
23171
23172
23173 })();
23174
23175 (function(){
23176   'use strict';
23177
23178   /**
23179    * @ngdoc overview
23180    * @name ui.grid.resizeColumns
23181    * @description
23182    *
23183    * # ui.grid.resizeColumns
23184    *
23185    * <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>
23186    *
23187    * This module allows columns to be resized.
23188    */
23189   var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
23190
23191   module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
23192     function (gridUtil, $q, $timeout) {
23193
23194       var service = {
23195         defaultGridOptions: function(gridOptions){
23196           //default option to true unless it was explicitly set to false
23197           /**
23198            *  @ngdoc object
23199            *  @name ui.grid.resizeColumns.api:GridOptions
23200            *
23201            *  @description GridOptions for resizeColumns feature, these are available to be
23202            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23203            */
23204
23205           /**
23206            *  @ngdoc object
23207            *  @name enableColumnResizing
23208            *  @propertyOf  ui.grid.resizeColumns.api:GridOptions
23209            *  @description Enable column resizing on the entire grid
23210            *  <br/>Defaults to true
23211            */
23212           gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
23213
23214           //legacy support
23215           //use old name if it is explicitly false
23216           if (gridOptions.enableColumnResize === false){
23217             gridOptions.enableColumnResizing = false;
23218           }
23219         },
23220
23221         colResizerColumnBuilder: function (colDef, col, gridOptions) {
23222
23223           var promises = [];
23224           /**
23225            *  @ngdoc object
23226            *  @name ui.grid.resizeColumns.api:ColumnDef
23227            *
23228            *  @description ColumnDef for resizeColumns feature, these are available to be
23229            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
23230            */
23231
23232           /**
23233            *  @ngdoc object
23234            *  @name enableColumnResizing
23235            *  @propertyOf  ui.grid.resizeColumns.api:ColumnDef
23236            *  @description Enable column resizing on an individual column
23237            *  <br/>Defaults to GridOptions.enableColumnResizing
23238            */
23239           //default to true unless gridOptions or colDef is explicitly false
23240           colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
23241
23242
23243           //legacy support of old option name
23244           if (colDef.enableColumnResize === false){
23245             colDef.enableColumnResizing = false;
23246           }
23247
23248           return $q.all(promises);
23249         },
23250
23251         registerPublicApi: function (grid) {
23252             /**
23253              *  @ngdoc object
23254              *  @name ui.grid.resizeColumns.api:PublicApi
23255              *  @description Public Api for column resize feature.
23256              */
23257             var publicApi = {
23258               events: {
23259                 /**
23260                  * @ngdoc event
23261                  * @name columnSizeChanged
23262                  * @eventOf  ui.grid.resizeColumns.api:PublicApi
23263                  * @description raised when column is resized
23264                  * <pre>
23265                  *      gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
23266                  * </pre>
23267                  * @param {object} colDef the column that was resized
23268                  * @param {integer} delta of the column size change
23269                  */
23270                 colResizable: {
23271                   columnSizeChanged: function (colDef, deltaChange) {
23272                   }
23273                 }
23274               }
23275             };
23276             grid.api.registerEventsFromObject(publicApi.events);
23277         },
23278
23279         fireColumnSizeChanged: function (grid, colDef, deltaChange) {
23280           $timeout(function () {
23281             if ( grid.api.colResizable ){
23282               grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
23283             } else {
23284               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.");
23285             }
23286           });
23287         },
23288
23289         // get either this column, or the column next to this column, to resize,
23290         // returns the column we're going to resize
23291         findTargetCol: function(col, position, rtlMultiplier){
23292           var renderContainer = col.getRenderContainer();
23293
23294           if (position === 'left') {
23295             // Get the column to the left of this one
23296             var colIndex = renderContainer.visibleColumnCache.indexOf(col);
23297             return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
23298           } else {
23299             return col;
23300           }
23301         }
23302
23303       };
23304
23305       return service;
23306
23307     }]);
23308
23309
23310   /**
23311    * @ngdoc directive
23312    * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
23313    * @element div
23314    * @restrict A
23315    * @description
23316    * 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
23317    * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
23318    *
23319    * @example
23320    <doc:example module="app">
23321    <doc:source>
23322    <script>
23323    var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
23324
23325    app.controller('MainCtrl', ['$scope', function ($scope) {
23326           $scope.gridOpts = {
23327             data: [
23328               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
23329               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
23330               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
23331               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
23332             ]
23333           };
23334         }]);
23335    </script>
23336
23337    <div ng-controller="MainCtrl">
23338    <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
23339    </div>
23340    </doc:source>
23341    <doc:scenario>
23342
23343    </doc:scenario>
23344    </doc:example>
23345    */
23346   module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
23347     return {
23348       replace: true,
23349       priority: 0,
23350       require: '^uiGrid',
23351       scope: false,
23352       compile: function () {
23353         return {
23354           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23355             uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
23356             uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
23357             uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
23358           },
23359           post: function ($scope, $elm, $attrs, uiGridCtrl) {
23360           }
23361         };
23362       }
23363     };
23364   }]);
23365
23366   // Extend the uiGridHeaderCell directive
23367   module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
23368     return {
23369       // Run after the original uiGridHeaderCell
23370       priority: -10,
23371       require: '^uiGrid',
23372       // scope: false,
23373       compile: function() {
23374         return {
23375           post: function ($scope, $elm, $attrs, uiGridCtrl) {
23376             var grid = uiGridCtrl.grid;
23377
23378             if (grid.options.enableColumnResizing) {
23379               var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
23380
23381               var rtlMultiplier = 1;
23382               //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
23383               if (grid.isRTL()) {
23384                 $scope.position = 'left';
23385                 rtlMultiplier = -1;
23386               }
23387
23388               var displayResizers = function(){
23389
23390                 // remove any existing resizers.
23391                 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
23392                 for ( var i = 0; i < resizers.length; i++ ){
23393                   angular.element(resizers[i]).remove();
23394                 }
23395
23396                 // get the target column for the left resizer
23397                 var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
23398                 var renderContainer = $scope.col.getRenderContainer();
23399
23400                 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
23401                 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
23402                   var resizerLeft = angular.element(columnResizerElm).clone();
23403                   resizerLeft.attr('position', 'left');
23404
23405                   $elm.prepend(resizerLeft);
23406                   $compile(resizerLeft)($scope);
23407                 }
23408
23409                 // Don't append the right resizer if this column has resizing disabled
23410                 if ($scope.col.colDef.enableColumnResizing !== false) {
23411                   var resizerRight = angular.element(columnResizerElm).clone();
23412                   resizerRight.attr('position', 'right');
23413
23414                   $elm.append(resizerRight);
23415                   $compile(resizerRight)($scope);
23416                 }
23417               };
23418
23419               displayResizers();
23420
23421               var waitDisplay = function(){
23422                 $timeout(displayResizers);
23423               };
23424
23425               var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
23426
23427               $scope.$on( '$destroy', dataChangeDereg );
23428             }
23429           }
23430         };
23431       }
23432     };
23433   }]);
23434
23435
23436
23437   /**
23438    * @ngdoc directive
23439    * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
23440    * @element div
23441    * @restrict A
23442    *
23443    * @description
23444    * Draggable handle that controls column resizing.
23445    *
23446    * @example
23447    <doc:example module="app">
23448      <doc:source>
23449        <script>
23450         var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
23451
23452         app.controller('MainCtrl', ['$scope', function ($scope) {
23453           $scope.gridOpts = {
23454             enableColumnResizing: true,
23455             data: [
23456               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
23457               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
23458               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
23459               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
23460             ]
23461           };
23462         }]);
23463        </script>
23464
23465        <div ng-controller="MainCtrl">
23466         <div class="testGrid" ui-grid="gridOpts"></div>
23467        </div>
23468      </doc:source>
23469      <doc:scenario>
23470       // TODO: e2e specs?
23471
23472       // TODO: post-resize a horizontal scroll event should be fired
23473      </doc:scenario>
23474    </doc:example>
23475    */
23476   module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
23477     var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
23478
23479     var resizer = {
23480       priority: 0,
23481       scope: {
23482         col: '=',
23483         position: '@',
23484         renderIndex: '='
23485       },
23486       require: '?^uiGrid',
23487       link: function ($scope, $elm, $attrs, uiGridCtrl) {
23488         var startX = 0,
23489             x = 0,
23490             gridLeft = 0,
23491             rtlMultiplier = 1;
23492
23493         //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
23494         if (uiGridCtrl.grid.isRTL()) {
23495           $scope.position = 'left';
23496           rtlMultiplier = -1;
23497         }
23498
23499         if ($scope.position === 'left') {
23500           $elm.addClass('left');
23501         }
23502         else if ($scope.position === 'right') {
23503           $elm.addClass('right');
23504         }
23505
23506         // Refresh the grid canvas
23507         //   takes an argument representing the diff along the X-axis that the resize had
23508         function refreshCanvas(xDiff) {
23509           // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
23510           uiGridCtrl.grid.refreshCanvas(true).then( function() {
23511             uiGridCtrl.grid.queueGridRefresh();
23512           });
23513         }
23514
23515         // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
23516         // Returns the new recommended with, after constraints applied
23517         function constrainWidth(col, width){
23518           var newWidth = width;
23519
23520           // If the new width would be less than the column's allowably minimum width, don't allow it
23521           if (col.minWidth && newWidth < col.minWidth) {
23522             newWidth = col.minWidth;
23523           }
23524           else if (col.maxWidth && newWidth > col.maxWidth) {
23525             newWidth = col.maxWidth;
23526           }
23527
23528           return newWidth;
23529         }
23530
23531
23532         /*
23533          * Our approach to event handling aims to deal with both touch devices and mouse devices
23534          * We register down handlers on both touch and mouse.  When a touchstart or mousedown event
23535          * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
23536          *
23537          * This way we can listen for both without worrying about the fact many touch devices also emulate
23538          * mouse events - basically whichever one we hear first is what we'll go with.
23539          */
23540         function moveFunction(event, args) {
23541           if (event.originalEvent) { event = event.originalEvent; }
23542           event.preventDefault();
23543
23544           x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
23545
23546           if (x < 0) { x = 0; }
23547           else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
23548
23549           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23550
23551           // Don't resize if it's disabled on this column
23552           if (col.colDef.enableColumnResizing === false) {
23553             return;
23554           }
23555
23556           if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
23557             uiGridCtrl.grid.element.addClass('column-resizing');
23558           }
23559
23560           // Get the diff along the X axis
23561           var xDiff = x - startX;
23562
23563           // Get the width that this mouse would give the column
23564           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
23565
23566           // check we're not outside the allowable bounds for this column
23567           x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
23568
23569           resizeOverlay.css({ left: x + 'px' });
23570
23571           uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
23572         }
23573
23574
23575         function upFunction(event, args) {
23576           if (event.originalEvent) { event = event.originalEvent; }
23577           event.preventDefault();
23578
23579           uiGridCtrl.grid.element.removeClass('column-resizing');
23580
23581           resizeOverlay.remove();
23582
23583           // Resize the column
23584           x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
23585           var xDiff = x - startX;
23586
23587           if (xDiff === 0) {
23588             // no movement, so just reset event handlers, including turning back on both
23589             // down events - we turned one off when this event started
23590             offAllEvents();
23591             onDownEvents();
23592             return;
23593           }
23594
23595           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23596
23597           // Don't resize if it's disabled on this column
23598           if (col.colDef.enableColumnResizing === false) {
23599             return;
23600           }
23601
23602           // Get the new width
23603           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
23604
23605           // check we're not outside the allowable bounds for this column
23606           col.width = constrainWidth(col, newWidth);
23607           col.hasCustomWidth = true;
23608
23609           refreshCanvas(xDiff);
23610
23611           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
23612
23613           // stop listening of up and move events - wait for next down
23614           // reset the down events - we will have turned one off when this event started
23615           offAllEvents();
23616           onDownEvents();
23617         }
23618
23619
23620         var downFunction = function(event, args) {
23621           if (event.originalEvent) { event = event.originalEvent; }
23622           event.stopPropagation();
23623
23624           // Get the left offset of the grid
23625           // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
23626           gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
23627
23628           // Get the starting X position, which is the X coordinate of the click minus the grid's offset
23629           startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
23630
23631           // Append the resizer overlay
23632           uiGridCtrl.grid.element.append(resizeOverlay);
23633
23634           // Place the resizer overlay at the start position
23635           resizeOverlay.css({ left: startX });
23636
23637           // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
23638           // we were touchdown then we listen for touchmove and touchup.  Also remove the handler for the equivalent
23639           // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
23640           // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
23641           if ( event.type === 'touchstart' ){
23642             $document.on('touchend', upFunction);
23643             $document.on('touchmove', moveFunction);
23644             $elm.off('mousedown', downFunction);
23645           } else {
23646             $document.on('mouseup', upFunction);
23647             $document.on('mousemove', moveFunction);
23648             $elm.off('touchstart', downFunction);
23649           }
23650         };
23651
23652         var onDownEvents = function() {
23653           $elm.on('mousedown', downFunction);
23654           $elm.on('touchstart', downFunction);
23655         };
23656
23657         var offAllEvents = function() {
23658           $document.off('mouseup', upFunction);
23659           $document.off('touchend', upFunction);
23660           $document.off('mousemove', moveFunction);
23661           $document.off('touchmove', moveFunction);
23662           $elm.off('mousedown', downFunction);
23663           $elm.off('touchstart', downFunction);
23664         };
23665
23666         onDownEvents();
23667
23668
23669         // On doubleclick, resize to fit all rendered cells
23670         var dblClickFn = function(event, args){
23671           event.stopPropagation();
23672
23673           var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23674
23675           // Don't resize if it's disabled on this column
23676           if (col.colDef.enableColumnResizing === false) {
23677             return;
23678           }
23679
23680           // Go through the rendered rows and find out the max size for the data in this column
23681           var maxWidth = 0;
23682           var xDiff = 0;
23683
23684           // Get the parent render container element
23685           var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
23686
23687           // 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
23688           var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
23689           Array.prototype.forEach.call(cells, function (cell) {
23690               // Get the cell width
23691               // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
23692
23693               // Account for the menu button if it exists
23694               var menuButton;
23695               if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
23696                 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
23697               }
23698
23699               gridUtil.fakeElement(cell, {}, function(newElm) {
23700                 // Make the element float since it's a div and can expand to fill its container
23701                 var e = angular.element(newElm);
23702                 e.attr('style', 'float: left');
23703
23704                 var width = gridUtil.elementWidth(e);
23705
23706                 if (menuButton) {
23707                   var menuButtonWidth = gridUtil.elementWidth(menuButton);
23708                   width = width + menuButtonWidth;
23709                 }
23710
23711                 if (width > maxWidth) {
23712                   maxWidth = width;
23713                   xDiff = maxWidth - width;
23714                 }
23715               });
23716             });
23717
23718           // check we're not outside the allowable bounds for this column
23719           col.width = constrainWidth(col, maxWidth);
23720           col.hasCustomWidth = true;
23721
23722           refreshCanvas(xDiff);
23723
23724           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);        };
23725         $elm.on('dblclick', dblClickFn);
23726
23727         $elm.on('$destroy', function() {
23728           $elm.off('dblclick', dblClickFn);
23729           offAllEvents();
23730         });
23731       }
23732     };
23733
23734     return resizer;
23735   }]);
23736
23737 })();
23738
23739 (function () {
23740   'use strict';
23741
23742   /**
23743    * @ngdoc overview
23744    * @name ui.grid.rowEdit
23745    * @description
23746    *
23747    * # ui.grid.rowEdit
23748    *
23749    * <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>
23750    *
23751    * This module extends the edit feature to provide tracking and saving of rows
23752    * of data.  The tutorial provides more information on how this feature is best
23753    * used {@link tutorial/205_row_editable here}.
23754    * <br/>
23755    * This feature depends on usage of the ui-grid-edit feature, and also benefits
23756    * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
23757    * experience
23758    *
23759    */
23760
23761   var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
23762
23763   /**
23764    *  @ngdoc object
23765    *  @name ui.grid.rowEdit.constant:uiGridRowEditConstants
23766    *
23767    *  @description constants available in row edit module
23768    */
23769   module.constant('uiGridRowEditConstants', {
23770   });
23771
23772   /**
23773    *  @ngdoc service
23774    *  @name ui.grid.rowEdit.service:uiGridRowEditService
23775    *
23776    *  @description Services for row editing features
23777    */
23778   module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
23779     function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
23780
23781       var service = {
23782
23783         initializeGrid: function (scope, grid) {
23784           /**
23785            *  @ngdoc object
23786            *  @name ui.grid.rowEdit.api:PublicApi
23787            *
23788            *  @description Public Api for rowEdit feature
23789            */
23790
23791           grid.rowEdit = {};
23792
23793           var publicApi = {
23794             events: {
23795               rowEdit: {
23796                 /**
23797                  * @ngdoc event
23798                  * @eventOf ui.grid.rowEdit.api:PublicApi
23799                  * @name saveRow
23800                  * @description raised when a row is ready for saving.  Once your
23801                  * row has saved you may need to use angular.extend to update the
23802                  * data entity with any changed data from your save (for example,
23803                  * lock version information if you're using optimistic locking,
23804                  * or last update time/user information).
23805                  *
23806                  * Your method should call setSavePromise somewhere in the body before
23807                  * returning control.  The feature will then wait, with the gridRow greyed out
23808                  * whilst this promise is being resolved.
23809                  *
23810                  * <pre>
23811                  *      gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
23812                  * </pre>
23813                  * and somewhere within the event handler:
23814                  * <pre>
23815                  *      gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
23816                  * </pre>
23817                  * @param {object} rowEntity the options.data element that was edited
23818                  * @returns {promise} Your saveRow method should return a promise, the
23819                  * promise should either be resolved (implying successful save), or
23820                  * rejected (implying an error).
23821                  */
23822                 saveRow: function (rowEntity) {
23823                 }
23824               }
23825             },
23826             methods: {
23827               rowEdit: {
23828                 /**
23829                  * @ngdoc method
23830                  * @methodOf ui.grid.rowEdit.api:PublicApi
23831                  * @name setSavePromise
23832                  * @description Sets the promise associated with the row save, mandatory that
23833                  * the saveRow event handler calls this method somewhere before returning.
23834                  * <pre>
23835                  *      gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
23836                  * </pre>
23837                  * @param {object} rowEntity a data row from the grid for which a save has
23838                  * been initiated
23839                  * @param {promise} savePromise the promise that will be resolved when the
23840                  * save is successful, or rejected if the save fails
23841                  *
23842                  */
23843                 setSavePromise: function ( rowEntity, savePromise) {
23844                   service.setSavePromise(grid, rowEntity, savePromise);
23845                 },
23846                 /**
23847                  * @ngdoc method
23848                  * @methodOf ui.grid.rowEdit.api:PublicApi
23849                  * @name getDirtyRows
23850                  * @description Returns all currently dirty rows
23851                  * <pre>
23852                  *      gridApi.rowEdit.getDirtyRows(grid)
23853                  * </pre>
23854                  * @returns {array} An array of gridRows that are currently dirty
23855                  *
23856                  */
23857                 getDirtyRows: function () {
23858                   return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
23859                 },
23860                 /**
23861                  * @ngdoc method
23862                  * @methodOf ui.grid.rowEdit.api:PublicApi
23863                  * @name getErrorRows
23864                  * @description Returns all currently errored rows
23865                  * <pre>
23866                  *      gridApi.rowEdit.getErrorRows(grid)
23867                  * </pre>
23868                  * @returns {array} An array of gridRows that are currently in error
23869                  *
23870                  */
23871                 getErrorRows: function () {
23872                   return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
23873                 },
23874                 /**
23875                  * @ngdoc method
23876                  * @methodOf ui.grid.rowEdit.api:PublicApi
23877                  * @name flushDirtyRows
23878                  * @description Triggers a save event for all currently dirty rows, could
23879                  * be used where user presses a save button or navigates away from the page
23880                  * <pre>
23881                  *      gridApi.rowEdit.flushDirtyRows(grid)
23882                  * </pre>
23883                  * @returns {promise} a promise that represents the aggregate of all
23884                  * of the individual save promises - i.e. it will be resolved when all
23885                  * the individual save promises have been resolved.
23886                  *
23887                  */
23888                 flushDirtyRows: function () {
23889                   return service.flushDirtyRows(grid);
23890                 },
23891
23892                 /**
23893                  * @ngdoc method
23894                  * @methodOf ui.grid.rowEdit.api:PublicApi
23895                  * @name setRowsDirty
23896                  * @description Sets each of the rows passed in dataRows
23897                  * to be dirty.  note that if you have only just inserted the
23898                  * rows into your data you will need to wait for a $digest cycle
23899                  * before the gridRows are present - so often you would wrap this
23900                  * call in a $interval or $timeout
23901                  * <pre>
23902                  *      $interval( function() {
23903                  *        gridApi.rowEdit.setRowsDirty(myDataRows);
23904                  *      }, 0, 1);
23905                  * </pre>
23906                  * @param {array} dataRows the data entities for which the gridRows
23907                  * should be set dirty.
23908                  *
23909                  */
23910                 setRowsDirty: function ( dataRows) {
23911                   service.setRowsDirty(grid, dataRows);
23912                 },
23913
23914                 /**
23915                  * @ngdoc method
23916                  * @methodOf ui.grid.rowEdit.api:PublicApi
23917                  * @name setRowsClean
23918                  * @description Sets each of the rows passed in dataRows
23919                  * to be clean, removing them from the dirty cache and the error cache,
23920                  * and clearing the error flag and the dirty flag
23921                  * <pre>
23922                  *      var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
23923                  *      var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
23924                  *      $scope.gridApi.rowEdit.setRowsClean( dataRows );
23925                  * </pre>
23926                  * @param {array} dataRows the data entities for which the gridRows
23927                  * should be set clean.
23928                  *
23929                  */
23930                 setRowsClean: function ( dataRows) {
23931                   service.setRowsClean(grid, dataRows);
23932                 }
23933               }
23934             }
23935           };
23936
23937           grid.api.registerEventsFromObject(publicApi.events);
23938           grid.api.registerMethodsFromObject(publicApi.methods);
23939
23940           grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
23941             grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
23942             grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
23943             grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
23944
23945             if ( grid.api.cellNav ) {
23946               grid.api.cellNav.on.navigate( scope, service.navigate );
23947             }
23948           });
23949
23950         },
23951
23952         defaultGridOptions: function (gridOptions) {
23953
23954           /**
23955            *  @ngdoc object
23956            *  @name ui.grid.rowEdit.api:GridOptions
23957            *
23958            *  @description Options for configuring the rowEdit feature, these are available to be
23959            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23960            */
23961
23962         },
23963
23964
23965         /**
23966          * @ngdoc method
23967          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23968          * @name saveRow
23969          * @description  Returns a function that saves the specified row from the grid,
23970          * and returns a promise
23971          * @param {object} grid the grid for which dirty rows should be flushed
23972          * @param {GridRow} gridRow the row that should be saved
23973          * @returns {function} the saveRow function returns a function.  That function
23974          * in turn, when called, returns a promise relating to the save callback
23975          */
23976         saveRow: function ( grid, gridRow ) {
23977           var self = this;
23978
23979           return function() {
23980             gridRow.isSaving = true;
23981
23982             if ( gridRow.rowEditSavePromise ){
23983               // don't save the row again if it's already saving - that causes stale object exceptions
23984               return gridRow.rowEditSavePromise;
23985             }
23986
23987             var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
23988
23989             if ( gridRow.rowEditSavePromise ){
23990               gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
23991             } else {
23992               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' );
23993             }
23994             return promise;
23995           };
23996         },
23997
23998
23999         /**
24000          * @ngdoc method
24001          * @methodOf  ui.grid.rowEdit.service:uiGridRowEditService
24002          * @name setSavePromise
24003          * @description Sets the promise associated with the row save, mandatory that
24004          * the saveRow event handler calls this method somewhere before returning.
24005          * <pre>
24006          *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
24007          * </pre>
24008          * @param {object} grid the grid for which dirty rows should be returned
24009          * @param {object} rowEntity a data row from the grid for which a save has
24010          * been initiated
24011          * @param {promise} savePromise the promise that will be resolved when the
24012          * save is successful, or rejected if the save fails
24013          *
24014          */
24015         setSavePromise: function (grid, rowEntity, savePromise) {
24016           var gridRow = grid.getRow( rowEntity );
24017           gridRow.rowEditSavePromise = savePromise;
24018         },
24019
24020
24021         /**
24022          * @ngdoc method
24023          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24024          * @name processSuccessPromise
24025          * @description  Returns a function that processes the successful
24026          * resolution of a save promise
24027          * @param {object} grid the grid for which the promise should be processed
24028          * @param {GridRow} gridRow the row that has been saved
24029          * @returns {function} the success handling function
24030          */
24031         processSuccessPromise: function ( grid, gridRow ) {
24032           var self = this;
24033
24034           return function() {
24035             delete gridRow.isSaving;
24036             delete gridRow.isDirty;
24037             delete gridRow.isError;
24038             delete gridRow.rowEditSaveTimer;
24039             delete gridRow.rowEditSavePromise;
24040             self.removeRow( grid.rowEdit.errorRows, gridRow );
24041             self.removeRow( grid.rowEdit.dirtyRows, gridRow );
24042           };
24043         },
24044
24045
24046         /**
24047          * @ngdoc method
24048          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24049          * @name processErrorPromise
24050          * @description  Returns a function that processes the failed
24051          * resolution of a save promise
24052          * @param {object} grid the grid for which the promise should be processed
24053          * @param {GridRow} gridRow the row that is now in error
24054          * @returns {function} the error handling function
24055          */
24056         processErrorPromise: function ( grid, gridRow ) {
24057           return function() {
24058             delete gridRow.isSaving;
24059             delete gridRow.rowEditSaveTimer;
24060             delete gridRow.rowEditSavePromise;
24061
24062             gridRow.isError = true;
24063
24064             if (!grid.rowEdit.errorRows){
24065               grid.rowEdit.errorRows = [];
24066             }
24067             if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
24068               grid.rowEdit.errorRows.push( gridRow );
24069             }
24070           };
24071         },
24072
24073
24074         /**
24075          * @ngdoc method
24076          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24077          * @name removeRow
24078          * @description  Removes a row from a cache of rows - either
24079          * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows.  If the row
24080          * is not present silently does nothing.
24081          * @param {array} rowArray the array from which to remove the row
24082          * @param {GridRow} gridRow the row that should be removed
24083          */
24084         removeRow: function( rowArray, removeGridRow ){
24085           if (typeof(rowArray) === 'undefined' || rowArray === null){
24086             return;
24087           }
24088
24089           rowArray.forEach( function( gridRow, index ){
24090             if ( gridRow.uid === removeGridRow.uid ){
24091               rowArray.splice( index, 1);
24092             }
24093           });
24094         },
24095
24096
24097         /**
24098          * @ngdoc method
24099          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24100          * @name isRowPresent
24101          * @description  Checks whether a row is already present
24102          * in the given array
24103          * @param {array} rowArray the array in which to look for the row
24104          * @param {GridRow} gridRow the row that should be looked for
24105          */
24106         isRowPresent: function( rowArray, removeGridRow ){
24107           var present = false;
24108           rowArray.forEach( function( gridRow, index ){
24109             if ( gridRow.uid === removeGridRow.uid ){
24110               present = true;
24111             }
24112           });
24113           return present;
24114         },
24115
24116
24117         /**
24118          * @ngdoc method
24119          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24120          * @name flushDirtyRows
24121          * @description Triggers a save event for all currently dirty rows, could
24122          * be used where user presses a save button or navigates away from the page
24123          * <pre>
24124          *      gridApi.rowEdit.flushDirtyRows(grid)
24125          * </pre>
24126          * @param {object} grid the grid for which dirty rows should be flushed
24127          * @returns {promise} a promise that represents the aggregate of all
24128          * of the individual save promises - i.e. it will be resolved when all
24129          * the individual save promises have been resolved.
24130          *
24131          */
24132         flushDirtyRows: function(grid){
24133           var promises = [];
24134           grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
24135             service.saveRow( grid, gridRow )();
24136             promises.push( gridRow.rowEditSavePromise );
24137           });
24138
24139           return $q.all( promises );
24140         },
24141
24142
24143         /**
24144          * @ngdoc method
24145          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24146          * @name endEditCell
24147          * @description Receives an afterCellEdit event from the edit function,
24148          * and sets flags as appropriate.  Only the rowEntity parameter
24149          * is processed, although other params are available.  Grid
24150          * is automatically provided by the gridApi.
24151          * @param {object} rowEntity the data entity for which the cell
24152          * was edited
24153          */
24154         endEditCell: function( rowEntity, colDef, newValue, previousValue ){
24155           var grid = this.grid;
24156           var gridRow = grid.getRow( rowEntity );
24157           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
24158
24159           if ( newValue !== previousValue || gridRow.isDirty ){
24160             if ( !grid.rowEdit.dirtyRows ){
24161               grid.rowEdit.dirtyRows = [];
24162             }
24163
24164             if ( !gridRow.isDirty ){
24165               gridRow.isDirty = true;
24166               grid.rowEdit.dirtyRows.push( gridRow );
24167             }
24168
24169             delete gridRow.isError;
24170
24171             service.considerSetTimer( grid, gridRow );
24172           }
24173         },
24174
24175
24176         /**
24177          * @ngdoc method
24178          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24179          * @name beginEditCell
24180          * @description Receives a beginCellEdit event from the edit function,
24181          * and cancels any rowEditSaveTimers if present, as the user is still editing
24182          * this row.  Only the rowEntity parameter
24183          * is processed, although other params are available.  Grid
24184          * is automatically provided by the gridApi.
24185          * @param {object} rowEntity the data entity for which the cell
24186          * editing has commenced
24187          */
24188         beginEditCell: function( rowEntity, colDef ){
24189           var grid = this.grid;
24190           var gridRow = grid.getRow( rowEntity );
24191           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
24192
24193           service.cancelTimer( grid, gridRow );
24194         },
24195
24196
24197         /**
24198          * @ngdoc method
24199          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24200          * @name cancelEditCell
24201          * @description Receives a cancelCellEdit event from the edit function,
24202          * and if the row was already dirty, restarts the save timer.  If the row
24203          * was not already dirty, then it's not dirty now either and does nothing.
24204          *
24205          * Only the rowEntity parameter
24206          * is processed, although other params are available.  Grid
24207          * is automatically provided by the gridApi.
24208          *
24209          * @param {object} rowEntity the data entity for which the cell
24210          * editing was cancelled
24211          */
24212         cancelEditCell: function( rowEntity, colDef ){
24213           var grid = this.grid;
24214           var gridRow = grid.getRow( rowEntity );
24215           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
24216
24217           service.considerSetTimer( grid, gridRow );
24218         },
24219
24220
24221         /**
24222          * @ngdoc method
24223          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24224          * @name navigate
24225          * @description cellNav tells us that the selected cell has changed.  If
24226          * the new row had a timer running, then stop it similar to in a beginCellEdit
24227          * call.  If the old row is dirty and not the same as the new row, then
24228          * start a timer on it.
24229          * @param {object} newRowCol the row and column that were selected
24230          * @param {object} oldRowCol the row and column that was left
24231          *
24232          */
24233         navigate: function( newRowCol, oldRowCol ){
24234           var grid = this.grid;
24235           if ( newRowCol.row.rowEditSaveTimer ){
24236             service.cancelTimer( grid, newRowCol.row );
24237           }
24238
24239           if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
24240             service.considerSetTimer( grid, oldRowCol.row );
24241           }
24242         },
24243
24244
24245         /**
24246          * @ngdoc property
24247          * @propertyOf ui.grid.rowEdit.api:GridOptions
24248          * @name rowEditWaitInterval
24249          * @description How long the grid should wait for another change on this row
24250          * before triggering a save (in milliseconds).  If set to -1, then saves are
24251          * never triggered by timer (implying that the user will call flushDirtyRows()
24252          * manually)
24253          *
24254          * @example
24255          * Setting the wait interval to 4 seconds
24256          * <pre>
24257          *   $scope.gridOptions = { rowEditWaitInterval: 4000 }
24258          * </pre>
24259          *
24260          */
24261         /**
24262          * @ngdoc method
24263          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24264          * @name considerSetTimer
24265          * @description Consider setting a timer on this row (if it is dirty).  if there is a timer running
24266          * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
24267          * dirty and not currently saving then set a new timer
24268          * @param {object} grid the grid for which we are processing
24269          * @param {GridRow} gridRow the row for which the timer should be adjusted
24270          *
24271          */
24272         considerSetTimer: function( grid, gridRow ){
24273           service.cancelTimer( grid, gridRow );
24274
24275           if ( gridRow.isDirty && !gridRow.isSaving ){
24276             if ( grid.options.rowEditWaitInterval !== -1 ){
24277               var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
24278               gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
24279             }
24280           }
24281         },
24282
24283
24284         /**
24285          * @ngdoc method
24286          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24287          * @name cancelTimer
24288          * @description cancel the $interval for any timer running on this row
24289          * then delete the timer itself
24290          * @param {object} grid the grid for which we are processing
24291          * @param {GridRow} gridRow the row for which the timer should be adjusted
24292          *
24293          */
24294         cancelTimer: function( grid, gridRow ){
24295           if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
24296             $interval.cancel(gridRow.rowEditSaveTimer);
24297             delete gridRow.rowEditSaveTimer;
24298           }
24299         },
24300
24301
24302         /**
24303          * @ngdoc method
24304          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24305          * @name setRowsDirty
24306          * @description Sets each of the rows passed in dataRows
24307          * to be dirty.  note that if you have only just inserted the
24308          * rows into your data you will need to wait for a $digest cycle
24309          * before the gridRows are present - so often you would wrap this
24310          * call in a $interval or $timeout
24311          * <pre>
24312          *      $interval( function() {
24313          *        gridApi.rowEdit.setRowsDirty( myDataRows);
24314          *      }, 0, 1);
24315          * </pre>
24316          * @param {object} grid the grid for which rows should be set dirty
24317          * @param {array} dataRows the data entities for which the gridRows
24318          * should be set dirty.
24319          *
24320          */
24321         setRowsDirty: function( grid, myDataRows ) {
24322           var gridRow;
24323           myDataRows.forEach( function( value, index ){
24324             gridRow = grid.getRow( value );
24325             if ( gridRow ){
24326               if ( !grid.rowEdit.dirtyRows ){
24327                 grid.rowEdit.dirtyRows = [];
24328               }
24329
24330               if ( !gridRow.isDirty ){
24331                 gridRow.isDirty = true;
24332                 grid.rowEdit.dirtyRows.push( gridRow );
24333               }
24334
24335               delete gridRow.isError;
24336
24337               service.considerSetTimer( grid, gridRow );
24338             } else {
24339               gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
24340             }
24341           });
24342         },
24343
24344
24345         /**
24346          * @ngdoc method
24347          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24348          * @name setRowsClean
24349          * @description Sets each of the rows passed in dataRows
24350          * to be clean, clearing the dirty flag and the error flag, and removing
24351          * the rows from the dirty and error caches.
24352          * @param {object} grid the grid for which rows should be set clean
24353          * @param {array} dataRows the data entities for which the gridRows
24354          * should be set clean.
24355          *
24356          */
24357         setRowsClean: function( grid, myDataRows ) {
24358           var gridRow;
24359
24360           myDataRows.forEach( function( value, index ){
24361             gridRow = grid.getRow( value );
24362             if ( gridRow ){
24363               delete gridRow.isDirty;
24364               service.removeRow( grid.rowEdit.dirtyRows, gridRow );
24365               service.cancelTimer( grid, gridRow );
24366
24367               delete gridRow.isError;
24368               service.removeRow( grid.rowEdit.errorRows, gridRow );
24369             } else {
24370               gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
24371             }
24372           });
24373         }
24374
24375       };
24376
24377       return service;
24378
24379     }]);
24380
24381   /**
24382    *  @ngdoc directive
24383    *  @name ui.grid.rowEdit.directive:uiGridEdit
24384    *  @element div
24385    *  @restrict A
24386    *
24387    *  @description Adds row editing features to the ui-grid-edit directive.
24388    *
24389    */
24390   module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
24391   function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
24392     return {
24393       replace: true,
24394       priority: 0,
24395       require: '^uiGrid',
24396       scope: false,
24397       compile: function () {
24398         return {
24399           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24400             uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
24401           },
24402           post: function ($scope, $elm, $attrs, uiGridCtrl) {
24403           }
24404         };
24405       }
24406     };
24407   }]);
24408
24409
24410   /**
24411    *  @ngdoc directive
24412    *  @name ui.grid.rowEdit.directive:uiGridViewport
24413    *  @element div
24414    *
24415    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
24416    *  for the grid row to allow coloring of saving and error rows
24417    */
24418   module.directive('uiGridViewport',
24419     ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
24420       function ($compile, uiGridConstants, gridUtil, $parse) {
24421         return {
24422           priority: -200, // run after default  directive
24423           scope: false,
24424           compile: function ($elm, $attrs) {
24425             var rowRepeatDiv = angular.element($elm.children().children()[0]);
24426
24427             var existingNgClass = rowRepeatDiv.attr("ng-class");
24428             var newNgClass = '';
24429             if ( existingNgClass ) {
24430               newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
24431             } else {
24432               newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
24433             }
24434             rowRepeatDiv.attr("ng-class", newNgClass);
24435
24436             return {
24437               pre: function ($scope, $elm, $attrs, controllers) {
24438
24439               },
24440               post: function ($scope, $elm, $attrs, controllers) {
24441               }
24442             };
24443           }
24444         };
24445       }]);
24446
24447 })();
24448
24449 (function () {
24450   'use strict';
24451
24452   /**
24453    * @ngdoc overview
24454    * @name ui.grid.saveState
24455    * @description
24456    *
24457    * # ui.grid.saveState
24458    *
24459    * <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>
24460    *
24461    * This module provides the ability to save the grid state, and restore
24462    * it when the user returns to the page.
24463    *
24464    * No UI is provided, the caller should provide their own UI/buttons
24465    * as appropriate. Usually the navigate events would be used to save
24466    * the grid state and restore it.
24467    *
24468    * <br/>
24469    * <br/>
24470    *
24471    * <div doc-module-components="ui.grid.save-state"></div>
24472    */
24473
24474   var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
24475
24476   /**
24477    *  @ngdoc object
24478    *  @name ui.grid.saveState.constant:uiGridSaveStateConstants
24479    *
24480    *  @description constants available in save state module
24481    */
24482
24483   module.constant('uiGridSaveStateConstants', {
24484     featureName: 'saveState'
24485   });
24486
24487   /**
24488    *  @ngdoc service
24489    *  @name ui.grid.saveState.service:uiGridSaveStateService
24490    *
24491    *  @description Services for saveState feature
24492    */
24493   module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
24494     function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
24495
24496       var service = {
24497
24498         initializeGrid: function (grid) {
24499
24500           //add feature namespace and any properties to grid for needed state
24501           grid.saveState = {};
24502           this.defaultGridOptions(grid.options);
24503
24504           /**
24505            *  @ngdoc object
24506            *  @name ui.grid.saveState.api:PublicApi
24507            *
24508            *  @description Public Api for saveState feature
24509            */
24510           var publicApi = {
24511             events: {
24512               saveState: {
24513               }
24514             },
24515             methods: {
24516               saveState: {
24517                 /**
24518                  * @ngdoc function
24519                  * @name save
24520                  * @methodOf  ui.grid.saveState.api:PublicApi
24521                  * @description Packages the current state of the grid into
24522                  * an object, and provides it to the user for saving
24523                  * @returns {object} the state as a javascript object that can be saved
24524                  */
24525                 save: function () {
24526                   return service.save(grid);
24527                 },
24528                 /**
24529                  * @ngdoc function
24530                  * @name restore
24531                  * @methodOf  ui.grid.saveState.api:PublicApi
24532                  * @description Restores the provided state into the grid
24533                  * @param {scope} $scope a scope that we can broadcast on
24534                  * @param {object} state the state that should be restored into the grid
24535                  */
24536                 restore: function ( $scope, state) {
24537                   service.restore(grid, $scope, state);
24538                 }
24539               }
24540             }
24541           };
24542
24543           grid.api.registerEventsFromObject(publicApi.events);
24544
24545           grid.api.registerMethodsFromObject(publicApi.methods);
24546
24547         },
24548
24549         defaultGridOptions: function (gridOptions) {
24550           //default option to true unless it was explicitly set to false
24551           /**
24552            * @ngdoc object
24553            * @name ui.grid.saveState.api:GridOptions
24554            *
24555            * @description GridOptions for saveState feature, these are available to be
24556            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
24557            */
24558           /**
24559            * @ngdoc object
24560            * @name saveWidths
24561            * @propertyOf  ui.grid.saveState.api:GridOptions
24562            * @description Save the current column widths.  Note that unless
24563            * you've provided the user with some way to resize their columns (say
24564            * the resize columns feature), then this makes little sense.
24565            * <br/>Defaults to true
24566            */
24567           gridOptions.saveWidths = gridOptions.saveWidths !== false;
24568           /**
24569            * @ngdoc object
24570            * @name saveOrder
24571            * @propertyOf  ui.grid.saveState.api:GridOptions
24572            * @description Restore the current column order.  Note that unless
24573            * you've provided the user with some way to reorder their columns (for
24574            * example the move columns feature), this makes little sense.
24575            * <br/>Defaults to true
24576            */
24577           gridOptions.saveOrder = gridOptions.saveOrder !== false;
24578           /**
24579            * @ngdoc object
24580            * @name saveScroll
24581            * @propertyOf  ui.grid.saveState.api:GridOptions
24582            * @description Save the current scroll position.  Note that this
24583            * is saved as the percentage of the grid scrolled - so if your
24584            * user returns to a grid with a significantly different number of
24585            * rows (perhaps some data has been deleted) then the scroll won't
24586            * actually show the same rows as before.  If you want to scroll to
24587            * a specific row then you should instead use the saveFocus option, which
24588            * is the default.
24589            *
24590            * Note that this element will only be saved if the cellNav feature is
24591            * enabled
24592            * <br/>Defaults to false
24593            */
24594           gridOptions.saveScroll = gridOptions.saveScroll === true;
24595           /**
24596            * @ngdoc object
24597            * @name saveFocus
24598            * @propertyOf  ui.grid.saveState.api:GridOptions
24599            * @description Save the current focused cell.  On returning
24600            * to this focused cell we'll also scroll.  This option is
24601            * preferred to the saveScroll option, so is set to true by
24602            * default.  If saveScroll is set to true then this option will
24603            * be disabled.
24604            *
24605            * By default this option saves the current row number and column
24606            * number, and returns to that row and column.  However, if you define
24607            * a saveRowIdentity function, then it will return you to the currently
24608            * selected column within that row (in a business sense - so if some
24609            * rows have been deleted, it will still find the same data, presuming it
24610            * still exists in the list.  If it isn't in the list then it will instead
24611            * return to the same row number - i.e. scroll percentage)
24612            *
24613            * Note that this option will do nothing if the cellNav
24614            * feature is not enabled.
24615            *
24616            * <br/>Defaults to true (unless saveScroll is true)
24617            */
24618           gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
24619           /**
24620            * @ngdoc object
24621            * @name saveRowIdentity
24622            * @propertyOf  ui.grid.saveState.api:GridOptions
24623            * @description A function that can be called, passing in a rowEntity,
24624            * and that will return a unique id for that row.  This might simply
24625            * return the `id` field from that row (if you have one), or it might
24626            * concatenate some fields within the row to make a unique value.
24627            *
24628            * This value will be used to find the same row again and set the focus
24629            * to it, if it exists when we return.
24630            *
24631            * <br/>Defaults to undefined
24632            */
24633           /**
24634            * @ngdoc object
24635            * @name saveVisible
24636            * @propertyOf  ui.grid.saveState.api:GridOptions
24637            * @description Save whether or not columns are visible.
24638            *
24639            * <br/>Defaults to true
24640            */
24641           gridOptions.saveVisible = gridOptions.saveVisible !== false;
24642           /**
24643            * @ngdoc object
24644            * @name saveSort
24645            * @propertyOf  ui.grid.saveState.api:GridOptions
24646            * @description Save the current sort state for each column
24647            *
24648            * <br/>Defaults to true
24649            */
24650           gridOptions.saveSort = gridOptions.saveSort !== false;
24651           /**
24652            * @ngdoc object
24653            * @name saveFilter
24654            * @propertyOf  ui.grid.saveState.api:GridOptions
24655            * @description Save the current filter state for each column
24656            *
24657            * <br/>Defaults to true
24658            */
24659           gridOptions.saveFilter = gridOptions.saveFilter !== false;
24660           /**
24661            * @ngdoc object
24662            * @name saveSelection
24663            * @propertyOf  ui.grid.saveState.api:GridOptions
24664            * @description Save the currently selected rows.  If the `saveRowIdentity` callback
24665            * is defined, then it will save the id of the row and select that.  If not, then
24666            * it will attempt to select the rows by row number, which will give the wrong results
24667            * if the data set has changed in the mean-time.
24668            *
24669            * Note that this option only does anything
24670            * if the selection feature is enabled.
24671            *
24672            * <br/>Defaults to true
24673            */
24674           gridOptions.saveSelection = gridOptions.saveSelection !== false;
24675           /**
24676            * @ngdoc object
24677            * @name saveGrouping
24678            * @propertyOf  ui.grid.saveState.api:GridOptions
24679            * @description Save the grouping configuration.  If set to true and the
24680            * grouping feature is not enabled then does nothing.
24681            *
24682            * <br/>Defaults to true
24683            */
24684           gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
24685           /**
24686            * @ngdoc object
24687            * @name saveGroupingExpandedStates
24688            * @propertyOf  ui.grid.saveState.api:GridOptions
24689            * @description Save the grouping row expanded states.  If set to true and the
24690            * grouping feature is not enabled then does nothing.
24691            *
24692            * This can be quite a bit of data, in many cases you wouldn't want to save this
24693            * information.
24694            *
24695            * <br/>Defaults to false
24696            */
24697           gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true;
24698           /**
24699            * @ngdoc object
24700            * @name savePinning
24701            * @propertyOf ui.grid.saveState.api:GridOptions
24702            * @description Save pinning state for columns.
24703            *
24704            * <br/>Defaults to true
24705            */
24706           gridOptions.savePinning = gridOptions.savePinning !== false;
24707           /**
24708            * @ngdoc object
24709            * @name saveTreeView
24710            * @propertyOf  ui.grid.saveState.api:GridOptions
24711            * @description Save the treeView configuration.  If set to true and the
24712            * treeView feature is not enabled then does nothing.
24713            *
24714            * <br/>Defaults to true
24715            */
24716           gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
24717         },
24718
24719
24720
24721         /**
24722          * @ngdoc function
24723          * @name save
24724          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24725          * @description Saves the current grid state into an object, and
24726          * passes that object back to the caller
24727          * @param {Grid} grid the grid whose state we'd like to save
24728          * @returns {object} the state ready to be saved
24729          */
24730         save: function (grid) {
24731           var savedState = {};
24732
24733           savedState.columns = service.saveColumns( grid );
24734           savedState.scrollFocus = service.saveScrollFocus( grid );
24735           savedState.selection = service.saveSelection( grid );
24736           savedState.grouping = service.saveGrouping( grid );
24737           savedState.treeView = service.saveTreeView( grid );
24738           savedState.pagination = service.savePagination( grid );
24739
24740           return savedState;
24741         },
24742
24743
24744         /**
24745          * @ngdoc function
24746          * @name restore
24747          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24748          * @description Applies the provided state to the grid
24749          *
24750          * @param {Grid} grid the grid whose state we'd like to restore
24751          * @param {scope} $scope a scope that we can broadcast on
24752          * @param {object} state the state we'd like to restore
24753          */
24754         restore: function( grid, $scope, state ){
24755           if ( state.columns ) {
24756             service.restoreColumns( grid, state.columns );
24757           }
24758
24759           if ( state.scrollFocus ){
24760             service.restoreScrollFocus( grid, $scope, state.scrollFocus );
24761           }
24762
24763           if ( state.selection ){
24764             service.restoreSelection( grid, state.selection );
24765           }
24766
24767           if ( state.grouping ){
24768             service.restoreGrouping( grid, state.grouping );
24769           }
24770
24771           if ( state.treeView ){
24772             service.restoreTreeView( grid, state.treeView );
24773           }
24774
24775           if ( state.pagination ){
24776             service.restorePagination( grid, state.pagination );
24777           }
24778
24779           grid.refresh();
24780         },
24781
24782
24783         /**
24784          * @ngdoc function
24785          * @name saveColumns
24786          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24787          * @description Saves the column setup, including sort, filters, ordering,
24788          * pinning and column widths.
24789          *
24790          * Works through the current columns, storing them in order.  Stores the
24791          * column name, then the visible flag, width, sort and filters for each column.
24792          *
24793          * @param {Grid} grid the grid whose state we'd like to save
24794          * @returns {array} the columns state ready to be saved
24795          */
24796         saveColumns: function( grid ) {
24797           var columns = [];
24798           grid.getOnlyDataColumns().forEach( function( column ) {
24799             var savedColumn = {};
24800             savedColumn.name = column.name;
24801
24802             if ( grid.options.saveVisible ){
24803               savedColumn.visible = column.visible;
24804             }
24805
24806             if ( grid.options.saveWidths ){
24807               savedColumn.width = column.width;
24808             }
24809
24810             // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
24811             if ( grid.options.saveSort ){
24812               savedColumn.sort = angular.copy( column.sort );
24813             }
24814
24815             if ( grid.options.saveFilter ){
24816               savedColumn.filters = [];
24817               column.filters.forEach( function( filter ){
24818                 var copiedFilter = {};
24819                 angular.forEach( filter, function( value, key) {
24820                   if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
24821                     copiedFilter[key] = value;
24822                   }
24823                 });
24824                 savedColumn.filters.push(copiedFilter);
24825               });
24826             }
24827
24828             if ( !!grid.api.pinning && grid.options.savePinning ){
24829               savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
24830             }
24831
24832             columns.push( savedColumn );
24833           });
24834
24835           return columns;
24836         },
24837
24838
24839         /**
24840          * @ngdoc function
24841          * @name saveScrollFocus
24842          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24843          * @description Saves the currently scroll or focus.
24844          *
24845          * If cellNav isn't present then does nothing - we can't return
24846          * to the scroll position without cellNav anyway.
24847          *
24848          * If the cellNav module is present, and saveFocus is true, then
24849          * it saves the currently focused cell.  If rowIdentity is present
24850          * then saves using rowIdentity, otherwise saves visibleRowNum.
24851          *
24852          * If the cellNav module is not present, and saveScroll is true, then
24853          * it approximates the current scroll row and column, and saves that.
24854          *
24855          * @param {Grid} grid the grid whose state we'd like to save
24856          * @returns {object} the selection state ready to be saved
24857          */
24858         saveScrollFocus: function( grid ){
24859           if ( !grid.api.cellNav ){
24860             return {};
24861           }
24862
24863           var scrollFocus = {};
24864           if ( grid.options.saveFocus ){
24865             scrollFocus.focus = true;
24866             var rowCol = grid.api.cellNav.getFocusedCell();
24867             if ( rowCol !== null ) {
24868               if ( rowCol.col !== null ){
24869                 scrollFocus.colName = rowCol.col.colDef.name;
24870               }
24871               if ( rowCol.row !== null ){
24872                 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
24873               }
24874             }
24875           }
24876
24877           if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
24878             scrollFocus.focus = false;
24879             if ( grid.renderContainers.body.prevRowScrollIndex ){
24880               scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
24881             }
24882
24883             if ( grid.renderContainers.body.prevColScrollIndex ){
24884               scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
24885             }
24886           }
24887
24888           return scrollFocus;
24889         },
24890
24891
24892         /**
24893          * @ngdoc function
24894          * @name saveSelection
24895          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24896          * @description Saves the currently selected rows, if the selection feature is enabled
24897          * @param {Grid} grid the grid whose state we'd like to save
24898          * @returns {array} the selection state ready to be saved
24899          */
24900         saveSelection: function( grid ){
24901           if ( !grid.api.selection || !grid.options.saveSelection ){
24902             return [];
24903           }
24904
24905           var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
24906             return service.getRowVal( grid, gridRow );
24907           });
24908
24909           return selection;
24910         },
24911
24912
24913         /**
24914          * @ngdoc function
24915          * @name saveGrouping
24916          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24917          * @description Saves the grouping state, if the grouping feature is enabled
24918          * @param {Grid} grid the grid whose state we'd like to save
24919          * @returns {object} the grouping state ready to be saved
24920          */
24921         saveGrouping: function( grid ){
24922           if ( !grid.api.grouping || !grid.options.saveGrouping ){
24923             return {};
24924           }
24925
24926           return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
24927         },
24928
24929
24930         /**
24931          * @ngdoc function
24932          * @name savePagination
24933          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24934          * @description Saves the pagination state, if the pagination feature is enabled
24935          * @param {Grid} grid the grid whose state we'd like to save
24936          * @returns {object} the pagination state ready to be saved
24937          */
24938         savePagination: function( grid ) {
24939           if ( !grid.api.pagination || !grid.options.paginationPageSize ){
24940             return {};
24941           }
24942
24943           return {
24944             paginationCurrentPage: grid.options.paginationCurrentPage,
24945             paginationPageSize: grid.options.paginationPageSize
24946           };
24947         },
24948
24949
24950         /**
24951          * @ngdoc function
24952          * @name saveTreeView
24953          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24954          * @description Saves the tree view state, if the tree feature is enabled
24955          * @param {Grid} grid the grid whose state we'd like to save
24956          * @returns {object} the tree view state ready to be saved
24957          */
24958         saveTreeView: function( grid ){
24959           if ( !grid.api.treeView || !grid.options.saveTreeView ){
24960             return {};
24961           }
24962
24963           return grid.api.treeView.getTreeView();
24964         },
24965
24966
24967         /**
24968          * @ngdoc function
24969          * @name getRowVal
24970          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24971          * @description Helper function that gets either the rowNum or
24972          * the saveRowIdentity, given a gridRow
24973          * @param {Grid} grid the grid the row is in
24974          * @param {GridRow} gridRow the row we want the rowNum for
24975          * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
24976          *
24977          */
24978         getRowVal: function( grid, gridRow ){
24979           if ( !gridRow ) {
24980             return null;
24981           }
24982
24983           var rowVal = {};
24984           if ( grid.options.saveRowIdentity ){
24985             rowVal.identity = true;
24986             rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
24987           } else {
24988             rowVal.identity = false;
24989             rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
24990           }
24991           return rowVal;
24992         },
24993
24994
24995         /**
24996          * @ngdoc function
24997          * @name restoreColumns
24998          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
24999          * @description Restores the columns, including order, visible, width,
25000          * pinning, sort and filters.
25001          *
25002          * @param {Grid} grid the grid whose state we'd like to restore
25003          * @param {object} columnsState the list of columns we had before, with their state
25004          */
25005         restoreColumns: function( grid, columnsState ){
25006           var isSortChanged = false;
25007
25008           columnsState.forEach( function( columnState, index ) {
25009             var currentCol = grid.getColumn( columnState.name );
25010
25011             if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
25012               if ( grid.options.saveVisible &&
25013                    ( currentCol.visible !== columnState.visible ||
25014                      currentCol.colDef.visible !== columnState.visible ) ){
25015                 currentCol.visible = columnState.visible;
25016                 currentCol.colDef.visible = columnState.visible;
25017                 grid.api.core.raise.columnVisibilityChanged(currentCol);
25018               }
25019
25020               if ( grid.options.saveWidths && currentCol.width !== columnState.width){
25021                 currentCol.width = columnState.width;
25022                 currentCol.hasCustomWidth = true;
25023               }
25024
25025               if ( grid.options.saveSort &&
25026                    !angular.equals(currentCol.sort, columnState.sort) &&
25027                    !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
25028                 currentCol.sort = angular.copy( columnState.sort );
25029                 isSortChanged = true;
25030               }
25031
25032               if ( grid.options.saveFilter &&
25033                    !angular.equals(currentCol.filters, columnState.filters ) ){
25034                 columnState.filters.forEach( function( filter, index ){
25035                   angular.extend( currentCol.filters[index], filter );
25036                   if ( typeof(filter.term) === 'undefined' || filter.term === null ){
25037                     delete currentCol.filters[index].term;
25038                   }
25039                 });
25040                 grid.api.core.raise.filterChanged();
25041               }
25042
25043               if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
25044                 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
25045               }
25046
25047               var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
25048               if (currentIndex !== -1) {
25049                 if (grid.options.saveOrder && currentIndex !== index) {
25050                   var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
25051                   grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
25052                 }
25053               }
25054             }
25055           });
25056
25057           if ( isSortChanged ) {
25058             grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
25059           }
25060         },
25061
25062
25063         /**
25064          * @ngdoc function
25065          * @name restoreScrollFocus
25066          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25067          * @description Scrolls to the position that was saved.  If focus is true, then
25068          * sets focus to the specified row/col.  If focus is false, then scrolls to the
25069          * specified row/col.
25070          *
25071          * @param {Grid} grid the grid whose state we'd like to restore
25072          * @param {scope} $scope a scope that we can broadcast on
25073          * @param {object} scrollFocusState the scroll/focus state ready to be restored
25074          */
25075         restoreScrollFocus: function( grid, $scope, scrollFocusState ){
25076           if ( !grid.api.cellNav ){
25077             return;
25078           }
25079
25080           var colDef, row;
25081           if ( scrollFocusState.colName ){
25082             var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
25083             if ( colDefs.length > 0 ){
25084               colDef = colDefs[0];
25085             }
25086           }
25087
25088           if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
25089             if ( scrollFocusState.rowVal.identity ){
25090               row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
25091             } else {
25092               row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
25093             }
25094           }
25095
25096           var entity = row && row.entity ? row.entity : null ;
25097
25098           if ( colDef || entity ) {
25099             if (scrollFocusState.focus ){
25100               grid.api.cellNav.scrollToFocus( entity, colDef );
25101             } else {
25102               grid.scrollTo( entity, colDef );
25103             }
25104           }
25105         },
25106
25107
25108         /**
25109          * @ngdoc function
25110          * @name restoreSelection
25111          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25112          * @description Selects the rows that are provided in the selection
25113          * state.  If you are using `saveRowIdentity` and more than one row matches the identity
25114          * function then only the first is selected.
25115          * @param {Grid} grid the grid whose state we'd like to restore
25116          * @param {object} selectionState the selection state ready to be restored
25117          */
25118         restoreSelection: function( grid, selectionState ){
25119           if ( !grid.api.selection ){
25120             return;
25121           }
25122
25123           grid.api.selection.clearSelectedRows();
25124
25125           selectionState.forEach(  function( rowVal ) {
25126             if ( rowVal.identity ){
25127               var foundRow = service.findRowByIdentity( grid, rowVal );
25128
25129               if ( foundRow ){
25130                 grid.api.selection.selectRow( foundRow.entity );
25131               }
25132
25133             } else {
25134               grid.api.selection.selectRowByVisibleIndex( rowVal.row );
25135             }
25136           });
25137         },
25138
25139
25140         /**
25141          * @ngdoc function
25142          * @name restoreGrouping
25143          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25144          * @description Restores the grouping configuration, if the grouping feature
25145          * is enabled.
25146          * @param {Grid} grid the grid whose state we'd like to restore
25147          * @param {object} groupingState the grouping state ready to be restored
25148          */
25149         restoreGrouping: function( grid, groupingState ){
25150           if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
25151             return;
25152           }
25153
25154           grid.api.grouping.setGrouping( groupingState );
25155         },
25156
25157         /**
25158          * @ngdoc function
25159          * @name restoreTreeView
25160          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25161          * @description Restores the tree view configuration, if the tree view feature
25162          * is enabled.
25163          * @param {Grid} grid the grid whose state we'd like to restore
25164          * @param {object} treeViewState the tree view state ready to be restored
25165          */
25166         restoreTreeView: function( grid, treeViewState ){
25167           if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
25168             return;
25169           }
25170
25171           grid.api.treeView.setTreeView( treeViewState );
25172         },
25173
25174         /**
25175          * @ngdoc function
25176          * @name restorePagination
25177          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25178          * @description Restores the pagination information, if pagination is enabled.
25179          * @param {Grid} grid the grid whose state we'd like to restore
25180          * @param {object} pagination the pagination object to be restored
25181          * @param {number} pagination.paginationCurrentPage the page number to restore
25182          * @param {number} pagination.paginationPageSize the number of items displayed per page
25183          */
25184         restorePagination: function( grid, pagination ){
25185           if ( !grid.api.pagination || !grid.options.paginationPageSize ){
25186             return;
25187           }
25188
25189           grid.options.paginationCurrentPage = pagination.paginationCurrentPage;
25190           grid.options.paginationPageSize = pagination.paginationPageSize;
25191         },
25192
25193         /**
25194          * @ngdoc function
25195          * @name findRowByIdentity
25196          * @methodOf  ui.grid.saveState.service:uiGridSaveStateService
25197          * @description Finds a row given it's identity value, returns the first found row
25198          * if any are found, otherwise returns null if no rows are found.
25199          * @param {Grid} grid the grid whose state we'd like to restore
25200          * @param {object} rowVal the row we'd like to find
25201          * @returns {gridRow} the found row, or null if none found
25202          */
25203         findRowByIdentity: function( grid, rowVal ){
25204           if ( !grid.options.saveRowIdentity ){
25205             return null;
25206           }
25207
25208           var filteredRows = grid.rows.filter( function( gridRow ) {
25209             if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
25210               return true;
25211             } else {
25212               return false;
25213             }
25214           });
25215
25216           if ( filteredRows.length > 0 ){
25217             return filteredRows[0];
25218           } else {
25219             return null;
25220           }
25221         }
25222       };
25223
25224       return service;
25225
25226     }
25227   ]);
25228
25229   /**
25230    *  @ngdoc directive
25231    *  @name ui.grid.saveState.directive:uiGridSaveState
25232    *  @element div
25233    *  @restrict A
25234    *
25235    *  @description Adds saveState features to grid
25236    *
25237    *  @example
25238    <example module="app">
25239    <file name="app.js">
25240    var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
25241
25242    app.controller('MainCtrl', ['$scope', function ($scope) {
25243       $scope.data = [
25244         { name: 'Bob', title: 'CEO' },
25245         { name: 'Frank', title: 'Lowly Developer' }
25246       ];
25247
25248       $scope.gridOptions = {
25249         columnDefs: [
25250           {name: 'name'},
25251           {name: 'title', enableCellEdit: true}
25252         ],
25253         data: $scope.data
25254       };
25255     }]);
25256    </file>
25257    <file name="index.html">
25258    <div ng-controller="MainCtrl">
25259    <div ui-grid="gridOptions" ui-grid-save-state></div>
25260    </div>
25261    </file>
25262    </example>
25263    */
25264   module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
25265     function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
25266       return {
25267         replace: true,
25268         priority: 0,
25269         require: '^uiGrid',
25270         scope: false,
25271         link: function ($scope, $elm, $attrs, uiGridCtrl) {
25272           uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
25273         }
25274       };
25275     }
25276   ]);
25277 })();
25278
25279 (function () {
25280   'use strict';
25281
25282   /**
25283    * @ngdoc overview
25284    * @name ui.grid.selection
25285    * @description
25286    *
25287    * # ui.grid.selection
25288    * This module provides row selection
25289    *
25290    * <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>
25291    *
25292    * <div doc-module-components="ui.grid.selection"></div>
25293    */
25294
25295   var module = angular.module('ui.grid.selection', ['ui.grid']);
25296
25297   /**
25298    *  @ngdoc object
25299    *  @name ui.grid.selection.constant:uiGridSelectionConstants
25300    *
25301    *  @description constants available in selection module
25302    */
25303   module.constant('uiGridSelectionConstants', {
25304     featureName: "selection",
25305     selectionRowHeaderColName: 'selectionRowHeaderCol'
25306   });
25307
25308   //add methods to GridRow
25309   angular.module('ui.grid').config(['$provide', function($provide) {
25310     $provide.decorator('GridRow', ['$delegate', function($delegate) {
25311
25312       /**
25313        *  @ngdoc object
25314        *  @name ui.grid.selection.api:GridRow
25315        *
25316        *  @description GridRow prototype functions added for selection
25317        */
25318
25319       /**
25320        *  @ngdoc object
25321        *  @name enableSelection
25322        *  @propertyOf  ui.grid.selection.api:GridRow
25323        *  @description Enable row selection for this row, only settable by internal code.
25324        *
25325        *  The grouping feature, for example, might set group header rows to not be selectable.
25326        *  <br/>Defaults to true
25327        */
25328
25329       /**
25330        *  @ngdoc object
25331        *  @name isSelected
25332        *  @propertyOf  ui.grid.selection.api:GridRow
25333        *  @description Selected state of row.  Should be readonly. Make any changes to selected state using setSelected().
25334        *  <br/>Defaults to false
25335        */
25336
25337
25338         /**
25339          * @ngdoc function
25340          * @name setSelected
25341          * @methodOf ui.grid.selection.api:GridRow
25342          * @description Sets the isSelected property and updates the selectedCount
25343          * Changes to isSelected state should only be made via this function
25344          * @param {bool} selected value to set
25345          */
25346         $delegate.prototype.setSelected = function(selected) {
25347           if (selected !== this.isSelected) {
25348             this.isSelected = selected;
25349             this.grid.selection.selectedCount += selected ? 1 : -1;
25350           }
25351         };
25352
25353       return $delegate;
25354     }]);
25355   }]);
25356
25357   /**
25358    *  @ngdoc service
25359    *  @name ui.grid.selection.service:uiGridSelectionService
25360    *
25361    *  @description Services for selection features
25362    */
25363   module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
25364     function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
25365
25366       var service = {
25367
25368         initializeGrid: function (grid) {
25369
25370           //add feature namespace and any properties to grid for needed
25371           /**
25372            *  @ngdoc object
25373            *  @name ui.grid.selection.grid:selection
25374            *
25375            *  @description Grid properties and functions added for selection
25376            */
25377           grid.selection = {};
25378           grid.selection.lastSelectedRow = null;
25379           grid.selection.selectAll = false;
25380
25381
25382           /**
25383            *  @ngdoc object
25384            *  @name selectedCount
25385            *  @propertyOf  ui.grid.selection.grid:selection
25386            *  @description Current count of selected rows
25387            *  @example
25388            *  var count = grid.selection.selectedCount
25389            */
25390           grid.selection.selectedCount = 0;
25391
25392           service.defaultGridOptions(grid.options);
25393
25394           /**
25395            *  @ngdoc object
25396            *  @name ui.grid.selection.api:PublicApi
25397            *
25398            *  @description Public Api for selection feature
25399            */
25400           var publicApi = {
25401             events: {
25402               selection: {
25403                 /**
25404                  * @ngdoc event
25405                  * @name rowSelectionChanged
25406                  * @eventOf  ui.grid.selection.api:PublicApi
25407                  * @description  is raised after the row.isSelected state is changed
25408                  * @param {GridRow} row the row that was selected/deselected
25409                  * @param {Event} event object if raised from an event
25410                  */
25411                 rowSelectionChanged: function (scope, row, evt) {
25412                 },
25413                 /**
25414                  * @ngdoc event
25415                  * @name rowSelectionChangedBatch
25416                  * @eventOf  ui.grid.selection.api:PublicApi
25417                  * @description  is raised after the row.isSelected state is changed
25418                  * in bulk, if the `enableSelectionBatchEvent` option is set to true
25419                  * (which it is by default).  This allows more efficient processing
25420                  * of bulk events.
25421                  * @param {array} rows the rows that were selected/deselected
25422                  * @param {Event} event object if raised from an event
25423                  */
25424                 rowSelectionChangedBatch: function (scope, rows, evt) {
25425                 }
25426               }
25427             },
25428             methods: {
25429               selection: {
25430                 /**
25431                  * @ngdoc function
25432                  * @name toggleRowSelection
25433                  * @methodOf  ui.grid.selection.api:PublicApi
25434                  * @description Toggles data row as selected or unselected
25435                  * @param {object} rowEntity gridOptions.data[] array instance
25436                  * @param {Event} event object if raised from an event
25437                  */
25438                 toggleRowSelection: function (rowEntity, evt) {
25439                   var row = grid.getRow(rowEntity);
25440                   if (row !== null) {
25441                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25442                   }
25443                 },
25444                 /**
25445                  * @ngdoc function
25446                  * @name selectRow
25447                  * @methodOf  ui.grid.selection.api:PublicApi
25448                  * @description Select the data row
25449                  * @param {object} rowEntity gridOptions.data[] array instance
25450                  * @param {Event} event object if raised from an event
25451                  */
25452                 selectRow: function (rowEntity, evt) {
25453                   var row = grid.getRow(rowEntity);
25454                   if (row !== null && !row.isSelected) {
25455                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25456                   }
25457                 },
25458                 /**
25459                  * @ngdoc function
25460                  * @name selectRowByVisibleIndex
25461                  * @methodOf  ui.grid.selection.api:PublicApi
25462                  * @description Select the specified row by visible index (i.e. if you
25463                  * specify row 0 you'll get the first visible row selected).  In this context
25464                  * visible means of those rows that are theoretically visible (i.e. not filtered),
25465                  * rather than rows currently rendered on the screen.
25466                  * @param {number} index index within the rowsVisible array
25467                  * @param {Event} event object if raised from an event
25468                  */
25469                 selectRowByVisibleIndex: function ( rowNum, evt ) {
25470                   var row = grid.renderContainers.body.visibleRowCache[rowNum];
25471                   if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
25472                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25473                   }
25474                 },
25475                 /**
25476                  * @ngdoc function
25477                  * @name unSelectRow
25478                  * @methodOf  ui.grid.selection.api:PublicApi
25479                  * @description UnSelect the data row
25480                  * @param {object} rowEntity gridOptions.data[] array instance
25481                  * @param {Event} event object if raised from an event
25482                  */
25483                 unSelectRow: function (rowEntity, evt) {
25484                   var row = grid.getRow(rowEntity);
25485                   if (row !== null && row.isSelected) {
25486                     service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25487                   }
25488                 },
25489                 /**
25490                  * @ngdoc function
25491                  * @name selectAllRows
25492                  * @methodOf  ui.grid.selection.api:PublicApi
25493                  * @description Selects all rows.  Does nothing if multiSelect = false
25494                  * @param {Event} event object if raised from an event
25495                  */
25496                 selectAllRows: function (evt) {
25497                   if (grid.options.multiSelect === false) {
25498                     return;
25499                   }
25500
25501                   var changedRows = [];
25502                   grid.rows.forEach(function (row) {
25503                     if ( !row.isSelected && row.enableSelection !== false ){
25504                       row.setSelected(true);
25505                       service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25506                     }
25507                   });
25508                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25509                   grid.selection.selectAll = true;
25510                 },
25511                 /**
25512                  * @ngdoc function
25513                  * @name selectAllVisibleRows
25514                  * @methodOf  ui.grid.selection.api:PublicApi
25515                  * @description Selects all visible rows.  Does nothing if multiSelect = false
25516                  * @param {Event} event object if raised from an event
25517                  */
25518                 selectAllVisibleRows: function (evt) {
25519                   if (grid.options.multiSelect === false) {
25520                     return;
25521                   }
25522
25523                   var changedRows = [];
25524                   grid.rows.forEach(function (row) {
25525                     if (row.visible) {
25526                       if (!row.isSelected && row.enableSelection !== false){
25527                         row.setSelected(true);
25528                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25529                       }
25530                     } else {
25531                       if (row.isSelected){
25532                         row.setSelected(false);
25533                         service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25534                       }
25535                     }
25536                   });
25537                   service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25538                   grid.selection.selectAll = true;
25539                 },
25540                 /**
25541                  * @ngdoc function
25542                  * @name clearSelectedRows
25543                  * @methodOf  ui.grid.selection.api:PublicApi
25544                  * @description Unselects all rows
25545                  * @param {Event} event object if raised from an event
25546                  */
25547                 clearSelectedRows: function (evt) {
25548                   service.clearSelectedRows(grid, evt);
25549                 },
25550                 /**
25551                  * @ngdoc function
25552                  * @name getSelectedRows
25553                  * @methodOf  ui.grid.selection.api:PublicApi
25554                  * @description returns all selectedRow's entity references
25555                  */
25556                 getSelectedRows: function () {
25557                   return service.getSelectedRows(grid).map(function (gridRow) {
25558                     return gridRow.entity;
25559                   });
25560                 },
25561                 /**
25562                  * @ngdoc function
25563                  * @name getSelectedGridRows
25564                  * @methodOf  ui.grid.selection.api:PublicApi
25565                  * @description returns all selectedRow's as gridRows
25566                  */
25567                 getSelectedGridRows: function () {
25568                   return service.getSelectedRows(grid);
25569                 },
25570                 /**
25571                  * @ngdoc function
25572                  * @name getSelectedCount
25573                  * @methodOf  ui.grid.selection.api:PublicApi
25574                  * @description returns the number of rows selected
25575                  */
25576                 getSelectedCount: function () {
25577                   return grid.selection.selectedCount;
25578                 },
25579                 /**
25580                  * @ngdoc function
25581                  * @name setMultiSelect
25582                  * @methodOf  ui.grid.selection.api:PublicApi
25583                  * @description Sets the current gridOption.multiSelect to true or false
25584                  * @param {bool} multiSelect true to allow multiple rows
25585                  */
25586                 setMultiSelect: function (multiSelect) {
25587                   grid.options.multiSelect = multiSelect;
25588                 },
25589                 /**
25590                  * @ngdoc function
25591                  * @name setModifierKeysToMultiSelect
25592                  * @methodOf  ui.grid.selection.api:PublicApi
25593                  * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
25594                  * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
25595                  */
25596                 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
25597                   grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
25598                 },
25599                 /**
25600                  * @ngdoc function
25601                  * @name getSelectAllState
25602                  * @methodOf  ui.grid.selection.api:PublicApi
25603                  * @description Returns whether or not the selectAll checkbox is currently ticked.  The
25604                  * grid doesn't automatically select rows when you add extra data - so when you add data
25605                  * you need to explicitly check whether the selectAll is set, and then call setVisible rows
25606                  * if it is
25607                  */
25608                 getSelectAllState: function () {
25609                   return grid.selection.selectAll;
25610                 }
25611
25612               }
25613             }
25614           };
25615
25616           grid.api.registerEventsFromObject(publicApi.events);
25617
25618           grid.api.registerMethodsFromObject(publicApi.methods);
25619
25620         },
25621
25622         defaultGridOptions: function (gridOptions) {
25623           //default option to true unless it was explicitly set to false
25624           /**
25625            *  @ngdoc object
25626            *  @name ui.grid.selection.api:GridOptions
25627            *
25628            *  @description GridOptions for selection feature, these are available to be
25629            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
25630            */
25631
25632           /**
25633            *  @ngdoc object
25634            *  @name enableRowSelection
25635            *  @propertyOf  ui.grid.selection.api:GridOptions
25636            *  @description Enable row selection for entire grid.
25637            *  <br/>Defaults to true
25638            */
25639           gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
25640           /**
25641            *  @ngdoc object
25642            *  @name multiSelect
25643            *  @propertyOf  ui.grid.selection.api:GridOptions
25644            *  @description Enable multiple row selection for entire grid
25645            *  <br/>Defaults to true
25646            */
25647           gridOptions.multiSelect = gridOptions.multiSelect !== false;
25648           /**
25649            *  @ngdoc object
25650            *  @name noUnselect
25651            *  @propertyOf  ui.grid.selection.api:GridOptions
25652            *  @description Prevent a row from being unselected.  Works in conjunction
25653            *  with `multiselect = false` and `gridApi.selection.selectRow()` to allow
25654            *  you to create a single selection only grid - a row is always selected, you
25655            *  can only select different rows, you can't unselect the row.
25656            *  <br/>Defaults to false
25657            */
25658           gridOptions.noUnselect = gridOptions.noUnselect === true;
25659           /**
25660            *  @ngdoc object
25661            *  @name modifierKeysToMultiSelect
25662            *  @propertyOf  ui.grid.selection.api:GridOptions
25663            *  @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
25664            *  <br/>Defaults to false
25665            */
25666           gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
25667           /**
25668            *  @ngdoc object
25669            *  @name enableRowHeaderSelection
25670            *  @propertyOf  ui.grid.selection.api:GridOptions
25671            *  @description Enable a row header to be used for selection
25672            *  <br/>Defaults to true
25673            */
25674           gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
25675           /**
25676            *  @ngdoc object
25677            *  @name enableFullRowSelection
25678            *  @propertyOf  ui.grid.selection.api:GridOptions
25679            *  @description Enable selection by clicking anywhere on the row.  Defaults to
25680            *  false if `enableRowHeaderSelection` is true, otherwise defaults to false.
25681            */
25682           if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
25683             gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
25684           }
25685           /**
25686            *  @ngdoc object
25687            *  @name enableSelectAll
25688            *  @propertyOf  ui.grid.selection.api:GridOptions
25689            *  @description Enable the select all checkbox at the top of the selectionRowHeader
25690            *  <br/>Defaults to true
25691            */
25692           gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
25693           /**
25694            *  @ngdoc object
25695            *  @name enableSelectionBatchEvent
25696            *  @propertyOf  ui.grid.selection.api:GridOptions
25697            *  @description If selected rows are changed in bulk, either via the API or
25698            *  via the selectAll checkbox, then a separate event is fired.  Setting this
25699            *  option to false will cause the rowSelectionChanged event to be called multiple times
25700            *  instead
25701            *  <br/>Defaults to true
25702            */
25703           gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
25704           /**
25705            *  @ngdoc object
25706            *  @name selectionRowHeaderWidth
25707            *  @propertyOf  ui.grid.selection.api:GridOptions
25708            *  @description can be used to set a custom width for the row header selection column
25709            *  <br/>Defaults to 30px
25710            */
25711           gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
25712
25713           /**
25714            *  @ngdoc object
25715            *  @name enableFooterTotalSelected
25716            *  @propertyOf  ui.grid.selection.api:GridOptions
25717            *  @description Shows the total number of selected items in footer if true.
25718            *  <br/>Defaults to true.
25719            *  <br/>GridOptions.showGridFooter must also be set to true.
25720            */
25721           gridOptions.enableFooterTotalSelected = gridOptions.enableFooterTotalSelected !== false;
25722
25723           /**
25724            *  @ngdoc object
25725            *  @name isRowSelectable
25726            *  @propertyOf  ui.grid.selection.api:GridOptions
25727            *  @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
25728            */
25729
25730           gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
25731         },
25732
25733         /**
25734          * @ngdoc function
25735          * @name toggleRowSelection
25736          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25737          * @description Toggles row as selected or unselected
25738          * @param {Grid} grid grid object
25739          * @param {GridRow} row row to select or deselect
25740          * @param {Event} event object if resulting from event
25741          * @param {bool} multiSelect if false, only one row at time can be selected
25742          * @param {bool} noUnselect if true then rows cannot be unselected
25743          */
25744         toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
25745           var selected = row.isSelected;
25746
25747           if ( row.enableSelection === false && !selected ){
25748             return;
25749           }
25750
25751           var selectedRows;
25752           if (!multiSelect && !selected) {
25753             service.clearSelectedRows(grid, evt);
25754           } else if (!multiSelect && selected) {
25755             selectedRows = service.getSelectedRows(grid);
25756             if (selectedRows.length > 1) {
25757               selected = false; // Enable reselect of the row
25758               service.clearSelectedRows(grid, evt);
25759             }
25760           }
25761
25762           if (selected && noUnselect){
25763             // don't deselect the row
25764           } else {
25765             row.setSelected(!selected);
25766             if (row.isSelected === true) {
25767               grid.selection.lastSelectedRow = row;
25768             }
25769
25770             selectedRows = service.getSelectedRows(grid);
25771             grid.selection.selectAll = grid.rows.length === selectedRows.length;
25772
25773             grid.api.selection.raise.rowSelectionChanged(row, evt);
25774           }
25775         },
25776         /**
25777          * @ngdoc function
25778          * @name shiftSelect
25779          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25780          * @description selects a group of rows from the last selected row using the shift key
25781          * @param {Grid} grid grid object
25782          * @param {GridRow} clicked row
25783          * @param {Event} event object if raised from an event
25784          * @param {bool} multiSelect if false, does nothing this is for multiSelect only
25785          */
25786         shiftSelect: function (grid, row, evt, multiSelect) {
25787           if (!multiSelect) {
25788             return;
25789           }
25790           var selectedRows = service.getSelectedRows(grid);
25791           var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
25792           var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
25793           //reverse select direction
25794           if (fromRow > toRow) {
25795             var tmp = fromRow;
25796             fromRow = toRow;
25797             toRow = tmp;
25798           }
25799
25800           var changedRows = [];
25801           for (var i = fromRow; i <= toRow; i++) {
25802             var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
25803             if (rowToSelect) {
25804               if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
25805                 rowToSelect.setSelected(true);
25806                 grid.selection.lastSelectedRow = rowToSelect;
25807                 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
25808               }
25809             }
25810           }
25811           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25812         },
25813         /**
25814          * @ngdoc function
25815          * @name getSelectedRows
25816          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25817          * @description Returns all the selected rows
25818          * @param {Grid} grid grid object
25819          */
25820         getSelectedRows: function (grid) {
25821           return grid.rows.filter(function (row) {
25822             return row.isSelected;
25823           });
25824         },
25825
25826         /**
25827          * @ngdoc function
25828          * @name clearSelectedRows
25829          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25830          * @description Clears all selected rows
25831          * @param {Grid} grid grid object
25832          * @param {Event} event object if raised from an event
25833          */
25834         clearSelectedRows: function (grid, evt) {
25835           var changedRows = [];
25836           service.getSelectedRows(grid).forEach(function (row) {
25837             if ( row.isSelected ){
25838               row.setSelected(false);
25839               service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25840             }
25841           });
25842           service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25843           grid.selection.selectAll = false;
25844           grid.selection.selectedCount = 0;
25845         },
25846
25847         /**
25848          * @ngdoc function
25849          * @name decideRaiseSelectionEvent
25850          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25851          * @description Decides whether to raise a single event or a batch event
25852          * @param {Grid} grid grid object
25853          * @param {GridRow} row row that has changed
25854          * @param {array} changedRows an array to which we can append the changed
25855          * @param {Event} event object if raised from an event
25856          * row if we're doing batch events
25857          */
25858         decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
25859           if ( !grid.options.enableSelectionBatchEvent ){
25860             grid.api.selection.raise.rowSelectionChanged(row, evt);
25861           } else {
25862             changedRows.push(row);
25863           }
25864         },
25865
25866         /**
25867          * @ngdoc function
25868          * @name raiseSelectionEvent
25869          * @methodOf  ui.grid.selection.service:uiGridSelectionService
25870          * @description Decides whether we need to raise a batch event, and
25871          * raises it if we do.
25872          * @param {Grid} grid grid object
25873          * @param {array} changedRows an array of changed rows, only populated
25874          * @param {Event} event object if raised from an event
25875          * if we're doing batch events
25876          */
25877         decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
25878           if ( changedRows.length > 0 ){
25879             grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
25880           }
25881         }
25882       };
25883
25884       return service;
25885
25886     }]);
25887
25888   /**
25889    *  @ngdoc directive
25890    *  @name ui.grid.selection.directive:uiGridSelection
25891    *  @element div
25892    *  @restrict A
25893    *
25894    *  @description Adds selection features to grid
25895    *
25896    *  @example
25897    <example module="app">
25898    <file name="app.js">
25899    var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
25900
25901    app.controller('MainCtrl', ['$scope', function ($scope) {
25902       $scope.data = [
25903         { name: 'Bob', title: 'CEO' },
25904             { name: 'Frank', title: 'Lowly Developer' }
25905       ];
25906
25907       $scope.columnDefs = [
25908         {name: 'name', enableCellEdit: true},
25909         {name: 'title', enableCellEdit: true}
25910       ];
25911     }]);
25912    </file>
25913    <file name="index.html">
25914    <div ng-controller="MainCtrl">
25915    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
25916    </div>
25917    </file>
25918    </example>
25919    */
25920   module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
25921     function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
25922       return {
25923         replace: true,
25924         priority: 0,
25925         require: '^uiGrid',
25926         scope: false,
25927         compile: function () {
25928           return {
25929             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
25930               uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
25931               if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
25932                 var selectionRowHeaderDef = {
25933                   name: uiGridSelectionConstants.selectionRowHeaderColName,
25934                   displayName: '',
25935                   width:  uiGridCtrl.grid.options.selectionRowHeaderWidth,
25936                   minWidth: 10,
25937                   cellTemplate: 'ui-grid/selectionRowHeader',
25938                   headerCellTemplate: 'ui-grid/selectionHeaderCell',
25939                   enableColumnResizing: false,
25940                   enableColumnMenu: false,
25941                   exporterSuppressExport: true,
25942                   allowCellFocus: true
25943                 };
25944
25945                 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef, 0);
25946               }
25947
25948               var processorSet = false;
25949
25950               var processSelectableRows = function( rows ){
25951                 rows.forEach(function(row){
25952                   row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
25953                 });
25954                 return rows;
25955               };
25956
25957               var updateOptions = function(){
25958                 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
25959                   uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
25960                   processorSet = true;
25961                 }
25962               };
25963
25964               updateOptions();
25965
25966               var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
25967
25968               $scope.$on( '$destroy', dataChangeDereg);
25969             },
25970             post: function ($scope, $elm, $attrs, uiGridCtrl) {
25971
25972             }
25973           };
25974         }
25975       };
25976     }]);
25977
25978   module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService', 'gridUtil',
25979     function ($templateCache, uiGridSelectionService, gridUtil) {
25980       return {
25981         replace: true,
25982         restrict: 'E',
25983         template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
25984         scope: true,
25985         require: '^uiGrid',
25986         link: function($scope, $elm, $attrs, uiGridCtrl) {
25987           var self = uiGridCtrl.grid;
25988           $scope.selectButtonClick = selectButtonClick;
25989
25990           // On IE, prevent mousedowns on the select button from starting a selection.
25991           //   If this is not done and you shift+click on another row, the browser will select a big chunk of text
25992           if (gridUtil.detectBrowser() === 'ie') {
25993             $elm.on('mousedown', selectButtonMouseDown);
25994           }
25995
25996
25997           function selectButtonClick(row, evt) {
25998             evt.stopPropagation();
25999
26000             if (evt.shiftKey) {
26001               uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
26002             }
26003             else if (evt.ctrlKey || evt.metaKey) {
26004               uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
26005             }
26006             else {
26007               uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
26008             }
26009           }
26010
26011           function selectButtonMouseDown(evt) {
26012             if (evt.ctrlKey || evt.shiftKey) {
26013               evt.target.onselectstart = function () { return false; };
26014               window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
26015             }
26016           }
26017
26018           $scope.$on('$destroy', function unbindEvents() {
26019             $elm.off();
26020           });
26021         }
26022       };
26023     }]);
26024
26025   module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
26026     function ($templateCache, uiGridSelectionService) {
26027       return {
26028         replace: true,
26029         restrict: 'E',
26030         template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
26031         scope: false,
26032         link: function($scope, $elm, $attrs, uiGridCtrl) {
26033           var self = $scope.col.grid;
26034
26035           $scope.headerButtonClick = function(row, evt) {
26036             if ( self.selection.selectAll ){
26037               uiGridSelectionService.clearSelectedRows(self, evt);
26038               if ( self.options.noUnselect ){
26039                 self.api.selection.selectRowByVisibleIndex(0, evt);
26040               }
26041               self.selection.selectAll = false;
26042             } else {
26043               if ( self.options.multiSelect ){
26044                 self.api.selection.selectAllVisibleRows(evt);
26045                 self.selection.selectAll = true;
26046               }
26047             }
26048           };
26049         }
26050       };
26051     }]);
26052
26053   /**
26054    *  @ngdoc directive
26055    *  @name ui.grid.selection.directive:uiGridViewport
26056    *  @element div
26057    *
26058    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
26059    *  for the grid row
26060    */
26061   module.directive('uiGridViewport',
26062     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
26063       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
26064         return {
26065           priority: -200, // run after default  directive
26066           scope: false,
26067           compile: function ($elm, $attrs) {
26068             var rowRepeatDiv = angular.element($elm.children().children()[0]);
26069
26070             var existingNgClass = rowRepeatDiv.attr("ng-class");
26071             var newNgClass = '';
26072             if ( existingNgClass ) {
26073               newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
26074             } else {
26075               newNgClass = "{'ui-grid-row-selected': row.isSelected}";
26076             }
26077             rowRepeatDiv.attr("ng-class", newNgClass);
26078
26079             return {
26080               pre: function ($scope, $elm, $attrs, controllers) {
26081
26082               },
26083               post: function ($scope, $elm, $attrs, controllers) {
26084               }
26085             };
26086           }
26087         };
26088       }]);
26089
26090   /**
26091    *  @ngdoc directive
26092    *  @name ui.grid.selection.directive:uiGridCell
26093    *  @element div
26094    *  @restrict A
26095    *
26096    *  @description Stacks on top of ui.grid.uiGridCell to provide selection feature
26097    */
26098   module.directive('uiGridCell',
26099     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
26100       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
26101         return {
26102           priority: -200, // run after default uiGridCell directive
26103           restrict: 'A',
26104           require: '?^uiGrid',
26105           scope: false,
26106           link: function ($scope, $elm, $attrs, uiGridCtrl) {
26107
26108             var touchStartTime = 0;
26109             var touchTimeout = 300;
26110
26111             // Bind to keydown events in the render container
26112             if (uiGridCtrl.grid.api.cellNav) {
26113
26114               uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
26115                 if (rowCol === null ||
26116                   rowCol.row !== $scope.row ||
26117                   rowCol.col !== $scope.col) {
26118                   return;
26119                 }
26120
26121                 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
26122                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26123                   $scope.$apply();
26124                 }
26125
26126               //  uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
26127               });
26128             }
26129
26130             //$elm.bind('keydown', function (evt) {
26131             //  if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
26132             //    uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26133             //    $scope.$apply();
26134             //  }
26135             //});
26136
26137             var selectCells = function(evt){
26138               // if you click on expandable icon doesn't trigger selection
26139               if (evt.target.className === "ui-grid-icon-minus-squared" || evt.target.className === "ui-grid-icon-plus-squared") {
26140                 return;
26141               }
26142
26143               // if we get a click, then stop listening for touchend
26144               $elm.off('touchend', touchEnd);
26145
26146               if (evt.shiftKey) {
26147                 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
26148               }
26149               else if (evt.ctrlKey || evt.metaKey) {
26150                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
26151               }
26152               else {
26153                 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26154               }
26155               $scope.$apply();
26156
26157               // don't re-enable the touchend handler for a little while - some devices generate both, and it will
26158               // take a little while to move your hand from the mouse to the screen if you have both modes of input
26159               $timeout(function() {
26160                 $elm.on('touchend', touchEnd);
26161               }, touchTimeout);
26162             };
26163
26164             var touchStart = function(evt){
26165               touchStartTime = (new Date()).getTime();
26166
26167               // if we get a touch event, then stop listening for click
26168               $elm.off('click', selectCells);
26169             };
26170
26171             var touchEnd = function(evt) {
26172               var touchEndTime = (new Date()).getTime();
26173               var touchTime = touchEndTime - touchStartTime;
26174
26175               if (touchTime < touchTimeout ) {
26176                 // short touch
26177                 selectCells(evt);
26178               }
26179
26180               // don't re-enable the click handler for a little while - some devices generate both, and it will
26181               // take a little while to move your hand from the screen to the mouse if you have both modes of input
26182               $timeout(function() {
26183                 $elm.on('click', selectCells);
26184               }, touchTimeout);
26185             };
26186
26187             function registerRowSelectionEvents() {
26188               if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
26189                 $elm.addClass('ui-grid-disable-selection');
26190                 $elm.on('touchstart', touchStart);
26191                 $elm.on('touchend', touchEnd);
26192                 $elm.on('click', selectCells);
26193
26194                 $scope.registered = true;
26195               }
26196             }
26197
26198             function deregisterRowSelectionEvents() {
26199               if ($scope.registered){
26200                 $elm.removeClass('ui-grid-disable-selection');
26201
26202                 $elm.off('touchstart', touchStart);
26203                 $elm.off('touchend', touchEnd);
26204                 $elm.off('click', selectCells);
26205
26206                 $scope.registered = false;
26207               }
26208             }
26209
26210             registerRowSelectionEvents();
26211             // register a dataChange callback so that we can change the selection configuration dynamically
26212             // if the user changes the options
26213             var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
26214               if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
26215                 !$scope.registered ){
26216                 registerRowSelectionEvents();
26217               } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
26218                 $scope.registered ){
26219                 deregisterRowSelectionEvents();
26220               }
26221             }, [uiGridConstants.dataChange.OPTIONS] );
26222
26223             $elm.on( '$destroy', dataChangeDereg);
26224           }
26225         };
26226       }]);
26227
26228   module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
26229     return {
26230       restrict: 'EA',
26231       replace: true,
26232       priority: -1000,
26233       require: '^uiGrid',
26234       scope: true,
26235       compile: function ($elm, $attrs) {
26236         return {
26237           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
26238
26239             if (!uiGridCtrl.grid.options.showGridFooter) {
26240               return;
26241             }
26242
26243
26244             gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
26245               .then(function (contents) {
26246                 var template = angular.element(contents);
26247
26248                 var newElm = $compile(template)($scope);
26249
26250                 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
26251               });
26252           },
26253
26254           post: function ($scope, $elm, $attrs, controllers) {
26255
26256           }
26257         };
26258       }
26259     };
26260   }]);
26261
26262 })();
26263
26264 (function () {
26265   'use strict';
26266
26267   /**
26268    * @ngdoc overview
26269    * @name ui.grid.treeBase
26270    * @description
26271    *
26272    * # ui.grid.treeBase
26273    *
26274    * <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>
26275    *
26276    * This module provides base tree handling functions that are shared by other features, notably grouping
26277    * and treeView.  It provides a tree view of the data, with nodes in that
26278    * tree and leaves.
26279    *
26280    * Design information:
26281    * -------------------
26282    *
26283    * The raw data that is provided must come with a $$treeLevel on any non-leaf node.  Grouping will create
26284    * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
26285    * TreeBase will run a rowsProcessor that:
26286    *  - builds `treeBase.tree` out of the provided rows
26287    *  - permits a recursive sort of the tree
26288    *  - maintains the expand/collapse state of each node
26289    *  - provides the expand/collapse all button and the expand/collapse buttons
26290    *  - maintains the count of children for each node
26291    *
26292    * Each row is updated with a link to the tree node that represents it.  Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
26293    * for information.
26294    *
26295    *  TreeBase adds information to the rows
26296    *  - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
26297    *  - treeNode: pointer to the node in the grid.treeBase.tree that refers
26298    *    to this row, allowing us to manipulate the state
26299    *
26300    * Since the logic is baked into the rowsProcessors, it should get triggered whenever
26301    * row order or filtering or anything like that is changed.  We recall the expanded state
26302    * across invocations of the rowsProcessors by the reference to the treeNode on the individual
26303    * rows.  We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
26304    * get the state, but we overwrite the other data in that treeNode.
26305    *
26306    * By default rows are collapsed, which means all data rows have their visible property
26307    * set to false, and only level 0 group rows are set to visible.
26308    *
26309    * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
26310    * grid.treeBase.tree, then call refresh.  This is because we can't easily change the visible
26311    * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
26312    * well use it all the time.
26313    *
26314    * Tree base provides sorting (on non-grouped columns).
26315    *
26316    * Sorting works in two passes.  The standard sorting is performed for any columns that are important to building
26317    * the tree (for example, any grouped columns).  Then after the tree is built, a recursive tree sort is performed
26318    * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
26319    * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
26320    *
26321    * To achieve this we make use of the `ignoreSort` property on the sort configuration.  The parent feature (treeView or grouping)
26322    * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
26323    * the `ignoreSort`on any sort that it wants to run on the tree.  TreeBase will clear the ignoreSort on all sorts - so it
26324    * will turn on any sorts that haven't run.  It will then call a recursive sort on the tree.
26325    *
26326    * Tree base provides treeAggregation.  It checks the treeAggregation configuration on each column, and aggregates based on
26327    * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
26328    * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
26329    * treeAggregations in the column footer.  Aggregation information will be collected in the format:
26330    *
26331    * ```
26332    *   {
26333    *     type: 'count',
26334    *     value: 4,
26335    *     label: 'count: ',
26336    *     rendered: 'count: 4'
26337    *   }
26338    * ```
26339    *
26340    * A callback is provided to format the value once it is finalised (aka a valueFilter).
26341    *
26342    * <br/>
26343    * <br/>
26344    *
26345    * <div doc-module-components="ui.grid.treeBase"></div>
26346    */
26347
26348   var module = angular.module('ui.grid.treeBase', ['ui.grid']);
26349
26350   /**
26351    *  @ngdoc object
26352    *  @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
26353    *
26354    *  @description constants available in treeBase module.
26355    *
26356    *  These constants are manually copied into grouping and treeView,
26357    *  as I haven't found a way to simply include them, and it's not worth
26358    *  investing time in for something that changes very infrequently.
26359    *
26360    */
26361   module.constant('uiGridTreeBaseConstants', {
26362     featureName: "treeBase",
26363     rowHeaderColName: 'treeBaseRowHeaderCol',
26364     EXPANDED: 'expanded',
26365     COLLAPSED: 'collapsed',
26366     aggregation: {
26367       COUNT: 'count',
26368       SUM: 'sum',
26369       MAX: 'max',
26370       MIN: 'min',
26371       AVG: 'avg'
26372     }
26373   });
26374
26375   /**
26376    *  @ngdoc service
26377    *  @name ui.grid.treeBase.service:uiGridTreeBaseService
26378    *
26379    *  @description Services for treeBase feature
26380    */
26381   /**
26382    *  @ngdoc object
26383    *  @name ui.grid.treeBase.api:ColumnDef
26384    *
26385    *  @description ColumnDef for tree feature, these are available to be
26386    *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
26387    */
26388
26389   module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
26390   function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
26391
26392     var service = {
26393
26394       initializeGrid: function (grid, $scope) {
26395
26396         //add feature namespace and any properties to grid for needed
26397         /**
26398          *  @ngdoc object
26399          *  @name ui.grid.treeBase.grid:treeBase
26400          *
26401          *  @description Grid properties and functions added for treeBase
26402          */
26403         grid.treeBase = {};
26404
26405         /**
26406          *  @ngdoc property
26407          *  @propertyOf ui.grid.treeBase.grid:treeBase
26408          *  @name numberLevels
26409          *
26410          *  @description Total number of tree levels currently used, calculated by the rowsProcessor by
26411          *  retaining the highest tree level it sees
26412          */
26413         grid.treeBase.numberLevels = 0;
26414
26415         /**
26416          *  @ngdoc property
26417          *  @propertyOf ui.grid.treeBase.grid:treeBase
26418          *  @name expandAll
26419          *
26420          *  @description Whether or not the expandAll box is selected
26421          */
26422         grid.treeBase.expandAll = false;
26423
26424         /**
26425          *  @ngdoc property
26426          *  @propertyOf ui.grid.treeBase.grid:treeBase
26427          *  @name tree
26428          *
26429          *  @description Tree represented as a nested array that holds the state of each node, along with a
26430          *  pointer to the row.  The array order is material - we will display the children in the order
26431          *  they are stored in the array
26432          *
26433          *  Each node stores:
26434          *
26435          *    - the state of this node
26436          *    - an array of children of this node
26437          *    - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
26438          *    - the number of children of this node
26439          *    - aggregation information calculated from the nodes
26440          *
26441          *  ```
26442          *    [{
26443          *      state: 'expanded',
26444          *      row: <reference to row>,
26445          *      parentRow: null,
26446          *      aggregations: [{
26447          *        type: 'count',
26448          *        col: <gridCol>,
26449          *        value: 2,
26450          *        label: 'count: ',
26451          *        rendered: 'count: 2'
26452          *      }],
26453          *      children: [
26454          *        {
26455          *          state: 'expanded',
26456          *          row: <reference to row>,
26457          *          parentRow: <reference to row>,
26458          *          aggregations: [{
26459          *            type: 'count',
26460          *            col: '<gridCol>,
26461          *            value: 4,
26462          *            label: 'count: ',
26463          *            rendered: 'count: 4'
26464          *          }],
26465          *          children: [
26466          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26467          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
26468          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26469          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
26470          *          ]
26471          *        },
26472          *        {
26473          *          state: 'collapsed',
26474          *          row: <reference to row>,
26475          *          parentRow: <reference to row>,
26476          *          aggregations: [{
26477          *            type: 'count',
26478          *            col: <gridCol>,
26479          *            value: 3,
26480          *            label: 'count: ',
26481          *            rendered: 'count: 3'
26482          *          }],
26483          *          children: [
26484          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26485          *            { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
26486          *            { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
26487          *          ]
26488          *        }
26489          *      ]
26490          *    }, {<another level 0 node maybe>} ]
26491          *  ```
26492          *  Missing state values are false - meaning they aren't expanded.
26493          *
26494          *  This is used because the rowProcessors run every time the grid is refreshed, so
26495          *  we'd lose the expanded state every time the grid was refreshed.  This instead gives
26496          *  us a reliable lookup that persists across rowProcessors.
26497          *
26498          *  This tree is rebuilt every time we run the rowsProcessors.  Since each row holds a pointer
26499          *  to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
26500          *  all transient information on the tree (children, childCount) and recalculate it
26501          *
26502          */
26503         grid.treeBase.tree = [];
26504
26505         service.defaultGridOptions(grid.options);
26506
26507         grid.registerRowsProcessor(service.treeRows, 410);
26508
26509         grid.registerColumnBuilder( service.treeBaseColumnBuilder );
26510
26511         service.createRowHeader( grid );
26512
26513         /**
26514          *  @ngdoc object
26515          *  @name ui.grid.treeBase.api:PublicApi
26516          *
26517          *  @description Public Api for treeBase feature
26518          */
26519         var publicApi = {
26520           events: {
26521             treeBase: {
26522               /**
26523                * @ngdoc event
26524                * @eventOf ui.grid.treeBase.api:PublicApi
26525                * @name rowExpanded
26526                * @description raised whenever a row is expanded.  If you are dynamically
26527                * rendering your tree you can listen to this event, and then retrieve
26528                * the children of this row and load them into the grid data.
26529                *
26530                * When the data is loaded the grid will automatically refresh to show these new rows
26531                *
26532                * <pre>
26533                *      gridApi.treeBase.on.rowExpanded(scope,function(row){})
26534                * </pre>
26535                * @param {gridRow} row the row that was expanded.  You can also
26536                * retrieve the grid from this row with row.grid
26537                */
26538               rowExpanded: {},
26539
26540               /**
26541                * @ngdoc event
26542                * @eventOf ui.grid.treeBase.api:PublicApi
26543                * @name rowCollapsed
26544                * @description raised whenever a row is collapsed.  Doesn't really have
26545                * a purpose at the moment, included for symmetry
26546                *
26547                * <pre>
26548                *      gridApi.treeBase.on.rowCollapsed(scope,function(row){})
26549                * </pre>
26550                * @param {gridRow} row the row that was collapsed.  You can also
26551                * retrieve the grid from this row with row.grid
26552                */
26553               rowCollapsed: {}
26554             }
26555           },
26556
26557           methods: {
26558             treeBase: {
26559               /**
26560                * @ngdoc function
26561                * @name expandAllRows
26562                * @methodOf  ui.grid.treeBase.api:PublicApi
26563                * @description Expands all tree rows
26564                */
26565               expandAllRows: function () {
26566                 service.expandAllRows(grid);
26567               },
26568
26569               /**
26570                * @ngdoc function
26571                * @name collapseAllRows
26572                * @methodOf  ui.grid.treeBase.api:PublicApi
26573                * @description collapse all tree rows
26574                */
26575               collapseAllRows: function () {
26576                 service.collapseAllRows(grid);
26577               },
26578
26579               /**
26580                * @ngdoc function
26581                * @name toggleRowTreeState
26582                * @methodOf  ui.grid.treeBase.api:PublicApi
26583                * @description  call expand if the row is collapsed, collapse if it is expanded
26584                * @param {gridRow} row the row you wish to toggle
26585                */
26586               toggleRowTreeState: function (row) {
26587                 service.toggleRowTreeState(grid, row);
26588               },
26589
26590               /**
26591                * @ngdoc function
26592                * @name expandRow
26593                * @methodOf  ui.grid.treeBase.api:PublicApi
26594                * @description expand the immediate children of the specified row
26595                * @param {gridRow} row the row you wish to expand
26596                */
26597               expandRow: function (row) {
26598                 service.expandRow(grid, row);
26599               },
26600
26601               /**
26602                * @ngdoc function
26603                * @name expandRowChildren
26604                * @methodOf  ui.grid.treeBase.api:PublicApi
26605                * @description expand all children of the specified row
26606                * @param {gridRow} row the row you wish to expand
26607                */
26608               expandRowChildren: function (row) {
26609                 service.expandRowChildren(grid, row);
26610               },
26611
26612               /**
26613                * @ngdoc function
26614                * @name collapseRow
26615                * @methodOf  ui.grid.treeBase.api:PublicApi
26616                * @description collapse  the specified row.  When
26617                * you expand the row again, all grandchildren will retain their state
26618                * @param {gridRow} row the row you wish to collapse
26619                */
26620               collapseRow: function ( row ) {
26621                 service.collapseRow(grid, row);
26622               },
26623
26624               /**
26625                * @ngdoc function
26626                * @name collapseRowChildren
26627                * @methodOf  ui.grid.treeBase.api:PublicApi
26628                * @description collapse all children of the specified row.  When
26629                * you expand the row again, all grandchildren will be collapsed
26630                * @param {gridRow} row the row you wish to collapse children for
26631                */
26632               collapseRowChildren: function ( row ) {
26633                 service.collapseRowChildren(grid, row);
26634               },
26635
26636               /**
26637                * @ngdoc function
26638                * @name getTreeState
26639                * @methodOf  ui.grid.treeBase.api:PublicApi
26640                * @description Get the tree state for this grid,
26641                * used by the saveState feature
26642                * Returned treeState as an object
26643                *   `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
26644                * where expandedState is a hash of row uid and the current expanded state
26645                *
26646                * @returns {object} tree state
26647                *
26648                * TODO - this needs work - we need an identifier that persists across instantiations,
26649                * not uid.  This really means we need a row identity defined, but that won't work for
26650                * grouping.  Perhaps this needs to be moved up to treeView and grouping, rather than
26651                * being in base.
26652                */
26653               getTreeExpandedState: function () {
26654                 return { expandedState: service.getTreeState(grid) };
26655               },
26656
26657               /**
26658                * @ngdoc function
26659                * @name setTreeState
26660                * @methodOf  ui.grid.treeBase.api:PublicApi
26661                * @description Set the expanded states of the tree
26662                * @param {object} config the config you want to apply, in the format
26663                * provided by getTreeState
26664                */
26665               setTreeState: function ( config ) {
26666                 service.setTreeState( grid, config );
26667               },
26668
26669               /**
26670                * @ngdoc function
26671                * @name getRowChildren
26672                * @methodOf  ui.grid.treeBase.api:PublicApi
26673                * @description Get the children of the specified row
26674                * @param {GridRow} row the row you want the children of
26675                * @returns {Array} array of children of this row, the children
26676                * are all gridRows
26677                */
26678               getRowChildren: function ( row ){
26679                 return row.treeNode.children.map( function( childNode ){
26680                   return childNode.row;
26681                 });
26682               }
26683             }
26684           }
26685         };
26686
26687         grid.api.registerEventsFromObject(publicApi.events);
26688
26689         grid.api.registerMethodsFromObject(publicApi.methods);
26690       },
26691
26692
26693       defaultGridOptions: function (gridOptions) {
26694         //default option to true unless it was explicitly set to false
26695         /**
26696          *  @ngdoc object
26697          *  @name ui.grid.treeBase.api:GridOptions
26698          *
26699          *  @description GridOptions for treeBase feature, these are available to be
26700          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
26701          */
26702
26703         /**
26704          *  @ngdoc object
26705          *  @name treeRowHeaderBaseWidth
26706          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26707          *  @description Base width of the tree header, provides for a single level of tree.  This
26708          *  is incremented by `treeIndent` for each extra level
26709          *  <br/>Defaults to 30
26710          */
26711         gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
26712
26713         /**
26714          *  @ngdoc object
26715          *  @name treeIndent
26716          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26717          *  @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
26718          *  but will make the tree row header wider
26719          *  <br/>Defaults to 10
26720          */
26721         gridOptions.treeIndent = gridOptions.treeIndent || 10;
26722
26723         /**
26724          *  @ngdoc object
26725          *  @name showTreeRowHeader
26726          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26727          *  @description If set to false, don't create the row header.  You'll need to programmatically control the expand
26728          *  states
26729          *  <br/>Defaults to true
26730          */
26731         gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
26732
26733         /**
26734          *  @ngdoc object
26735          *  @name showTreeExpandNoChildren
26736          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26737          *  @description If set to true, show the expand/collapse button even if there are no
26738          *  children of a node.  You'd use this if you're planning to dynamically load the children
26739          *
26740          *  <br/>Defaults to true, grouping overrides to false
26741          */
26742         gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
26743
26744         /**
26745          *  @ngdoc object
26746          *  @name treeRowHeaderAlwaysVisible
26747          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26748          *  @description If set to true, row header even if there are no tree nodes
26749          *
26750          *  <br/>Defaults to true
26751          */
26752         gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
26753
26754         /**
26755          *  @ngdoc object
26756          *  @name treeCustomAggregations
26757          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26758          *  @description Define custom aggregation functions. The properties of this object will be
26759          *  aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
26760          *  If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
26761          *  The object format is:
26762          *
26763          *  <pre>
26764          *    {
26765          *      aggregationName: {
26766          *        label: (optional) string,
26767          *        aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
26768          *        finalizerFn: (optional) function( aggregation ){...}
26769        *        },
26770          *      mean: {
26771          *        label: 'mean',
26772          *        aggregationFn: function( aggregation, fieldValue, numValue ){
26773        *            aggregation.count = (aggregation.count || 1) + 1;
26774          *          aggregation.sum = (aggregation.sum || 0) + numValue;
26775          *        },
26776          *        finalizerFn: function( aggregation ){
26777          *          aggregation.value = aggregation.sum / aggregation.count
26778          *        }
26779          *      }
26780          *    }
26781          *  </pre>
26782          *
26783          *  <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
26784          *  apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
26785          *  rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
26786          *  the label and the value.
26787          *
26788          *  <br/>Defaults to {}
26789          */
26790         gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
26791
26792         /**
26793          *  @ngdoc object
26794          *  @name enableExpandAll
26795          *  @propertyOf  ui.grid.treeBase.api:GridOptions
26796          *  @description Enable the expand all button at the top of the row header
26797          *
26798          *  <br/>Defaults to true
26799          */
26800         gridOptions.enableExpandAll = gridOptions.enableExpandAll !== false;
26801       },
26802
26803
26804       /**
26805        * @ngdoc function
26806        * @name treeBaseColumnBuilder
26807        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26808        * @description Sets the tree defaults based on the columnDefs
26809        *
26810        * @param {object} colDef columnDef we're basing on
26811        * @param {GridCol} col the column we're to update
26812        * @param {object} gridOptions the options we should use
26813        * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
26814        */
26815       treeBaseColumnBuilder: function (colDef, col, gridOptions) {
26816
26817
26818         /**
26819          *  @ngdoc object
26820          *  @name customTreeAggregationFn
26821          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26822          *  @description A custom function that aggregates rows into some form of
26823          *  total.  Aggregations run row-by-row, the function needs to be capable of
26824          *  creating a running total.
26825          *
26826          *  The function will be provided the aggregation item (in which you can store running
26827          *  totals), the row value that is to be aggregated, and that same row value converted to
26828          *  a number (most aggregations work on numbers)
26829          *  @example
26830          *  <pre>
26831          *    customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
26832          *      // calculates the average of the squares of the values
26833          *      if ( typeof(aggregation.count) === 'undefined' ){
26834          *        aggregation.count = 0;
26835          *      }
26836          *      aggregation.count++;
26837          *
26838          *      if ( !isNaN(numValue) ){
26839          *        if ( typeof(aggregation.total) === 'undefined' ){
26840          *          aggregation.total = 0;
26841          *        }
26842          *        aggregation.total = aggregation.total + numValue * numValue;
26843          *      }
26844          *
26845          *      aggregation.value = aggregation.total / aggregation.count;
26846          *    }
26847          *  </pre>
26848          *  <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
26849          */
26850         if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
26851           col.treeAggregationFn = colDef.customTreeAggregationFn;
26852         }
26853
26854         /**
26855          *  @ngdoc object
26856          *  @name treeAggregationType
26857          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26858          *  @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
26859          *  Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
26860          *  name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
26861          *
26862          *  <pre>
26863          *      treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
26864          *    }
26865          *  </pre>
26866          *
26867          *  If you are using aggregations you should either:
26868          *
26869          *   - also use grouping, in which case the aggregations are displayed in the group header, OR
26870          *   - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
26871          *     treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
26872          *     in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
26873          *
26874          *  <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
26875          *  <br/>Defaults to undefined.
26876          */
26877         if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
26878           col.treeAggregation = { type: colDef.treeAggregationType };
26879           if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
26880             col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
26881             col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
26882             col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
26883           } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
26884             col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
26885             col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
26886           }
26887         }
26888
26889          /**
26890          *  @ngdoc object
26891          *  @name treeAggregationLabel
26892          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26893          *  @description A custom label to use for this aggregation. If provided we don't use native i18n.
26894          */
26895         if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
26896           if (typeof(col.treeAggregation) === 'undefined' ){
26897             col.treeAggregation = {};
26898           }
26899           col.treeAggregation.label = colDef.treeAggregationLabel;
26900         }
26901
26902         /**
26903          *  @ngdoc object
26904          *  @name treeAggregationUpdateEntity
26905          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26906          *  @description Store calculated aggregations into the entity, allowing them
26907          *  to be displayed in the grid using a standard cellTemplate.  This defaults to true,
26908          *  if you are using grouping then you shouldn't set it to false, as then the aggregations won't
26909          *  display.
26910          *
26911          *  If you are using treeView in most cases you'll want to set this to true.  This will result in
26912          *  getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
26913          *  the entity.  If you want to render the underlying entity value (and do something else with the aggregation)
26914          *  then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
26915          *
26916          *  <br/>Defaults to true
26917          *
26918          *  @example
26919          *  <pre>
26920          *    gridOptions.columns = [{
26921          *      name: 'myCol',
26922          *      treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
26923          *      treeAggregationUpdateEntity: true
26924          *      cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
26925          *    }];
26926          * </pre>
26927          */
26928         col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
26929
26930         /**
26931          *  @ngdoc object
26932          *  @name customTreeAggregationFinalizerFn
26933          *  @propertyOf  ui.grid.treeBase.api:ColumnDef
26934          *  @description A custom function that populates aggregation.rendered, this is called when
26935          *  a particular aggregation has been fully calculated, and we want to render the value.
26936          *
26937          *  With the native aggregation options we just concatenate `aggregation.label` and
26938          *  `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
26939          *  or the value, you can do so with this function. This function will be called after the
26940          *  the default `finalizerFn`.
26941          *
26942          *  @example
26943          *  <pre>
26944          *    customTreeAggregationFinalizerFn = function ( aggregation ){
26945          *      aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
26946          *    }
26947          *  </pre>
26948          *  <br/>Defaults to undefined.
26949          */
26950         if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
26951           col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
26952         }
26953
26954       },
26955
26956
26957       /**
26958        * @ngdoc function
26959        * @name createRowHeader
26960        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26961        * @description Create the rowHeader.  If treeRowHeaderAlwaysVisible then
26962        * set it to visible, otherwise set it to invisible
26963        *
26964        * @param {Grid} grid grid object
26965        */
26966       createRowHeader: function( grid ){
26967         var rowHeaderColumnDef = {
26968           name: uiGridTreeBaseConstants.rowHeaderColName,
26969           displayName: '',
26970           width:  grid.options.treeRowHeaderBaseWidth,
26971           minWidth: 10,
26972           cellTemplate: 'ui-grid/treeBaseRowHeader',
26973           headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
26974           enableColumnResizing: false,
26975           enableColumnMenu: false,
26976           exporterSuppressExport: true,
26977           allowCellFocus: true
26978         };
26979
26980         rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
26981         grid.addRowHeaderColumn( rowHeaderColumnDef, -100 );
26982       },
26983
26984
26985       /**
26986        * @ngdoc function
26987        * @name expandAllRows
26988        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
26989        * @description Expands all nodes in the tree
26990        *
26991        * @param {Grid} grid grid object
26992        */
26993       expandAllRows: function (grid) {
26994         grid.treeBase.tree.forEach( function( node ) {
26995           service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
26996         });
26997         grid.treeBase.expandAll = true;
26998         grid.queueGridRefresh();
26999       },
27000
27001
27002       /**
27003        * @ngdoc function
27004        * @name collapseAllRows
27005        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27006        * @description Collapses all nodes in the tree
27007        *
27008        * @param {Grid} grid grid object
27009        */
27010       collapseAllRows: function (grid) {
27011         grid.treeBase.tree.forEach( function( node ) {
27012           service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
27013         });
27014         grid.treeBase.expandAll = false;
27015         grid.queueGridRefresh();
27016       },
27017
27018
27019       /**
27020        * @ngdoc function
27021        * @name setAllNodes
27022        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27023        * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
27024        * all child nodes (and their descendents) of the provided node to the given state.
27025        *
27026        * Calls itself recursively on all nodes so as to achieve this.
27027        *
27028        * @param {Grid} grid the grid we're operating on (so we can raise events)
27029        * @param {object} treeNode a node in the tree that we want to update
27030        * @param {string} targetState the state we want to set it to
27031        */
27032       setAllNodes: function (grid, treeNode, targetState) {
27033         if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
27034           treeNode.state = targetState;
27035
27036           if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
27037             grid.api.treeBase.raise.rowExpanded(treeNode.row);
27038           } else {
27039             grid.api.treeBase.raise.rowCollapsed(treeNode.row);
27040           }
27041         }
27042
27043         // set all child nodes
27044         if ( treeNode.children ){
27045           treeNode.children.forEach(function( childNode ){
27046             service.setAllNodes(grid, childNode, targetState);
27047           });
27048         }
27049       },
27050
27051
27052       /**
27053        * @ngdoc function
27054        * @name toggleRowTreeState
27055        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27056        * @description Toggles the expand or collapse state of this grouped row, if
27057        * it's a parent row
27058        *
27059        * @param {Grid} grid grid object
27060        * @param {GridRow} row the row we want to toggle
27061        */
27062       toggleRowTreeState: function ( grid, row ){
27063         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27064           return;
27065         }
27066
27067         if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
27068           service.collapseRow(grid, row);
27069         } else {
27070           service.expandRow(grid, row);
27071         }
27072
27073         grid.queueGridRefresh();
27074       },
27075
27076
27077       /**
27078        * @ngdoc function
27079        * @name expandRow
27080        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27081        * @description Expands this specific row, showing only immediate children.
27082        *
27083        * @param {Grid} grid grid object
27084        * @param {GridRow} row the row we want to expand
27085        */
27086       expandRow: function ( grid, row ){
27087         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27088           return;
27089         }
27090
27091         if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
27092           row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
27093           grid.api.treeBase.raise.rowExpanded(row);
27094           grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
27095           grid.queueGridRefresh();
27096         }
27097       },
27098
27099
27100       /**
27101        * @ngdoc function
27102        * @name expandRowChildren
27103        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27104        * @description Expands this specific row, showing all children.
27105        *
27106        * @param {Grid} grid grid object
27107        * @param {GridRow} row the row we want to expand
27108        */
27109       expandRowChildren: function ( grid, row ){
27110         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27111           return;
27112         }
27113
27114         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
27115         grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
27116         grid.queueGridRefresh();
27117       },
27118
27119
27120       /**
27121        * @ngdoc function
27122        * @name collapseRow
27123        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27124        * @description Collapses this specific row
27125        *
27126        * @param {Grid} grid grid object
27127        * @param {GridRow} row the row we want to collapse
27128        */
27129       collapseRow: function( grid, row ){
27130         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27131           return;
27132         }
27133
27134         if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
27135           row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
27136           grid.treeBase.expandAll = false;
27137           grid.api.treeBase.raise.rowCollapsed(row);
27138           grid.queueGridRefresh();
27139         }
27140       },
27141
27142
27143       /**
27144        * @ngdoc function
27145        * @name collapseRowChildren
27146        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27147        * @description Collapses this specific row and all children
27148        *
27149        * @param {Grid} grid grid object
27150        * @param {GridRow} row the row we want to collapse
27151        */
27152       collapseRowChildren: function( grid, row ){
27153         if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27154           return;
27155         }
27156
27157         service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
27158         grid.treeBase.expandAll = false;
27159         grid.queueGridRefresh();
27160       },
27161
27162
27163       /**
27164        * @ngdoc function
27165        * @name allExpanded
27166        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27167        * @description Returns true if all rows are expanded, false
27168        * if they're not.  Walks the tree to determine this.  Used
27169        * to set the expandAll state.
27170        *
27171        * If the node has no children, then return true (it's immaterial
27172        * whether it is expanded).  If the node has children, then return
27173        * false if this node is collapsed, or if any child node is not all expanded
27174        *
27175        * @param {object} tree the grid to check
27176        * @returns {boolean} whether or not the tree is all expanded
27177        */
27178       allExpanded: function( tree ){
27179         var allExpanded = true;
27180         tree.forEach( function( node ){
27181           if ( !service.allExpandedInternal( node ) ){
27182             allExpanded = false;
27183           }
27184         });
27185         return allExpanded;
27186       },
27187
27188       allExpandedInternal: function( treeNode ){
27189         if ( treeNode.children && treeNode.children.length > 0 ){
27190           if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
27191             return false;
27192           }
27193           var allExpanded = true;
27194           treeNode.children.forEach( function( node ){
27195             if ( !service.allExpandedInternal( node ) ){
27196               allExpanded = false;
27197             }
27198           });
27199           return allExpanded;
27200         } else {
27201           return true;
27202         }
27203       },
27204
27205
27206       /**
27207        * @ngdoc function
27208        * @name treeRows
27209        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27210        * @description The rowProcessor that adds the nodes to the tree, and sets the visible
27211        * state of each row based on it's parent state
27212        *
27213        * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
27214        * Performs any tree sorts itself after having built the tree
27215        *
27216        * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
27217        * entity, and setting the visible state based on the parent's state.
27218        *
27219        * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
27220        * sized.
27221        *
27222        * Aggregates if necessary along the way.
27223        *
27224        * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
27225        * @returns {array} the updated rows
27226        */
27227       treeRows: function( renderableRows ) {
27228         if (renderableRows.length === 0){
27229           return renderableRows;
27230         }
27231
27232         var grid = this;
27233         var currentLevel = 0;
27234         var currentState = uiGridTreeBaseConstants.EXPANDED;
27235         var parents = [];
27236
27237         grid.treeBase.tree = service.createTree( grid, renderableRows );
27238         service.updateRowHeaderWidth( grid );
27239
27240         service.sortTree( grid );
27241         service.fixFilter( grid );
27242
27243         return service.renderTree( grid.treeBase.tree );
27244       },
27245
27246
27247       /**
27248        * @ngdoc function
27249        * @name createOrUpdateRowHeaderWidth
27250        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27251        * @description Calculates the rowHeader width.
27252        *
27253        * If rowHeader is always present, updates the width.
27254        *
27255        * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
27256        * should be one, then creates or removes it as appropriate, with the created rowHeader having the
27257        * right width.
27258        *
27259        * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
27260        *
27261        * @param {Grid} grid the grid we want to set the row header on
27262        */
27263       updateRowHeaderWidth: function( grid ){
27264         var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
27265
27266         var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
27267         if ( rowHeader && newWidth !== rowHeader.width ){
27268           rowHeader.width = newWidth;
27269           grid.queueRefresh();
27270         }
27271
27272         var newVisibility = true;
27273         if ( grid.options.showTreeRowHeader === false ){
27274           newVisibility = false;
27275         }
27276         if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
27277           newVisibility = false;
27278         }
27279         if ( rowHeader.visible !== newVisibility ) {
27280           rowHeader.visible = newVisibility;
27281           rowHeader.colDef.visible = newVisibility;
27282           grid.queueGridRefresh();
27283         }
27284       },
27285
27286
27287       /**
27288        * @ngdoc function
27289        * @name renderTree
27290        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27291        * @description Creates an array of rows based on the tree, exporting only
27292        * the visible nodes and leaves
27293        *
27294        * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
27295        * we're calling recursively
27296        * @returns {array} renderable rows
27297        */
27298       renderTree: function( nodeList ){
27299         var renderableRows = [];
27300
27301         nodeList.forEach( function ( node ){
27302           if ( node.row.visible ){
27303             renderableRows.push( node.row );
27304           }
27305           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
27306             renderableRows = renderableRows.concat( service.renderTree( node.children ) );
27307           }
27308         });
27309         return renderableRows;
27310       },
27311
27312
27313       /**
27314        * @ngdoc function
27315        * @name createTree
27316        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27317        * @description Creates a tree from the renderableRows
27318        *
27319        * @param {Grid} grid the grid
27320        * @param {array} renderableRows the rows we want to create a tree from
27321        * @returns {object} the tree we've build
27322        */
27323       createTree: function( grid, renderableRows ) {
27324         var currentLevel = -1;
27325         var parents = [];
27326         var currentState;
27327         grid.treeBase.tree = [];
27328         grid.treeBase.numberLevels = 0;
27329         var aggregations = service.getAggregations( grid );
27330
27331         var createNode = function( row ){
27332           if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
27333             row.treeLevel = row.entity.$$treeLevel;
27334           }
27335
27336           if ( row.treeLevel <= currentLevel ){
27337             // pop any levels that aren't parents of this level, formatting the aggregation at the same time
27338             while ( row.treeLevel <= currentLevel ){
27339               var lastParent = parents.pop();
27340               service.finaliseAggregations( lastParent );
27341               currentLevel--;
27342             }
27343
27344             // reset our current state based on the new parent, set to expanded if this is a level 0 node
27345             if ( parents.length > 0 ){
27346               currentState = service.setCurrentState(parents);
27347             } else {
27348               currentState = uiGridTreeBaseConstants.EXPANDED;
27349             }
27350           }
27351
27352           // aggregate if this is a leaf node
27353           if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible  ){
27354             service.aggregate( grid, row, parents );
27355           }
27356
27357           // add this node to the tree
27358           service.addOrUseNode(grid, row, parents, aggregations);
27359
27360           if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
27361             parents.push(row);
27362             currentLevel++;
27363             currentState = service.setCurrentState(parents);
27364           }
27365
27366           // update the tree number of levels, so we can set header width if we need to
27367           if ( grid.treeBase.numberLevels < row.treeLevel + 1){
27368             grid.treeBase.numberLevels = row.treeLevel + 1;
27369           }
27370         };
27371
27372         renderableRows.forEach( createNode );
27373
27374         // finalise remaining aggregations
27375         while ( parents.length > 0 ){
27376           var lastParent = parents.pop();
27377           service.finaliseAggregations( lastParent );
27378         }
27379
27380         return grid.treeBase.tree;
27381       },
27382
27383
27384       /**
27385        * @ngdoc function
27386        * @name addOrUseNode
27387        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27388        * @description Creates a tree node for this row.  If this row already has a treeNode
27389        * recorded against it, preserves the state, but otherwise overwrites the data.
27390        *
27391        * @param {grid} grid the grid we're operating on
27392        * @param {gridRow} row the row we want to set
27393        * @param {array} parents an array of the parents this row should have
27394        * @param {array} aggregationBase empty aggregation information
27395        * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
27396        * grid.treeBase.tree
27397        */
27398       addOrUseNode: function( grid, row, parents, aggregationBase ){
27399         var newAggregations = [];
27400         aggregationBase.forEach( function(aggregation){
27401           newAggregations.push(service.buildAggregationObject(aggregation.col));
27402         });
27403
27404         var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
27405         if ( row.treeNode ){
27406           newNode.state = row.treeNode.state;
27407         }
27408         if ( parents.length > 0 ){
27409           newNode.parentRow = parents[parents.length - 1];
27410         }
27411         row.treeNode = newNode;
27412
27413         if ( parents.length === 0 ){
27414           grid.treeBase.tree.push( newNode );
27415         } else {
27416           parents[parents.length - 1].treeNode.children.push( newNode );
27417         }
27418       },
27419
27420
27421       /**
27422        * @ngdoc function
27423        * @name setCurrentState
27424        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27425        * @description Looks at the parents array to determine our current state.
27426        * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
27427        * expanded.
27428        *
27429        * @param {array} parents an array of the parents this row should have
27430        * @returns {string} the state we should be setting to any nodes we see
27431        */
27432       setCurrentState: function( parents ){
27433         var currentState = uiGridTreeBaseConstants.EXPANDED;
27434         parents.forEach( function(parent){
27435           if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
27436             currentState = uiGridTreeBaseConstants.COLLAPSED;
27437           }
27438         });
27439         return currentState;
27440       },
27441
27442
27443       /**
27444        * @ngdoc function
27445        * @name sortTree
27446        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27447        * @description Performs a recursive sort on the tree nodes, sorting the
27448        * children of each node and putting them back into the children array.
27449        *
27450        * Before doing this it turns back on all the sortIgnore - things that were previously
27451        * ignored we process now.  Since we're sorting within the nodes, presumably anything
27452        * that was already sorted is how we derived the nodes, we can keep those sorts too.
27453        *
27454        * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
27455        * nodes
27456        *
27457        * @param {Grid} grid the grid to get the aggregation information from
27458        * @returns {array} the aggregation information
27459        */
27460       sortTree: function( grid ){
27461         grid.columns.forEach( function( column ) {
27462           if ( column.sort && column.sort.ignoreSort ){
27463             delete column.sort.ignoreSort;
27464           }
27465         });
27466
27467         grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
27468       },
27469
27470       sortInternal: function( grid, treeList ){
27471         var rows = treeList.map( function( node ){
27472           return node.row;
27473         });
27474
27475         rows = rowSorter.sort( grid, rows, grid.columns );
27476
27477         var treeNodes = rows.map( function( row ){
27478           return row.treeNode;
27479         });
27480
27481         treeNodes.forEach( function( node ){
27482           if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
27483             node.children = service.sortInternal( grid, node.children );
27484           }
27485         });
27486
27487         return treeNodes;
27488       },
27489
27490       /**
27491        * @ngdoc function
27492        * @name fixFilter
27493        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27494        * @description After filtering has run, we need to go back through the tree
27495        * and make sure the parent rows are always visible if any of the child rows
27496        * are visible (filtering may make a child visible, but the parent may not
27497        * match the filter criteria)
27498        *
27499        * This has a risk of being computationally expensive, we do it by walking
27500        * the tree and remembering whether there are any invisible nodes on the
27501        * way down.
27502        *
27503        * @param {Grid} grid the grid to fix filters on
27504        */
27505       fixFilter: function( grid ){
27506         var parentsVisible;
27507
27508         grid.treeBase.tree.forEach( function( node ){
27509           if ( node.children && node.children.length > 0 ){
27510             parentsVisible = node.row.visible;
27511             service.fixFilterInternal( node.children, parentsVisible );
27512           }
27513         });
27514       },
27515
27516       fixFilterInternal: function( nodes, parentsVisible) {
27517         nodes.forEach( function( node ){
27518           if ( node.row.visible && !parentsVisible ){
27519             service.setParentsVisible( node );
27520             parentsVisible = true;
27521           }
27522
27523           if ( node.children && node.children.length > 0 ){
27524             if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
27525               parentsVisible = true;
27526             }
27527           }
27528         });
27529
27530         return parentsVisible;
27531       },
27532
27533       setParentsVisible: function( node ){
27534         while ( node.parentRow ){
27535           node.parentRow.visible = true;
27536           node = node.parentRow.treeNode;
27537         }
27538       },
27539
27540       /**
27541        * @ngdoc function
27542        * @name buildAggregationObject
27543        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27544        * @description Build the object which is stored on the column for holding meta-data about the aggregation.
27545        * This method should only be called with columns which have an aggregation.
27546        *
27547        * @param {Column} the column which this object relates to
27548        * @returns {object} {col: Column object, label: string, type: string (optional)}
27549        */
27550       buildAggregationObject: function( column ){
27551         var newAggregation = { col: column };
27552
27553         if ( column.treeAggregation && column.treeAggregation.type ){
27554           newAggregation.type = column.treeAggregation.type;
27555         }
27556
27557         if ( column.treeAggregation && column.treeAggregation.label ){
27558           newAggregation.label = column.treeAggregation.label;
27559         }
27560
27561         return newAggregation;
27562       },
27563
27564       /**
27565        * @ngdoc function
27566        * @name getAggregations
27567        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27568        * @description Looks through the grid columns to find those with aggregations,
27569        * and collates the aggregation information into an array, returns that array
27570        *
27571        * @param {Grid} grid the grid to get the aggregation information from
27572        * @returns {array} the aggregation information
27573        */
27574       getAggregations: function( grid ){
27575         var aggregateArray = [];
27576
27577         grid.columns.forEach( function(column){
27578           if ( typeof(column.treeAggregationFn) !== 'undefined' ){
27579             aggregateArray.push( service.buildAggregationObject(column) );
27580
27581             if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
27582               // Add aggregation object for footer
27583               column.treeFooterAggregation = service.buildAggregationObject(column);
27584               column.aggregationType = service.treeFooterAggregationType;
27585             }
27586           }
27587         });
27588         return aggregateArray;
27589       },
27590
27591
27592       /**
27593        * @ngdoc function
27594        * @name aggregate
27595        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27596        * @description Accumulate the data from this row onto the aggregations for each parent
27597        *
27598        * Iterate over the parents, then iterate over the aggregations for each of those parents,
27599        * and perform the aggregation for each individual aggregation
27600        *
27601        * @param {Grid} grid grid object
27602        * @param {GridRow} row the row we want to set grouping visibility on
27603        * @param {array} parents the parents that we would want to aggregate onto
27604        */
27605       aggregate: function( grid, row, parents ){
27606         if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
27607           row.treeNode.aggregations.forEach(function(aggregation){
27608             // Calculate aggregations for footer even if there are no grouped rows
27609             if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
27610               var fieldValue = grid.getCellValue(row, aggregation.col);
27611               var numValue = Number(fieldValue);
27612               aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
27613             }
27614           });
27615         }
27616
27617         parents.forEach( function( parent, index ){
27618           if ( parent.treeNode.aggregations ){
27619             parent.treeNode.aggregations.forEach( function( aggregation ){
27620               var fieldValue = grid.getCellValue(row, aggregation.col);
27621               var numValue = Number(fieldValue);
27622               aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
27623
27624               if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
27625                 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
27626               }
27627             });
27628           }
27629         });
27630       },
27631
27632
27633       // Aggregation routines - no doco needed as self evident
27634       nativeAggregations: function() {
27635         var nativeAggregations = {
27636           count: {
27637             label: i18nService.get().aggregation.count,
27638             menuTitle: i18nService.get().grouping.aggregate_count,
27639             aggregationFn: function (aggregation, fieldValue, numValue) {
27640               if (typeof(aggregation.value) === 'undefined') {
27641                 aggregation.value = 1;
27642               } else {
27643                 aggregation.value++;
27644               }
27645             }
27646           },
27647
27648           sum: {
27649             label: i18nService.get().aggregation.sum,
27650             menuTitle: i18nService.get().grouping.aggregate_sum,
27651             aggregationFn: function( aggregation, fieldValue, numValue ) {
27652               if (!isNaN(numValue)) {
27653                 if (typeof(aggregation.value) === 'undefined') {
27654                   aggregation.value = numValue;
27655                 } else {
27656                   aggregation.value += numValue;
27657                 }
27658               }
27659             }
27660           },
27661
27662           min: {
27663             label: i18nService.get().aggregation.min,
27664             menuTitle: i18nService.get().grouping.aggregate_min,
27665             aggregationFn: function( aggregation, fieldValue, numValue ) {
27666               if (typeof(aggregation.value) === 'undefined') {
27667                 aggregation.value = fieldValue;
27668               } else {
27669                 if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
27670                   aggregation.value = fieldValue;
27671                 }
27672               }
27673             }
27674           },
27675
27676           max: {
27677             label: i18nService.get().aggregation.max,
27678             menuTitle: i18nService.get().grouping.aggregate_max,
27679             aggregationFn: function( aggregation, fieldValue, numValue ){
27680               if ( typeof(aggregation.value) === 'undefined' ){
27681                 aggregation.value = fieldValue;
27682               } else {
27683                 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
27684                   aggregation.value = fieldValue;
27685                 }
27686               }
27687             }
27688           },
27689
27690           avg: {
27691             label: i18nService.get().aggregation.avg,
27692             menuTitle: i18nService.get().grouping.aggregate_avg,
27693             aggregationFn: function( aggregation, fieldValue, numValue ){
27694               if ( typeof(aggregation.count) === 'undefined' ){
27695                 aggregation.count = 1;
27696               } else {
27697                 aggregation.count++;
27698               }
27699
27700               if ( isNaN(numValue) ){
27701                 return;
27702               }
27703
27704               if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
27705                 aggregation.value = numValue;
27706                 aggregation.sum = numValue;
27707               } else {
27708                 aggregation.sum += numValue;
27709                 aggregation.value = aggregation.sum / aggregation.count;
27710               }
27711             }
27712           }
27713         };
27714         return nativeAggregations;
27715       },
27716
27717       /**
27718        * @ngdoc function
27719        * @name finaliseAggregation
27720        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27721        * @description Helper function used to finalize aggregation nodes and footer cells
27722        *
27723        * @param {gridRow} row the parent we're finalising
27724        * @param {aggregation} the aggregation object manipulated by the aggregationFn
27725        */
27726       finaliseAggregation: function(row, aggregation){
27727         if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
27728           angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
27729         }
27730
27731         if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
27732           aggregation.col.treeAggregationFinalizerFn( aggregation );
27733         }
27734         if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
27735           aggregation.col.customTreeAggregationFinalizerFn( aggregation );
27736         }
27737         if ( typeof(aggregation.rendered) === 'undefined' ){
27738           aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
27739         }
27740       },
27741
27742       /**
27743        * @ngdoc function
27744        * @name finaliseAggregations
27745        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27746        * @description Format the data from the aggregation into the rendered text
27747        * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
27748        *
27749        * As part of this we call any formatting callback routines we've been provided.
27750        *
27751        * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
27752        * set on the column - we don't overwrite any information that's already there, we append
27753        * to it so that grouping can have set the groupVal beforehand without us overwriting it.
27754        *
27755        * We need to copy the data from the row.entity first before we finalise the aggregation,
27756        * we need that information for the finaliserFn
27757        *
27758        * @param {gridRow} row the parent we're finalising
27759        */
27760       finaliseAggregations: function( row ){
27761         if ( row == null || typeof(row.treeNode.aggregations) === 'undefined' ){
27762           return;
27763         }
27764
27765         row.treeNode.aggregations.forEach( function( aggregation ) {
27766           service.finaliseAggregation(row, aggregation);
27767
27768           if ( aggregation.col.treeAggregationUpdateEntity ){
27769             var aggregationCopy = {};
27770             angular.forEach( aggregation, function( value, key ){
27771               if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
27772                 aggregationCopy[key] = value;
27773               }
27774             });
27775
27776             row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
27777           }
27778         });
27779       },
27780
27781       /**
27782        * @ngdoc function
27783        * @name treeFooterAggregationType
27784        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
27785        * @description Uses the tree aggregation functions and finalizers to set the
27786        * column footer aggregations.
27787        *
27788        * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
27789        * @param {gridColumn} the column we are finalizing
27790        */
27791       treeFooterAggregationType: function( rows, column ) {
27792         service.finaliseAggregation(undefined, column.treeFooterAggregation);
27793         if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
27794           // The was apparently no aggregation performed (perhaps this is a grouped column
27795           return '';
27796         }
27797         return column.treeFooterAggregation.rendered;
27798       }
27799     };
27800
27801     return service;
27802
27803   }]);
27804
27805
27806   /**
27807    *  @ngdoc directive
27808    *  @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
27809    *  @element div
27810    *
27811    *  @description Provides the expand/collapse button on rows
27812    */
27813   module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
27814   function ($templateCache, uiGridTreeBaseService) {
27815     return {
27816       replace: true,
27817       restrict: 'E',
27818       template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
27819       scope: true,
27820       require: '^uiGrid',
27821       link: function($scope, $elm, $attrs, uiGridCtrl) {
27822         var self = uiGridCtrl.grid;
27823         $scope.treeButtonClick = function(row, evt) {
27824           uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
27825         };
27826       }
27827     };
27828   }]);
27829
27830
27831   /**
27832    *  @ngdoc directive
27833    *  @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
27834    *  @element div
27835    *
27836    *  @description Provides the expand/collapse all button
27837    */
27838   module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
27839   function ($templateCache, uiGridTreeBaseService) {
27840     return {
27841       replace: true,
27842       restrict: 'E',
27843       template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
27844       scope: false,
27845       link: function($scope, $elm, $attrs, uiGridCtrl) {
27846         var self = $scope.col.grid;
27847
27848         $scope.headerButtonClick = function(row, evt) {
27849           if ( self.treeBase.expandAll ){
27850             uiGridTreeBaseService.collapseAllRows(self, evt);
27851           } else {
27852             uiGridTreeBaseService.expandAllRows(self, evt);
27853           }
27854         };
27855       }
27856     };
27857   }]);
27858
27859
27860   /**
27861    *  @ngdoc directive
27862    *  @name ui.grid.treeBase.directive:uiGridViewport
27863    *  @element div
27864    *
27865    *  @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
27866    */
27867   module.directive('uiGridViewport',
27868   ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
27869     function ($compile, uiGridConstants, gridUtil, $parse) {
27870       return {
27871         priority: -200, // run after default  directive
27872         scope: false,
27873         compile: function ($elm, $attrs) {
27874           var rowRepeatDiv = angular.element($elm.children().children()[0]);
27875
27876           var existingNgClass = rowRepeatDiv.attr("ng-class");
27877           var newNgClass = '';
27878           if ( existingNgClass ) {
27879             newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
27880           } else {
27881             newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
27882           }
27883           rowRepeatDiv.attr("ng-class", newNgClass);
27884
27885           return {
27886             pre: function ($scope, $elm, $attrs, controllers) {
27887
27888             },
27889             post: function ($scope, $elm, $attrs, controllers) {
27890             }
27891           };
27892         }
27893       };
27894     }]);
27895 })();
27896
27897 (function () {
27898   'use strict';
27899
27900   /**
27901    * @ngdoc overview
27902    * @name ui.grid.treeView
27903    * @description
27904    *
27905    * # ui.grid.treeView
27906    *
27907    * <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>
27908    *
27909    * This module provides a tree view of the data that it is provided, with nodes in that
27910    * tree and leaves.  Unlike grouping, the tree is an inherent property of the data and must
27911    * be provided with your data array.
27912    *
27913    * Design information:
27914    * -------------------
27915    *
27916    * TreeView uses treeBase for the underlying functionality, and is a very thin wrapper around
27917    * that logic.  Most of the design information has now moved to treebase.
27918    * <br/>
27919    * <br/>
27920    *
27921    * <div doc-module-components="ui.grid.treeView"></div>
27922    */
27923
27924   var module = angular.module('ui.grid.treeView', ['ui.grid', 'ui.grid.treeBase']);
27925
27926   /**
27927    *  @ngdoc object
27928    *  @name ui.grid.treeView.constant:uiGridTreeViewConstants
27929    *
27930    *  @description constants available in treeView module, this includes
27931    *  all the constants declared in the treeBase module (these are manually copied
27932    *  as there isn't an easy way to include constants in another constants file, and
27933    *  we don't want to make users include treeBase)
27934    *
27935    */
27936   module.constant('uiGridTreeViewConstants', {
27937     featureName: "treeView",
27938     rowHeaderColName: 'treeBaseRowHeaderCol',
27939     EXPANDED: 'expanded',
27940     COLLAPSED: 'collapsed',
27941     aggregation: {
27942       COUNT: 'count',
27943       SUM: 'sum',
27944       MAX: 'max',
27945       MIN: 'min',
27946       AVG: 'avg'
27947     }
27948   });
27949
27950   /**
27951    *  @ngdoc service
27952    *  @name ui.grid.treeView.service:uiGridTreeViewService
27953    *
27954    *  @description Services for treeView features
27955    */
27956   module.service('uiGridTreeViewService', ['$q', 'uiGridTreeViewConstants', 'uiGridTreeBaseConstants', 'uiGridTreeBaseService', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants',
27957   function ($q, uiGridTreeViewConstants, uiGridTreeBaseConstants, uiGridTreeBaseService, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants) {
27958
27959     var service = {
27960
27961       initializeGrid: function (grid, $scope) {
27962         uiGridTreeBaseService.initializeGrid( grid, $scope );
27963
27964         /**
27965          *  @ngdoc object
27966          *  @name ui.grid.treeView.grid:treeView
27967          *
27968          *  @description Grid properties and functions added for treeView
27969          */
27970         grid.treeView = {};
27971
27972         grid.registerRowsProcessor(service.adjustSorting, 60);
27973
27974         /**
27975          *  @ngdoc object
27976          *  @name ui.grid.treeView.api:PublicApi
27977          *
27978          *  @description Public Api for treeView feature
27979          */
27980         var publicApi = {
27981           events: {
27982             treeView: {
27983             }
27984           },
27985           methods: {
27986             treeView: {
27987             }
27988           }
27989         };
27990
27991         grid.api.registerEventsFromObject(publicApi.events);
27992
27993         grid.api.registerMethodsFromObject(publicApi.methods);
27994
27995       },
27996
27997       defaultGridOptions: function (gridOptions) {
27998         //default option to true unless it was explicitly set to false
27999         /**
28000          *  @ngdoc object
28001          *  @name ui.grid.treeView.api:GridOptions
28002          *
28003          *  @description GridOptions for treeView feature, these are available to be
28004          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
28005          *
28006          *  Many tree options are set on treeBase, make sure to look at that feature in
28007          *  conjunction with these options.
28008          */
28009
28010         /**
28011          *  @ngdoc object
28012          *  @name enableTreeView
28013          *  @propertyOf  ui.grid.treeView.api:GridOptions
28014          *  @description Enable row tree view for entire grid.
28015          *  <br/>Defaults to true
28016          */
28017         gridOptions.enableTreeView = gridOptions.enableTreeView !== false;
28018
28019       },
28020
28021
28022       /**
28023        * @ngdoc function
28024        * @name adjustSorting
28025        * @methodOf  ui.grid.treeBase.service:uiGridTreeBaseService
28026        * @description Trees cannot be sorted the same as flat lists of rows -
28027        * trees are sorted recursively within each level - so the children of each
28028        * node are sorted, but not the full set of rows.
28029        *
28030        * To achieve this, we suppress the normal sorting by setting ignoreSort on
28031        * each of the sort columns.  When the treeBase rowsProcessor runs it will then
28032        * unignore these, and will perform a recursive sort against the tree that it builds.
28033        *
28034        * @param {array} renderableRows the rows that we need to pass on through
28035        * @returns {array} renderableRows that we passed on through
28036        */
28037       adjustSorting: function( renderableRows ) {
28038         var grid = this;
28039
28040         grid.columns.forEach( function( column ){
28041           if ( column.sort ){
28042             column.sort.ignoreSort = true;
28043           }
28044         });
28045
28046         return renderableRows;
28047       }
28048
28049     };
28050
28051     return service;
28052
28053   }]);
28054
28055   /**
28056    *  @ngdoc directive
28057    *  @name ui.grid.treeView.directive:uiGridTreeView
28058    *  @element div
28059    *  @restrict A
28060    *
28061    *  @description Adds treeView features to grid
28062    *
28063    *  @example
28064    <example module="app">
28065    <file name="app.js">
28066    var app = angular.module('app', ['ui.grid', 'ui.grid.treeView']);
28067
28068    app.controller('MainCtrl', ['$scope', function ($scope) {
28069       $scope.data = [
28070         { name: 'Bob', title: 'CEO' },
28071             { name: 'Frank', title: 'Lowly Developer' }
28072       ];
28073
28074       $scope.columnDefs = [
28075         {name: 'name', enableCellEdit: true},
28076         {name: 'title', enableCellEdit: true}
28077       ];
28078
28079       $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
28080     }]);
28081    </file>
28082    <file name="index.html">
28083    <div ng-controller="MainCtrl">
28084    <div ui-grid="gridOptions" ui-grid-tree-view></div>
28085    </div>
28086    </file>
28087    </example>
28088    */
28089   module.directive('uiGridTreeView', ['uiGridTreeViewConstants', 'uiGridTreeViewService', '$templateCache',
28090   function (uiGridTreeViewConstants, uiGridTreeViewService, $templateCache) {
28091     return {
28092       replace: true,
28093       priority: 0,
28094       require: '^uiGrid',
28095       scope: false,
28096       compile: function () {
28097         return {
28098           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
28099             if (uiGridCtrl.grid.options.enableTreeView !== false){
28100               uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
28101             }
28102           },
28103           post: function ($scope, $elm, $attrs, uiGridCtrl) {
28104
28105           }
28106         };
28107       }
28108     };
28109   }]);
28110 })();
28111
28112 (function () {
28113   'use strict';
28114   
28115   /**
28116    * @ngdoc overview
28117    * @name ui.grid.validate
28118    * @description
28119    *
28120    * # ui.grid.validate
28121    *
28122    * <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>
28123    *
28124    * This module provides the ability to validate cells upon change.
28125    *
28126    * Design information:
28127    * -------------------
28128    *
28129    * Validation is not based on angularjs validation, since it would work only when editing the field.
28130    * 
28131    * Instead it adds custom properties to any field considered as invalid.
28132    *
28133    * <br/>
28134    * <br/>
28135    *
28136    * <div doc-module-components="ui.grid.expandable"></div>
28137    */
28138
28139   var module = angular.module('ui.grid.validate', ['ui.grid']);
28140   
28141   
28142   /**
28143    *  @ngdoc service
28144    *  @name ui.grid.validate.service:uiGridValidateService
28145    *
28146    *  @description Services for validation features
28147    */
28148   module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) {
28149
28150     var service = {
28151       
28152       /**
28153        *  @ngdoc object
28154        *  @name validatorFactories
28155        *  @propertyOf ui.grid.validate.service:uiGridValidateService
28156        *  @description object containing all the factories used to validate data.<br/>
28157        *  These factories will be in the form <br/>
28158        *  ```
28159        *  {
28160        *    validatorFactory: function(argument) {
28161        *                        return function(newValue, oldValue, rowEntity, colDef) {
28162        *                          return true || false || promise
28163        *                        }
28164        *                      },
28165        *    messageFunction: function(argument) {
28166        *                       return string
28167        *                     }
28168        *  }
28169        *  ```
28170        *
28171        * Promises should return true or false as result according to the result of validation.
28172        */
28173       validatorFactories: {},
28174
28175       
28176       /**
28177        * @ngdoc service
28178        * @name setExternalFactoryFunction
28179        * @methodOf ui.grid.validate.service:uiGridValidateService
28180        * @description Adds a way to retrieve validators from an external service
28181        * <p>Validators from this external service have a higher priority than default
28182        * ones
28183        * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a
28184        * validator factory and that returns an object with the same properties as 
28185        * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories}
28186        */
28187       setExternalFactoryFunction: function(externalFactoryFunction) {
28188         service.externalFactoryFunction = externalFactoryFunction;
28189       },
28190       
28191       /**
28192        * @ngdoc service
28193        * @name clearExternalFactory
28194        * @methodOf ui.grid.validate.service:uiGridValidateService
28195        * @description Removes any link to external factory from this service
28196        */
28197       clearExternalFactory: function() {
28198         delete service.externalFactoryFunction;
28199       },
28200
28201       /**
28202        * @ngdoc service
28203        * @name getValidatorFromExternalFactory
28204        * @methodOf ui.grid.validate.service:uiGridValidateService
28205        * @description Retrieves a validator by executing a validatorFactory
28206        * stored in an external service.
28207        * @param {string} name the name of the validator to retrieve
28208        * @param {object} argument an argument to pass to the validator factory
28209        */
28210       getValidatorFromExternalFactory: function(name, argument) {
28211         return service.externalFactoryFunction(name, argument).validatorFactory(argument);
28212       },
28213       
28214       /**
28215        * @ngdoc service
28216        * @name getMessageFromExternalFactory
28217        * @methodOf ui.grid.validate.service:uiGridValidateService
28218        * @description Retrieves a message stored in an external service.
28219        * @param {string} name the name of the validator
28220        * @param {object} argument an argument to pass to the message function
28221        */
28222       getMessageFromExternalFactory: function(name, argument) {
28223         return service.externalFactoryFunction(name, argument).messageFunction(argument);
28224       },
28225       
28226       /**
28227        * @ngdoc service
28228        * @name setValidator
28229        * @methodOf ui.grid.validate.service:uiGridValidateService
28230        * @description  Adds a new validator to the service
28231        * @param {string} name the name of the validator, must be unique
28232        * @param {function} validatorFactory a factory that return a validatorFunction
28233        * @param {function} messageFunction a function that return the error message
28234        */
28235       setValidator: function(name, validatorFactory, messageFunction) {
28236         service.validatorFactories[name] = {
28237           validatorFactory: validatorFactory,
28238           messageFunction: messageFunction
28239         };
28240       },
28241
28242       /**
28243        * @ngdoc service
28244        * @name getValidator
28245        * @methodOf ui.grid.validate.service:uiGridValidateService
28246        * @description Returns a validator registered to the service
28247        * or retrieved from the external factory
28248        * @param {string} name the name of the validator to retrieve
28249        * @param {object} argument an argument to pass to the validator factory
28250        * @returns {object} the validator function
28251        */
28252       getValidator: function(name, argument) {
28253         if (service.externalFactoryFunction) {
28254           var validator = service.getValidatorFromExternalFactory(name, argument);
28255           if (validator) {
28256             return validator;
28257           }
28258         }
28259         if (!service.validatorFactories[name]) {
28260           throw ("Invalid validator name: " + name);
28261         }
28262         return service.validatorFactories[name].validatorFactory(argument);
28263       },
28264
28265       /**
28266        * @ngdoc service
28267        * @name getMessage
28268        * @methodOf ui.grid.validate.service:uiGridValidateService
28269        * @description Returns the error message related to the validator 
28270        * @param {string} name the name of the validator
28271        * @param {object} argument an argument to pass to the message function
28272        * @returns {string} the error message related to the validator
28273        */
28274       getMessage: function(name, argument) {
28275         if (service.externalFactoryFunction) {
28276           var message = service.getMessageFromExternalFactory(name, argument);
28277           if (message) {
28278             return message;
28279           }
28280         }
28281         return service.validatorFactories[name].messageFunction(argument);
28282       },
28283
28284       /**
28285        * @ngdoc service
28286        * @name isInvalid
28287        * @methodOf ui.grid.validate.service:uiGridValidateService
28288        * @description Returns true if the cell (identified by rowEntity, colDef) is invalid 
28289        * @param {object} rowEntity the row entity of the cell
28290        * @param {object} colDef the colDef of the cell
28291        * @returns {boolean} true if the cell is invalid
28292        */
28293       isInvalid: function (rowEntity, colDef) {
28294         return rowEntity['$$invalid'+colDef.name];
28295       },
28296
28297       /**
28298        * @ngdoc service
28299        * @name setInvalid
28300        * @methodOf ui.grid.validate.service:uiGridValidateService
28301        * @description Makes the cell invalid by adding the proper field to the entity
28302        * @param {object} rowEntity the row entity of the cell
28303        * @param {object} colDef the colDef of the cell
28304        */
28305       setInvalid: function (rowEntity, colDef) {
28306         rowEntity['$$invalid'+colDef.name] = true;
28307       },
28308     
28309       /**
28310        * @ngdoc service
28311        * @name setValid
28312        * @methodOf ui.grid.validate.service:uiGridValidateService
28313        * @description Makes the cell valid by removing the proper error field from the entity
28314        * @param {object} rowEntity the row entity of the cell
28315        * @param {object} colDef the colDef of the cell
28316        */
28317       setValid: function (rowEntity, colDef) {
28318         delete rowEntity['$$invalid'+colDef.name];
28319       },
28320
28321       /**
28322        * @ngdoc service
28323        * @name setError
28324        * @methodOf ui.grid.validate.service:uiGridValidateService
28325        * @description Adds the proper error to the entity errors field
28326        * @param {object} rowEntity the row entity of the cell
28327        * @param {object} colDef the colDef of the cell
28328        * @param {string} validatorName the name of the validator that is failing
28329        */
28330       setError: function(rowEntity, colDef, validatorName) {
28331         if (!rowEntity['$$errors'+colDef.name]) {
28332           rowEntity['$$errors'+colDef.name] = {};
28333         }
28334         rowEntity['$$errors'+colDef.name][validatorName] = true;
28335       },
28336
28337       /**
28338        * @ngdoc service
28339        * @name clearError
28340        * @methodOf ui.grid.validate.service:uiGridValidateService
28341        * @description Removes the proper error from the entity errors field
28342        * @param {object} rowEntity the row entity of the cell
28343        * @param {object} colDef the colDef of the cell
28344        * @param {string} validatorName the name of the validator that is failing
28345        */
28346       clearError: function(rowEntity, colDef, validatorName) {
28347         if (!rowEntity['$$errors'+colDef.name]) {
28348           return;
28349         }
28350         if (validatorName in rowEntity['$$errors'+colDef.name]) {
28351             delete rowEntity['$$errors'+colDef.name][validatorName];
28352         }
28353       },
28354       
28355       /**
28356        * @ngdoc function
28357        * @name getErrorMessages
28358        * @methodOf ui.grid.validate.service:uiGridValidateService
28359        * @description returns an array of i18n-ed error messages.
28360        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28361        * @param {object} colDef the column whose errors we are looking for
28362        * @returns {array} An array of strings containing all the error messages for the cell
28363        */
28364       getErrorMessages: function(rowEntity, colDef) {
28365         var errors = [];
28366
28367         if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) {
28368           return errors;
28369         }
28370
28371         Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) {
28372           errors.push(service.getMessage(validatorName, colDef.validators[validatorName]));
28373         });
28374         
28375         return errors;
28376       },
28377       
28378       /**
28379        * @ngdoc function
28380        * @name getFormattedErrors
28381        * @methodOf  ui.grid.validate.service:uiGridValidateService
28382        * @description returns the error i18n-ed and formatted in html to be shown inside the page.
28383        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28384        * @param {object} colDef the column whose errors we are looking for
28385        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28386        * message inside the page (i.e. inside a div)
28387        */
28388       getFormattedErrors: function(rowEntity, colDef) {
28389
28390         var msgString = "";
28391
28392         var errors = service.getErrorMessages(rowEntity, colDef);
28393         
28394         if (!errors.length) {
28395           return;
28396         }
28397         
28398         errors.forEach(function(errorMsg) {
28399           msgString += errorMsg + "<br/>";
28400         });
28401
28402         return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString );
28403       },
28404
28405       /**
28406        * @ngdoc function
28407        * @name getTitleFormattedErrors
28408        * @methodOf ui.grid.validate.service:uiGridValidateService
28409        * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
28410        * title attribute.
28411        * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28412        * @param {object} colDef the column whose errors we are looking for
28413        * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28414        * message inside an html title attribute
28415        */
28416       getTitleFormattedErrors: function(rowEntity, colDef) {
28417
28418         var newLine = "\n";
28419
28420         var msgString = "";
28421         
28422         var errors = service.getErrorMessages(rowEntity, colDef);
28423         
28424         if (!errors.length) {
28425           return;
28426         }
28427         
28428         errors.forEach(function(errorMsg) {
28429           msgString += errorMsg + newLine;
28430         });
28431
28432         return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString);
28433       },
28434
28435       /**
28436        * @ngdoc function
28437        * @name getTitleFormattedErrors
28438        * @methodOf ui.grid.validate.service:uiGridValidateService
28439        * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors
28440        * @param {object} rowEntity the row entity of the cell we want to run the validators on
28441        * @param {object} colDef the column definition of the cell we want to run the validators on
28442        * @param {object} newValue the value the user just entered
28443        * @param {object} oldValue the value the field had before
28444        */
28445       runValidators: function(rowEntity, colDef, newValue, oldValue, grid) {
28446         
28447         if (newValue === oldValue) {
28448           // If the value has not changed we perform no validation
28449           return;
28450         }
28451         
28452         if (typeof(colDef.name) === 'undefined' || !colDef.name) {
28453           throw new Error('colDef.name is required to perform validation');
28454         }
28455         
28456         service.setValid(rowEntity, colDef);
28457         
28458         var validateClosureFactory = function(rowEntity, colDef, validatorName) {
28459           return function(value) {
28460             if (!value) {
28461               service.setInvalid(rowEntity, colDef);
28462               service.setError(rowEntity, colDef, validatorName);
28463               if (grid) {
28464                 grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue);
28465               }
28466             }
28467           };
28468         };
28469
28470         var promises = [];
28471
28472         for (var validatorName in colDef.validators) {
28473           service.clearError(rowEntity, colDef, validatorName);
28474           var msg;
28475           var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]);
28476           // We pass the arguments as oldValue, newValue so they are in the same order 
28477           // as ng-model validators (modelValue, viewValue)
28478           var promise = $q
28479                         .when(validatorFunction(oldValue, newValue, rowEntity, colDef))
28480                         .then(validateClosureFactory(rowEntity, colDef, validatorName));
28481           promises.push(promise);
28482         }
28483         
28484         return $q.all(promises);
28485       },
28486
28487       /**
28488        * @ngdoc function
28489        * @name createDefaultValidators
28490        * @methodOf ui.grid.validate.service:uiGridValidateService
28491        * @description adds the basic validators to the list of service validators
28492        */
28493       createDefaultValidators: function() {
28494         service.setValidator('minLength',
28495                              function (argument) {
28496                                return function (oldValue, newValue, rowEntity, colDef) {
28497                                  if (newValue === undefined || newValue === null || newValue === '') {
28498                                    return true;
28499                                  }
28500                                  return newValue.length >= argument;
28501                                };
28502                              },
28503                                function(argument) {
28504                                  return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument);
28505                                });
28506         
28507         service.setValidator('maxLength',
28508                              function (argument) {
28509                                return function (oldValue, newValue, rowEntity, colDef) {
28510                                  if (newValue === undefined || newValue === null || newValue === '') {
28511                                    return true;
28512                                  }
28513                                  return newValue.length <= argument;
28514                                };
28515                              },
28516                              function(threshold) {
28517                                return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold);
28518                              });
28519         
28520         service.setValidator('required',
28521                              function (argument) {
28522                                return function (oldValue, newValue, rowEntity, colDef) {
28523                                  if (argument) {
28524                                    return !(newValue === undefined || newValue === null || newValue === '');
28525                                  }
28526                                  return true;
28527                                };
28528                              },
28529                              function(argument) {
28530                                return i18nService.getSafeText('validate.required');
28531                              });
28532       },
28533
28534       initializeGrid: function (scope, grid) {
28535         grid.validate = {
28536         
28537           isInvalid: service.isInvalid,
28538
28539           getFormattedErrors: service.getFormattedErrors,
28540          
28541           getTitleFormattedErrors: service.getTitleFormattedErrors,
28542
28543           runValidators: service.runValidators
28544         };
28545         
28546         /**
28547          *  @ngdoc object
28548          *  @name ui.grid.validate.api:PublicApi
28549          *
28550          *  @description Public Api for validation feature
28551          */
28552         var publicApi = {
28553           events: {
28554             validate: {
28555               /**
28556                * @ngdoc event
28557                * @name validationFailed
28558                * @eventOf  ui.grid.validate.api:PublicApi
28559                * @description raised when one or more failure happened during validation 
28560                * <pre>
28561                *      gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...})
28562                * </pre>
28563                * @param {object} rowEntity the options.data element whose validation failed
28564                * @param {object} colDef the column whose validation failed
28565                * @param {object} newValue new value
28566                * @param {object} oldValue old value
28567                */
28568               validationFailed: function (rowEntity, colDef, newValue, oldValue) {
28569               }
28570             }
28571           },
28572           methods: {
28573             validate: {
28574               /**
28575                * @ngdoc function
28576                * @name isInvalid
28577                * @methodOf  ui.grid.validate.api:PublicApi
28578                * @description checks if a cell (identified by rowEntity, colDef) is invalid
28579                * @param {object} rowEntity gridOptions.data[] array instance we want to check
28580                * @param {object} colDef the column whose errors we want to check
28581                * @returns {boolean} true if the cell value is not valid
28582                */
28583               isInvalid: function(rowEntity, colDef) {
28584                 return grid.validate.isInvalid(rowEntity, colDef);
28585               },
28586               /**
28587                * @ngdoc function
28588                * @name getErrorMessages
28589                * @methodOf  ui.grid.validate.api:PublicApi
28590                * @description returns an array of i18n-ed error messages.
28591                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28592                * @param {object} colDef the column whose errors we are looking for
28593                * @returns {array} An array of strings containing all the error messages for the cell
28594                */
28595               getErrorMessages: function (rowEntity, colDef) {
28596                 return grid.validate.getErrorMessages(rowEntity, colDef);
28597               },
28598               /**
28599                * @ngdoc function
28600                * @name getFormattedErrors
28601                * @methodOf  ui.grid.validate.api:PublicApi
28602                * @description returns the error i18n-ed and formatted in html to be shown inside the page.
28603                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28604                * @param {object} colDef the column whose errors we are looking for
28605                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28606                * message inside the page (i.e. inside a div)
28607                */
28608               getFormattedErrors: function (rowEntity, colDef) {
28609                 return grid.validate.getFormattedErrors(rowEntity, colDef);
28610               },
28611               /**
28612                * @ngdoc function
28613                * @name getTitleFormattedErrors
28614                * @methodOf  ui.grid.validate.api:PublicApi
28615                * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html 
28616                * title attribute.
28617                * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28618                * @param {object} colDef the column whose errors we are looking for
28619                * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28620                * message inside an html title attribute
28621                */
28622               getTitleFormattedErrors: function (rowEntity, colDef) {
28623                 return grid.validate.getTitleFormattedErrors(rowEntity, colDef);
28624               }
28625             } 
28626           }
28627         };
28628         
28629         grid.api.registerEventsFromObject(publicApi.events);
28630         grid.api.registerMethodsFromObject(publicApi.methods);
28631
28632         if (grid.edit) {
28633           grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) {
28634             grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid);
28635           });
28636         }
28637
28638         service.createDefaultValidators();
28639       }
28640       
28641     };
28642   
28643     return service;
28644   }]);
28645   
28646   
28647   /**
28648    *  @ngdoc directive
28649    *  @name ui.grid.validate.directive:uiGridValidate
28650    *  @element div
28651    *  @restrict A
28652    *  @description Adds validating features to the ui-grid directive.
28653    *  @example
28654    <example module="app">
28655    <file name="app.js">
28656    var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']);
28657
28658    app.controller('MainCtrl', ['$scope', function ($scope) {
28659       $scope.data = [
28660         { name: 'Bob', title: 'CEO' },
28661             { name: 'Frank', title: 'Lowly Developer' }
28662       ];
28663
28664       $scope.columnDefs = [
28665         {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'},
28666         {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'}
28667       ];
28668     }]);
28669    </file>
28670    <file name="index.html">
28671    <div ng-controller="MainCtrl">
28672    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div>
28673    </div>
28674    </file>
28675    </example>
28676    */
28677
28678   module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) {
28679     return {
28680       priority: 0,
28681       replace: true,
28682       require: '^uiGrid',
28683       scope: false,
28684       compile: function () {
28685         return {
28686           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
28687             uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid);
28688           },
28689           post: function ($scope, $elm, $attrs, uiGridCtrl) {
28690           }
28691         };
28692       }
28693     };
28694   }]);
28695 })();
28696
28697 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
28698   'use strict';
28699
28700   $templateCache.put('ui-grid/ui-grid-filter',
28701     "<div class=\"ui-grid-filter-container\" ng-style=\"col.extraStyle\" 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-show=\"colFilter.selectOptions.length > 0\" 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>"
28702   );
28703
28704
28705   $templateCache.put('ui-grid/ui-grid-footer',
28706     "<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>"
28707   );
28708
28709
28710   $templateCache.put('ui-grid/ui-grid-grid-footer',
28711     "<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>"
28712   );
28713
28714
28715   $templateCache.put('ui-grid/ui-grid-group-panel',
28716     "<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>"
28717   );
28718
28719
28720   $templateCache.put('ui-grid/ui-grid-header',
28721     "<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>"
28722   );
28723
28724
28725   $templateCache.put('ui-grid/ui-grid-menu-button',
28726     "<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>"
28727   );
28728
28729
28730   $templateCache.put('ui-grid/ui-grid-no-header',
28731     "<div class=\"ui-grid-top-panel\"></div>"
28732   );
28733
28734
28735   $templateCache.put('ui-grid/ui-grid-row',
28736     "<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>"
28737   );
28738
28739
28740   $templateCache.put('ui-grid/ui-grid',
28741     "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
28742     "      /* Styles for the grid */\n" +
28743     "    }\n" +
28744     "\n" +
28745     "    .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
28746     "      height: {{ grid.options.rowHeight }}px;\n" +
28747     "    }\n" +
28748     "\n" +
28749     "    .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
28750     "      border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
28751     "    }\n" +
28752     "\n" +
28753     "    {{ grid.verticalScrollbarStyles }}\n" +
28754     "    {{ grid.horizontalScrollbarStyles }}\n" +
28755     "\n" +
28756     "    /*\n" +
28757     "    .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
28758     "      padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
28759     "    }\n" +
28760     "    */\n" +
28761     "\n" +
28762     "    {{ 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>"
28763   );
28764
28765
28766   $templateCache.put('ui-grid/uiGridCell',
28767     "<div class=\"ui-grid-cell-contents\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
28768   );
28769
28770
28771   $templateCache.put('ui-grid/uiGridColumnMenu',
28772     "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
28773     "    <div class=\"inner\" ng-show=\"menuShown\">\n" +
28774     "      <ul>\n" +
28775     "        <div ng-show=\"grid.options.enableSorting\">\n" +
28776     "          <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" +
28777     "          <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" +
28778     "          <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
28779     "        </div>\n" +
28780     "      </ul>\n" +
28781     "    </div>\n" +
28782     "  </div> --></div></div>"
28783   );
28784
28785
28786   $templateCache.put('ui-grid/uiGridFooterCell',
28787     "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationText() + ( col.getAggregationValue() CUSTOM_FILTERS ) }}</div></div>"
28788   );
28789
28790
28791   $templateCache.put('ui-grid/uiGridHeaderCell',
28792     "<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>"
28793   );
28794
28795
28796   $templateCache.put('ui-grid/uiGridMenu',
28797     "<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>"
28798   );
28799
28800
28801   $templateCache.put('ui-grid/uiGridMenuItem',
28802     "<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>"
28803   );
28804
28805
28806   $templateCache.put('ui-grid/uiGridRenderContainer',
28807     "<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>"
28808   );
28809
28810
28811   $templateCache.put('ui-grid/uiGridViewport',
28812     "<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>"
28813   );
28814
28815
28816   $templateCache.put('ui-grid/cellEditor',
28817     "<div><form name=\"inputForm\"><input type=\"INPUT_TYPE\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
28818   );
28819
28820
28821   $templateCache.put('ui-grid/dropdownEditor',
28822     "<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>"
28823   );
28824
28825
28826   $templateCache.put('ui-grid/fileChooserEditor',
28827     "<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>"
28828   );
28829
28830
28831   $templateCache.put('ui-grid/emptyBaseLayerContainer',
28832     "<div class=\"ui-grid-empty-base-layer-container ui-grid-canvas\"><div class=\"ui-grid-row\" ng-repeat=\"(rowRenderIndex, row) in grid.baseLayer.emptyRows track by $index\" ng-style=\"Viewport.rowStyle(rowRenderIndex)\"><div><div><div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell {{ col.getColClass(false) }}\"></div></div></div></div></div>"
28833   );
28834
28835
28836   $templateCache.put('ui-grid/expandableRow',
28837     "<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>"
28838   );
28839
28840
28841   $templateCache.put('ui-grid/expandableRowHeader',
28842     "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-if=\"!row.groupHeader==true\" 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>"
28843   );
28844
28845
28846   $templateCache.put('ui-grid/expandableScrollFiller',
28847     "<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>"
28848   );
28849
28850
28851   $templateCache.put('ui-grid/expandableTopRowHeader',
28852     "<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>"
28853   );
28854
28855
28856   $templateCache.put('ui-grid/csvLink',
28857     "<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>"
28858   );
28859
28860
28861   $templateCache.put('ui-grid/importerMenuItem',
28862     "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
28863   );
28864
28865
28866   $templateCache.put('ui-grid/importerMenuItemContainer',
28867     "<div ui-grid-importer-menu-item></div>"
28868   );
28869
28870
28871   $templateCache.put('ui-grid/pagination',
28872     "<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 && !grid.options.useCustomPagination\"><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\">{{ 1 + paginationApi.getFirstRowIndex() }} <abbr ui-grid-one-bind-title=\"paginationThrough\">-</abbr> {{ 1 + paginationApi.getLastRowIndex() }} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
28873   );
28874
28875
28876   $templateCache.put('ui-grid/columnResizer',
28877     "<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>"
28878   );
28879
28880
28881   $templateCache.put('ui-grid/gridFooterSelectedItems',
28882     "<span ng-if=\"grid.selection.selectedCount !== 0 && grid.options.enableFooterTotalSelected\">({{\"search.selectedItems\" | t}} {{grid.selection.selectedCount}})</span>"
28883   );
28884
28885
28886   $templateCache.put('ui-grid/selectionHeaderCell',
28887     "<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>"
28888   );
28889
28890
28891   $templateCache.put('ui-grid/selectionRowHeader',
28892     "<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>"
28893   );
28894
28895
28896   $templateCache.put('ui-grid/selectionRowHeaderButtons',
28897     "<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>"
28898   );
28899
28900
28901   $templateCache.put('ui-grid/selectionSelectAllButtons',
28902     "<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>"
28903   );
28904
28905
28906   $templateCache.put('ui-grid/treeBaseExpandAllButtons',
28907     "<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>"
28908   );
28909
28910
28911   $templateCache.put('ui-grid/treeBaseHeaderCell',
28912     "<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>"
28913   );
28914
28915
28916   $templateCache.put('ui-grid/treeBaseRowHeader',
28917     "<div class=\"ui-grid-cell-contents\"><ui-grid-tree-base-row-header-buttons></ui-grid-tree-base-row-header-buttons></div>"
28918   );
28919
28920
28921   $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
28922     "<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>"
28923   );
28924
28925
28926   $templateCache.put('ui-grid/cellTitleValidator',
28927     "<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>"
28928   );
28929
28930
28931   $templateCache.put('ui-grid/cellTooltipValidator',
28932     "<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>"
28933   );
28934
28935 }]);