Add config folder to be able to change config
[clamp.git] / src / main / resources / META-INF / resources / designer / lib / ui-grid-unstable.js
1 /*! ui-grid - v3.0.0-rc.16-234dd76 - 2014-11-22
2 * Copyright (c) 2014 ; License: MIT */
3 (function () {
4   'use strict';
5   angular.module('ui.grid.i18n', []);
6   angular.module('ui.grid', ['ui.grid.i18n']);
7 })();
8 (function () {
9   'use strict';
10   angular.module('ui.grid').constant('uiGridConstants', {
11     LOG_DEBUG_MESSAGES: true,
12     LOG_WARN_MESSAGES: true,
13     LOG_ERROR_MESSAGES: true,
14     CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
15     COL_FIELD: /COL_FIELD/g,
16     MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
17     DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
18     TEMPLATE_REGEXP: /<.+>/,
19     FUNC_REGEXP: /(\([^)]*\))?$/,
20     DOT_REGEXP: /\./g,
21     APOS_REGEXP: /'/g,
22     BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
23     COL_CLASS_PREFIX: 'ui-grid-col',
24     events: {
25       GRID_SCROLL: 'uiGridScroll',
26       COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
27       ITEM_DRAGGING: 'uiGridItemDragStart' // For any item being dragged
28     },
29     // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
30     keymap: {
31       TAB: 9,
32       STRG: 17,
33       CTRL: 17,
34       CTRLRIGHT: 18,
35       CTRLR: 18,
36       SHIFT: 16,
37       RETURN: 13,
38       ENTER: 13,
39       BACKSPACE: 8,
40       BCKSP: 8,
41       ALT: 18,
42       ALTR: 17,
43       ALTRIGHT: 17,
44       SPACE: 32,
45       WIN: 91,
46       MAC: 91,
47       FN: null,
48       UP: 38,
49       DOWN: 40,
50       LEFT: 37,
51       RIGHT: 39,
52       ESC: 27,
53       DEL: 46,
54       F1: 112,
55       F2: 113,
56       F3: 114,
57       F4: 115,
58       F5: 116,
59       F6: 117,
60       F7: 118,
61       F8: 119,
62       F9: 120,
63       F10: 121,
64       F11: 122,
65       F12: 123
66     },
67     ASC: 'asc',
68     DESC: 'desc',
69     filter: {
70       STARTS_WITH: 2,
71       ENDS_WITH: 4,
72       EXACT: 8,
73       CONTAINS: 16,
74       GREATER_THAN: 32,
75       GREATER_THAN_OR_EQUAL: 64,
76       LESS_THAN: 128,
77       LESS_THAN_OR_EQUAL: 256,
78       NOT_EQUAL: 512
79     },
80
81     aggregationTypes: {
82       sum: 2,
83       count: 4,
84       avg: 8,
85       min: 16,
86       max: 32
87     },
88
89     // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
90     CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
91     
92     dataChange: {
93       ALL: 'all',
94       EDIT: 'edit',
95       ROW: 'row',
96       COLUMN: 'column'
97     },
98     scrollbars: {
99       NEVER: 0,
100       ALWAYS: 1,
101       WHEN_NEEDED: 2
102     }
103   });
104
105 })();
106 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
107   var uiGridCell = {
108     priority: 0,
109     scope: false,
110     require: '?^uiGrid',
111     compile: function() {
112       return {
113         pre: function($scope, $elm, $attrs, uiGridCtrl) {
114           function compileTemplate() {
115             var compiledElementFn = $scope.col.compiledElementFn;
116
117             compiledElementFn($scope, function(clonedElement, scope) {
118               $elm.append(clonedElement);
119             });
120           }
121
122           // If the grid controller is present, use it to get the compiled cell template function
123           if (uiGridCtrl && $scope.col.compiledElementFn) {
124              compileTemplate();
125           }
126           // No controller, compile the element manually (for unit tests)
127           else {
128             if ( uiGridCtrl && !$scope.col.compiledElementFn ){
129               // gridUtil.logError('Render has been called before precompile.  Please log a ui-grid issue');  
130
131               $scope.col.getCompiledElementFn()
132                 .then(function (compiledElementFn) {
133                   compiledElementFn($scope, function(clonedElement, scope) {
134                     $elm.append(clonedElement);
135                   });
136                 });
137             }
138             else {
139               var html = $scope.col.cellTemplate
140                 .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
141                 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
142
143               var cellElement = $compile(html)($scope);
144               $elm.append(cellElement);
145             }
146           }
147         },
148         post: function($scope, $elm, $attrs, uiGridCtrl) {
149           $elm.addClass($scope.col.getColClass(false));
150
151           var classAdded;
152           var updateClass = function( grid ){
153             var contents = $elm;
154             if ( classAdded ){
155               contents.removeClass( classAdded );
156               classAdded = null;
157             }
158
159             if (angular.isFunction($scope.col.cellClass)) {
160               classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
161             }
162             else {
163               classAdded = $scope.col.cellClass;
164             }
165             contents.addClass(classAdded);
166           };
167
168           if ($scope.col.cellClass) {
169             updateClass();
170           }
171           
172           // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
173           var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
174           
175           var deregisterFunction = function() {
176            $scope.grid.deregisterDataChangeCallback( watchUid ); 
177           };
178           
179           $scope.$on( '$destroy', deregisterFunction );
180         }
181       };
182     }
183   };
184
185   return uiGridCell;
186 }]);
187
188
189 (function(){
190
191 angular.module('ui.grid')
192 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil', 
193 function ( i18nService, uiGridConstants, gridUtil ) {
194 /**
195  *  @ngdoc service
196  *  @name ui.grid.service:uiGridColumnMenuService
197  *
198  *  @description Services for working with column menus, factored out
199  *  to make the code easier to understand
200  */
201
202   var service = {
203     /**
204      * @ngdoc method
205      * @methodOf ui.grid.service:uiGridColumnMenuService
206      * @name initialize
207      * @description  Sets defaults, puts a reference to the $scope on 
208      * the uiGridController
209      * @param {$scope} $scope the $scope from the uiGridColumnMenu
210      * @param {controller} uiGridCtrl the uiGridController for the grid
211      * we're on
212      * 
213      */
214     initialize: function( $scope, uiGridCtrl ){
215       $scope.grid = uiGridCtrl.grid;
216
217       // Store a reference to this link/controller in the main uiGrid controller
218       // to allow showMenu later
219       uiGridCtrl.columnMenuScope = $scope;
220       
221       // Save whether we're shown or not so the columns can check
222       $scope.menuShown = false;
223     },
224     
225     
226     /**
227      * @ngdoc method
228      * @methodOf ui.grid.service:uiGridColumnMenuService
229      * @name setColMenuItemWatch
230      * @description  Setup a watch on $scope.col.menuItems, and update
231      * menuItems based on this.  $scope.col needs to be set by the column
232      * before calling the menu.
233      * @param {$scope} $scope the $scope from the uiGridColumnMenu
234      * @param {controller} uiGridCtrl the uiGridController for the grid
235      * we're on
236      * 
237      */    
238     setColMenuItemWatch: function ( $scope ){
239       var deregFunction = $scope.$watch('col.menuItems', function (n, o) {
240         if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
241           n.forEach(function (item) {
242             if (typeof(item.context) === 'undefined' || !item.context) {
243               item.context = {};
244             }
245             item.context.col = $scope.col;
246           });
247
248           $scope.menuItems = $scope.defaultMenuItems.concat(n);
249         }
250         else {
251           $scope.menuItems = $scope.defaultMenuItems;
252         }
253       }); 
254       
255       $scope.$on( '$destroy', deregFunction );     
256     },
257
258
259     /**
260      * @ngdoc boolean
261      * @name enableSorting
262      * @propertyOf ui.grid.class:GridOptions.columnDef
263      * @description (optional) True by default. When enabled, this setting adds sort
264      * widgets to the column header, allowing sorting of the data in the individual column.
265      */
266     /**
267      * @ngdoc method
268      * @methodOf ui.grid.service:uiGridColumnMenuService
269      * @name sortable
270      * @description  determines whether this column is sortable
271      * @param {$scope} $scope the $scope from the uiGridColumnMenu
272      * 
273      */    
274     sortable: function( $scope ) {
275       if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
276         return true;
277       }
278       else {
279         return false;
280       }
281     },
282     
283     /**
284      * @ngdoc method
285      * @methodOf ui.grid.service:uiGridColumnMenuService
286      * @name isActiveSort
287      * @description  determines whether the requested sort direction is current active, to 
288      * allow highlighting in the menu
289      * @param {$scope} $scope the $scope from the uiGridColumnMenu
290      * @param {string} direction the direction that we'd have selected for us to be active
291      * 
292      */  
293     isActiveSort: function( $scope, direction ){
294       return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' && 
295               typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
296       
297     },
298     
299     /**
300      * @ngdoc boolean
301      * @name suppressRemoveSort
302      * @propertyOf ui.grid.class:GridOptions.columnDef
303      * @description (optional) False by default. When enabled, this setting hides the removeSort option
304      * in the menu.
305      */
306     /**
307      * @ngdoc method
308      * @methodOf ui.grid.service:uiGridColumnMenuService
309      * @name suppressRemoveSort
310      * @description  determines whether we should suppress the removeSort option
311      * @param {$scope} $scope the $scope from the uiGridColumnMenu
312      * 
313      */  
314     suppressRemoveSort: function( $scope ) {
315       if ($scope.col && $scope.col.colDef && $scope.col.colDef.suppressRemoveSort) {
316         return true;
317       }
318       else {
319         return false;
320       }
321     },       
322
323
324     /**
325      * @ngdoc boolean
326      * @name enableHiding
327      * @propertyOf ui.grid.class:GridOptions.columnDef
328      * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
329      * using the column menu or the grid menu.
330      */
331     /**
332      * @ngdoc method
333      * @methodOf ui.grid.service:uiGridColumnMenuService
334      * @name hideable
335      * @description  determines whether a column can be hidden, by checking the enableHiding columnDef option
336      * @param {$scope} $scope the $scope from the uiGridColumnMenu
337      * 
338      */  
339     hideable: function( $scope ) {
340       if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
341         return false;
342       }
343       else {
344         return true;
345       }
346     },     
347
348
349     /**
350      * @ngdoc method
351      * @methodOf ui.grid.service:uiGridColumnMenuService
352      * @name getDefaultMenuItems
353      * @description  returns the default menu items for a column menu
354      * @param {$scope} $scope the $scope from the uiGridColumnMenu
355      * 
356      */     
357     getDefaultMenuItems: function( $scope ){
358       return [
359         {
360           title: i18nService.getSafeText('sort.ascending'),
361           icon: 'ui-grid-icon-sort-alt-up',
362           action: function($event) {
363             $event.stopPropagation();
364             $scope.sortColumn($event, uiGridConstants.ASC);
365           },
366           shown: function () {
367             return service.sortable( $scope );
368           },
369           active: function() {
370             return service.isActiveSort( $scope, uiGridConstants.ASC);
371           }
372         },
373         {
374           title: i18nService.getSafeText('sort.descending'),
375           icon: 'ui-grid-icon-sort-alt-down',
376           action: function($event) {
377             $event.stopPropagation();
378             $scope.sortColumn($event, uiGridConstants.DESC);
379           },
380           shown: function() {
381             return service.sortable( $scope );
382           },
383           active: function() {
384             return service.isActiveSort( $scope, uiGridConstants.DESC);
385           }
386         },
387         {
388           title: i18nService.getSafeText('sort.remove'),
389           icon: 'ui-grid-icon-cancel',
390           action: function ($event) {
391             $event.stopPropagation();
392             $scope.unsortColumn();
393           },
394           shown: function() {
395             return service.sortable( $scope ) && 
396                    typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' && 
397                    typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
398                   !service.suppressRemoveSort( $scope );
399           }
400         },
401         {
402           title: i18nService.getSafeText('column.hide'),
403           icon: 'ui-grid-icon-cancel',
404           shown: function() {
405             return service.hideable( $scope );
406           },
407           action: function ($event) {
408             $event.stopPropagation();
409             $scope.hideColumn();
410           }
411         }
412       ];
413     },
414     
415
416     /**
417      * @ngdoc method
418      * @methodOf ui.grid.service:uiGridColumnMenuService
419      * @name getColumnElementPosition
420      * @description  gets the position information needed to place the column
421      * menu below the column header
422      * @param {$scope} $scope the $scope from the uiGridColumnMenu
423      * @param {GridCol} column the column we want to position below
424      * @param {element} $columnElement the column element we want to position below
425      * @returns {hash} containing left, top, offset, height, width
426      * 
427      */  
428     getColumnElementPosition: function( $scope, column, $columnElement ){
429       var positionData = {};
430       positionData.left = $columnElement[0].offsetLeft;
431       positionData.top = $columnElement[0].offsetTop;
432
433       // Get the grid scrollLeft
434       positionData.offset = 0;
435       if (column.grid.options.offsetLeft) {
436         positionData.offset = column.grid.options.offsetLeft;
437       }
438
439       positionData.height = gridUtil.elementHeight($columnElement, true);
440       positionData.width = gridUtil.elementWidth($columnElement, true);
441       
442       return positionData;
443     },
444     
445
446     /**
447      * @ngdoc method
448      * @methodOf ui.grid.service:uiGridColumnMenuService
449      * @name repositionMenu
450      * @description  Reposition the menu below the new column.  If the menu has no child nodes 
451      * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
452      * later to fix it
453      * @param {$scope} $scope the $scope from the uiGridColumnMenu
454      * @param {GridCol} column the column we want to position below
455      * @param {hash} positionData a hash containing left, top, offset, height, width
456      * @param {element} $elm the column menu element that we want to reposition
457      * @param {element} $columnElement the column element that we want to reposition underneath
458      * 
459      */  
460     repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
461       var menu = $elm[0].querySelectorAll('.ui-grid-menu');
462       var containerId = column.renderContainer ? column.renderContainer : 'body';
463       var renderContainer = column.grid.renderContainers[containerId];
464
465       // It's possible that the render container of the column we're attaching to is 
466       // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft 
467       // between the render container and the grid
468       var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
469       var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
470
471       var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
472
473       // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
474       var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
475       var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
476       
477       if ( menu.length !== 0 ){
478         var mid = menu[0].querySelectorAll('.ui-grid-menu-mid'); 
479         if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
480           myWidth = gridUtil.elementWidth(menu, true);
481           $scope.lastMenuWidth = myWidth;
482           column.lastMenuWidth = myWidth;
483   
484           // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
485           // Get the column menu right padding
486           paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
487           $scope.lastMenuPaddingRight = paddingRight;
488           column.lastMenuPaddingRight = paddingRight;
489         }
490       }
491       
492       var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.width - myWidth + paddingRight;
493       if (left < positionData.offset){
494         left = positionData.offset;
495       }
496
497       $elm.css('left', left + 'px');
498       $elm.css('top', (positionData.top + positionData.height) + 'px');
499     }    
500
501   };
502   
503   return service;
504 }])
505
506
507 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', 
508 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) {
509 /**
510  * @ngdoc directive
511  * @name ui.grid.directive:uiGridColumnMenu
512  * @description  Provides the column menu framework, leverages uiGridMenu underneath
513  * 
514  */
515
516   var uiGridColumnMenu = {
517     priority: 0,
518     scope: true,
519     require: '?^uiGrid',
520     templateUrl: 'ui-grid/uiGridColumnMenu',
521     replace: true,
522     link: function ($scope, $elm, $attrs, uiGridCtrl) {
523       var self = this;
524       
525       uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
526
527       $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
528
529       // Set the menu items for use with the column menu. The user can later add additional items via the watch
530       $scope.menuItems = $scope.defaultMenuItems;
531       uiGridColumnMenuService.setColMenuItemWatch( $scope );
532
533   
534       /**
535        * @ngdoc method
536        * @methodOf ui.grid.directive:uiGridColumnMenu
537        * @name showMenu
538        * @description Shows the column menu.  If the menu is already displayed it
539        * calls the menu to ask it to hide (it will animate), then it repositions the menu
540        * to the right place whilst hidden (it will make an assumption on menu width), 
541        * then it asks the menu to show (it will animate), then it repositions the menu again 
542        * once we can calculate it's size.
543        * @param {GridCol} column the column we want to position below
544        * @param {element} $columnElement the column element we want to position below
545        */
546       $scope.showMenu = function(column, $columnElement, event) {
547         // Swap to this column
548         $scope.col = column;
549
550         // Get the position information for the column element
551         var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
552
553         if ($scope.menuShown) {
554           // we want to hide, then reposition, then show, but we want to wait for animations
555           // we set a variable, and then rely on the menu-hidden event to call the reposition and show
556           $scope.colElement = $columnElement;
557           $scope.colElementPosition = colElementPosition;
558           $scope.hideThenShow = true;
559
560           $scope.$broadcast('hide-menu', { originalEvent: event });
561         } else {
562           self.shown = $scope.menuShown = true;
563           uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
564
565           $scope.colElement = $columnElement;
566           $scope.colElementPosition = colElementPosition;
567           $scope.$broadcast('show-menu', { originalEvent: event });
568         } 
569
570       };
571
572
573       /**
574        * @ngdoc method
575        * @methodOf ui.grid.directive:uiGridColumnMenu
576        * @name hideMenu
577        * @description Hides the column menu.
578        * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
579        * from the menu itself - in which case don't broadcast again as we'll get
580        * an infinite loop
581        */
582       $scope.hideMenu = function( broadcastTrigger ) {
583         // delete $scope.col;
584         $scope.menuShown = false;
585         
586         if ( !broadcastTrigger ){
587           $scope.$broadcast('hide-menu');
588         }
589       };
590
591       
592       $scope.$on('menu-hidden', function() {
593         if ( $scope.hideThenShow ){
594           delete $scope.hideThenShow;
595
596           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
597           $scope.$broadcast('show-menu');
598
599           $scope.menuShown = true;
600         } else {
601           $scope.hideMenu( true );
602         }
603       });
604       
605       $scope.$on('menu-shown', function() {
606         $timeout( function() {
607           uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
608           delete $scope.colElementPosition;
609           delete $scope.columnElement;
610         }, 200);
611       });
612
613  
614       /* Column methods */
615       $scope.sortColumn = function (event, dir) {
616         event.stopPropagation();
617
618         $scope.grid.sortColumn($scope.col, dir, true)
619           .then(function () {
620             $scope.grid.refresh();
621             $scope.hideMenu();
622           });
623       };
624
625       $scope.unsortColumn = function () {
626         $scope.col.unsort();
627
628         $scope.grid.refresh();
629         $scope.hideMenu();
630       };
631
632       $scope.hideColumn = function () {
633         $scope.col.colDef.visible = false;
634
635         $scope.grid.refresh();
636         $scope.hideMenu();
637       };
638     },
639     
640     
641     
642     controller: ['$scope', function ($scope) {
643       var self = this;
644       
645       $scope.$watch('menuItems', function (n, o) {
646         self.menuItems = n;
647       });
648     }]
649   };
650
651   return uiGridColumnMenu;
652
653 }]);
654
655 })();
656 (function () {
657   'use strict';
658
659   angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
660   function ($timeout, gridUtil, uiGridConstants, $compile) {
661     var uiGridFooterCell = {
662       priority: 0,
663       scope: {
664         col: '=',
665         row: '=',
666         renderIndex: '='
667       },
668       replace: true,
669       require: '^uiGrid',
670       compile: function compile(tElement, tAttrs, transclude) {
671         return {
672           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
673             function compileTemplate(template) {
674               gridUtil.getTemplate(template).then(function (contents) {
675                 var linkFunction = $compile(contents);
676                 var html = linkFunction($scope);
677                 $elm.append(html);
678               });
679             }
680
681             //compile the footer template
682             if ($scope.col.footerCellTemplate) {
683               //compile the custom template
684               compileTemplate($scope.col.footerCellTemplate);
685             }
686             else {
687               //use default template
688               compileTemplate('ui-grid/uiGridFooterCell');
689             }
690           },
691           post: function ($scope, $elm, $attrs, uiGridCtrl) {
692             //$elm.addClass($scope.col.getColClass(false));
693             $scope.grid = uiGridCtrl.grid;
694             $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
695
696             $elm.addClass($scope.col.getColClass(false));
697
698             // apply any footerCellClass
699             var classAdded;
700             var updateClass = function( grid ){
701               var contents = $elm;
702               if ( classAdded ){
703                 contents.removeClass( classAdded );
704                 classAdded = null;
705               }
706   
707               if (angular.isFunction($scope.col.footerCellClass)) {
708                 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
709               }
710               else {
711                 classAdded = $scope.col.footerCellClass;
712               }
713               contents.addClass(classAdded);
714             };
715   
716             if ($scope.col.footerCellClass) {
717               updateClass();
718             }
719
720             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
721             var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
722
723             $scope.$on( '$destroy', function() {
724               $scope.grid.deregisterDataChangeCallback( watchUid ); 
725             });
726           }
727         };
728       }
729     };
730
731     return uiGridFooterCell;
732   }]);
733
734 })();
735
736 (function () {
737   'use strict';
738
739   angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
740     var defaultTemplate = 'ui-grid/ui-grid-footer';
741
742     return {
743       restrict: 'EA',
744       replace: true,
745       // priority: 1000,
746       require: ['^uiGrid', '^uiGridRenderContainer'],
747       scope: true,
748       compile: function ($elm, $attrs) {
749         return {
750           pre: function ($scope, $elm, $attrs, controllers) {
751             var uiGridCtrl = controllers[0];
752             var containerCtrl = controllers[1];
753
754             $scope.grid = uiGridCtrl.grid;
755             $scope.colContainer = containerCtrl.colContainer;
756             $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
757
758             containerCtrl.footer = $elm;
759
760             var footerTemplate = ($scope.grid.options.footerTemplate) ? $scope.grid.options.footerTemplate : defaultTemplate;
761             gridUtil.getTemplate(footerTemplate)
762               .then(function (contents) {
763                 var template = angular.element(contents);
764
765                 var newElm = $compile(template)($scope);
766                 $elm.append(newElm);
767
768                 if (containerCtrl) {
769                   // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
770                   var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
771
772                   if (footerViewport) {
773                     containerCtrl.footerViewport = footerViewport;
774                   }
775                 }
776               });
777           },
778
779           post: function ($scope, $elm, $attrs, controllers) {
780             var uiGridCtrl = controllers[0];
781             var containerCtrl = controllers[1];
782
783             // gridUtil.logDebug('ui-grid-footer link');
784
785             var grid = uiGridCtrl.grid;
786
787             // Don't animate footer cells
788             gridUtil.disableAnimations($elm);
789
790             containerCtrl.footer = $elm;
791
792             var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
793             if (footerViewport) {
794               containerCtrl.footerViewport = footerViewport;
795             }
796           }
797         };
798       }
799     };
800   }]);
801
802 })();
803 (function(){
804   'use strict';
805
806   angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
807     var defaultTemplate = 'ui-grid/ui-grid-group-panel';
808
809     return {
810       restrict: 'EA',
811       replace: true,
812       require: '?^uiGrid',
813       scope: false,
814       compile: function($elm, $attrs) {
815         return {
816           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
817             var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
818
819              gridUtil.getTemplate(groupPanelTemplate)
820               .then(function (contents) {
821                 var template = angular.element(contents);
822                 
823                 var newElm = $compile(template)($scope);
824                 $elm.append(newElm);
825               });
826           },
827
828           post: function ($scope, $elm, $attrs, uiGridCtrl) {
829             $elm.bind('$destroy', function() {
830               // scrollUnbinder();
831             });
832           }
833         };
834       }
835     };
836   }]);
837
838 })();
839 (function(){
840   'use strict';
841
842   angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 
843   function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
844     // Do stuff after mouse has been down this many ms on the header cell
845     var mousedownTimeout = 500;
846
847     var uiGridHeaderCell = {
848       priority: 0,
849       scope: {
850         col: '=',
851         row: '=',
852         renderIndex: '='
853       },
854       require: ['?^uiGrid', '^uiGridRenderContainer'],
855       replace: true,
856       compile: function() {
857         return {
858           pre: function ($scope, $elm, $attrs) {
859             var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
860             $elm.append(cellHeader);
861           },
862           
863           post: function ($scope, $elm, $attrs, controllers) {
864             var uiGridCtrl = controllers[0];
865             var renderContainerCtrl = controllers[1];
866
867             $scope.grid = uiGridCtrl.grid;
868             $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
869
870             $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
871             
872             $elm.addClass($scope.col.getColClass(false));
873     
874             // Hide the menu by default
875             $scope.menuShown = false;
876     
877             // Put asc and desc sort directions in scope
878             $scope.asc = uiGridConstants.ASC;
879             $scope.desc = uiGridConstants.DESC;
880     
881             // Store a reference to menu element
882             var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
883     
884             var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
885     
886
887             // apply any headerCellClass
888             var classAdded;
889             var updateClass = function( grid ){
890               var contents = $elm;
891               if ( classAdded ){
892                 contents.removeClass( classAdded );
893                 classAdded = null;
894               }
895   
896               if (angular.isFunction($scope.col.headerCellClass)) {
897                 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
898               }
899               else {
900                 classAdded = $scope.col.headerCellClass;
901               }
902               contents.addClass(classAdded);
903             };
904   
905             if ($scope.col.headerCellClass) {
906               updateClass();
907             }
908             
909             // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
910             var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
911
912             var deregisterFunction = function() {
913               $scope.grid.deregisterDataChangeCallback( watchUid ); 
914             };
915
916             $scope.$on( '$destroy', deregisterFunction );            
917
918
919             // Figure out whether this column is sortable or not
920             if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
921               $scope.sortable = true;
922             }
923             else {
924               $scope.sortable = false;
925             }
926     
927             if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
928               $scope.filterable = true;
929             }
930             else {
931               $scope.filterable = false;
932             }
933     
934             function handleClick(evt) {
935               // If the shift key is being held down, add this column to the sort
936               var add = false;
937               if (evt.shiftKey) {
938                 add = true;
939               }
940     
941               // Sort this column then rebuild the grid's rows
942               uiGridCtrl.grid.sortColumn($scope.col, add)
943                 .then(function () {
944                   if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
945                   uiGridCtrl.grid.refresh();
946                 });
947             }
948     
949             /**
950             * @ngdoc property
951             * @name enableColumnMenu
952             * @propertyOf ui.grid.class:GridOptions.columnDef
953             * @description if column menus are enabled, controls the column menus for this specific
954             * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
955             * using this option. If gridOptions.enableColumnMenus === false then you get no column
956             * menus irrespective of the value of this option ).  Defaults to true.
957             *
958             */
959             /**
960             * @ngdoc property
961             * @name enableColumnMenus
962             * @propertyOf ui.grid.class:GridOptions.columnDef
963             * @description Override for column menus everywhere - if set to false then you get no
964             * column menus.  Defaults to true.
965             *
966             */
967
968             // Long-click (for mobile)
969             var cancelMousedownTimeout;
970             var mousedownStartTime = 0;
971             $contentsElm.on('mousedown touchstart', function(event) {
972               if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
973                 event = event.originalEvent;
974               }
975     
976               // Don't show the menu if it's not the left button
977               if (event.button && event.button !== 0) {
978                 return;
979               }
980     
981               mousedownStartTime = (new Date()).getTime();
982     
983               cancelMousedownTimeout = $timeout(function() { }, mousedownTimeout);
984     
985               cancelMousedownTimeout.then(function () {
986                 if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false && 
987                     $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false) {
988                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
989                 }
990               });
991             });
992     
993             $contentsElm.on('mouseup touchend', function () {
994               $timeout.cancel(cancelMousedownTimeout);
995             });
996
997             $scope.$on('$destroy', function () {
998               $contentsElm.off('mousedown touchstart');
999             });
1000
1001
1002             $scope.toggleMenu = function($event) {
1003               $event.stopPropagation();
1004     
1005               // If the menu is already showing...
1006               if (uiGridCtrl.columnMenuScope.menuShown) {
1007                 // ... and we're the column the menu is on...
1008                 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1009                   // ... hide it
1010                   uiGridCtrl.columnMenuScope.hideMenu();
1011                 }
1012                 // ... and we're NOT the column the menu is on
1013                 else {
1014                   // ... move the menu to our column
1015                   uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1016                 }
1017               }
1018               // If the menu is NOT showing
1019               else {
1020                 // ... show it on our column
1021                 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1022               }
1023             };
1024     
1025             // If this column is sortable, add a click event handler
1026             if ($scope.sortable) {
1027               $contentsElm.on('click touchend', function(evt) {
1028                 evt.stopPropagation();
1029     
1030                 $timeout.cancel(cancelMousedownTimeout);
1031     
1032                 var mousedownEndTime = (new Date()).getTime();
1033                 var mousedownTime = mousedownEndTime - mousedownStartTime;
1034     
1035                 if (mousedownTime > mousedownTimeout) {
1036                   // long click, handled above with mousedown
1037                 }
1038                 else {
1039                   // short click
1040                   handleClick(evt);
1041                 }
1042               });
1043     
1044               $scope.$on('$destroy', function () {
1045                 // Cancel any pending long-click timeout
1046                 $timeout.cancel(cancelMousedownTimeout);
1047               });
1048             }
1049     
1050             if ($scope.filterable) {
1051               var filterDeregisters = [];
1052               angular.forEach($scope.col.filters, function(filter, i) {
1053                 filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1054                   if (n !== o) {
1055                     uiGridCtrl.grid.api.core.raise.filterChanged();
1056                     uiGridCtrl.grid.refresh()
1057                       .then(function () {
1058                         if (uiGridCtrl.prevScrollArgs && uiGridCtrl.prevScrollArgs.y && uiGridCtrl.prevScrollArgs.y.percentage) {
1059                            uiGridCtrl.fireScrollingEvent({ y: { percentage: uiGridCtrl.prevScrollArgs.y.percentage } });
1060                         }
1061                         // uiGridCtrl.fireEvent('force-vertical-scroll');
1062                       });
1063                   }
1064                 }));  
1065               });
1066               $scope.$on('$destroy', function() {
1067                 angular.forEach(filterDeregisters, function(filterDeregister) {
1068                   filterDeregister();
1069                 });
1070               });
1071             }
1072           }
1073         };
1074       }
1075     };
1076
1077     return uiGridHeaderCell;
1078   }]);
1079
1080 })();
1081
1082 (function(){
1083   'use strict';
1084
1085   angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1086     var defaultTemplate = 'ui-grid/ui-grid-header';
1087     var emptyTemplate = 'ui-grid/ui-grid-no-header';
1088
1089     return {
1090       restrict: 'EA',
1091       // templateUrl: 'ui-grid/ui-grid-header',
1092       replace: true,
1093       // priority: 1000,
1094       require: ['^uiGrid', '^uiGridRenderContainer'],
1095       scope: true,
1096       compile: function($elm, $attrs) {
1097         return {
1098           pre: function ($scope, $elm, $attrs, controllers) {
1099             var uiGridCtrl = controllers[0];
1100             var containerCtrl = controllers[1];
1101
1102             $scope.grid = uiGridCtrl.grid;
1103             $scope.colContainer = containerCtrl.colContainer;
1104             $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
1105
1106             containerCtrl.header = $elm;
1107             containerCtrl.colContainer.header = $elm;
1108
1109             /**
1110              * @ngdoc property
1111              * @name hideHeader
1112              * @propertyOf ui.grid.class:GridOptions
1113              * @description Null by default. When set to true, this setting will replace the
1114              * standard header template with '<div></div>', resulting in no header being shown.
1115              */
1116             
1117             var headerTemplate;
1118             if ($scope.grid.options.hideHeader){
1119               headerTemplate = emptyTemplate;
1120             } else {
1121               headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
1122             }
1123
1124              gridUtil.getTemplate(headerTemplate)
1125               .then(function (contents) {
1126                 var template = angular.element(contents);
1127                 
1128                 var newElm = $compile(template)($scope);
1129                 $elm.replaceWith(newElm);
1130
1131                 // Replace the reference to the container's header element with this new element
1132                 containerCtrl.header = newElm;
1133                 containerCtrl.colContainer.header = newElm;
1134
1135                 // And update $elm to be the new element
1136                 $elm = newElm;
1137
1138                 if (containerCtrl) {
1139                   // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1140                   var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1141
1142                   if (headerViewport) {
1143                     containerCtrl.headerViewport = headerViewport;
1144                   }
1145                 }
1146               });
1147           },
1148
1149           post: function ($scope, $elm, $attrs, controllers) {
1150             var uiGridCtrl = controllers[0];
1151             var containerCtrl = controllers[1];
1152
1153             // gridUtil.logDebug('ui-grid-header link');
1154
1155             var grid = uiGridCtrl.grid;
1156
1157             // Don't animate header cells
1158             gridUtil.disableAnimations($elm);
1159
1160             function updateColumnWidths() {
1161               // Get the width of the viewport
1162               var availableWidth = containerCtrl.colContainer.getViewportWidth();
1163
1164               if (typeof(uiGridCtrl.grid.verticalScrollbarWidth) !== 'undefined' && uiGridCtrl.grid.verticalScrollbarWidth !== undefined && uiGridCtrl.grid.verticalScrollbarWidth > 0) {
1165                 availableWidth = availableWidth + uiGridCtrl.grid.verticalScrollbarWidth;
1166               }
1167
1168               // The total number of columns
1169               // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
1170               // var equalWidth = availableWidth / equalWidthColumnCount;
1171
1172               var columnCache = containerCtrl.colContainer.visibleColumnCache,
1173                   canvasWidth = 0,
1174                   asteriskNum = 0,
1175                   oneAsterisk = 0,
1176                   leftoverWidth = availableWidth,
1177                   hasVariableWidth = false;
1178               
1179               var getColWidth = function(column){
1180                 if (column.widthType === "manual"){ 
1181                   return +column.width; 
1182                 }
1183                 else if (column.widthType === "percent"){ 
1184                   return parseInt(column.width.replace(/%/g, ''), 10) * availableWidth / 100;
1185                 }
1186                 else if (column.widthType === "auto"){
1187                   // leftOverWidth is subtracted from after each call to this
1188                   // function so we need to calculate oneAsterisk size only once
1189                   if (oneAsterisk === 0) {
1190                     oneAsterisk = parseInt(leftoverWidth / asteriskNum, 10);
1191                   }
1192                   return column.width.length * oneAsterisk; 
1193                 }
1194               };
1195               
1196               // Populate / determine column width types:
1197               columnCache.forEach(function(column){
1198                 column.widthType = null;
1199                 if (isFinite(+column.width)){
1200                   column.widthType = "manual";
1201                 }
1202                 else if (gridUtil.endsWith(column.width, "%")){
1203                   column.widthType = "percent";
1204                   hasVariableWidth = true;
1205                 }
1206                 else if (angular.isString(column.width) && column.width.indexOf('*') !== -1){
1207                   column.widthType = "auto";
1208                   asteriskNum += column.width.length;
1209                   hasVariableWidth = true;
1210                 }
1211               });
1212               
1213               // For sorting, calculate width from first to last:
1214               var colWidthPriority = ["manual", "percent", "auto"];
1215               columnCache.filter(function(column){
1216                 // Only draw visible items with a widthType
1217                 return (column.visible && column.widthType); 
1218               }).sort(function(a,b){
1219                 // Calculate widths in order, so that manual comes first, etc.
1220                 return colWidthPriority.indexOf(a.widthType) - colWidthPriority.indexOf(b.widthType);
1221               }).forEach(function(column){
1222                 // Calculate widths:
1223                 var colWidth = getColWidth(column);
1224                 if (column.minWidth){
1225                   colWidth = Math.max(colWidth, column.minWidth);
1226                 }
1227                 if (column.maxWidth){
1228                   colWidth = Math.min(colWidth, column.maxWidth);
1229                 }
1230                 column.drawnWidth = Math.floor(colWidth);
1231                 canvasWidth += column.drawnWidth;
1232                 leftoverWidth -= column.drawnWidth;
1233               });
1234
1235               // If the grid width didn't divide evenly into the column widths and we have pixels left over, dole them out to the columns one by one to make everything fit
1236               if (hasVariableWidth && leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
1237                 var remFn = function (column) {
1238                   if (leftoverWidth > 0 && (column.widthType === "auto" || column.widthType === "percent")) {
1239                     column.drawnWidth = column.drawnWidth + 1;
1240                     canvasWidth = canvasWidth + 1;
1241                     leftoverWidth--;
1242                   }
1243                 };
1244                 var prevLeftover = 0;
1245                 do {
1246                   prevLeftover = leftoverWidth;
1247                   columnCache.forEach(remFn);
1248                 } while (leftoverWidth > 0 && leftoverWidth !== prevLeftover );
1249               }
1250               canvasWidth = Math.max(canvasWidth, availableWidth);
1251
1252               // Build the CSS
1253               // uiGridCtrl.grid.columns.forEach(function (column) {
1254               var ret = '';
1255               columnCache.forEach(function (column) {
1256                 ret = ret + column.getColClassDefinition();
1257               });
1258
1259               // Add the vertical scrollbar width back in to the canvas width, it's taken out in getViewportWidth
1260               if (grid.verticalScrollbarWidth) {
1261                 canvasWidth = canvasWidth + grid.verticalScrollbarWidth;
1262               }
1263               // canvasWidth = canvasWidth + 1;
1264
1265               // if we have a grid menu, then we prune the width of the last column header
1266               // to allow room for the button whilst still getting to the column menu
1267               if (columnCache.length > 0) { // && grid.options.enableGridMenu) {
1268                 columnCache[columnCache.length - 1].headerWidth = columnCache[columnCache.length - 1].drawnWidth - 30;
1269               }
1270
1271               containerCtrl.colContainer.canvasWidth = parseInt(canvasWidth, 10);
1272
1273               // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1274               return ret;
1275             }
1276             
1277             containerCtrl.header = $elm;
1278             
1279             var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1280             if (headerViewport) {
1281               containerCtrl.headerViewport = headerViewport;
1282             }
1283
1284             //todo: remove this if by injecting gridCtrl into unit tests
1285             if (uiGridCtrl) {
1286               uiGridCtrl.grid.registerStyleComputation({
1287                 priority: 5,
1288                 func: updateColumnWidths
1289               });
1290             }
1291           }
1292         };
1293       }
1294     };
1295   }]);
1296
1297 })();
1298
1299 (function(){
1300
1301 angular.module('ui.grid')
1302 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', function( gridUtil, i18nService ) {
1303   /**
1304    *  @ngdoc service
1305    *  @name ui.grid.gridMenuService
1306    *
1307    *  @description Methods for working with the grid menu
1308    */
1309
1310   var service = {
1311     /**
1312      * @ngdoc method
1313      * @methodOf ui.grid.gridMenuService
1314      * @name initialize
1315      * @description Sets up the gridMenu. Most importantly, sets our
1316      * scope onto the grid object as grid.gridMenuScope, allowing us
1317      * to operate when passed only the grid.  Second most importantly, 
1318      * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1319      * on the core api.
1320      * @param {$scope} $scope the scope of this gridMenu
1321      * @param {Grid} grid the grid to which this gridMenu is associated
1322      */
1323     initialize: function( $scope, grid ){
1324       grid.gridMenuScope = $scope;
1325       $scope.grid = grid;
1326       $scope.registeredMenuItems = [];
1327       
1328       // not certain this is needed, but would be bad to create a memory leak
1329       $scope.$on('$destroy', function() {
1330         if ( $scope.grid && $scope.grid.gridMenuScope ){
1331           $scope.grid.gridMenuScope = null;
1332         }
1333         if ( $scope.grid ){
1334           $scope.grid = null;
1335         }
1336         if ( $scope.registeredMenuItems ){
1337           $scope.registeredMenuItems = null;
1338         }
1339       });
1340       
1341       $scope.registeredMenuItems = [];
1342
1343       /**
1344        * @ngdoc function
1345        * @name addToGridMenu
1346        * @methodOf ui.grid.core.api:PublicApi
1347        * @description add items to the grid menu.  Used by features
1348        * to add their menu items if they are enabled, can also be used by
1349        * end users to add menu items.  This method has the advantage of allowing
1350        * remove again, which can simplify management of which items are included
1351        * in the menu when.  (Noting that in most cases the shown and active functions
1352        * provide a better way to handle visibility of menu items)
1353        * @param {Grid} grid the grid on which we are acting
1354        * @param {array} items menu items in the format as described in the tutorial, with 
1355        * the added note that if you want to use remove you must also specify an `id` field,
1356        * which is provided when you want to remove an item.  The id should be unique.
1357        * 
1358        */
1359       grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1360   
1361       /**
1362        * @ngdoc function
1363        * @name removeFromGridMenu
1364        * @methodOf ui.grid.core.api:PublicApi
1365        * @description Remove an item from the grid menu based on a provided id. Assumes
1366        * that the id is unique, removes only the last instance of that id. Does nothing if
1367        * the specified id is not found
1368        * @param {Grid} grid the grid on which we are acting
1369        * @param {string} id the id we'd like to remove from the menu
1370        * 
1371        */
1372       grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1373     },
1374  
1375     
1376     /**
1377      * @ngdoc function
1378      * @name addToGridMenu
1379      * @propertyOf ui.grid.class:GridOptions
1380      * @description add items to the grid menu.  Used by features
1381      * to add their menu items if they are enabled, can also be used by
1382      * end users to add menu items.  This method has the advantage of allowing
1383      * remove again, which can simplify management of which items are included
1384      * in the menu when.  (Noting that in most cases the shown and active functions
1385      * provide a better way to handle visibility of menu items)
1386      * @param {Grid} grid the grid on which we are acting
1387      * @param {array} items menu items in the format as described in the tutorial, with 
1388      * the added note that if you want to use remove you must also specify an `id` field,
1389      * which is provided when you want to remove an item.  The id should be unique.
1390      * 
1391      */
1392     addToGridMenu: function( grid, menuItems ) {
1393       if ( !angular.isArray( menuItems ) ) {
1394         gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1395       } else {
1396         if ( grid.gridMenuScope ){
1397           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1398           grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1399         } else {
1400           gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
1401         }
1402       }  
1403     },
1404     
1405
1406     /**
1407      * @ngdoc function
1408      * @name removeFromGridMenu
1409      * @methodOf ui.grid.core.api:PublicApi
1410      * @description Remove an item from the grid menu based on a provided id.  Assumes
1411      * that the id is unique, removes only the last instance of that id.  Does nothing if
1412      * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
1413      * then do nothing silently - the desired result is those menu items not be present and they
1414      * aren't.
1415      * @param {Grid} grid the grid on which we are acting
1416      * @param {string} id the id we'd like to remove from the menu
1417      * 
1418      */    
1419     removeFromGridMenu: function( grid, id ){
1420       var foundIndex = -1;
1421       
1422       if ( grid && grid.gridMenuScope ){
1423         grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1424           if ( value.id === id ){
1425             if (foundIndex > -1) {
1426               gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1427             } else {
1428               
1429               foundIndex = index;
1430             }
1431           }
1432         });
1433       }
1434
1435       if ( foundIndex > -1 ){
1436         grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1437       }
1438     },
1439     
1440         
1441     /**
1442      * @ngdoc array
1443      * @name gridMenuCustomItems
1444      * @propertyOf ui.grid.class:GridOptions
1445      * @description (optional) An array of menu items that should be added to
1446      * the gridMenu.  Follow the format documented in the tutorial for column
1447      * menu customisation.  The context provided to the action function will 
1448      * include context.grid.  An alternative if working with dynamic menus is to use the 
1449      * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1450      * some of the management of items for you.
1451      * 
1452      */
1453     /**
1454      * @ngdoc boolean
1455      * @name gridMenuShowHideColumns
1456      * @propertyOf ui.grid.class:GridOptions
1457      * @description true by default, whether the grid menu should allow hide/show
1458      * of columns
1459      * 
1460      */
1461     /**
1462      * @ngdoc method
1463      * @methodOf ui.grid.gridMenuService
1464      * @name getMenuItems
1465      * @description Decides the menu items to show in the menu.  This is a
1466      * combination of:
1467      * 
1468      * - the default menu items that are always included, 
1469      * - any menu items that have been provided through the addMenuItem api. These
1470      *   are typically added by features within the grid
1471      * - any menu items included in grid.options.gridMenuCustomItems.  These can be
1472      *   changed dynamically, as they're always recalculated whenever we show the
1473      *   menu
1474      * @param {$scope} $scope the scope of this gridMenu, from which we can find all 
1475      * the information that we need
1476      * @returns {array} an array of menu items that can be shown 
1477      */
1478     getMenuItems: function( $scope ) {
1479       var menuItems = [
1480         // this is where we add any menu items we want to always include
1481       ];
1482       
1483       if ( $scope.grid.options.gridMenuCustomItems ){
1484         if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){ 
1485           gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not'); 
1486         } else {
1487           menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1488         }
1489       }
1490   
1491       menuItems = menuItems.concat( $scope.registeredMenuItems );
1492       
1493       if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1494         menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1495       }
1496       
1497       return menuItems;
1498     },
1499     
1500     
1501     /**
1502      * @ngdoc array
1503      * @name gridMenuTitleFilter
1504      * @propertyOf ui.grid.class:GridOptions
1505      * @description (optional) A function that takes a title string 
1506      * (usually the col.displayName), and converts it into a display value.  The function
1507      * must return either a string or a promise.
1508      * 
1509      * Used for internationalization of the grid menu column names - for angular-translate
1510      * you can pass $translate as the function, for i18nService you can pass getSafeText as the 
1511      * function
1512      * @example
1513      * <pre>
1514      *   gridOptions = {
1515      *     gridMenuTitleFilter: $translate
1516      *   }
1517      * </pre>
1518      */
1519     /**
1520      * @ngdoc method
1521      * @methodOf ui.grid.gridMenuService
1522      * @name showHideColumns
1523      * @description Adds two menu items for each of the columns in columnDefs.  One
1524      * menu item for hide, one menu item for show.  Each is visible when appropriate
1525      * (show when column is not visible, hide when column is visible).  Each toggles
1526      * the visible property on the columnDef using toggleColumnVisibility
1527      * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1528      */
1529     showHideColumns: function( $scope ){
1530       var showHideColumns = [];
1531       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1532         return showHideColumns;
1533       }
1534       
1535       // add header for columns
1536       showHideColumns.push({
1537         title: i18nService.getSafeText('gridMenu.columns')
1538       });
1539       
1540       $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };  
1541       
1542       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1543         if ( colDef.enableHiding !== false ){
1544           // add hide menu item - shows an OK icon as we only show when column is already visible
1545           var menuItem = {
1546             icon: 'ui-grid-icon-ok',
1547             action: function($event) {
1548               $event.stopPropagation();
1549               service.toggleColumnVisibility( this.context.gridCol );
1550             },
1551             shown: function() {
1552               return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1553             },
1554             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) }
1555           };
1556           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1557           showHideColumns.push( menuItem );
1558
1559           // add show menu item - shows no icon as we only show when column is invisible
1560           menuItem = {
1561             icon: 'ui-grid-icon-cancel',
1562             action: function($event) {
1563               $event.stopPropagation();
1564               service.toggleColumnVisibility( this.context.gridCol );
1565             },
1566             shown: function() {
1567               return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1568             },
1569             context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) }
1570           };
1571           service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1572           showHideColumns.push( menuItem );
1573         }
1574       });
1575       return showHideColumns;
1576     },
1577     
1578     
1579     /**
1580      * @ngdoc method
1581      * @methodOf ui.grid.gridMenuService
1582      * @name setMenuItemTitle
1583      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1584      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1585      * putting the result into the title 
1586      * @param {object} menuItem the menuItem we want to put the title on
1587      * @param {object} colDef the colDef from which we can get displayName, name or field
1588      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1589      * 
1590      */
1591     setMenuItemTitle: function( menuItem, colDef, grid ){
1592       var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field );
1593       
1594       if ( typeof(title) === 'string' ){
1595         menuItem.title = title;
1596       } else if ( title.then ){
1597         // must be a promise
1598         menuItem.title = "";
1599         title.then( function( successValue ) {
1600           menuItem.title = successValue;
1601         }, function( errorValue ) {
1602           menuItem.title = errorValue;
1603         });
1604       } else {
1605         gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
1606         menuItem.title = 'badconfig';
1607       }
1608     },
1609
1610     /**
1611      * @ngdoc method
1612      * @methodOf ui.grid.gridMenuService
1613      * @name toggleColumnVisibility
1614      * @description Toggles the visibility of an individual column.  Expects to be
1615      * provided a context that has on it a gridColumn, which is the column that
1616      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
1617      * @param {GridCol} gridCol the column that we want to toggle
1618      * 
1619      */
1620     toggleColumnVisibility: function( gridCol ) {
1621       gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); 
1622       
1623       gridCol.grid.refresh();
1624     }
1625   };
1626   
1627   return service;
1628 }])
1629
1630
1631
1632 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 
1633 function (gridUtil, uiGridConstants, uiGridGridMenuService) {
1634
1635   return {
1636     priority: 0,
1637     scope: true,
1638     require: ['?^uiGrid'],
1639     templateUrl: 'ui-grid/ui-grid-menu-button',
1640     replace: true,
1641
1642
1643     link: function ($scope, $elm, $attrs, controllers) {
1644       var uiGridCtrl = controllers[0];
1645
1646       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
1647       
1648       $scope.shown = false;
1649
1650       $scope.toggleMenu = function () {
1651         if ( $scope.shown ){
1652           $scope.$broadcast('hide-menu');
1653           $scope.shown = false;
1654         } else {
1655           $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
1656           $scope.$broadcast('show-menu');
1657           $scope.shown = true;
1658         }
1659       };
1660       
1661       $scope.$on('menu-hidden', function() {
1662         $scope.shown = false;
1663       });
1664     }
1665   };
1666
1667 }]);
1668
1669 })();
1670 (function(){
1671
1672 /**
1673  * @ngdoc directive
1674  * @name ui.grid.directive:uiGridColumnMenu
1675  * @element style
1676  * @restrict A
1677  *
1678  * @description
1679  * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1680  *
1681  * @example
1682  <doc:example module="app">
1683  <doc:source>
1684  <script>
1685  var app = angular.module('app', ['ui.grid']);
1686
1687  app.controller('MainCtrl', ['$scope', function ($scope) {
1688    
1689  }]);
1690  </script>
1691
1692  <div ng-controller="MainCtrl">
1693    <div ui-grid-menu shown="true"  ></div>
1694  </div>
1695  </doc:source>
1696  <doc:scenario>
1697  </doc:scenario>
1698  </doc:example>
1699  */
1700 angular.module('ui.grid')
1701
1702 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 
1703 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
1704   var uiGridMenu = {
1705     priority: 0,
1706     scope: {
1707       // shown: '&',
1708       menuItems: '=',
1709       autoHide: '=?'
1710     },
1711     require: '?^uiGrid',
1712     templateUrl: 'ui-grid/uiGridMenu',
1713     replace: false,
1714     link: function ($scope, $elm, $attrs, uiGridCtrl) {
1715       var self = this;
1716       var menuMid;
1717       var $animate;
1718      
1719     // *** Show/Hide functions ******
1720       self.showMenu = $scope.showMenu = function(event, args) {
1721         if ( !$scope.shown ){
1722
1723           /*
1724            * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
1725            * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
1726            * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
1727            * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
1728            * on scroll events.
1729            * 
1730            * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
1731            * like they do, but angular has a default $animate provider that is just a stub, and that's what's
1732            * being called.  ALso don't be fooled by the fact that your browser has actually loaded the 
1733            * angular-translate.js, it's not using it.  You need to test animations in an external application. 
1734            */
1735           $scope.shown = true;
1736
1737           $timeout( function() {
1738             $scope.shownMid = true;
1739             $scope.$emit('menu-shown');
1740           });
1741         } else if ( !$scope.shownMid ) {
1742           // we're probably doing a hide then show, so we don't need to wait for ng-if
1743           $scope.shownMid = true;
1744           $scope.$emit('menu-shown');
1745         }
1746
1747         var docEventType = 'click';
1748         if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
1749           docEventType = args.originalEvent.type;
1750         }
1751
1752         // Turn off an existing document click handler
1753         angular.element(document).off('click touchstart', applyHideMenu);
1754
1755         // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
1756         $timeout(function() {
1757           angular.element(document).on(docEventType, applyHideMenu);
1758         });
1759       };
1760
1761
1762       self.hideMenu = $scope.hideMenu = function(event, args) {
1763         if ( $scope.shown ){
1764           /*
1765            * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
1766            * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
1767            * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
1768            *   
1769            * The user may have clicked on the menu again whilst
1770            * we're waiting, so we check that the mid isn't shown before applying the ng-if.
1771            */
1772           $scope.shownMid = false;
1773           $timeout( function() {
1774             if ( !$scope.shownMid ){
1775               $scope.shown = false;
1776               $scope.$emit('menu-hidden');
1777             }
1778           }, 200);
1779         }
1780
1781         angular.element(document).off('click touchstart', applyHideMenu);
1782       };
1783
1784       $scope.$on('hide-menu', function (event, args) {
1785         $scope.hideMenu(event, args);
1786       });
1787
1788       $scope.$on('show-menu', function (event, args) {
1789         $scope.showMenu(event, args);
1790       });
1791
1792       
1793     // *** Auto hide when click elsewhere ******
1794       var applyHideMenu = function(){
1795         if ($scope.shown) {
1796           $scope.$apply(function () {
1797             $scope.hideMenu();
1798           });
1799         }
1800       };
1801     
1802       if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
1803         $scope.autoHide = true;
1804       }
1805
1806       if ($scope.autoHide) {
1807         angular.element($window).on('resize', applyHideMenu);
1808       }
1809
1810       $scope.$on('$destroy', function () {
1811         angular.element(document).off('click touchstart', applyHideMenu);
1812       });
1813       
1814
1815       $scope.$on('$destroy', function() {
1816         angular.element($window).off('resize', applyHideMenu);
1817       });
1818
1819       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.GRID_SCROLL, applyHideMenu ));
1820
1821       $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
1822     },
1823     
1824     
1825     controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
1826       var self = this;
1827     }]
1828   };
1829
1830   return uiGridMenu;
1831 }])
1832
1833 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
1834   var uiGridMenuItem = {
1835     priority: 0,
1836     scope: {
1837       title: '=',
1838       active: '=',
1839       action: '=',
1840       icon: '=',
1841       shown: '=',
1842       context: '=',
1843       templateUrl: '='
1844     },
1845     require: ['?^uiGrid', '^uiGridMenu'],
1846     templateUrl: 'ui-grid/uiGridMenuItem',
1847     replace: true,
1848     compile: function($elm, $attrs) {
1849       return {
1850         pre: function ($scope, $elm, $attrs, controllers) {
1851           var uiGridCtrl = controllers[0],
1852               uiGridMenuCtrl = controllers[1];
1853           
1854           if ($scope.templateUrl) {
1855             gridUtil.getTemplate($scope.templateUrl)
1856                 .then(function (contents) {
1857                   var template = angular.element(contents);
1858                     
1859                   var newElm = $compile(template)($scope);
1860                   $elm.replaceWith(newElm);
1861                 });
1862           }
1863         },
1864         post: function ($scope, $elm, $attrs, controllers) {
1865           var uiGridCtrl = controllers[0],
1866               uiGridMenuCtrl = controllers[1];
1867
1868           // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
1869           // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
1870           //   throw new TypeError("$scope.shown is defined but not a function");
1871           // }
1872           if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
1873             $scope.shown = function() { return true; };
1874           }
1875
1876           $scope.itemShown = function () {
1877             var context = {};
1878             if ($scope.context) {
1879               context.context = $scope.context;
1880             }
1881
1882             if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1883               context.grid = uiGridCtrl.grid;
1884             }
1885
1886             return $scope.shown.call(context);
1887           };
1888
1889           $scope.itemAction = function($event,title) {
1890             // gridUtil.logDebug('itemAction');
1891             $event.stopPropagation();
1892
1893             if (typeof($scope.action) === 'function') {
1894               var context = {};
1895
1896               if ($scope.context) {
1897                 context.context = $scope.context;
1898               }
1899
1900               // Add the grid to the function call context if the uiGrid controller is present
1901               if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1902                 context.grid = uiGridCtrl.grid;
1903               }
1904
1905               $scope.action.call(context, $event, title);
1906
1907               $scope.$emit('hide-menu');
1908             }
1909           };
1910
1911           $scope.i18n = i18nService.get();
1912         }
1913       };
1914     }
1915   };
1916
1917   return uiGridMenuItem;
1918 }]);
1919
1920 })();
1921 (function () {
1922 // 'use strict';
1923
1924   angular.module('ui.grid').directive('uiGridNativeScrollbar', ['$timeout', '$document', 'uiGridConstants', 'gridUtil',
1925     function ($timeout, $document, uiGridConstants, gridUtil) {
1926     var scrollBarWidth = gridUtil.getScrollbarWidth();
1927
1928     // scrollBarWidth = scrollBarWidth > 0 ? scrollBarWidth : 17;
1929     if (!angular.isNumber(scrollBarWidth)) {
1930       scrollBarWidth = 0;
1931     }
1932
1933     // If the browser is IE, add 1px to the scrollbar container, otherwise scroll events won't work right (in IE11 at least)
1934     var browser = gridUtil.detectBrowser();
1935     if (browser === 'ie') {
1936       scrollBarWidth = scrollBarWidth + 1;
1937     }
1938
1939     return {
1940       scope: {
1941         type: '@'
1942       },
1943       require: ['^uiGrid', '^uiGridRenderContainer'],
1944       link: function ($scope, $elm, $attrs, controllers) {
1945         var uiGridCtrl = controllers[0];
1946         var containerCtrl = controllers[1];
1947         var rowContainer = containerCtrl.rowContainer;
1948         var colContainer = containerCtrl.colContainer;
1949         var grid = uiGridCtrl.grid;
1950
1951         var contents = angular.element('<div class="contents">&nbsp;</div>');
1952
1953         $elm.addClass('ui-grid-native-scrollbar');
1954
1955         var previousScrollPosition;
1956
1957         var elmMaxScroll = 0;
1958
1959         if ($scope.type === 'vertical') {
1960           // Update the width based on native scrollbar width
1961           $elm.css('width', scrollBarWidth + 'px');
1962
1963           $elm.addClass('vertical');
1964
1965           grid.verticalScrollbarWidth = grid.options.enableVerticalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? 0 : scrollBarWidth;
1966           colContainer.verticalScrollbarWidth = grid.verticalScrollbarWidth;
1967
1968           // Save the initial scroll position for use in scroll events
1969           previousScrollPosition = $elm[0].scrollTop;
1970         }
1971         else if ($scope.type === 'horizontal') {
1972           // Update the height based on native scrollbar height
1973           $elm.css('height', scrollBarWidth + 'px');
1974
1975           $elm.addClass('horizontal');
1976
1977           // Save this scrollbar's dimension in the grid properties
1978           grid.horizontalScrollbarHeight = grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? 0 : scrollBarWidth;
1979           rowContainer.horizontalScrollbarHeight = grid.horizontalScrollbarHeight;
1980
1981           // Save the initial scroll position for use in scroll events
1982           previousScrollPosition = gridUtil.normalizeScrollLeft($elm);
1983         }
1984
1985         // Save the contents elm inside the scrollbar elm so it sizes correctly
1986         $elm.append(contents);
1987
1988         // Get the relevant element dimension now that the contents are in it
1989         if ($scope.type === 'vertical') {
1990           elmMaxScroll = gridUtil.elementHeight($elm);
1991         }
1992         else if ($scope.type === 'horizontal') {
1993           elmMaxScroll = gridUtil.elementWidth($elm);
1994         }
1995
1996         function updateNativeVerticalScrollbar() {
1997           // Get the height that the scrollbar should have
1998           var height = rowContainer.getViewportHeight();
1999
2000           // Update the vertical scrollbar's content height so it's the same as the canvas
2001           var contentHeight = rowContainer.getCanvasHeight();
2002
2003           // TODO(c0bra): set scrollbar `top` by height of header row
2004           // var headerHeight = gridUtil.outerElementHeight(containerCtrl.header);
2005           var headerHeight = colContainer.headerHeight ? colContainer.headerHeight : grid.headerHeight;
2006
2007           // gridUtil.logDebug('headerHeight in scrollbar', headerHeight);
2008
2009           var ondemand  = grid.options.enableVerticalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? "overflow-y:auto;" : "";
2010           // var ret = '.grid' + uiGridCtrl.grid.id + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + h + 'px; }';
2011           var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + contentHeight + 'px; }';
2012           ret += '\n .grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical { height: ' + height + 'px; top: ' + headerHeight + 'px;' +ondemand +'}';
2013
2014           elmMaxScroll = contentHeight;
2015
2016           return ret;
2017         }
2018
2019         // Get the grid's bottom border height (TODO(c0bra): need to account for footer here!)
2020         var gridElm = gridUtil.closestElm($elm, '.ui-grid');
2021         var gridBottomBorder = gridUtil.getBorderSize(gridElm, 'bottom');
2022
2023         function updateNativeHorizontalScrollbar() {
2024           var w = colContainer.getCanvasWidth();
2025
2026           var bottom = gridBottomBorder;
2027           if (grid.options.showFooter) {
2028             bottom -= 1;
2029           }
2030           
2031           var adjustment = colContainer.getViewportAdjustment();
2032           bottom -= adjustment.height;
2033           
2034           var ondemand = grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? "overflow-x:auto" : "";
2035           var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal { bottom: ' + bottom + 'px;' +ondemand + ' }';
2036           ret += '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal .contents { width: ' + w + 'px; }';
2037
2038           elmMaxScroll = w;
2039
2040           return ret;
2041         }
2042
2043         // NOTE: priority 6 so they run after the column widths update, which in turn update the canvas width
2044         if ($scope.type === 'vertical') {
2045           grid.registerStyleComputation({
2046             priority: 6,
2047             func: updateNativeVerticalScrollbar
2048           });
2049         }
2050         else if ($scope.type === 'horizontal') {
2051           grid.registerStyleComputation({
2052             priority: 6,
2053             func: updateNativeHorizontalScrollbar
2054           });
2055         }
2056
2057
2058         $scope.scrollSource = null;
2059
2060         function scrollEvent(evt) {
2061           if ($scope.type === 'vertical') {
2062             grid.flagScrollingVertically();
2063             var newScrollTop = $elm[0].scrollTop;
2064
2065             var yDiff = previousScrollPosition - newScrollTop;
2066
2067             var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2068
2069             // Subtract the h. scrollbar height from the vertical length if it's present
2070             if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
2071               vertScrollLength = vertScrollLength - uiGridCtrl.grid.horizontalScrollbarHeight;
2072             }
2073
2074             var vertScrollPercentage = newScrollTop / vertScrollLength;
2075
2076             if (vertScrollPercentage > 1) {
2077               vertScrollPercentage = 1;
2078             }
2079             if (vertScrollPercentage < 0) {
2080               vertScrollPercentage = 0;
2081             }
2082
2083             var yArgs = {
2084               target: $elm,
2085               y: {
2086                 percentage: vertScrollPercentage
2087               }
2088             };
2089
2090             // If the source of this scroll is defined (i.e., not us, then don't fire the scroll event because we'll be re-triggering)
2091             if (!$scope.scrollSource) {
2092               uiGridCtrl.fireScrollingEvent(yArgs);
2093             }
2094             else {
2095               // Reset the scroll source for the next scroll event
2096               $scope.scrollSource = null;
2097             }
2098
2099             previousScrollPosition = newScrollTop;
2100           }
2101           else if ($scope.type === 'horizontal') {
2102             grid.flagScrollingHorizontally();
2103             // var newScrollLeft = $elm[0].scrollLeft;
2104             var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
2105
2106             var xDiff = previousScrollPosition - newScrollLeft;
2107
2108             var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2109             var horizScrollPercentage = newScrollLeft / horizScrollLength;
2110
2111             var xArgs = {
2112               target: $elm,
2113               x: {
2114                 percentage: horizScrollPercentage
2115               }
2116             };
2117
2118             // If the source of this scroll is defined (i.e., not us, then don't fire the scroll event because we'll be re-triggering)
2119             if (!$scope.scrollSource) {
2120               uiGridCtrl.fireScrollingEvent(xArgs);
2121             }
2122             else {
2123               // Reset the scroll source for the next scroll event
2124               $scope.scrollSource = null;
2125             }
2126
2127             previousScrollPosition = newScrollLeft;
2128           }
2129         }
2130
2131         $elm.on('scroll', scrollEvent);
2132
2133         $elm.on('$destroy', function () {
2134           $elm.off('scroll');
2135         });
2136
2137         function gridScroll(evt, args) {
2138           // Don't listen to our own scroll event!
2139           if (args.target && (args.target === $elm || angular.element(args.target).hasClass('ui-grid-native-scrollbar'))) {
2140             return;
2141           }
2142
2143           // Set the source of the scroll event in our scope so it's available in our 'scroll' event handler
2144           $scope.scrollSource = args.target;
2145
2146           if ($scope.type === 'vertical') {
2147             if (args.y && typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
2148               grid.flagScrollingVertically();
2149               var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2150
2151               var newScrollTop = Math.max(0, args.y.percentage * vertScrollLength);
2152
2153               $elm[0].scrollTop = newScrollTop;
2154
2155
2156             }
2157           }
2158           else if ($scope.type === 'horizontal') {
2159             if (args.x && typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
2160               grid.flagScrollingHorizontally();
2161               var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2162
2163               var newScrollLeft = Math.max(0, args.x.percentage * horizScrollLength);
2164
2165               // $elm[0].scrollLeft = newScrollLeft;
2166               $elm[0].scrollLeft = gridUtil.denormalizeScrollLeft($elm, newScrollLeft);
2167             }
2168           }
2169         }
2170
2171         var gridScrollDereg = $scope.$on(uiGridConstants.events.GRID_SCROLL, gridScroll);
2172         $scope.$on('$destroy', gridScrollDereg);
2173
2174
2175
2176       }
2177     };
2178   }]);
2179 })();
2180 (function () {
2181   'use strict';
2182
2183   var module = angular.module('ui.grid');
2184   
2185   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil',
2186     function($timeout, $document, uiGridConstants, GridUtil) {
2187     return {
2188       replace: true,
2189       transclude: true,
2190       templateUrl: 'ui-grid/uiGridRenderContainer',
2191       require: ['^uiGrid', 'uiGridRenderContainer'],
2192       scope: {
2193         containerId: '=',
2194         rowContainerName: '=',
2195         colContainerName: '=',
2196         bindScrollHorizontal: '=',
2197         bindScrollVertical: '=',
2198         enableVerticalScrollbar: '=',
2199         enableHorizontalScrollbar: '='
2200       },
2201       controller: 'uiGridRenderContainer as RenderContainer',
2202       compile: function () {
2203         return {
2204           pre: function prelink($scope, $elm, $attrs, controllers) {
2205             // gridUtil.logDebug('render container ' + $scope.containerId + ' pre-link');
2206
2207             var uiGridCtrl = controllers[0];
2208             var containerCtrl = controllers[1];
2209
2210             var grid = $scope.grid = uiGridCtrl.grid;
2211
2212             // Verify that the render container for this element exists
2213             if (!$scope.rowContainerName) {
2214               throw "No row render container name specified";
2215             }
2216             if (!$scope.colContainerName) {
2217               throw "No column render container name specified";
2218             }
2219
2220             if (!grid.renderContainers[$scope.rowContainerName]) {
2221               throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2222             }
2223             if (!grid.renderContainers[$scope.colContainerName]) {
2224               throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2225             }
2226
2227             var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2228             var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2229             
2230             containerCtrl.containerId = $scope.containerId;
2231             containerCtrl.rowContainer = rowContainer;
2232             containerCtrl.colContainer = colContainer;
2233           },
2234           post: function postlink($scope, $elm, $attrs, controllers) {
2235             // gridUtil.logDebug('render container ' + $scope.containerId + ' post-link');
2236
2237             var uiGridCtrl = controllers[0];
2238             var containerCtrl = controllers[1];
2239
2240             var grid = uiGridCtrl.grid;
2241             var rowContainer = containerCtrl.rowContainer;
2242             var colContainer = containerCtrl.colContainer;
2243
2244             var renderContainer = grid.renderContainers[$scope.containerId];
2245
2246             // Put the container name on this element as a class
2247             $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2248
2249             // Bind to left/right-scroll events
2250             var scrollUnbinder;
2251             if ($scope.bindScrollHorizontal || $scope.bindScrollVertical) {
2252               scrollUnbinder = $scope.$on(uiGridConstants.events.GRID_SCROLL, scrollHandler);
2253             }
2254
2255             function scrollHandler (evt, args) {
2256               // Vertical scroll
2257               if (args.y && $scope.bindScrollVertical) {
2258                 containerCtrl.prevScrollArgs = args;
2259
2260                 var scrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2261
2262                 // Add the height of the native horizontal scrollbar, if it's there. Otherwise it will mask over the final row
2263                 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
2264                   scrollLength = scrollLength + grid.horizontalScrollbarHeight;
2265                 }
2266
2267                 var oldScrollTop = containerCtrl.viewport[0].scrollTop;
2268                 
2269                 var scrollYPercentage;
2270                 if (typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
2271                   scrollYPercentage = args.y.percentage;
2272                 }
2273                 else if (typeof(args.y.pixels) !== 'undefined' && args.y.pixels !== undefined) {
2274                   scrollYPercentage = args.y.percentage = (oldScrollTop + args.y.pixels) / scrollLength;
2275                   // gridUtil.logDebug('y.percentage', args.y.percentage);
2276                 }
2277                 else {
2278                   throw new Error("No percentage or pixel value provided for scroll event Y axis");
2279                 }
2280
2281                 var newScrollTop = Math.max(0, scrollYPercentage * scrollLength);
2282
2283                 containerCtrl.viewport[0].scrollTop = newScrollTop;
2284                 
2285                 // TOOD(c0bra): what's this for?
2286                 // grid.options.offsetTop = newScrollTop;
2287
2288                 containerCtrl.prevScrollArgs.y.pixels = newScrollTop - oldScrollTop;
2289               }
2290
2291               // Horizontal scroll
2292               if (args.x && $scope.bindScrollHorizontal) {
2293                 containerCtrl.prevScrollArgs = args;
2294
2295                 var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2296
2297                 // var oldScrollLeft = containerCtrl.viewport[0].scrollLeft;
2298                 var oldScrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
2299
2300                 var scrollXPercentage;
2301                 if (typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
2302                   scrollXPercentage = args.x.percentage;
2303                 }
2304                 else if (typeof(args.x.pixels) !== 'undefined' && args.x.pixels !== undefined) {
2305                   scrollXPercentage = args.x.percentage = (oldScrollLeft + args.x.pixels) / scrollWidth;
2306                 }
2307                 else {
2308                   throw new Error("No percentage or pixel value provided for scroll event X axis");
2309                 }
2310
2311                 var newScrollLeft = Math.max(0, scrollXPercentage * scrollWidth);
2312                 
2313                 // uiGridCtrl.adjustScrollHorizontal(newScrollLeft, scrollXPercentage);
2314
2315                 // containerCtrl.viewport[0].scrollLeft = newScrollLeft;
2316                 containerCtrl.viewport[0].scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.viewport, newScrollLeft);
2317
2318                 containerCtrl.prevScrollLeft = newScrollLeft;
2319
2320                 if (containerCtrl.headerViewport) {
2321                   // containerCtrl.headerViewport.scrollLeft = newScrollLeft;
2322                   containerCtrl.headerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.headerViewport, newScrollLeft);
2323                 }
2324
2325                 if (containerCtrl.footerViewport) {
2326                   // containerCtrl.footerViewport.scrollLeft = newScrollLeft;
2327                   containerCtrl.footerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.footerViewport, newScrollLeft);
2328                 }
2329
2330                 // uiGridCtrl.grid.options.offsetLeft = newScrollLeft;
2331
2332                 containerCtrl.prevScrollArgs.x.pixels = newScrollLeft - oldScrollLeft;
2333               }
2334             }
2335
2336             // Scroll the render container viewport when the mousewheel is used
2337             $elm.bind('wheel mousewheel DomMouseScroll MozMousePixelScroll', function(evt) {
2338               // use wheelDeltaY
2339               evt.preventDefault();
2340
2341               var newEvent = GridUtil.normalizeWheelEvent(evt);
2342
2343               var args = { target: $elm };
2344               if (newEvent.deltaY !== 0) {
2345                 var scrollYAmount = newEvent.deltaY * -120;
2346
2347                 // Get the scroll percentage
2348                 var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYAmount) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2349
2350                 // Keep scrollPercentage within the range 0-1.
2351                 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2352                 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2353
2354                 args.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2355               }
2356               if (newEvent.deltaX !== 0) {
2357                 var scrollXAmount = newEvent.deltaX * -120;
2358
2359                 // Get the scroll percentage
2360                 var scrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
2361                 var scrollXPercentage = (scrollLeft + scrollXAmount) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2362
2363                 // Keep scrollPercentage within the range 0-1.
2364                 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2365                 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2366
2367                 args.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2368               }
2369               
2370               uiGridCtrl.fireScrollingEvent(args);
2371             });
2372             
2373
2374             var startY = 0,
2375             startX = 0,
2376             scrollTopStart = 0,
2377             scrollLeftStart = 0,
2378             directionY = 1,
2379             directionX = 1,
2380             moveStart;
2381
2382             function touchmove(event) {
2383               if (event.originalEvent) {
2384                 event = event.originalEvent;
2385               }
2386
2387               event.preventDefault();
2388
2389               var deltaX, deltaY, newX, newY;
2390               newX = event.targetTouches[0].screenX;
2391               newY = event.targetTouches[0].screenY;
2392               deltaX = -(newX - startX);
2393               deltaY = -(newY - startY);
2394
2395               directionY = (deltaY < 1) ? -1 : 1;
2396               directionX = (deltaX < 1) ? -1 : 1;
2397
2398               deltaY *= 2;
2399               deltaX *= 2;
2400
2401               var args = { target: event.target };
2402
2403               if (deltaY !== 0) {
2404                 var scrollYPercentage = (scrollTopStart + deltaY) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2405
2406                 if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2407                 else if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2408
2409                 args.y = { percentage: scrollYPercentage, pixels: deltaY };
2410               }
2411               if (deltaX !== 0) {
2412                 var scrollXPercentage = (scrollLeftStart + deltaX) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2413
2414                 if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2415                 else if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2416
2417                 args.x = { percentage: scrollXPercentage, pixels: deltaX };
2418               }
2419
2420               uiGridCtrl.fireScrollingEvent(args);
2421             }
2422             
2423             function touchend(event) {
2424               if (event.originalEvent) {
2425                 event = event.originalEvent;
2426               }
2427
2428               event.preventDefault();
2429
2430               $document.unbind('touchmove', touchmove);
2431               $document.unbind('touchend', touchend);
2432               $document.unbind('touchcancel', touchend);
2433
2434               // Get the distance we moved on the Y axis
2435               var scrollTopEnd = containerCtrl.viewport[0].scrollTop;
2436               var scrollLeftEnd = containerCtrl.viewport[0].scrollTop;
2437               var deltaY = Math.abs(scrollTopEnd - scrollTopStart);
2438               var deltaX = Math.abs(scrollLeftEnd - scrollLeftStart);
2439
2440               // Get the duration it took to move this far
2441               var moveDuration = (new Date()) - moveStart;
2442
2443               // Scale the amount moved by the time it took to move it (i.e. quicker, longer moves == more scrolling after the move is over)
2444               var moveYScale = deltaY / moveDuration;
2445               var moveXScale = deltaX / moveDuration;
2446
2447               var decelerateInterval = 63; // 1/16th second
2448               var decelerateCount = 8; // == 1/2 second
2449               var scrollYLength = 120 * directionY * moveYScale;
2450               var scrollXLength = 120 * directionX * moveXScale;
2451
2452               function decelerate() {
2453                 $timeout(function() {
2454                   var args = { target: event.target };
2455
2456                   if (scrollYLength !== 0) {
2457                     var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYLength) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2458
2459                     args.y = { percentage: scrollYPercentage, pixels: scrollYLength };
2460                   }
2461
2462                   if (scrollXLength !== 0) {
2463                     var scrollXPercentage = (containerCtrl.viewport[0].scrollLeft + scrollXLength) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2464                     args.x = { percentage: scrollXPercentage, pixels: scrollXLength };
2465                   }
2466
2467                   uiGridCtrl.fireScrollingEvent(args);
2468
2469                   decelerateCount = decelerateCount -1;
2470                   scrollYLength = scrollYLength / 2;
2471                   scrollXLength = scrollXLength / 2;
2472
2473                   if (decelerateCount > 0) {
2474                     decelerate();
2475                   }
2476                   else {
2477                     uiGridCtrl.scrollbars.forEach(function (sbar) {
2478                       sbar.removeClass('ui-grid-scrollbar-visible');
2479                       sbar.removeClass('ui-grid-scrolling');
2480                     });
2481                   }
2482                 }, decelerateInterval);
2483               }
2484
2485               decelerate();
2486             }
2487
2488             if (GridUtil.isTouchEnabled()) {
2489               $elm.bind('touchstart', function (event) {
2490                 if (event.originalEvent) {
2491                   event = event.originalEvent;
2492                 }
2493
2494                 event.preventDefault();
2495
2496                 uiGridCtrl.scrollbars.forEach(function (sbar) {
2497                   sbar.addClass('ui-grid-scrollbar-visible');
2498                   sbar.addClass('ui-grid-scrolling');
2499                 });
2500
2501                 moveStart = new Date();
2502                 startY = event.targetTouches[0].screenY;
2503                 startX = event.targetTouches[0].screenX;
2504                 scrollTopStart = containerCtrl.viewport[0].scrollTop;
2505                 scrollLeftStart = containerCtrl.viewport[0].scrollLeft;
2506                 
2507                 $document.on('touchmove', touchmove);
2508                 $document.on('touchend touchcancel', touchend);
2509               });
2510             }
2511
2512             $elm.bind('$destroy', function() {
2513               scrollUnbinder();
2514               $elm.unbind('keydown');
2515
2516               ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2517                 $elm.unbind(eventName);
2518               });
2519             });
2520             
2521             // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2522             function update() {
2523               var ret = '';
2524
2525               var canvasWidth = colContainer.getCanvasWidth();
2526               var viewportWidth = colContainer.getViewportWidth();
2527
2528               var canvasHeight = rowContainer.getCanvasHeight();
2529               var viewportHeight = rowContainer.getViewportHeight();
2530
2531               var headerViewportWidth = colContainer.getHeaderViewportWidth();
2532               var footerViewportWidth = colContainer.getHeaderViewportWidth();
2533               
2534               // Set canvas dimensions
2535               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2536               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + canvasWidth + 'px; }';
2537               
2538               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2539               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2540
2541               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + canvasWidth + 'px; }';
2542               ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2543
2544               // If the render container has an "explicit" header height (such as in the case that its header is smaller than the other headers and needs to be explicitly set to be the same, ue thae)
2545               if (renderContainer.explicitHeaderHeight !== undefined && renderContainer.explicitHeaderHeight !== null && renderContainer.explicitHeaderHeight > 0) {
2546                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-cell { height: ' + renderContainer.explicitHeaderHeight + 'px; }';
2547               }
2548               // Otherwise if the render container has an INNER header height, use that on the header cells (so that all the header cells are the same height and those that have less elements don't have undersized borders)
2549               else if (renderContainer.innerHeaderHeight !== undefined && renderContainer.innerHeaderHeight !== null && renderContainer.innerHeaderHeight > 0) {
2550                 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-cell { height: ' + renderContainer.innerHeaderHeight + 'px; }';
2551               }
2552
2553               return ret;
2554             }
2555             
2556             uiGridCtrl.grid.registerStyleComputation({
2557               priority: 6,
2558               func: update
2559             });
2560           }
2561         };
2562       }
2563     };
2564
2565   }]);
2566
2567   module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2568     var self = this;
2569
2570     self.rowStyle = function (index) {
2571       var renderContainer = $scope.grid.renderContainers[$scope.containerId];
2572
2573       var styles = {};
2574       
2575       if (!renderContainer.disableRowOffset) {
2576         if (index === 0 && self.currentTopRow !== 0) {
2577           // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
2578           var hiddenRowWidth = ($scope.rowContainer.currentTopRow) *
2579             $scope.rowContainer.visibleRowCache[$scope.rowContainer.currentTopRow].height;
2580
2581           // return { 'margin-top': hiddenRowWidth + 'px' };
2582           styles['margin-top'] = hiddenRowWidth + 'px';
2583         }
2584       }
2585       
2586       if (!renderContainer.disableColumnOffset && $scope.colContainer.currentFirstColumn !== 0) {
2587         if ($scope.grid.isRTL()) {
2588           styles['margin-right'] = $scope.colContainer.columnOffset + 'px';
2589         }
2590         else {
2591           styles['margin-left'] = $scope.colContainer.columnOffset + 'px';
2592         }
2593       }
2594
2595       return styles;
2596     };
2597
2598     self.columnStyle = function (index) {
2599       var renderContainer = $scope.grid.renderContainers[$scope.containerId];
2600
2601       var self = this;
2602
2603       if (!renderContainer.disableColumnOffset) {
2604         if (index === 0 && $scope.colContainer.currentFirstColumn !== 0) {
2605           var offset = $scope.colContainer.columnOffset;
2606
2607           if ($scope.grid.isRTL()) {
2608             return { 'margin-right': offset + 'px' };
2609           }
2610           else {
2611             return { 'margin-left': offset + 'px' }; 
2612           }
2613         }
2614       }
2615
2616       return null;
2617     };
2618   }]);
2619
2620 })();
2621 (function(){
2622   'use strict';
2623
2624   angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2625     return {
2626       replace: true,
2627       // priority: 2001,
2628       // templateUrl: 'ui-grid/ui-grid-row',
2629       require: ['^uiGrid', '^uiGridRenderContainer'],
2630       scope: {
2631          row: '=uiGridRow',
2632          //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2633          rowRenderIndex: '='
2634       },
2635       compile: function() {
2636         return {
2637           pre: function($scope, $elm, $attrs, controllers) {
2638             var uiGridCtrl = controllers[0];
2639             var containerCtrl = controllers[1];
2640
2641             var grid = uiGridCtrl.grid;
2642
2643             $scope.grid = uiGridCtrl.grid;
2644             $scope.colContainer = containerCtrl.colContainer;
2645
2646             grid.getRowTemplateFn.then(function (templateFn) {
2647               templateFn($scope, function(clonedElement, scope) {
2648                 $elm.replaceWith(clonedElement);
2649               });
2650             });
2651           },
2652           post: function($scope, $elm, $attrs, controllers) {
2653             var uiGridCtrl = controllers[0];
2654             var containerCtrl = controllers[1];
2655
2656             //add optional reference to externalScopes function to scope
2657             //so it can be retrieved in lower elements
2658             $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
2659           }
2660         };
2661       }
2662     };
2663   }]);
2664
2665 })();
2666 (function(){
2667 // 'use strict';
2668
2669   /**
2670    * @ngdoc directive
2671    * @name ui.grid.directive:uiGridStyle
2672    * @element style
2673    * @restrict A
2674    *
2675    * @description
2676    * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2677    *
2678    * @example
2679    <doc:example module="app">
2680    <doc:source>
2681    <script>
2682    var app = angular.module('app', ['ui.grid']);
2683
2684    app.controller('MainCtrl', ['$scope', function ($scope) {
2685           $scope.myStyle = '.blah { border: 1px solid }';
2686         }]);
2687    </script>
2688
2689    <div ng-controller="MainCtrl">
2690    <style ui-grid-style>{{ myStyle }}</style>
2691    <span class="blah">I am in a box.</span>
2692    </div>
2693    </doc:source>
2694    <doc:scenario>
2695    it('should apply the right class to the element', function () {
2696         element(by.css('.blah')).getCssValue('border')
2697           .then(function(c) {
2698             expect(c).toContain('1px solid');
2699           });
2700       });
2701    </doc:scenario>
2702    </doc:example>
2703    */
2704
2705
2706   angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
2707     return {
2708       // restrict: 'A',
2709       // priority: 1000,
2710       // require: '?^uiGrid',
2711       link: function($scope, $elm, $attrs, uiGridCtrl) {
2712         // gridUtil.logDebug('ui-grid-style link');
2713         // if (uiGridCtrl === undefined) {
2714         //    gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2715         // }
2716
2717         var interpolateFn = $interpolate($elm.text(), true);
2718
2719         if (interpolateFn) {
2720           $scope.$watch(interpolateFn, function(value) {
2721             $elm.text(value);
2722           });
2723         }
2724
2725           // uiGridCtrl.recalcRowStyles = function() {
2726           //   var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2727           //   var rowHeight = scope.options.rowHeight;
2728
2729           //   var ret = '';
2730           //   var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2731           //   for (var i = 1; i <= rowStyleCount; i++) {
2732           //     ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2733           //     offset = offset + rowHeight;
2734           //   }
2735
2736           //   scope.rowStyles = ret;
2737           // };
2738
2739           // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2740
2741       }
2742     };
2743   }]);
2744
2745 })();
2746 (function(){
2747   'use strict';
2748
2749   angular.module('ui.grid').directive('uiGridViewport', ['gridUtil',
2750     function(gridUtil) {
2751       return {
2752         replace: true,
2753         scope: {},
2754         templateUrl: 'ui-grid/uiGridViewport',
2755         require: ['^uiGrid', '^uiGridRenderContainer'],
2756         link: function($scope, $elm, $attrs, controllers) {
2757           // gridUtil.logDebug('viewport post-link');
2758
2759           var uiGridCtrl = controllers[0];
2760           var containerCtrl = controllers[1];
2761
2762           $scope.containerCtrl = containerCtrl;
2763
2764           var rowContainer = containerCtrl.rowContainer;
2765           var colContainer = containerCtrl.colContainer;
2766
2767           var grid = uiGridCtrl.grid;
2768
2769           $scope.grid = uiGridCtrl.grid;
2770
2771           // Put the containers in scope so we can get rows and columns from them
2772           $scope.rowContainer = containerCtrl.rowContainer;
2773           $scope.colContainer = containerCtrl.colContainer;
2774
2775           // Register this viewport with its container 
2776           containerCtrl.viewport = $elm;
2777
2778           $elm.on('scroll', function (evt) {
2779             var newScrollTop = $elm[0].scrollTop;
2780             // var newScrollLeft = $elm[0].scrollLeft;
2781             var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
2782             var horizScrollPercentage = -1;
2783             var vertScrollPercentage = -1;
2784
2785             // Handle RTL here
2786
2787             if (newScrollLeft !== colContainer.prevScrollLeft) {
2788               var xDiff = newScrollLeft - colContainer.prevScrollLeft;
2789
2790               var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2791               horizScrollPercentage = newScrollLeft / horizScrollLength;
2792
2793               colContainer.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
2794             }
2795
2796             if (newScrollTop !== rowContainer.prevScrollTop) {
2797               var yDiff = newScrollTop - rowContainer.prevScrollTop;
2798
2799               // uiGridCtrl.fireScrollingEvent({ y: { pixels: diff } });
2800               var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2801               // var vertScrollPercentage = (uiGridCtrl.prevScrollTop + yDiff) / vertScrollLength;
2802               vertScrollPercentage = newScrollTop / vertScrollLength;
2803
2804               if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
2805               if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
2806               
2807               rowContainer.adjustScrollVertical(newScrollTop, vertScrollPercentage);
2808             }
2809             
2810             if ( !$scope.grid.isScrollingVertically && !$scope.grid.isScrollingHorizontally ){
2811               // viewport scroll that didn't come from fireScrollEvent, so fire a scroll to keep 
2812               // the header in sync
2813               var args = {};
2814               if ( horizScrollPercentage > -1 ){
2815                 args.x = { percentage: horizScrollPercentage };
2816               }
2817
2818               if ( vertScrollPercentage > -1 ){
2819                 args.y = { percentage: vertScrollPercentage };
2820               }
2821               uiGridCtrl.fireScrollingEvent(args); 
2822             }
2823           });
2824         }
2825       };
2826     }
2827   ]);
2828
2829 })();
2830 (function() {
2831
2832 angular.module('ui.grid')
2833 .directive('uiGridVisible', function uiGridVisibleAction() {
2834   return function ($scope, $elm, $attr) {
2835     $scope.$watch($attr.uiGridVisible, function (visible) {
2836         // $elm.css('visibility', visible ? 'visible' : 'hidden');
2837         $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
2838     });
2839   };
2840 });
2841
2842 })();
2843 (function () {
2844   'use strict';
2845
2846   angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
2847                     '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
2848     function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
2849               $templateCache, gridClassFactory, $timeout, $parse, $compile) {
2850       // gridUtil.logDebug('ui-grid controller');
2851
2852       var self = this;
2853
2854       // Extend options with ui-grid attribute reference
2855       self.grid = gridClassFactory.createGrid($scope.uiGrid);
2856       $elm.addClass('grid' + self.grid.id);
2857       self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
2858
2859
2860       //add optional reference to externalScopes function to controller
2861       //so it can be retrieved in lower elements that have isolate scope
2862       self.getExternalScopes = $scope.getExternalScopes;
2863
2864       // angular.extend(self.grid.options, );
2865
2866       //all properties of grid are available on scope
2867       $scope.grid = self.grid;
2868
2869       if ($attrs.uiGridColumns) {
2870         $attrs.$observe('uiGridColumns', function(value) {
2871           self.grid.options.columnDefs = value;
2872           self.grid.buildColumns()
2873             .then(function(){
2874               self.grid.preCompileCellTemplates();
2875
2876               self.grid.refreshCanvas(true);
2877             });
2878         });
2879       }
2880
2881
2882       var dataWatchCollectionDereg;
2883       if (angular.isString($scope.uiGrid.data)) {
2884         dataWatchCollectionDereg = $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction);
2885       }
2886       else {
2887         dataWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction);
2888       }
2889
2890       var columnDefWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction);
2891
2892       function columnDefsWatchFunction(n, o) {
2893         if (n && n !== o) {
2894           self.grid.options.columnDefs = n;
2895           self.grid.buildColumns()
2896             .then(function(){
2897
2898               self.grid.preCompileCellTemplates();
2899
2900               self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
2901             });
2902         }
2903       }
2904
2905       function dataWatchFunction(newData) {
2906         // gridUtil.logDebug('dataWatch fired');
2907         var promises = [];
2908         
2909         if (newData) {
2910           if (
2911             // If we have no columns (i.e. columns length is either 0 or equal to the number of row header columns, which don't count because they're created automatically)
2912             self.grid.columns.length === (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0) &&
2913             // ... and we don't have a ui-grid-columns attribute, which would define columns for us
2914             !$attrs.uiGridColumns &&
2915             // ... and we have no pre-defined columns
2916             self.grid.options.columnDefs.length === 0 &&
2917             // ... but we DO have data
2918             newData.length > 0
2919           ) {
2920             // ... then build the column definitions from the data that we have
2921             self.grid.buildColumnDefsFromData(newData);
2922           }
2923
2924           // If we either have some columns defined, or some data defined
2925           if (self.grid.options.columnDefs.length > 0 || newData.length > 0) {
2926             // Build the column set, then pre-compile the column cell templates
2927             promises.push(self.grid.buildColumns()
2928               .then(function() {
2929                 self.grid.preCompileCellTemplates();
2930               }));
2931           }
2932
2933           $q.all(promises).then(function() {
2934             self.grid.modifyRows(newData)
2935               .then(function () {
2936                 // if (self.viewport) {
2937                   self.grid.redrawInPlace();
2938                 // }
2939
2940                 $scope.$evalAsync(function() {
2941                   self.grid.refreshCanvas(true);
2942                   self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
2943                 });
2944               });
2945           });
2946         }
2947       }
2948
2949
2950       $scope.$on('$destroy', function() {
2951         dataWatchCollectionDereg();
2952         columnDefWatchCollectionDereg();
2953       });
2954
2955       $scope.$watch(function () { return self.grid.styleComputations; }, function() {
2956         self.grid.refreshCanvas(true);
2957       });
2958
2959
2960       /* Event Methods */
2961
2962       self.fireScrollingEvent = gridUtil.throttle(function(args) {
2963         $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
2964       }, self.grid.options.scrollThrottle, {trailing: true});
2965
2966       self.fireEvent = function(eventName, args) {
2967         // Add the grid to the event arguments if it's not there
2968         if (typeof(args) === 'undefined' || args === undefined) {
2969           args = {};
2970         }
2971
2972         if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
2973           args.grid = self.grid;
2974         }
2975
2976         $scope.$broadcast(eventName, args);
2977       };
2978
2979       self.innerCompile = function innerCompile(elm) {
2980         $compile(elm)($scope);
2981       };
2982
2983     }]);
2984
2985 /**
2986  *  @ngdoc directive
2987  *  @name ui.grid.directive:uiGrid
2988  *  @element div
2989  *  @restrict EA
2990  *  @param {Object} uiGrid Options for the grid to use
2991  *  @param {Object=} external-scopes Add external-scopes='someScopeObjectYouNeed' attribute so you can access
2992  *            your scopes from within any custom templatedirective.  You access by $scope.getExternalScopes() function
2993  *
2994  *  @description Create a very basic grid.
2995  *
2996  *  @example
2997     <example module="app">
2998       <file name="app.js">
2999         var app = angular.module('app', ['ui.grid']);
3000
3001         app.controller('MainCtrl', ['$scope', function ($scope) {
3002           $scope.data = [
3003             { name: 'Bob', title: 'CEO' },
3004             { name: 'Frank', title: 'Lowly Developer' }
3005           ];
3006         }]);
3007       </file>
3008       <file name="index.html">
3009         <div ng-controller="MainCtrl">
3010           <div ui-grid="{ data: data }"></div>
3011         </div>
3012       </file>
3013     </example>
3014  */
3015 angular.module('ui.grid').directive('uiGrid',
3016   [
3017     '$compile',
3018     '$templateCache',
3019     'gridUtil',
3020     '$window',
3021     function(
3022       $compile,
3023       $templateCache,
3024       gridUtil,
3025       $window
3026       ) {
3027       return {
3028         templateUrl: 'ui-grid/ui-grid',
3029         scope: {
3030           uiGrid: '=',
3031           getExternalScopes: '&?externalScopes' //optional functionwrapper around any needed external scope instances
3032         },
3033         replace: true,
3034         transclude: true,
3035         controller: 'uiGridController',
3036         compile: function () {
3037           return {
3038             post: function ($scope, $elm, $attrs, uiGridCtrl) {
3039               // gridUtil.logDebug('ui-grid postlink');
3040
3041               var grid = uiGridCtrl.grid;
3042
3043               // Initialize scrollbars (TODO: move to controller??)
3044               uiGridCtrl.scrollbars = [];
3045
3046               //todo: assume it is ok to communicate that rendering is complete??
3047               grid.renderingComplete();
3048
3049               grid.element = $elm;
3050
3051               grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3052
3053               // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3054               grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3055
3056               grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3057
3058               // 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
3059               if (grid.gridHeight < grid.options.rowHeight) {
3060                 // Figure out the new height
3061                 var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3062                 var headerHeight = grid.options.hideHeader ? 0 : grid.options.headerRowHeight;
3063                 var footerHeight = grid.options.showFooter ? grid.options.footerRowHeight : 0;
3064                 var scrollbarHeight = grid.options.enableScrollbars ? gridUtil.getScrollbarWidth() : 0;
3065
3066                 var maxNumberOfFilters = 0;
3067                 // Calculates the maximum number of filters in the columns
3068                 angular.forEach(grid.options.columnDefs, function(col) {
3069                   if (col.hasOwnProperty('filter')) {
3070                     if (maxNumberOfFilters < 1) {
3071                         maxNumberOfFilters = 1;
3072                     }
3073                   }
3074                   else if (col.hasOwnProperty('filters')) {
3075                     if (maxNumberOfFilters < col.filters.length) {
3076                         maxNumberOfFilters = col.filters.length;
3077                     }
3078                   }
3079                 });
3080                 var filterHeight = maxNumberOfFilters * headerHeight;
3081
3082                 var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3083
3084                 $elm.css('height', newHeight + 'px');
3085
3086                 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3087               }
3088
3089               // Run initial canvas refresh
3090               grid.refreshCanvas();
3091
3092               //add pinned containers for row headers support
3093               //moved from pinning feature
3094               var left = angular.element('<div ng-if="grid.hasLeftContainer()" style="width: 0" ui-grid-pinned-container="\'left\'"></div>');
3095               $elm.prepend(left);
3096               uiGridCtrl.innerCompile(left);
3097
3098               var right = angular.element('<div  ng-if="grid.hasRightContainer()" style="width: 0" ui-grid-pinned-container="\'right\'"></div>');
3099               $elm.append(right);
3100               uiGridCtrl.innerCompile(right);
3101
3102
3103               //if we add a left container after render, we need to watch and react
3104               $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3105                 if (newValue === oldValue) {
3106                   return;
3107                 }
3108
3109                 //todo: remove this code.  it was commented out after moving from pinning because body is already float:left
3110 //                var bodyContainer = angular.element($elm[0].querySelectorAll('[container-id="body"]'));
3111 //                if (newValue){
3112 //                  bodyContainer.attr('style', 'float: left; position: inherit');
3113 //                }
3114 //                else {
3115 //                  bodyContainer.attr('style', 'float: left; position: relative');
3116 //                }
3117
3118                 grid.refreshCanvas(true);
3119               });
3120
3121               //if we add a right container after render, we need to watch and react
3122               $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3123                 if (newValue === oldValue) {
3124                   return;
3125                 }
3126                 grid.refreshCanvas(true);
3127               });
3128
3129
3130               // Resize the grid on window resize events
3131               function gridResize($event) {
3132                 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3133                 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3134
3135                 grid.queueRefresh();
3136               }
3137
3138               angular.element($window).on('resize', gridResize);
3139
3140               // Unbind from window resize events when the grid is destroyed
3141               $elm.on('$destroy', function () {
3142                 angular.element($window).off('resize', gridResize);
3143               });
3144             }
3145           };
3146         }
3147       };
3148     }
3149   ]);
3150
3151 })();
3152
3153 (function(){
3154   'use strict';
3155
3156   angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3157     return {
3158       restrict: 'EA',
3159       replace: true,
3160       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>',
3161       scope: {
3162         side: '=uiGridPinnedContainer'
3163       },
3164       require: '^uiGrid',
3165       compile: function compile() {
3166         return {
3167           post: function ($scope, $elm, $attrs, uiGridCtrl) {
3168             // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3169
3170             var grid = uiGridCtrl.grid;
3171
3172             var myWidth = 0;
3173
3174             $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3175
3176             function updateContainerWidth() {
3177               if ($scope.side === 'left' || $scope.side === 'right') {
3178                 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3179                 var width = 0;
3180                 for (var i = 0; i < cols.length; i++) {
3181                   var col = cols[i];
3182                   width += col.drawnWidth;
3183                 }
3184
3185                 myWidth = width;
3186               }              
3187             }
3188             
3189             function updateContainerDimensions() {
3190               // gridUtil.logDebug('update ' + $scope.side + ' dimensions');
3191
3192               var ret = '';
3193               
3194               // Column containers
3195               if ($scope.side === 'left' || $scope.side === 'right') {
3196                 updateContainerWidth();
3197
3198                 // gridUtil.logDebug('myWidth', myWidth);
3199
3200                 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3201                 $elm.attr('style', null);
3202
3203                 var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3204
3205                 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; height: ' + myHeight + 'px; } ';
3206               }
3207
3208               return ret;
3209             }
3210
3211             grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3212               if ( myWidth === 0 ){
3213                 updateContainerWidth();
3214               }
3215               // Subtract our own width
3216               adjustment.width -= myWidth;
3217
3218               return adjustment;
3219             });
3220
3221             // Register style computation to adjust for columns in `side`'s render container
3222             grid.registerStyleComputation({
3223               priority: 15,
3224               func: updateContainerDimensions
3225             });
3226           }
3227         };
3228       }
3229     };
3230   }]);
3231 })();
3232 (function(){
3233
3234 angular.module('ui.grid')
3235 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout',
3236     function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout) {
3237
3238 /**
3239  * @ngdoc object
3240  * @name ui.grid.core.api:PublicApi
3241  * @description Public Api for the core grid features
3242  *
3243  */
3244
3245 /**
3246    * @ngdoc function
3247    * @name ui.grid.class:Grid
3248    * @description Grid is the main viewModel.  Any properties or methods needed to maintain state are defined in
3249  * * this prototype.  One instance of Grid is created per Grid directive instance.
3250    * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3251    */
3252   var Grid = function Grid(options) {
3253     var self = this;
3254   // Get the id out of the options, then remove it
3255   if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3256     if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3257       throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3258     }
3259   }
3260   else {
3261     throw new Error('No ID provided. An ID must be given when creating a grid.');
3262   }
3263
3264   self.id = options.id;
3265   delete options.id;
3266
3267   // Get default options
3268   self.options = GridOptions.initialize( options );
3269
3270   self.headerHeight = self.options.headerRowHeight;
3271   self.footerHeight = self.options.showFooter === true ? self.options.footerRowHeight : 0;
3272
3273   self.rtl = false;
3274   self.gridHeight = 0;
3275   self.gridWidth = 0;
3276   self.columnBuilders = [];
3277   self.rowBuilders = [];
3278   self.rowsProcessors = [];
3279   self.columnsProcessors = [];
3280   self.styleComputations = [];
3281   self.viewportAdjusters = [];
3282   self.rowHeaderColumns = [];
3283   self.dataChangeCallbacks = {};
3284
3285   // self.visibleRowCache = [];
3286
3287   // Set of 'render' containers for self grid, which can render sets of rows
3288   self.renderContainers = {};
3289
3290   // Create a
3291   self.renderContainers.body = new GridRenderContainer('body', self);
3292
3293   self.cellValueGetterCache = {};
3294
3295   // Cached function to use with custom row templates
3296   self.getRowTemplateFn = null;
3297
3298
3299   //representation of the rows on the grid.
3300   //these are wrapped references to the actual data rows (options.data)
3301   self.rows = [];
3302
3303   //represents the columns on the grid
3304   self.columns = [];
3305
3306   /**
3307    * @ngdoc boolean
3308    * @name isScrollingVertically
3309    * @propertyOf ui.grid.class:Grid
3310    * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3311    */
3312   self.isScrollingVertically = false;
3313
3314   /**
3315    * @ngdoc boolean
3316    * @name isScrollingHorizontally
3317    * @propertyOf ui.grid.class:Grid
3318    * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3319    */
3320   self.isScrollingHorizontally = false;
3321
3322   var debouncedVertical = gridUtil.debounce(function () {
3323     self.isScrollingVertically = false;
3324   }, 300);
3325
3326   var debouncedHorizontal = gridUtil.debounce(function () {
3327     self.isScrollingHorizontally = false;
3328   }, 300);
3329
3330
3331   /**
3332    * @ngdoc function
3333    * @name flagScrollingVertically
3334    * @methodOf ui.grid.class:Grid
3335    * @description sets isScrollingVertically to true and sets it to false in a debounced function
3336    */
3337   self.flagScrollingVertically = function() {
3338     self.isScrollingVertically = true;
3339     debouncedVertical();
3340   };
3341
3342   /**
3343    * @ngdoc function
3344    * @name flagScrollingHorizontally
3345    * @methodOf ui.grid.class:Grid
3346    * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3347    */
3348   self.flagScrollingHorizontally = function() {
3349     self.isScrollingHorizontally = true;
3350     debouncedHorizontal();
3351   };
3352
3353
3354
3355   self.api = new GridApi(self);
3356
3357   /**
3358    * @ngdoc function
3359    * @name refresh
3360    * @methodOf ui.grid.core.api:PublicApi
3361    * @description Refresh the rendered grid on screen.
3362    * 
3363    */
3364   self.api.registerMethod( 'core', 'refresh', this.refresh );
3365
3366   /**
3367    * @ngdoc function
3368    * @name refreshRows
3369    * @methodOf ui.grid.core.api:PublicApi
3370    * @description Refresh the rendered grid on screen?  Note: not functional at present
3371    * @returns {promise} promise that is resolved when render completes?
3372    * 
3373    */
3374   self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3375
3376   /**
3377    * @ngdoc function
3378    * @name handleWindowResize
3379    * @methodOf ui.grid.core.api:PublicApi
3380    * @description Trigger a grid resize, normally this would be picked
3381    * up by a watch on window size, but in some circumstances it is necessary
3382    * to call this manually
3383    * @returns {promise} promise that is resolved when render completes?
3384    * 
3385    */
3386   self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3387
3388
3389   /**
3390    * @ngdoc function
3391    * @name addRowHeaderColumn
3392    * @methodOf ui.grid.core.api:PublicApi
3393    * @description adds a row header column to the grid
3394    * @param {object} column def
3395    * 
3396    */
3397   self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
3398
3399
3400   /**
3401    * @ngdoc function
3402    * @name sortHandleNulls
3403    * @methodOf ui.grid.core.api:PublicApi
3404    * @description A null handling method that can be used when building custom sort
3405    * functions
3406    * @example
3407    * <pre>
3408    *   mySortFn = function(a, b) {
3409    *   var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3410    *   if ( nulls !== null ){
3411    *     return nulls;
3412    *   } else {
3413    *     // your code for sorting here
3414    *   };
3415    * </pre>
3416    * @param {object} a sort value a
3417    * @param {object} b sort value b
3418    * @returns {number} null if there were no nulls/undefineds, otherwise returns
3419    * a sort value that should be passed back from the sort function
3420    * 
3421    */
3422   self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
3423
3424
3425   /**
3426    * @ngdoc function
3427    * @name sortChanged
3428    * @methodOf  ui.grid.core.api:PublicApi
3429    * @description The sort criteria on one or more columns has
3430    * changed.  Provides as parameters the grid and the output of
3431    * getColumnSorting, which is an array of gridColumns
3432    * that have sorting on them, sorted in priority order. 
3433    * 
3434    * @param {Grid} grid the grid
3435    * @param {array} sortColumns an array of columns with 
3436    * sorts on them, in priority order
3437    * 
3438    * @example
3439    * <pre>
3440    *      gridApi.core.on.sortChanged( grid, sortColumns );
3441    * </pre>
3442    */
3443   self.api.registerEvent( 'core', 'sortChanged' );
3444
3445   /**
3446    * @ngdoc method
3447    * @name notifyDataChange
3448    * @methodOf ui.grid.core.api:PublicApi
3449    * @description Notify the grid that a data or config change has occurred,
3450    * where that change isn't something the grid was otherwise noticing.  This 
3451    * might be particularly relevant where you've changed values within the data
3452    * and you'd like cell classes to be re-evaluated, or changed config within 
3453    * the columnDef and you'd like headerCellClasses to be re-evaluated.
3454    * @param {Grid} grid the grid
3455    * @param {string} type one of the 
3456    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3457    * us which refreshes to fire.
3458    * 
3459    */
3460   self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
3461   
3462   self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
3463   self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
3464 };
3465
3466     /**
3467      * @ngdoc function
3468      * @name isRTL
3469      * @methodOf ui.grid.class:Grid
3470      * @description Returns true if grid is RightToLeft
3471      */
3472     Grid.prototype.isRTL = function () {
3473       return this.rtl;
3474     };
3475
3476
3477       /**
3478    * @ngdoc function
3479    * @name registerColumnBuilder
3480    * @methodOf ui.grid.class:Grid
3481    * @description When the build creates columns from column definitions, the columnbuilders will be called to add
3482    * additional properties to the column.
3483    * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called
3484    */
3485   Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
3486     this.columnBuilders.push(columnBuilder);
3487   };
3488
3489   /**
3490    * @ngdoc function
3491    * @name buildColumnDefsFromData
3492    * @methodOf ui.grid.class:Grid
3493    * @description Populates columnDefs from the provided data
3494    * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
3495    */
3496   Grid.prototype.buildColumnDefsFromData = function (dataRows){
3497     this.options.columnDefs =  gridUtil.getColumnsFromData(dataRows,  this.options.excludeProperties);
3498   };
3499
3500   /**
3501    * @ngdoc function
3502    * @name registerRowBuilder
3503    * @methodOf ui.grid.class:Grid
3504    * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
3505    * additional properties to the row.
3506    * @param {function(row, gridOptions)} rowBuilder function to be called
3507    */
3508   Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
3509     this.rowBuilders.push(rowBuilder);
3510   };
3511
3512
3513   /**
3514    * @ngdoc function
3515    * @name registerDataChangeCallback
3516    * @methodOf ui.grid.class:Grid
3517    * @description When a data change occurs, the data change callbacks of the specified type
3518    * will be called.  The rules are:
3519    * 
3520    * - when the data watch fires, that is considered a ROW change (the data watch only notices
3521    *   added or removed rows)
3522    * - when the api is called to inform us of a change, the declared type of that change is used
3523    * - when a cell edit completes, the EDIT callbacks are triggered
3524    * - when the columnDef watch fires, the COLUMN callbacks are triggered
3525    * 
3526    * For a given event:
3527    * - ALL calls ROW, EDIT, COLUMN and ALL callbacks
3528    * - ROW calls ROW and ALL callbacks
3529    * - EDIT calls EDIT and ALL callbacks
3530    * - COLUMN calls COLUMN and ALL callbacks
3531    * 
3532    * @param {function(grid)} callback function to be called
3533    * @param {array} types the types of data change you want to be informed of.  Values from 
3534    * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ).  Optional and defaults to
3535    * ALL 
3536    * @returns {string} uid of the callback, can be used to deregister it again
3537    */
3538   Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) {
3539     var uid = gridUtil.nextUid();
3540     if ( !types ){
3541       types = [uiGridConstants.dataChange.ALL];
3542     }
3543     if ( !Array.isArray(types)){
3544       gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
3545     }
3546     this.dataChangeCallbacks[uid] = { callback: callback, types: types };
3547     return uid;
3548   };
3549
3550   /**
3551    * @ngdoc function
3552    * @name deregisterDataChangeCallback
3553    * @methodOf ui.grid.class:Grid
3554    * @description Delete the callback identified by the id.
3555    * @param {string} uid the uid of the function that is to be deregistered
3556    */
3557   Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) {
3558     delete this.dataChangeCallbacks[uid];
3559   };
3560
3561   /**
3562    * @ngdoc function
3563    * @name callDataChangeCallbacks
3564    * @methodOf ui.grid.class:Grid
3565    * @description Calls the callbacks based on the type of data change that
3566    * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the 
3567    * event type is matching, or if the type is ALL.
3568    * @param {number} type the type of event that occurred - one of the 
3569    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
3570    */
3571   Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
3572     angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
3573       if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
3574            callback.types.indexOf( type ) !== -1 ||
3575            type === uiGridConstants.dataChange.ALL ) {
3576         callback.callback( this );
3577       }
3578     }, this);
3579   };
3580   
3581   /**
3582    * @ngdoc function
3583    * @name notifyDataChange
3584    * @methodOf ui.grid.class:Grid
3585    * @description Notifies us that a data change has occurred, used in the public
3586    * api for users to tell us when they've changed data or some other event that 
3587    * our watches cannot pick up
3588    * @param {Grid} grid the grid
3589    * @param {string} type the type of event that occurred - one of the 
3590    * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
3591    */
3592   Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) {
3593     var constants = uiGridConstants.dataChange;
3594     if ( type === constants.ALL || 
3595          type === constants.COLUMN ||
3596          type === constants.EDIT ||
3597          type === constants.ROW ){
3598       grid.callDataChangeCallbacks( type );
3599     } else {
3600       gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
3601     }
3602   };
3603   
3604   
3605   /**
3606    * @ngdoc function
3607    * @name columnRefreshCallback
3608    * @methodOf ui.grid.class:Grid
3609    * @description refreshes the grid when a column refresh
3610    * is notified, which triggers handling of the visible flag. 
3611    * This is called on uiGridConstants.dataChange.COLUMN, and is 
3612    * registered as a dataChangeCallback in grid.js
3613    * @param {string} name column name
3614    */
3615   Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
3616     grid.refresh();
3617   };
3618     
3619
3620   /**
3621    * @ngdoc function
3622    * @name processRowsCallback
3623    * @methodOf ui.grid.class:Grid
3624    * @description calls the row processors, specifically
3625    * intended to reset the sorting when an edit is called,
3626    * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
3627    * @param {string} name column name
3628    */
3629   Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
3630     grid.refreshRows();
3631   };
3632     
3633
3634   /**
3635    * @ngdoc function
3636    * @name getColumn
3637    * @methodOf ui.grid.class:Grid
3638    * @description returns a grid column for the column name
3639    * @param {string} name column name
3640    */
3641   Grid.prototype.getColumn = function getColumn(name) {
3642     var columns = this.columns.filter(function (column) {
3643       return column.colDef.name === name;
3644     });
3645     return columns.length > 0 ? columns[0] : null;
3646   };
3647
3648   /**
3649    * @ngdoc function
3650    * @name getColDef
3651    * @methodOf ui.grid.class:Grid
3652    * @description returns a grid colDef for the column name
3653    * @param {string} name column.field
3654    */
3655   Grid.prototype.getColDef = function getColDef(name) {
3656     var colDefs = this.options.columnDefs.filter(function (colDef) {
3657       return colDef.name === name;
3658     });
3659     return colDefs.length > 0 ? colDefs[0] : null;
3660   };
3661
3662   /**
3663    * @ngdoc function
3664    * @name assignTypes
3665    * @methodOf ui.grid.class:Grid
3666    * @description uses the first row of data to assign colDef.type for any types not defined.
3667    */
3668   /**
3669    * @ngdoc property
3670    * @name type
3671    * @propertyOf ui.grid.class:GridOptions.columnDef
3672    * @description the type of the column, used in sorting.  If not provided then the 
3673    * grid will guess the type.  Add this only if the grid guessing is not to your
3674    * satisfaction.  Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for
3675    * a list of values the grid knows about.
3676    *
3677    */
3678   Grid.prototype.assignTypes = function(){
3679     var self = this;
3680     self.options.columnDefs.forEach(function (colDef, index) {
3681
3682       //Assign colDef type if not specified
3683       if (!colDef.type) {
3684         var col = new GridColumn(colDef, index, self);
3685         var firstRow = self.rows.length > 0 ? self.rows[0] : null;
3686         if (firstRow) {
3687           colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
3688         }
3689         else {
3690           gridUtil.logWarn('Unable to assign type from data, so defaulting to string');
3691           colDef.type = 'string';
3692         }
3693       }
3694     });
3695   };
3696
3697   /**
3698   * @ngdoc function
3699   * @name addRowHeaderColumn
3700   * @methodOf ui.grid.class:Grid
3701   * @description adds a row header column to the grid
3702   * @param {object} column def
3703   */
3704   Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
3705     var self = this;
3706     //self.createLeftContainer();
3707     var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self);
3708     rowHeaderCol.isRowHeader = true;
3709     if (self.isRTL()) {
3710       self.createRightContainer();
3711       rowHeaderCol.renderContainer = 'right';
3712     }
3713     else {
3714       self.createLeftContainer();
3715       rowHeaderCol.renderContainer = 'left';
3716     }
3717
3718     // relies on the default column builder being first in array, as it is instantiated
3719     // as part of grid creation
3720     self.columnBuilders[0](colDef,rowHeaderCol,self.options)
3721       .then(function(){
3722         rowHeaderCol.enableFiltering = false;
3723         rowHeaderCol.enableSorting = false;
3724         rowHeaderCol.enableHiding = false;
3725         self.rowHeaderColumns.push(rowHeaderCol);
3726         self.buildColumns()
3727           .then( function() {
3728             self.preCompileCellTemplates();
3729             self.handleWindowResize();
3730           });
3731       });
3732   };
3733
3734   /**
3735    * @ngdoc function
3736    * @name buildColumns
3737    * @methodOf ui.grid.class:Grid
3738    * @description creates GridColumn objects from the columnDefinition.  Calls each registered
3739    * columnBuilder to further process the column
3740    * @returns {Promise} a promise to load any needed column resources
3741    */
3742   Grid.prototype.buildColumns = function buildColumns() {
3743     // gridUtil.logDebug('buildColumns');
3744     var self = this;
3745     var builderPromises = [];
3746     var headerOffset = self.rowHeaderColumns.length;
3747     var i;
3748
3749     // Remove any columns for which a columnDef cannot be found
3750     // Deliberately don't use forEach, as it doesn't like splice being called in the middle
3751     // Also don't cache columns.length, as it will change during this operation
3752     for (i = 0; i < self.columns.length; i++){
3753       if (!self.getColDef(self.columns[i].name)) {
3754         self.columns.splice(i, 1);
3755         i--;
3756       }
3757     }
3758
3759     //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
3760     self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
3761       self.columns.unshift(rowHeaderColumn);
3762     });
3763
3764
3765     // look at each column def, and update column properties to match.  If the column def
3766     // doesn't have a column, then splice in a new gridCol
3767     self.options.columnDefs.forEach(function (colDef, index) {
3768       self.preprocessColDef(colDef);
3769       var col = self.getColumn(colDef.name);
3770
3771       if (!col) {
3772         col = new GridColumn(colDef, gridUtil.nextUid(), self);
3773         self.columns.splice(index + headerOffset, 0, col);
3774       }
3775       else {
3776         col.updateColumnDef(colDef);
3777       }
3778
3779       self.columnBuilders.forEach(function (builder) {
3780         builderPromises.push(builder.call(self, colDef, col, self.options));
3781       });
3782     });
3783     
3784     return $q.all(builderPromises);
3785   };
3786
3787 /**
3788  * @ngdoc function
3789  * @name preCompileCellTemplates
3790  * @methodOf ui.grid.class:Grid
3791  * @description precompiles all cell templates
3792  */
3793   Grid.prototype.preCompileCellTemplates = function() {
3794     var self = this;
3795     this.columns.forEach(function (col) {
3796       var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
3797       html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
3798
3799
3800       var compiledElementFn = $compile(html);
3801       col.compiledElementFn = compiledElementFn;
3802
3803       if (col.compiledElementFnDefer) {
3804         col.compiledElementFnDefer.resolve(col.compiledElementFn);
3805       }
3806     });
3807   };
3808
3809   /**
3810    * @ngdoc function
3811    * @name getGridQualifiedColField
3812    * @methodOf ui.grid.class:Grid
3813    * @description Returns the $parse-able accessor for a column within its $scope
3814    * @param {GridColumn} col col object
3815    */
3816   Grid.prototype.getQualifiedColField = function (col) {
3817     return 'row.entity.' + gridUtil.preEval(col.field);
3818   };
3819
3820   /**
3821    * @ngdoc function
3822    * @name createLeftContainer
3823    * @methodOf ui.grid.class:Grid
3824    * @description creates the left render container if it doesn't already exist
3825    */
3826   Grid.prototype.createLeftContainer = function() {
3827     if (!this.hasLeftContainer()) {
3828       this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
3829     }
3830   };
3831
3832   /**
3833    * @ngdoc function
3834    * @name createRightContainer
3835    * @methodOf ui.grid.class:Grid
3836    * @description creates the right render container if it doesn't already exist
3837    */
3838   Grid.prototype.createRightContainer = function() {
3839     if (!this.hasRightContainer()) {
3840       this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
3841     }
3842   };
3843
3844   /**
3845    * @ngdoc function
3846    * @name hasLeftContainer
3847    * @methodOf ui.grid.class:Grid
3848    * @description returns true if leftContainer exists
3849    */
3850   Grid.prototype.hasLeftContainer = function() {
3851     return this.renderContainers.left !== undefined;
3852   };
3853
3854   /**
3855    * @ngdoc function
3856    * @name hasLeftContainer
3857    * @methodOf ui.grid.class:Grid
3858    * @description returns true if rightContainer exists
3859    */
3860   Grid.prototype.hasRightContainer = function() {
3861     return this.renderContainers.right !== undefined;
3862   };
3863
3864
3865       /**
3866    * undocumented function
3867    * @name preprocessColDef
3868    * @methodOf ui.grid.class:Grid
3869    * @description defaults the name property from field to maintain backwards compatibility with 2.x
3870    * validates that name or field is present
3871    */
3872   Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
3873     if (!colDef.field && !colDef.name) {
3874       throw new Error('colDef.name or colDef.field property is required');
3875     }
3876
3877     //maintain backwards compatibility with 2.x
3878     //field was required in 2.x.  now name is required
3879     if (colDef.name === undefined && colDef.field !== undefined) {
3880       colDef.name = colDef.field;
3881     }
3882
3883   };
3884
3885   // 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
3886   Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
3887     var self = this;
3888
3889     var t = [];
3890     for (var i = 0; i < n.length; i++) {
3891       var nV = nAccessor ? n[i][nAccessor] : n[i];
3892       
3893       var found = false;
3894       for (var j = 0; j < o.length; j++) {
3895         var oV = oAccessor ? o[j][oAccessor] : o[j];
3896         if (self.options.rowEquality(nV, oV)) {
3897           found = true;
3898           break;
3899         }
3900       }
3901       if (!found) {
3902         t.push(nV);
3903       }
3904     }
3905     
3906     return t;
3907   };
3908
3909     /**
3910      * @ngdoc function
3911      * @name getRow
3912      * @methodOf ui.grid.class:Grid
3913      * @description returns the GridRow that contains the rowEntity
3914      * @param {object} rowEntity the gridOptions.data array element instance
3915      */
3916     Grid.prototype.getRow = function getRow(rowEntity) {
3917       var rows = this.rows.filter(function (row) {
3918         return row.entity === rowEntity;
3919       });
3920       return rows.length > 0 ? rows[0] : null;
3921     };
3922
3923
3924       /**
3925    * @ngdoc function
3926    * @name modifyRows
3927    * @methodOf ui.grid.class:Grid
3928    * @description creates or removes GridRow objects from the newRawData array.  Calls each registered
3929    * rowBuilder to further process the row
3930    *
3931    * Rows are identified using the gridOptions.rowEquality function
3932    */
3933   Grid.prototype.modifyRows = function modifyRows(newRawData) {
3934     var self = this,
3935         i,
3936         rowhash,
3937         found,
3938         newRow;
3939     if ((self.options.useExternalSorting || self.getColumnSorting().length === 0) && newRawData.length > 0) {
3940         var oldRowHash = self.rowHashMap;
3941         if (!oldRowHash) {
3942            oldRowHash = {get: function(){return null;}};
3943         }
3944         self.createRowHashMap();
3945         rowhash = self.rowHashMap;
3946         var wasEmpty = self.rows.length === 0;
3947         self.rows.length = 0;
3948         for (i = 0; i < newRawData.length; i++) {
3949             var newRawRow = newRawData[i];
3950             found = oldRowHash.get(newRawRow);
3951             if (found) {
3952               newRow = found.row; 
3953             }
3954             else {
3955               newRow = self.processRowBuilders(new GridRow(newRawRow, i, self));
3956             }
3957             self.rows.push(newRow);
3958             rowhash.put(newRawRow, {
3959                 i: i,
3960                 entity: newRawRow,
3961                 row:newRow
3962             });
3963         }
3964         //now that we have data, it is save to assign types to colDefs
3965         if (wasEmpty) {
3966            self.assignTypes();
3967         }
3968     } else {
3969     if (self.rows.length === 0 && newRawData.length > 0) {
3970       if (self.options.enableRowHashing) {
3971         if (!self.rowHashMap) {
3972           self.createRowHashMap();
3973         }
3974
3975         for (i = 0; i < newRawData.length; i++) {
3976           newRow = newRawData[i];
3977
3978           self.rowHashMap.put(newRow, {
3979             i: i,
3980             entity: newRow
3981           });
3982         }
3983       }
3984
3985       self.addRows(newRawData);
3986       //now that we have data, it is save to assign types to colDefs
3987       self.assignTypes();
3988     }
3989     else if (newRawData.length > 0) {
3990       var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind;
3991
3992       // If row hashing is turned on
3993       if (self.options.enableRowHashing) {
3994         // Array of new rows that haven't been found in the old rowset
3995         unfoundNewRows = [];
3996         // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist).
3997         unfoundNewRowsToFind = [];
3998         // Map of rows that have been found in the new rowset
3999         var foundOldRows = {};
4000         // Array of old rows that have NOT been found in the new rowset
4001         unfoundOldRows = [];
4002
4003         // Create the row HashMap if it doesn't exist already
4004         if (!self.rowHashMap) {
4005           self.createRowHashMap();
4006         }
4007         rowhash = self.rowHashMap;
4008         
4009         // Make sure every new row has a hash
4010         for (i = 0; i < newRawData.length; i++) {
4011           newRow = newRawData[i];
4012
4013           // Flag this row as needing to be manually found if it didn't come in with a $$hashKey
4014           var mustFind = false;
4015           if (!self.options.getRowIdentity(newRow)) {
4016             mustFind = true;
4017           }
4018
4019           // See if the new row is already in the rowhash
4020           found = rowhash.get(newRow);
4021           // If so...
4022           if (found) {
4023             // See if it's already being used by as GridRow
4024             if (found.row) {
4025               // If so, mark this new row as being found
4026               foundOldRows[self.options.rowIdentity(newRow)] = true;
4027             }
4028           }
4029           else {
4030             // Put the row in the hashmap with the index it corresponds to
4031             rowhash.put(newRow, {
4032               i: i,
4033               entity: newRow
4034             });
4035             
4036             // This row has to be searched for manually in the old row set
4037             if (mustFind) {
4038               unfoundNewRowsToFind.push(newRow);
4039             }
4040             else {
4041               unfoundNewRows.push(newRow);
4042             }
4043           }
4044         }
4045
4046         // Build the list of unfound old rows
4047         for (i = 0; i < self.rows.length; i++) {
4048           var row = self.rows[i];
4049           var hash = self.options.rowIdentity(row.entity);
4050           if (!foundOldRows[hash]) {
4051             unfoundOldRows.push(row);
4052           }
4053         }
4054       }
4055
4056       // Look for new rows
4057       var newRows = unfoundNewRows || [];
4058
4059       // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't
4060       var unfoundNew = (unfoundNewRowsToFind || newRawData);
4061
4062       // Search for real new rows in `unfoundNew` and concat them onto `newRows`
4063       newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity'));
4064       
4065       self.addRows(newRows); 
4066       
4067       var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData);
4068
4069       for (i = 0; i < deletedRows.length; i++) {
4070         if (self.options.enableRowHashing) {
4071           self.rowHashMap.remove(deletedRows[i].entity);
4072         }
4073
4074         self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 );
4075       }
4076     }
4077     // Empty data set
4078     else {
4079       // Reset the row HashMap
4080       self.createRowHashMap();
4081
4082       // Reset the rows length!
4083       self.rows.length = 0;
4084     }
4085     }
4086     
4087     var p1 = $q.when(self.processRowsProcessors(self.rows))
4088       .then(function (renderableRows) {
4089         return self.setVisibleRows(renderableRows);
4090       });
4091
4092     var p2 = $q.when(self.processColumnsProcessors(self.columns))
4093       .then(function (renderableColumns) {
4094         return self.setVisibleColumns(renderableColumns);
4095       });
4096
4097     return $q.all([p1, p2]);
4098   };
4099
4100   Grid.prototype.getDeletedRows = function(oldRows, newRows) {
4101     var self = this;
4102
4103     var olds = oldRows.filter(function (oldRow) {
4104       return !newRows.some(function (newItem) {
4105         return self.options.rowEquality(newItem, oldRow.entity);
4106       });
4107     });
4108     // var olds = self.newInN(newRows, oldRows, null, 'entity');
4109     // dump('olds', olds);
4110     return olds;
4111   };
4112
4113   /**
4114    * Private Undocumented Method
4115    * @name addRows
4116    * @methodOf ui.grid.class:Grid
4117    * @description adds the newRawData array of rows to the grid and calls all registered
4118    * rowBuilders. this keyword will reference the grid
4119    */
4120   Grid.prototype.addRows = function addRows(newRawData) {
4121     var self = this;
4122
4123     var existingRowCount = self.rows.length;
4124     for (var i = 0; i < newRawData.length; i++) {
4125       var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4126
4127       if (self.options.enableRowHashing) {
4128         var found = self.rowHashMap.get(newRow.entity);
4129         if (found) {
4130           found.row = newRow;
4131         }
4132       }
4133
4134       self.rows.push(newRow);
4135     }
4136   };
4137
4138   /**
4139    * @ngdoc function
4140    * @name processRowBuilders
4141    * @methodOf ui.grid.class:Grid
4142    * @description processes all RowBuilders for the gridRow
4143    * @param {GridRow} gridRow reference to gridRow
4144    * @returns {GridRow} the gridRow with all additional behavior added
4145    */
4146   Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4147     var self = this;
4148
4149     self.rowBuilders.forEach(function (builder) {
4150       builder.call(self, gridRow, self.options);
4151     });
4152
4153     return gridRow;
4154   };
4155
4156   /**
4157    * @ngdoc function
4158    * @name registerStyleComputation
4159    * @methodOf ui.grid.class:Grid
4160    * @description registered a styleComputation function
4161    * 
4162    * If the function returns a value it will be appended into the grid's `<style>` block
4163    * @param {function($scope)} styleComputation function
4164    */
4165   Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4166     this.styleComputations.push(styleComputationInfo);
4167   };
4168
4169
4170   // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4171   // Grid.prototype.registerRowFilter = function(filter) {
4172   //   // TODO(c0bra): validate filter?
4173
4174   //   this.rowFilters.push(filter);
4175   // };
4176
4177   // Grid.prototype.removeRowFilter = function(filter) {
4178   //   var idx = this.rowFilters.indexOf(filter);
4179
4180   //   if (typeof(idx) !== 'undefined' && idx !== undefined) {
4181   //     this.rowFilters.slice(idx, 1);
4182   //   }
4183   // };
4184   
4185   // Grid.prototype.processRowFilters = function(rows) {
4186   //   var self = this;
4187   //   self.rowFilters.forEach(function (filter) {
4188   //     filter.call(self, rows);
4189   //   });
4190   // };
4191
4192
4193   /**
4194    * @ngdoc function
4195    * @name registerRowsProcessor
4196    * @methodOf ui.grid.class:Grid
4197    * @param {function(renderableRows)} rows processor function
4198    * @returns {Array[GridRow]} Updated renderable rows
4199    * @description
4200
4201      Register a "rows processor" function. When the rows are updated,
4202      the grid calls each registered "rows processor", which has a chance
4203      to alter the set of rows (sorting, etc) as long as the count is not
4204      modified.
4205    */
4206   Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor) {
4207     if (!angular.isFunction(processor)) {
4208       throw 'Attempt to register non-function rows processor: ' + processor;
4209     }
4210
4211     this.rowsProcessors.push(processor);
4212   };
4213
4214   /**
4215    * @ngdoc function
4216    * @name removeRowsProcessor
4217    * @methodOf ui.grid.class:Grid
4218    * @param {function(renderableRows)} rows processor function
4219    * @description Remove a registered rows processor
4220    */
4221   Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
4222     var idx = this.rowsProcessors.indexOf(processor);
4223
4224     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4225       this.rowsProcessors.splice(idx, 1);
4226     }
4227   };
4228   
4229   /**
4230    * Private Undocumented Method
4231    * @name processRowsProcessors
4232    * @methodOf ui.grid.class:Grid
4233    * @param {Array[GridRow]} The array of "renderable" rows
4234    * @param {Array[GridColumn]} The array of columns
4235    * @description Run all the registered rows processors on the array of renderable rows
4236    */
4237   Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
4238     var self = this;
4239
4240     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4241     var myRenderableRows = renderableRows.slice(0);
4242     
4243     // self.rowsProcessors.forEach(function (processor) {
4244     //   myRenderableRows = processor.call(self, myRenderableRows, self.columns);
4245
4246     //   if (!renderableRows) {
4247     //     throw "Processor at index " + i + " did not return a set of renderable rows";
4248     //   }
4249
4250     //   if (!angular.isArray(renderableRows)) {
4251     //     throw "Processor at index " + i + " did not return an array";
4252     //   }
4253
4254     //   i++;
4255     // });
4256
4257     // Return myRenderableRows with no processing if we have no rows processors 
4258     if (self.rowsProcessors.length === 0) {
4259       return $q.when(myRenderableRows);
4260     }
4261   
4262     // Counter for iterating through rows processors
4263     var i = 0;
4264     
4265     // Promise for when we're done with all the processors
4266     var finished = $q.defer();
4267
4268     // This function will call the processor in self.rowsProcessors at index 'i', and then
4269     //   when done will call the next processor in the list, using the output from the processor
4270     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4271     //  
4272     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4273     //   the result.
4274     function startProcessor(i, renderedRowsToProcess) {
4275       // Get the processor at 'i'
4276       var processor = self.rowsProcessors[i];
4277
4278       // Call the processor, passing in the rows to process and the current columns
4279       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4280       return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
4281         .then(function handleProcessedRows(processedRows) {
4282           // Check for errors
4283           if (!processedRows) {
4284             throw "Processor at index " + i + " did not return a set of renderable rows";
4285           }
4286
4287           if (!angular.isArray(processedRows)) {
4288             throw "Processor at index " + i + " did not return an array";
4289           }
4290
4291           // Processor is done, increment the counter
4292           i++;
4293
4294           // If we're not done with the processors, call the next one
4295           if (i <= self.rowsProcessors.length - 1) {
4296             return startProcessor(i, processedRows);
4297           }
4298           // We're done! Resolve the 'finished' promise
4299           else {
4300             finished.resolve(processedRows);
4301           }
4302         });
4303     }
4304
4305     // Start on the first processor
4306     startProcessor(0, myRenderableRows);
4307     
4308     return finished.promise;
4309   };
4310
4311   Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
4312     // gridUtil.logDebug('setVisibleRows');
4313
4314     var self = this;
4315
4316     //var newVisibleRowCache = [];
4317
4318     // Reset all the render container row caches
4319     for (var i in self.renderContainers) {
4320       var container = self.renderContainers[i];
4321
4322       container.visibleRowCache.length = 0;
4323     }
4324     
4325     // rows.forEach(function (row) {
4326     for (var ri = 0; ri < rows.length; ri++) {
4327       var row = rows[ri];
4328
4329       // If the row is visible
4330       if (row.visible) {
4331         // newVisibleRowCache.push(row);
4332
4333         // If the row has a container specified
4334         if (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) {
4335           self.renderContainers[row.renderContainer].visibleRowCache.push(row);
4336         }
4337         // If not, put it into the body container
4338         else {
4339           self.renderContainers.body.visibleRowCache.push(row);
4340         }
4341       }
4342     }
4343   };
4344
4345   /**
4346    * @ngdoc function
4347    * @name registerColumnsProcessor
4348    * @methodOf ui.grid.class:Grid
4349    * @param {function(renderableColumns)} rows processor function
4350    * @returns {Array[GridColumn]} Updated renderable columns
4351    * @description
4352
4353      Register a "columns processor" function. When the columns are updated,
4354      the grid calls each registered "columns processor", which has a chance
4355      to alter the set of columns, as long as the count is not modified.
4356    */
4357   Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor) {
4358     if (!angular.isFunction(processor)) {
4359       throw 'Attempt to register non-function rows processor: ' + processor;
4360     }
4361
4362     this.columnsProcessors.push(processor);
4363   };
4364
4365   Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
4366     var idx = this.columnsProcessors.indexOf(processor);
4367
4368     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4369       this.columnsProcessors.splice(idx, 1);
4370     }
4371   };
4372
4373   Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
4374     var self = this;
4375
4376     // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4377     var myRenderableColumns = renderableColumns.slice(0);
4378
4379     // Return myRenderableRows with no processing if we have no rows processors 
4380     if (self.columnsProcessors.length === 0) {
4381       return $q.when(myRenderableColumns);
4382     }
4383   
4384     // Counter for iterating through rows processors
4385     var i = 0;
4386     
4387     // Promise for when we're done with all the processors
4388     var finished = $q.defer();
4389
4390     // This function will call the processor in self.rowsProcessors at index 'i', and then
4391     //   when done will call the next processor in the list, using the output from the processor
4392     //   at i as the argument for 'renderedRowsToProcess' on the next iteration.
4393     //  
4394     //   If we're at the end of the list of processors, we resolve our 'finished' callback with
4395     //   the result.
4396     function startProcessor(i, renderedColumnsToProcess) {
4397       // Get the processor at 'i'
4398       var processor = self.columnsProcessors[i];
4399
4400       // Call the processor, passing in the rows to process and the current columns
4401       //   (note: it's wrapped in $q.when() in case the processor does not return a promise)
4402       return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
4403         .then(function handleProcessedRows(processedColumns) {
4404           // Check for errors
4405           if (!processedColumns) {
4406             throw "Processor at index " + i + " did not return a set of renderable rows";
4407           }
4408
4409           if (!angular.isArray(processedColumns)) {
4410             throw "Processor at index " + i + " did not return an array";
4411           }
4412
4413           // Processor is done, increment the counter
4414           i++;
4415
4416           // If we're not done with the processors, call the next one
4417           if (i <= self.columnsProcessors.length - 1) {
4418             return startProcessor(i, myRenderableColumns);
4419           }
4420           // We're done! Resolve the 'finished' promise
4421           else {
4422             finished.resolve(myRenderableColumns);
4423           }
4424         });
4425     }
4426
4427     // Start on the first processor
4428     startProcessor(0, myRenderableColumns);
4429     
4430     return finished.promise;
4431   };
4432
4433   Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
4434     // gridUtil.logDebug('setVisibleColumns');
4435
4436     var self = this;
4437
4438     // Reset all the render container row caches
4439     for (var i in self.renderContainers) {
4440       var container = self.renderContainers[i];
4441
4442       container.visibleColumnCache.length = 0;
4443     }
4444
4445     for (var ci = 0; ci < columns.length; ci++) {
4446       var column = columns[ci];
4447
4448       // If the column is visible
4449       if (column.visible) {
4450         // If the column has a container specified
4451         if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
4452           self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
4453         }
4454         // If not, put it into the body container
4455         else {
4456           self.renderContainers.body.visibleColumnCache.push(column);
4457         }
4458       }
4459     }
4460   };
4461
4462   /**
4463    * @ngdoc function
4464    * @name handleWindowResize
4465    * @methodOf ui.grid.class:Grid
4466    * @description Triggered when the browser window resizes; automatically resizes the grid
4467    */
4468   Grid.prototype.handleWindowResize = function handleWindowResize($event) {
4469     var self = this;
4470
4471     self.gridWidth = gridUtil.elementWidth(self.element);
4472     self.gridHeight = gridUtil.elementHeight(self.element);
4473
4474     self.queueRefresh();
4475   };
4476
4477   /**
4478    * @ngdoc function
4479    * @name queueRefresh
4480    * @methodOf ui.grid.class:Grid
4481    * @description todo: @c0bra can you document this method?
4482    */
4483   Grid.prototype.queueRefresh = function queueRefresh() {
4484     var self = this;
4485
4486     if (self.refreshCanceller) {
4487       $timeout.cancel(self.refreshCanceller);
4488     }
4489
4490     self.refreshCanceller = $timeout(function () {
4491       self.refreshCanvas(true);
4492     });
4493
4494     self.refreshCanceller.then(function () {
4495       self.refreshCanceller = null;
4496     });
4497
4498     return self.refreshCanceller;
4499   };
4500
4501   /**
4502    * @ngdoc function
4503    * @name buildStyles
4504    * @methodOf ui.grid.class:Grid
4505    * @description calls each styleComputation function
4506    */
4507   // TODO: this used to take $scope, but couldn't see that it was used
4508   Grid.prototype.buildStyles = function buildStyles() {
4509     // gridUtil.logDebug('buildStyles');
4510
4511     var self = this;
4512     
4513     self.customStyles = '';
4514
4515     self.styleComputations
4516       .sort(function(a, b) {
4517         if (a.priority === null) { return 1; }
4518         if (b.priority === null) { return -1; }
4519         if (a.priority === null && b.priority === null) { return 0; }
4520         return a.priority - b.priority;
4521       })
4522       .forEach(function (compInfo) {
4523         // this used to provide $scope as a second parameter, but I couldn't find any 
4524         // style builders that used it, so removed it as part of moving to grid from controller
4525         var ret = compInfo.func.call(self);
4526
4527         if (angular.isString(ret)) {
4528           self.customStyles += '\n' + ret;
4529         }
4530       });
4531   };
4532
4533
4534   Grid.prototype.minColumnsToRender = function minColumnsToRender() {
4535     var self = this;
4536     var viewport = this.getViewportWidth();
4537
4538     var min = 0;
4539     var totalWidth = 0;
4540     self.columns.forEach(function(col, i) {
4541       if (totalWidth < viewport) {
4542         totalWidth += col.drawnWidth;
4543         min++;
4544       }
4545       else {
4546         var currWidth = 0;
4547         for (var j = i; j >= i - min; j--) {
4548           currWidth += self.columns[j].drawnWidth;
4549         }
4550         if (currWidth < viewport) {
4551           min++;
4552         }
4553       }
4554     });
4555
4556     return min;
4557   };
4558
4559   Grid.prototype.getBodyHeight = function getBodyHeight() {
4560     // Start with the viewportHeight
4561     var bodyHeight = this.getViewportHeight();
4562
4563     // Add the horizontal scrollbar height if there is one
4564     if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
4565       bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
4566     }
4567
4568     return bodyHeight;
4569   };
4570
4571   // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
4572   // TODO(c0bra): account for footer height
4573   Grid.prototype.getViewportHeight = function getViewportHeight() {
4574     var self = this;
4575
4576     var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
4577
4578     // Account for native horizontal scrollbar, if present
4579     if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
4580       viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
4581     }
4582
4583     var adjustment = self.getViewportAdjustment();
4584     
4585     viewPortHeight = viewPortHeight + adjustment.height;
4586
4587     // gridUtil.logDebug('viewPortHeight', viewPortHeight);
4588
4589     return viewPortHeight;
4590   };
4591
4592   Grid.prototype.getViewportWidth = function getViewportWidth() {
4593     var self = this;
4594
4595     var viewPortWidth = this.gridWidth;
4596
4597     if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
4598       viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
4599     }
4600
4601     var adjustment = self.getViewportAdjustment();
4602     
4603     viewPortWidth = viewPortWidth + adjustment.width;
4604
4605     // gridUtil.logDebug('getviewPortWidth', viewPortWidth);
4606
4607     return viewPortWidth;
4608   };
4609
4610   Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
4611     var viewPortWidth = this.getViewportWidth();
4612
4613     if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
4614       viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
4615     }
4616
4617     return viewPortWidth;
4618   };
4619
4620   Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
4621     this.viewportAdjusters.push(func);
4622   };
4623
4624   Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
4625     var idx = this.viewportAdjusters.indexOf(func);
4626
4627     if (typeof(idx) !== 'undefined' && idx !== undefined) {
4628       this.viewportAdjusters.splice(idx, 1);
4629     }
4630   };
4631
4632   Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
4633     var self = this;
4634
4635     var adjustment = { height: 0, width: 0 };
4636
4637     self.viewportAdjusters.forEach(function (func) {
4638       adjustment = func.call(this, adjustment);
4639     });
4640
4641     return adjustment;
4642   };
4643
4644   Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
4645     // var count = 0;
4646
4647     // this.rows.forEach(function (row) {
4648     //   if (row.visible) {
4649     //     count++;
4650     //   }
4651     // });
4652
4653     // return this.visibleRowCache.length;
4654     return this.renderContainers.body.visibleRowCache.length;
4655   };
4656
4657    Grid.prototype.getVisibleRows = function getVisibleRows() {
4658     return this.renderContainers.body.visibleRowCache;
4659    };
4660
4661   Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
4662     // var count = 0;
4663
4664     // this.rows.forEach(function (row) {
4665     //   if (row.visible) {
4666     //     count++;
4667     //   }
4668     // });
4669
4670     // return this.visibleRowCache.length;
4671     return this.renderContainers.body.visibleColumnCache.length;
4672   };
4673
4674
4675   Grid.prototype.searchRows = function searchRows(renderableRows) {
4676     return rowSearcher.search(this, renderableRows, this.columns);
4677   };
4678
4679   Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
4680     return rowSorter.sort(this, renderableRows, this.columns);
4681   };
4682
4683   /**
4684    * @ngdoc function
4685    * @name getCellValue
4686    * @methodOf ui.grid.class:Grid
4687    * @description Gets the value of a cell for a particular row and column
4688    * @param {GridRow} row Row to access
4689    * @param {GridColumn} col Column to access
4690    */
4691   Grid.prototype.getCellValue = function getCellValue(row, col){
4692     var self = this;
4693
4694     if (!self.cellValueGetterCache[col.colDef.name]) {
4695       self.cellValueGetterCache[col.colDef.name] = $parse(row.getEntityQualifiedColField(col));
4696     }
4697
4698     return self.cellValueGetterCache[col.colDef.name](row);
4699   };
4700
4701   
4702   Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
4703     var self = this,
4704         p = 0;
4705
4706     self.columns.forEach(function (col) {
4707       if (col.sort && col.sort.priority && col.sort.priority > p) {
4708         p = col.sort.priority;
4709       }
4710     });
4711
4712     return p + 1;
4713   };
4714
4715   /**
4716    * @ngdoc function
4717    * @name resetColumnSorting
4718    * @methodOf ui.grid.class:Grid
4719    * @description Return the columns that the grid is currently being sorted by
4720    * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
4721    */
4722   Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
4723     var self = this;
4724
4725     self.columns.forEach(function (col) {
4726       if (col !== excludeCol) {
4727         col.sort = {};
4728       }
4729     });
4730   };
4731
4732   /**
4733    * @ngdoc function
4734    * @name getColumnSorting
4735    * @methodOf ui.grid.class:Grid
4736    * @description Return the columns that the grid is currently being sorted by
4737    * @returns {Array[GridColumn]} An array of GridColumn objects
4738    */
4739   Grid.prototype.getColumnSorting = function getColumnSorting() {
4740     var self = this;
4741
4742     var sortedCols = [], myCols;
4743
4744     // Iterate through all the columns, sorted by priority
4745     // Make local copy of column list, because sorting is in-place and we do not want to
4746     // change the original sequence of columns
4747     myCols = self.columns.slice(0);
4748     myCols.sort(rowSorter.prioritySort).forEach(function (col) {
4749       if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
4750         sortedCols.push(col);
4751       }
4752     });
4753
4754     return sortedCols;
4755   };
4756
4757   /**
4758    * @ngdoc function
4759    * @name sortColumn
4760    * @methodOf ui.grid.class:Grid
4761    * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
4762    * Emits the sortChanged event whenever the sort criteria are changed.
4763    * @param {GridColumn} column Column to set the sorting on
4764    * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
4765    *   If not provided, the column will iterate through the sort directions: ascending, descending, unsorted.
4766    * @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
4767    *   by this column only
4768    * @returns {Promise} A resolved promise that supplies the column.
4769    */
4770   
4771   Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
4772     var self = this,
4773         direction = null;
4774
4775     if (typeof(column) === 'undefined' || !column) {
4776       throw new Error('No column parameter provided');
4777     }
4778
4779     // Second argument can either be a direction or whether to add this column to the existing sort.
4780     //   If it's a boolean, it's an add, otherwise, it's a direction
4781     if (typeof(directionOrAdd) === 'boolean') {
4782       add = directionOrAdd;
4783     }
4784     else {
4785       direction = directionOrAdd;
4786     }
4787     
4788     if (!add) {
4789       self.resetColumnSorting(column);
4790       column.sort.priority = 0;
4791     }
4792     else {
4793       column.sort.priority = self.getNextColumnSortPriority();
4794     }
4795
4796     if (!direction) {
4797       // Figure out the sort direction
4798       if (column.sort.direction && column.sort.direction === uiGridConstants.ASC) {
4799         column.sort.direction = uiGridConstants.DESC;
4800       }
4801       else if (column.sort.direction && column.sort.direction === uiGridConstants.DESC) {
4802         if ( column.colDef && column.colDef.suppressRemoveSort ){
4803           column.sort.direction = uiGridConstants.ASC;
4804         } else {
4805           column.sort.direction = null;
4806         }
4807       }
4808       else {
4809         column.sort.direction = uiGridConstants.ASC;
4810       }
4811     }
4812     else {
4813       column.sort.direction = direction;
4814     }
4815     
4816     self.api.core.raise.sortChanged( self, self.getColumnSorting() );
4817
4818     return $q.when(column);
4819   };
4820   
4821   /**
4822    * communicate to outside world that we are done with initial rendering
4823    */
4824   Grid.prototype.renderingComplete = function(){
4825     if (angular.isFunction(this.options.onRegisterApi)) {
4826       this.options.onRegisterApi(this.api);
4827     }
4828     this.api.core.raise.renderingComplete( this.api );
4829   };
4830
4831   Grid.prototype.createRowHashMap = function createRowHashMap() {
4832     var self = this;
4833
4834     var hashMap = new RowHashMap();
4835     hashMap.grid = self;
4836
4837     self.rowHashMap = hashMap;
4838   };
4839   
4840   
4841   /**
4842    * @ngdoc function
4843    * @name refresh
4844    * @methodOf ui.grid.class:Grid
4845    * @description Refresh the rendered grid on screen.
4846    * 
4847    */
4848   Grid.prototype.refresh = function refresh() {
4849     gridUtil.logDebug('grid refresh');
4850     
4851     var self = this;
4852     
4853     var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
4854       self.setVisibleRows(renderableRows);
4855     });
4856
4857     var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
4858       self.setVisibleColumns(renderableColumns);
4859     });
4860
4861     return $q.all([p1, p2]).then(function () {
4862       self.redrawInPlace();
4863
4864       self.refreshCanvas(true);
4865     });
4866   };  
4867   
4868   /**
4869    * @ngdoc function
4870    * @name refreshRows
4871    * @methodOf ui.grid.class:Grid
4872    * @description Refresh the rendered rows on screen?  Note: not functional at present 
4873    * @returns {promise} promise that is resolved when render completes?
4874    * 
4875    */
4876   Grid.prototype.refreshRows = function refreshRows() {
4877     var self = this;
4878     
4879     return self.processRowsProcessors(self.rows)
4880       .then(function (renderableRows) {
4881         self.setVisibleRows(renderableRows);
4882
4883         self.redrawInPlace();
4884
4885         self.refreshCanvas( true );
4886       });
4887   };
4888
4889   /**
4890    * @ngdoc function
4891    * @name redrawCanvas
4892    * @methodOf ui.grid.class:Grid
4893    * @description TBD
4894    * @params {object} buildStyles optional parameter.  Use TBD
4895    * @returns {promise} promise that is resolved when the canvas
4896    * has been refreshed
4897    * 
4898    */
4899   Grid.prototype.refreshCanvas = function(buildStyles) {
4900     var self = this;
4901     
4902     if (buildStyles) {
4903       self.buildStyles();
4904     }
4905
4906     var p = $q.defer();
4907
4908     // Get all the header heights
4909     var containerHeadersToRecalc = [];
4910     for (var containerId in self.renderContainers) {
4911       if (self.renderContainers.hasOwnProperty(containerId)) {
4912         var container = self.renderContainers[containerId];
4913
4914         // Skip containers that have no canvasWidth set yet
4915         if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
4916           continue;
4917         }
4918
4919         if (container.header) {
4920           containerHeadersToRecalc.push(container);
4921         }
4922       }
4923     }
4924
4925     if (containerHeadersToRecalc.length > 0) {
4926       // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
4927       $timeout(function() {
4928         // var oldHeaderHeight = self.grid.headerHeight;
4929         // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
4930
4931         var rebuildStyles = false;
4932
4933         // Get all the header heights
4934         var maxHeight = 0;
4935         var i, container;
4936         for (i = 0; i < containerHeadersToRecalc.length; i++) {
4937           container = containerHeadersToRecalc[i];
4938
4939           // Skip containers that have no canvasWidth set yet
4940           if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
4941             continue;
4942           }
4943
4944           if (container.header) {
4945             var oldHeaderHeight = container.headerHeight;
4946             var headerHeight = gridUtil.outerElementHeight(container.header);
4947
4948             container.headerHeight = parseInt(headerHeight, 10);
4949
4950             if (oldHeaderHeight !== headerHeight) {
4951               rebuildStyles = true;
4952             }
4953
4954             // 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
4955             var topBorder = gridUtil.getBorderSize(container.header, 'top');
4956             var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
4957             var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
4958
4959             innerHeaderHeight  = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
4960
4961             container.innerHeaderHeight = innerHeaderHeight;
4962
4963             // Save the largest header height for use later
4964             if (innerHeaderHeight > maxHeight) {
4965               maxHeight = innerHeaderHeight;
4966             }
4967           }
4968         }
4969
4970         // Go through all the headers
4971         for (i = 0; i < containerHeadersToRecalc.length; i++) {
4972           container = containerHeadersToRecalc[i];
4973
4974           // If this header's height is less than another header's height, then explicitly set it so they're the same and one isn't all offset and weird looking
4975           if (container.headerHeight < maxHeight) {
4976             container.explicitHeaderHeight = maxHeight;
4977           }
4978         }
4979
4980         // Rebuild styles if the header height has changed
4981         //   The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
4982         if (buildStyles && rebuildStyles) {
4983           self.buildStyles();
4984         }
4985
4986         p.resolve();
4987       });
4988     }
4989     else {
4990       // Timeout still needs to be here to trigger digest after styles have been rebuilt
4991       $timeout(function() {
4992         p.resolve();
4993       });
4994     }
4995
4996     return p.promise;
4997   };
4998
4999
5000   /**
5001    * @ngdoc function
5002    * @name redrawCanvas
5003    * @methodOf ui.grid.class:Grid
5004    * @description Redraw the rows and columns based on our current scroll position
5005    * 
5006    */
5007   Grid.prototype.redrawInPlace = function redrawInPlace() {
5008     // gridUtil.logDebug('redrawInPlace');
5009     
5010     var self = this;
5011
5012     for (var i in self.renderContainers) {
5013       var container = self.renderContainers[i];
5014
5015       // gridUtil.logDebug('redrawing container', i);
5016
5017       container.adjustRows(container.prevScrollTop, null);
5018       container.adjustColumns(container.prevScrollLeft, null);
5019     }
5020   };
5021
5022
5023   // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
5024   function RowHashMap() {}
5025
5026   RowHashMap.prototype = {
5027     /**
5028      * Store key value pair
5029      * @param key key to store can be any type
5030      * @param value value to store can be any type
5031      */
5032     put: function(key, value) {
5033       this[this.grid.options.rowIdentity(key)] = value;
5034     },
5035
5036     /**
5037      * @param key
5038      * @returns {Object} the value for the key
5039      */
5040     get: function(key) {
5041       return this[this.grid.options.rowIdentity(key)];
5042     },
5043
5044     /**
5045      * Remove the key/value pair
5046      * @param key
5047      */
5048     remove: function(key) {
5049       var value = this[key = this.grid.options.rowIdentity(key)];
5050       delete this[key];
5051       return value;
5052     }
5053   };
5054
5055
5056
5057   return Grid;
5058
5059 }]);
5060
5061 })();
5062
5063 (function () {
5064
5065   angular.module('ui.grid')
5066     .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
5067       function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
5068         /**
5069          * @ngdoc function
5070          * @name ui.grid.class:GridApi
5071          * @description GridApi provides the ability to register public methods events inside the grid and allow
5072          * for other components to use the api via featureName.methodName and featureName.on.eventName(function(args){}
5073          * @param {object} grid grid that owns api
5074          */
5075         var GridApi = function GridApi(grid) {
5076           this.grid = grid;
5077           this.listeners = [];
5078           
5079           /**
5080            * @ngdoc function
5081            * @name renderingComplete
5082            * @methodOf  ui.grid.core.api:PublicApi
5083            * @description Rendering is complete, called at the same
5084            * time as `onRegisterApi`, but provides a way to obtain
5085            * that same event within features without stopping end
5086            * users from getting at the onRegisterApi method.
5087            * 
5088            * Included in gridApi so that it's always there - otherwise
5089            * there is still a timing problem with when a feature can
5090            * call this. 
5091            * 
5092            * @param {GridApi} gridApi the grid api, as normally 
5093            * returned in the onRegisterApi method
5094            * 
5095            * @example
5096            * <pre>
5097            *      gridApi.core.on.renderingComplete( grid );
5098            * </pre>
5099            */
5100           this.registerEvent( 'core', 'renderingComplete' );
5101
5102           /**
5103            * @ngdoc event
5104            * @name filterChanged
5105            * @eventOf  ui.grid.core.api:PublicApi
5106            * @description  is raised after the filter is changed.  The nature
5107            * of the watch expression doesn't allow notification of what changed,
5108            * so the receiver of this event will need to re-extract the filter 
5109            * conditions from the columns.
5110            * 
5111            */
5112           this.registerEvent( 'core', 'filterChanged' );
5113
5114           /**
5115            * @ngdoc function
5116            * @name setRowInvisible
5117            * @methodOf  ui.grid.core.api:PublicApi
5118            * @description Sets an override on the row to make it always invisible,
5119            * which will override any filtering or other visibility calculations.  
5120            * If the row is currently visible then sets it to invisible and calls
5121            * both grid refresh and emits the rowsVisibleChanged event
5122            * @param {object} rowEntity gridOptions.data[] array instance
5123            */
5124           this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
5125       
5126           /**
5127            * @ngdoc function
5128            * @name clearRowInvisible
5129            * @methodOf  ui.grid.core.api:PublicApi
5130            * @description Clears any override on visibility for the row so that it returns to 
5131            * using normal filtering and other visibility calculations.  
5132            * If the row is currently invisible then sets it to visible and calls
5133            * both grid refresh and emits the rowsVisibleChanged event
5134            * TODO: if a filter is active then we can't just set it to visible?
5135            * @param {object} rowEntity gridOptions.data[] array instance
5136            */
5137           this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
5138       
5139           /**
5140            * @ngdoc function
5141            * @name getVisibleRows
5142            * @methodOf  ui.grid.core.api:PublicApi
5143            * @description Returns all visible rows
5144            * @param {Grid} grid the grid you want to get visible rows from
5145            * @returns {array} an array of gridRow 
5146            */
5147           this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
5148           
5149           /**
5150            * @ngdoc event
5151            * @name rowsVisibleChanged
5152            * @eventOf  ui.grid.core.api:PublicApi
5153            * @description  is raised after the rows that are visible
5154            * change.  The filtering is zero-based, so it isn't possible
5155            * to say which rows changed (unlike in the selection feature).
5156            * We can plausibly know which row was changed when setRowInvisible
5157            * is called, but in that situation the user already knows which row
5158            * they changed.  When a filter runs we don't know what changed, 
5159            * and that is the one that would have been useful.
5160            * 
5161            */
5162           this.registerEvent( 'core', 'rowsVisibleChanged' );
5163         };
5164
5165         /**
5166          * @ngdoc function
5167          * @name ui.grid.class:suppressEvents
5168          * @methodOf ui.grid.class:GridApi
5169          * @description Used to execute a function while disabling the specified event listeners.
5170          * Disables the listenerFunctions, executes the callbackFn, and then enables
5171          * the listenerFunctions again
5172          * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
5173          * functions that were used in the .on.eventName method
5174          * @param {object} callBackFn function to execute
5175          * @example
5176          * <pre>
5177          *    var navigate = function (newRowCol, oldRowCol){
5178          *       //do something on navigate
5179          *    }
5180          *
5181          *    gridApi.cellNav.on.navigate(scope,navigate);
5182          *
5183          *
5184          *    //call the scrollTo event and suppress our navigate listener
5185          *    //scrollTo will still raise the event for other listeners
5186          *    gridApi.suppressEvents(navigate, function(){
5187          *       gridApi.cellNav.scrollTo(aRow, aCol);
5188          *    });
5189          *
5190          * </pre>
5191          */
5192         GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
5193           var self = this;
5194           var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
5195
5196           //find all registered listeners
5197           var foundListeners = [];
5198           listeners.forEach(function (l) {
5199             foundListeners = self.listeners.filter(function (lstnr) {
5200               return l === lstnr.handler;
5201             });
5202           });
5203
5204           //deregister all the listeners
5205           foundListeners.forEach(function(l){
5206             l.dereg();
5207           });
5208
5209           callBackFn();
5210
5211           //reregister all the listeners
5212           foundListeners.forEach(function(l){
5213               l.dereg = registerEventWithAngular(l.scope, l.eventId, l.handler, self.grid);
5214           });
5215
5216         };
5217
5218         /**
5219          * @ngdoc function
5220          * @name registerEvent
5221          * @methodOf ui.grid.class:GridApi
5222          * @description Registers a new event for the given feature
5223          * @param {string} featureName name of the feature that raises the event
5224          * @param {string} eventName  name of the event
5225          */
5226         GridApi.prototype.registerEvent = function (featureName, eventName) {
5227           var self = this;
5228           if (!self[featureName]) {
5229             self[featureName] = {};
5230           }
5231
5232           var feature = self[featureName];
5233           if (!feature.on) {
5234             feature.on = {};
5235             feature.raise = {};
5236           }
5237
5238           var eventId = self.grid.id + featureName + eventName;
5239
5240           // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
5241           feature.raise[eventName] = function () {
5242             $rootScope.$broadcast.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
5243           };
5244
5245           // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
5246           feature.on[eventName] = function (scope, handler) {
5247             var dereg = registerEventWithAngular(scope, eventId, handler, self.grid);
5248
5249             //track our listener so we can turn off and on
5250             var listener = {handler: handler, dereg: dereg, eventId: eventId, scope: scope};
5251             self.listeners.push(listener);
5252
5253             //destroy tracking when scope is destroyed
5254             //wanted to remove the listener from the array but angular does
5255             //strange things in scope.$destroy so I could not access the listener array
5256             scope.$on('$destroy', function() {
5257               listener.dereg = null;
5258               listener.handler = null;
5259               listener.eventId = null;
5260               listener.scope = null;
5261             });
5262           };
5263         };
5264
5265         function registerEventWithAngular(scope, eventId, handler, grid) {
5266           return scope.$on(eventId, function (event) {
5267             var args = Array.prototype.slice.call(arguments);
5268             args.splice(0, 1); //remove evt argument
5269             handler.apply(grid.api, args);
5270           });
5271         }
5272
5273         /**
5274          * @ngdoc function
5275          * @name registerEventsFromObject
5276          * @methodOf ui.grid.class:GridApi
5277          * @description Registers features and events from a simple objectMap.
5278          * eventObjectMap must be in this format (multiple features allowed)
5279          * <pre>
5280          * {featureName:
5281          *        {
5282          *          eventNameOne:function(args){},
5283          *          eventNameTwo:function(args){}
5284          *        }
5285          *  }
5286          * </pre>
5287          * @param {object} eventObjectMap map of feature/event names
5288          */
5289         GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
5290           var self = this;
5291           var features = [];
5292           angular.forEach(eventObjectMap, function (featProp, featPropName) {
5293             var feature = {name: featPropName, events: []};
5294             angular.forEach(featProp, function (prop, propName) {
5295               feature.events.push(propName);
5296             });
5297             features.push(feature);
5298           });
5299
5300           features.forEach(function (feature) {
5301             feature.events.forEach(function (event) {
5302               self.registerEvent(feature.name, event);
5303             });
5304           });
5305
5306         };
5307
5308         /**
5309          * @ngdoc function
5310          * @name registerMethod
5311          * @methodOf ui.grid.class:GridApi
5312          * @description Registers a new event for the given feature
5313          * @param {string} featureName name of the feature
5314          * @param {string} methodName  name of the method
5315          * @param {object} callBackFn function to execute
5316          * @param {object} thisArg binds callBackFn 'this' to thisArg.  Defaults to gridApi.grid
5317          */
5318         GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, thisArg) {
5319           if (!this[featureName]) {
5320             this[featureName] = {};
5321           }
5322
5323           var feature = this[featureName];
5324
5325           feature[methodName] = gridUtil.createBoundedWrapper(thisArg || this.grid, callBackFn);
5326         };
5327
5328         /**
5329          * @ngdoc function
5330          * @name registerMethodsFromObject
5331          * @methodOf ui.grid.class:GridApi
5332          * @description Registers features and methods from a simple objectMap.
5333          * eventObjectMap must be in this format (multiple features allowed)
5334          * <br>
5335          * {featureName:
5336          *        {
5337          *          methodNameOne:function(args){},
5338          *          methodNameTwo:function(args){}
5339          *        }
5340          * @param {object} eventObjectMap map of feature/event names
5341          * @param {object} thisArg binds this to thisArg for all functions.  Defaults to gridApi.grid
5342          */
5343         GridApi.prototype.registerMethodsFromObject = function (methodMap, thisArg) {
5344           var self = this;
5345           var features = [];
5346           angular.forEach(methodMap, function (featProp, featPropName) {
5347             var feature = {name: featPropName, methods: []};
5348             angular.forEach(featProp, function (prop, propName) {
5349               feature.methods.push({name: propName, fn: prop});
5350             });
5351             features.push(feature);
5352           });
5353
5354           features.forEach(function (feature) {
5355             feature.methods.forEach(function (method) {
5356               self.registerMethod(feature.name, method.name, method.fn, thisArg);
5357             });
5358           });
5359
5360         };
5361         
5362         return GridApi;
5363
5364       }]);
5365
5366 })();
5367
5368 (function(){
5369
5370 angular.module('ui.grid')
5371 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
5372
5373   /**
5374    * @ngdoc function
5375    * @name ui.grid.class:GridColumn
5376    * @description Represents the viewModel for each column.  Any state or methods needed for a Grid Column
5377    * are defined on this prototype
5378    * @param {ColDef} colDef Column definition.
5379    * @param {number} index the current position of the column in the array
5380    * @param {Grid} grid reference to the grid
5381    */
5382    
5383    /**
5384     * ******************************************************************************************
5385     * PaulL1: Ugly hack here in documentation.  These properties are clearly properties of GridColumn, 
5386     * and need to be noted as such for those extending and building ui-grid itself.
5387     * However, from an end-developer perspective, they interact with all these through columnDefs,
5388     * and they really need to be documented there.  I feel like they're relatively static, and
5389     * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
5390     * comment block.  Ugh.
5391     * 
5392     */
5393
5394    /** 
5395     * @ngdoc property
5396     * @name name
5397     * @propertyOf ui.grid.class:GridColumn
5398     * @description (mandatory) each column should have a name, although for backward
5399     * compatibility with 2.x name can be omitted if field is present
5400     *
5401     */
5402
5403    /** 
5404     * @ngdoc property
5405     * @name name
5406     * @propertyOf ui.grid.class:GridOptions.columnDef
5407     * @description (mandatory) each column should have a name, although for backward
5408     * compatibility with 2.x name can be omitted if field is present
5409     *
5410     */
5411     
5412     /** 
5413     * @ngdoc property
5414     * @name displayName
5415     * @propertyOf ui.grid.class:GridColumn
5416     * @description Column name that will be shown in the header.  If displayName is not
5417     * provided then one is generated using the name.
5418     *
5419     */
5420
5421     /** 
5422     * @ngdoc property
5423     * @name displayName
5424     * @propertyOf ui.grid.class:GridOptions.columnDef
5425     * @description Column name that will be shown in the header.  If displayName is not
5426     * provided then one is generated using the name.
5427     *
5428     */
5429        
5430     /** 
5431     * @ngdoc property
5432     * @name field
5433     * @propertyOf ui.grid.class:GridColumn
5434     * @description field must be provided if you wish to bind to a 
5435     * property in the data source.  Should be an angular expression that evaluates against grid.options.data 
5436     * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
5437     * See the angular docs on binding expressions.
5438     *
5439     */
5440     
5441     /** 
5442     * @ngdoc property
5443     * @name field
5444     * @propertyOf ui.grid.class:GridOptions.columnDef
5445     * @description field must be provided if you wish to bind to a 
5446     * property in the data source.  Should be an angular expression that evaluates against grid.options.data 
5447     * array element.  Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
5448     * See the angular docs on binding expressions.
5449     *
5450     */
5451     
5452     /** 
5453     * @ngdoc property
5454     * @name filter
5455     * @propertyOf ui.grid.class:GridColumn
5456     * @description Filter on this column.  
5457     * @example
5458     * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...' }</pre>
5459     *
5460     */
5461
5462     /** 
5463     * @ngdoc property
5464     * @name filter
5465     * @propertyOf ui.grid.class:GridOptions.columnDef
5466     * @description Specify a single filter field on this column.
5467     * @example
5468     * <pre>$scope.gridOptions.columnDefs = [ 
5469     *   {
5470     *     field: 'field1',
5471     *     filter: {
5472     *       condition: uiGridConstants.filter.STARTS_WITH,
5473     *       placeholder: 'starts with...'
5474     *     }
5475     *   }
5476     * ]; </pre>
5477     *
5478     */
5479     
5480   /**
5481    * @ngdoc method
5482    * @methodOf ui.grid.class:GridColumn
5483    * @name GridColumn
5484    * @description Initializes a gridColumn
5485    * @param {ColumnDef} colDef the column def to associate with this column
5486    * @param {number} uid the unique and immutable uid we'd like to allocate to this column
5487    * @param {Grid} grid the grid we'd like to create this column in
5488    */ 
5489   function GridColumn(colDef, uid, grid) {
5490     var self = this;
5491
5492     self.grid = grid;
5493     self.uid = uid;
5494
5495     self.updateColumnDef(colDef);
5496   }
5497
5498
5499   /**
5500    * @ngdoc method
5501    * @methodOf ui.grid.class:GridColumn
5502    * @name setPropertyOrDefault
5503    * @description Sets a property on the column using the passed in columnDef, and
5504    * setting the defaultValue if the value cannot be found on the colDef
5505    * @param {ColumnDef} colDef the column def to look in for the property value
5506    * @param {string} propName the property name we'd like to set
5507    * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
5508    */ 
5509   GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
5510     var self = this;
5511
5512     // Use the column definition filter if we were passed it
5513     if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
5514       self[propName] = colDef[propName];
5515     }
5516     // Otherwise use our own if it's set
5517     else if (typeof(self[propName]) !== 'undefined') {
5518       self[propName] = self[propName];
5519     }
5520     // Default to empty object for the filter
5521     else {
5522       self[propName] = defaultValue ? defaultValue : {};
5523     }
5524   };
5525
5526   
5527   
5528    /** 
5529     * @ngdoc property
5530     * @name width
5531     * @propertyOf ui.grid.class:GridOptions.columnDef
5532     * @description sets the column width.  Can be either 
5533     * a number or a percentage, or an * for auto.
5534     * @example
5535     * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
5536     *                                          { field: 'field2', width: '20%'},
5537     *                                          { field: 'field3', width: '*' }]; </pre>
5538     *
5539     */
5540
5541    /** 
5542     * @ngdoc property
5543     * @name minWidth
5544     * @propertyOf ui.grid.class:GridOptions.columnDef
5545     * @description sets the minimum column width.  Should be a number.
5546     * @example
5547     * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
5548     *
5549     */
5550
5551    /** 
5552     * @ngdoc property
5553     * @name maxWidth
5554     * @propertyOf ui.grid.class:GridOptions.columnDef
5555     * @description sets the maximum column width.  Should be a number.
5556     * @example
5557     * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
5558     *
5559     */
5560
5561    /** 
5562     * @ngdoc property
5563     * @name visible
5564     * @propertyOf ui.grid.class:GridOptions.columnDef
5565     * @description sets whether or not the column is visible
5566     * </br>Default is true
5567     * @example
5568     * <pre>  $scope.gridOptions.columnDefs = [ 
5569     *     { field: 'field1', visible: true},
5570     *     { field: 'field2', visible: false }
5571     *   ]; </pre>
5572     *
5573     */
5574    
5575   /**
5576    * @ngdoc property
5577    * @name sort
5578    * @propertyOf ui.grid.class:GridOptions.columnDef
5579    * @description Can be used to set the sort direction for the column, values are
5580    * uiGridConstants.ASC or uiGridConstants.DESC
5581    * @example
5582    * <pre>  $scope.gridOptions.columnDefs = [ { field: 'field1', sort: { direction: uiGridConstants.ASC }}] </pre>
5583    */
5584   
5585
5586     /** 
5587     * @ngdoc property
5588     * @name sortingAlgorithm
5589     * @propertyOf ui.grid.class:GridColumn
5590     * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters 
5591     * like any normal sorting function.
5592     *
5593     */
5594
5595     /** 
5596     * @ngdoc property
5597     * @name sortingAlgorithm
5598     * @propertyOf ui.grid.class:GridOptions.columnDef
5599     * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters 
5600     * like any normal sorting function.
5601     *
5602     */
5603       
5604    /** 
5605     * @ngdoc array
5606     * @name filters
5607     * @propertyOf ui.grid.class:GridOptions.columnDef
5608     * @description Specify multiple filter fields.
5609     * @example
5610     * <pre>$scope.gridOptions.columnDefs = [ 
5611     *   {
5612     *     field: 'field1', filters: [
5613     *       {
5614     *         condition: uiGridConstants.filter.STARTS_WITH,
5615     *         placeholder: 'starts with...'
5616     *       },
5617     *       {
5618     *         condition: uiGridConstants.filter.ENDS_WITH,
5619     *         placeholder: 'ends with...'
5620     *       }
5621     *     ]
5622     *   }
5623     * ]; </pre>
5624     *
5625     * 
5626     */ 
5627    
5628    /** 
5629     * @ngdoc array
5630     * @name filters
5631     * @propertyOf ui.grid.class:GridColumn
5632     * @description Filters for this column. Includes 'term' property bound to filter input elements.
5633     * @example
5634     * <pre>[
5635     *   {
5636     *     term: 'foo', // ngModel for <input>
5637     *     condition: uiGridConstants.filter.STARTS_WITH,
5638     *     placeholder: 'starts with...'
5639     *   },
5640     *   {
5641     *     term: 'baz',
5642     *     condition: uiGridConstants.filter.ENDS_WITH,
5643     *     placeholder: 'ends with...'
5644     *   }
5645     * ] </pre>
5646     *
5647     * 
5648     */   
5649
5650    /** 
5651     * @ngdoc array
5652     * @name menuItems
5653     * @propertyOf ui.grid.class:GridOptions.columnDef
5654     * @description used to add menu items to a column.  Refer to the tutorial on this 
5655     * functionality.  A number of settings are supported:
5656     * 
5657     * - title: controls the title that is displayed in the menu
5658     * - icon: the icon shown alongside that title
5659     * - action: the method to call when the menu is clicked
5660     * - shown: a function to evaluate to determine whether or not to show the item
5661     * - active: a function to evaluate to determine whether or not the item is currently selected
5662     * - context: context to pass to the action function??
5663     * @example
5664     * <pre>  $scope.gridOptions.columnDefs = [ 
5665     *   { field: 'field1', menuItems: [
5666     *     {
5667     *       title: 'Outer Scope Alert',
5668     *       icon: 'ui-grid-icon-info-circled',
5669     *       action: function($event) {
5670     *         this.context.blargh(); // $scope.blargh() would work too, this is just an example
5671     *       },
5672     *       shown: function() { return true; },
5673     *       active: function() { return true; },
5674     *       context: $scope
5675     *     },
5676     *     {
5677     *       title: 'Grid ID',
5678     *       action: function() {
5679     *         alert('Grid ID: ' + this.grid.id);
5680     *       }
5681     *     }
5682     *   ] }]; </pre>
5683     *
5684     */   
5685
5686   /**
5687    * @ngdoc method
5688    * @methodOf ui.grid.class:GridColumn
5689    * @name updateColumnDef
5690    * @description Moves settings from the columnDef down onto the column,
5691    * and sets properties as appropriate
5692    * @param {ColumnDef} colDef the column def to look in for the property value
5693    */ 
5694   GridColumn.prototype.updateColumnDef = function(colDef) {
5695     var self = this;
5696
5697     self.colDef = colDef;
5698
5699     if (colDef.name === undefined) {
5700       throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
5701     }
5702     
5703     var parseErrorMsg = "Cannot parse column width '" + colDef.width + "' for column named '" + colDef.name + "'";
5704
5705     // If width is not defined, set it to a single star
5706     if (gridUtil.isNullOrUndefined(self.width) || !angular.isNumber(self.width)) {
5707       if (gridUtil.isNullOrUndefined(colDef.width)) {
5708         self.width = '*';
5709       }
5710       else {
5711         // If the width is not a number
5712         if (!angular.isNumber(colDef.width)) {
5713           // See if it ends with a percent
5714           if (gridUtil.endsWith(colDef.width, '%')) {
5715             // If so we should be able to parse the non-percent-sign part to a number
5716             var percentStr = colDef.width.replace(/%/g, '');
5717             var percent = parseInt(percentStr, 10);
5718             if (isNaN(percent)) {
5719               throw new Error(parseErrorMsg);
5720             }
5721             self.width = colDef.width;
5722           }
5723           // And see if it's a number string
5724           else if (colDef.width.match(/^(\d+)$/)) {
5725             self.width = parseInt(colDef.width.match(/^(\d+)$/)[1], 10);
5726           }
5727           // Otherwise it should be a string of asterisks
5728           else if (colDef.width.match(/^\*+$/)) {
5729             self.width = colDef.width;
5730           }
5731           // No idea, throw an Error
5732           else {
5733             throw new Error(parseErrorMsg);
5734           }
5735         }
5736         // Is a number, use it as the width
5737         else {
5738           self.width = colDef.width;
5739         }
5740       }
5741     }
5742
5743     // Remove this column from the grid sorting
5744     GridColumn.prototype.unsort = function () {
5745       this.sort = {};
5746       self.grid.api.core.raise.sortChanged( self, self.grid.getColumnSorting() );
5747     };
5748
5749     self.minWidth = !colDef.minWidth ? 30 : colDef.minWidth;
5750     self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth;
5751
5752     //use field if it is defined; name if it is not
5753     self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
5754     
5755     if ( typeof( self.field ) !== 'string' ){
5756       gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
5757     }
5758     
5759     self.name = colDef.name;
5760
5761     // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
5762     self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
5763
5764     //self.originalIndex = index;
5765
5766     self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
5767     self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
5768
5769     /**
5770      * @ngdoc property
5771      * @name footerCellClass
5772      * @propertyOf ui.grid.class:GridOptions.columnDef
5773      * @description footerCellClass can be a string specifying the class to append to a cell
5774      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5775      *
5776      */
5777     self.footerCellClass = colDef.footerCellClass;
5778
5779     /**
5780      * @ngdoc property
5781      * @name cellClass
5782      * @propertyOf ui.grid.class:GridOptions.columnDef
5783      * @description cellClass can be a string specifying the class to append to a cell
5784      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5785      *
5786      */
5787     self.cellClass = colDef.cellClass;
5788
5789     /**
5790      * @ngdoc property
5791      * @name headerCellClass
5792      * @propertyOf ui.grid.class:GridOptions.columnDef
5793      * @description headerCellClass can be a string specifying the class to append to a cell
5794      * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5795      *
5796      */
5797     self.headerCellClass = colDef.headerCellClass;
5798
5799     /**
5800      * @ngdoc property
5801      * @name cellFilter
5802      * @propertyOf ui.grid.class:GridOptions.columnDef
5803      * @description cellFilter is a filter to apply to the content of each cell
5804      * @example
5805      * <pre>
5806      *   gridOptions.columnDefs[0].cellFilter = 'date'
5807      *
5808      */
5809     self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
5810
5811     /**
5812      * @ngdoc property
5813      * @name headerCellFilter
5814      * @propertyOf ui.grid.class:GridOptions.columnDef
5815      * @description headerCellFilter is a filter to apply to the content of the column header
5816      * @example
5817      * <pre>
5818      *   gridOptions.columnDefs[0].headerCellFilter = 'translate'
5819      *
5820      */
5821     self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
5822
5823     self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
5824
5825     self.headerClass = colDef.headerClass;
5826     //self.cursor = self.sortable ? 'pointer' : 'default';
5827
5828     self.visible = true;
5829
5830     // Turn on sorting by default
5831     self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
5832     self.sortingAlgorithm = colDef.sortingAlgorithm;
5833
5834     // Turn on filtering by default (it's disabled by default at the Grid level)
5835     self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
5836
5837     // self.menuItems = colDef.menuItems;
5838     self.setPropertyOrDefault(colDef, 'menuItems', []);
5839
5840     // Use the column definition sort if we were passed it
5841     self.setPropertyOrDefault(colDef, 'sort');
5842
5843     // Set up default filters array for when one is not provided.
5844     //   In other words, this (in column def):
5845     //   
5846     //       filter: { term: 'something', flags: {}, condition: [CONDITION] }
5847     //       
5848     //   is just shorthand for this:
5849     //   
5850     //       filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
5851     //       
5852     var defaultFilters = [];
5853     if (colDef.filter) {
5854       defaultFilters.push(colDef.filter);
5855     }
5856     else if (self.enableFiltering && self.grid.options.enableFiltering) {
5857       // Add an empty filter definition object, which will
5858       // translate to a guessed condition and no pre-populated
5859       // value for the filter <input>.
5860       defaultFilters.push({});
5861     }
5862
5863     /**
5864      * @ngdoc object
5865      * @name ui.grid.class:GridOptions.columnDef.filter
5866      * @propertyOf ui.grid.class:GridOptions.columnDef
5867      * @description An object defining filtering for a column.
5868      */    
5869
5870     /**
5871      * @ngdoc property
5872      * @name condition
5873      * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5874      * @description Defines how rows are chosen as matching the filter term. This can be set to
5875      * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
5876      * that gets passed the following arguments: [searchTerm, cellValue, row, column].
5877      */
5878     
5879     /**
5880      * @ngdoc property
5881      * @name term
5882      * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5883      * @description If set, the filter field will be pre-populated
5884      * with this value.
5885      */
5886
5887     /**
5888      * @ngdoc property
5889      * @name placeholder
5890      * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5891      * @description String that will be set to the <input>.placeholder attribute.
5892      */
5893
5894     /*
5895
5896       self.filters = [
5897         {
5898           term: 'search term'
5899           condition: uiGridConstants.filter.CONTAINS,
5900           placeholder: 'my placeholder',
5901           flags: {
5902             caseSensitive: true
5903           }
5904         }
5905       ]
5906
5907     */
5908
5909     self.setPropertyOrDefault(colDef, 'filter');
5910     self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
5911   };
5912
5913
5914
5915
5916     /**
5917      * @ngdoc function
5918      * @name getColClass
5919      * @methodOf ui.grid.class:GridColumn
5920      * @description Returns the class name for the column
5921      * @param {bool} prefixDot  if true, will return .className instead of className
5922      */
5923     GridColumn.prototype.getColClass = function (prefixDot) {
5924       var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
5925
5926       return prefixDot ? '.' + cls : cls;
5927     };
5928
5929     /**
5930      * @ngdoc function
5931      * @name getColClassDefinition
5932      * @methodOf ui.grid.class:GridColumn
5933      * @description Returns the class definition for th column
5934      */
5935     GridColumn.prototype.getColClassDefinition = function () {
5936       return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { width: ' + this.drawnWidth + 'px; }';
5937     };
5938
5939     /**
5940      * @ngdoc function
5941      * @name getRenderContainer
5942      * @methodOf ui.grid.class:GridColumn
5943      * @description Returns the render container object that this column belongs to.
5944      *
5945      * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
5946      */
5947     GridColumn.prototype.getRenderContainer = function getRenderContainer() {
5948       var self = this;
5949
5950       var containerId = self.renderContainer;
5951
5952       if (containerId === null || containerId === '' || containerId === undefined) {
5953         containerId = 'body';
5954       }
5955
5956       return self.grid.renderContainers[containerId];
5957     };
5958
5959     /**
5960      * @ngdoc function
5961      * @name showColumn
5962      * @methodOf ui.grid.class:GridColumn
5963      * @description Makes the column visible by setting colDef.visible = true
5964      */
5965     GridColumn.prototype.showColumn = function() {
5966         this.colDef.visible = true;
5967     };
5968
5969     /**
5970      * @ngdoc function
5971      * @name hideColumn
5972      * @methodOf ui.grid.class:GridColumn
5973      * @description Hides the column by setting colDef.visible = false
5974      */
5975     GridColumn.prototype.hideColumn = function() {
5976         this.colDef.visible = false;
5977     };
5978
5979     /**
5980      * @ngdoc function
5981      * @name getAggregationValue
5982      * @methodOf ui.grid.class:GridColumn
5983      * @description gets the aggregation value based on the aggregation type for this column
5984      */
5985     GridColumn.prototype.getAggregationValue = function () {
5986       var self = this;
5987       var result = 0;
5988       var visibleRows = self.grid.getVisibleRows();
5989       var cellValues = [];
5990       angular.forEach(visibleRows, function (row) {
5991         var cellValue = self.grid.getCellValue(row, self);
5992         if (angular.isNumber(cellValue)) {
5993           cellValues.push(cellValue);
5994         }
5995       });
5996       if (angular.isFunction(self.aggregationType)) {
5997         return self.aggregationType(visibleRows, self);
5998       }
5999       else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6000         return self.getAggregationText('aggregation.count', self.grid.getVisibleRowCount());
6001       }
6002       else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6003         angular.forEach(cellValues, function (value) {
6004           result += value;
6005         });
6006         return self.getAggregationText('aggregation.sum', result);
6007       }
6008       else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6009         angular.forEach(cellValues, function (value) {
6010           result += value;
6011         });
6012         result = result / cellValues.length;
6013         return self.getAggregationText('aggregation.avg', result);
6014       }
6015       else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6016         return self.getAggregationText('aggregation.min', Math.min.apply(null, cellValues));
6017       }
6018       else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6019         return self.getAggregationText('aggregation.max', Math.max.apply(null, cellValues));
6020       }
6021       else {
6022         return '\u00A0';
6023       }
6024     };
6025     
6026    /** 
6027     * @ngdoc property
6028     * @name aggregationHideLabel
6029     * @propertyOf ui.grid.class:GridOptions.columnDef
6030     * @description defaults to false, if set to true hides the label text
6031     * in the aggregation footer, so only the value is displayed.
6032     *
6033     */
6034     /**
6035      * @ngdoc function
6036      * @name getAggregationText
6037      * @methodOf ui.grid.class:GridColumn
6038      * @description converts the aggregation value into a text string, including 
6039      * i18n and deciding whether or not to display based on colDef.aggregationHideLabel
6040      * 
6041      * @param {string} label the i18n lookup value to use for the column label
6042      * @param {number} value the calculated aggregate value for this column
6043      * 
6044      */
6045     GridColumn.prototype.getAggregationText = function ( label, value ) {
6046       var self = this;
6047       if ( self.colDef.aggregationHideLabel ){
6048         return value;
6049       } else {
6050         return i18nService.getSafeText(label) + value;
6051       }
6052     };
6053
6054     GridColumn.prototype.getCellTemplate = function () {
6055       var self = this;
6056
6057       return self.cellTemplatePromise;
6058     };
6059
6060     GridColumn.prototype.getCompiledElementFn = function () {
6061       var self = this;
6062       
6063       return self.compiledElementFnDefer.promise;
6064     };
6065
6066     return GridColumn;
6067   }]);
6068
6069 })();
6070
6071   (function(){
6072
6073 angular.module('ui.grid')
6074 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
6075
6076   /**
6077    * @ngdoc function
6078    * @name ui.grid.class:GridOptions
6079    * @description Default GridOptions class.  GridOptions are defined by the application developer and overlaid
6080    * over this object.  Setting gridOptions within your controller is the most common method for an application 
6081    * developer to configure the behaviour of their ui-grid
6082    * 
6083    * @example To define your gridOptions within your controller:
6084    * <pre>$scope.gridOptions = {
6085    *   data: $scope.myData,
6086    *   columnDefs: [ 
6087    *     { name: 'field1', displayName: 'pretty display name' },
6088    *     { name: 'field2', visible: false }
6089    *  ]
6090    * };</pre>
6091    * 
6092    * You can then use this within your html template, when you define your grid:
6093    * <pre>&lt;div ui-grid="gridOptions"&gt;&lt;/div&gt;</pre>
6094    *
6095    * To provide default options for all of the grids within your application, use an angular
6096    * decorator to modify the GridOptions factory.
6097    * <pre>app.config(function($provide){
6098    *    $provide.decorator('GridOptions',function($delegate){
6099    *      return function(){
6100    *        var defaultOptions = new $delegate();
6101    *        defaultOptions.excludeProperties = ['id' ,'$$hashKey'];
6102    *        return defaultOptions;
6103    *      };
6104    *    })
6105    *  })</pre>
6106    */
6107   return {
6108     initialize: function( baseOptions ){
6109       baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
6110   
6111       /**
6112        * @ngdoc object
6113        * @name data
6114        * @propertyOf ui.grid.class:GridOptions
6115        * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for 
6116        * the grid.  The most common case is an array of objects, where each object has a number of attributes.
6117        * Each attribute automatically becomes a column in your grid.  This array could, for example, be sourced from
6118        * an angularJS $resource query request.  The array can also contain complex objects.
6119        * 
6120        */
6121       baseOptions.data = baseOptions.data || [];
6122   
6123       /**
6124        * @ngdoc array
6125        * @name columnDefs
6126        * @propertyOf  ui.grid.class:GridOptions
6127        * @description Array of columnDef objects.  Only required property is name.
6128        * The individual options available in columnDefs are documented in the
6129        * {@link ui.grid.class:GridOptions.columnDef columnDef} section
6130        * </br>_field property can be used in place of name for backwards compatibility with 2.x_
6131        *  @example
6132        *
6133        * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
6134        *
6135        */
6136       baseOptions.columnDefs = baseOptions.columnDefs || [];
6137   
6138       /**
6139        * @ngdoc object
6140        * @name ui.grid.class:GridOptions.columnDef
6141        * @description Definition / configuration of an individual column, which would typically be
6142        * one of many column definitions within the gridOptions.columnDefs array
6143        * @example
6144        * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
6145        *
6146        */
6147   
6148           
6149       /**
6150        * @ngdoc array
6151        * @name excludeProperties
6152        * @propertyOf  ui.grid.class:GridOptions
6153        * @description Array of property names in data to ignore when auto-generating column names.  Provides the
6154        * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
6155        * to exclude. 
6156        * 
6157        * If columnDefs is defined, this will be ignored.
6158        * 
6159        * Defaults to ['$$hashKey']
6160        */
6161       
6162       baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
6163   
6164       /**
6165        * @ngdoc boolean
6166        * @name enableRowHashing
6167        * @propertyOf ui.grid.class:GridOptions
6168        * @description True by default. When enabled, this setting allows uiGrid to add
6169        * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
6170        * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
6171        * 
6172        * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
6173        * 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
6174        * and are altering the data set often.
6175        */
6176       baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
6177   
6178       /**
6179        * @ngdoc function
6180        * @name rowIdentity
6181        * @methodOf ui.grid.class:GridOptions
6182        * @description This function is used to get and, if necessary, set the value uniquely identifying this row.
6183        * 
6184        * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
6185        */
6186       baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
6187         return gridUtil.hashKey(row);
6188       };
6189   
6190       /**
6191        * @ngdoc function
6192        * @name getRowIdentity
6193        * @methodOf ui.grid.class:GridOptions
6194        * @description This function returns the identity value uniquely identifying this row.
6195        * 
6196        * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
6197        */
6198       baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
6199         return row.$$hashKey;
6200       };
6201   
6202       /**
6203        * @ngdoc property
6204        * @name headerRowHeight
6205        * @propertyOf ui.grid.class:GridOptions
6206        * @description The height of the header in pixels, defaults to 30
6207        *
6208        */
6209       baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
6210
6211       /**
6212        * @ngdoc property
6213        * @name rowHeight
6214        * @propertyOf ui.grid.class:GridOptions
6215        * @description The height of the row in pixels, defaults to 30
6216        *
6217        */
6218       baseOptions.rowHeight = baseOptions.rowHeight || 30;
6219       
6220       /**
6221        * @ngdoc property
6222        * @name maxVisibleRowCount
6223        * @propertyOf ui.grid.class:GridOptions
6224        * @description Defaults to 200
6225        *
6226        */
6227       baseOptions.maxVisibleRowCount = baseOptions.maxVisibleRowCount || 200;
6228   
6229       /**
6230        * @ngdoc integer
6231        * @name minRowsToShow
6232        * @propertyOf ui.grid.class:GridOptions
6233        * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
6234        */
6235       baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
6236   
6237       /**
6238        * @ngdoc property
6239        * @name showFooter
6240        * @propertyOf ui.grid.class:GridOptions
6241        * @description Whether or not to show the footer, defaults to false
6242        *
6243        */
6244       baseOptions.showFooter = baseOptions.showFooter === true;
6245
6246       /**
6247        * @ngdoc property
6248        * @name footerRowHeight
6249        * @propertyOf ui.grid.class:GridOptions
6250        * @description The height of the footer in pixels
6251        *
6252        */
6253       baseOptions.footerRowHeight = typeof(baseOptions.footerRowHeight) !== "undefined" ? baseOptions.footerRowHeight : 30;
6254   
6255       baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
6256
6257       /**
6258        * @ngdoc property
6259        * @name maxVisibleColumnCount
6260        * @propertyOf ui.grid.class:GridOptions
6261        * @description Defaults to 200
6262        *
6263        */
6264       baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
6265   
6266       /**
6267        * @ngdoc property
6268        * @name virtualizationThreshold
6269        * @propertyOf ui.grid.class:GridOptions
6270        * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
6271        */
6272       baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
6273   
6274       /**
6275        * @ngdoc property
6276        * @name columnVirtualizationThreshold
6277        * @propertyOf ui.grid.class:GridOptions
6278        * @description Turn virtualization on when number of columns goes over this number, defaults to 10
6279        */
6280       baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
6281   
6282       /**
6283        * @ngdoc property
6284        * @name excessRows
6285        * @propertyOf ui.grid.class:GridOptions
6286        * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
6287        * Defaults to 4
6288        */
6289       baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
6290       /**
6291        * @ngdoc property
6292        * @name scrollThreshold
6293        * @propertyOf ui.grid.class:GridOptions
6294        * @description Defaults to 4
6295        */
6296       baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
6297   
6298       /**
6299        * @ngdoc property
6300        * @name excessColumns
6301        * @propertyOf ui.grid.class:GridOptions
6302        * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
6303        * Defaults to 4
6304        */
6305       baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
6306       /**
6307        * @ngdoc property
6308        * @name horizontalScrollThreshold
6309        * @propertyOf ui.grid.class:GridOptions
6310        * @description Defaults to 4
6311        */
6312       baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
6313   
6314       /**
6315        * @ngdoc property
6316        * @name scrollThrottle
6317        * @propertyOf ui.grid.class:GridOptions
6318        * @description Default time to throttle scroll events to, defaults to 70ms
6319        */
6320       baseOptions.scrollThrottle = typeof(baseOptions.scrollThrottle) !== "undefined" ? baseOptions.scrollThrottle : 70;
6321   
6322       /**
6323        * @ngdoc boolean
6324        * @name enableSorting
6325        * @propertyOf ui.grid.class:GridOptions
6326        * @description True by default. When enabled, this setting adds sort
6327        * widgets to the column headers, allowing sorting of the data for the entire grid.
6328        * Sorting can then be disabled on individual columns using the columnDefs.
6329        */
6330       baseOptions.enableSorting = baseOptions.enableSorting !== false;
6331   
6332       /**
6333        * @ngdoc boolean
6334        * @name enableFiltering
6335        * @propertyOf ui.grid.class:GridOptions
6336        * @description False by default. When enabled, this setting adds filter 
6337        * boxes to each column header, allowing filtering within the column for the entire grid.
6338        * Filtering can then be disabled on individual columns using the columnDefs. 
6339        */
6340       baseOptions.enableFiltering = baseOptions.enableFiltering === true;
6341   
6342       /**
6343        * @ngdoc boolean
6344        * @name enableColumnMenus
6345        * @propertyOf ui.grid.class:GridOptions
6346        * @description True by default. When enabled, this setting displays a column
6347        * menu within each column.
6348        */
6349       baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
6350   
6351       /**
6352        * @ngdoc boolean
6353        * @name enableVerticalScrollbar
6354        * @propertyOf ui.grid.class:GridOptions
6355        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
6356        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED.
6357        */
6358       baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
6359       
6360       /**
6361        * @ngdoc boolean
6362        * @name enableHorizontalScrollbar
6363        * @propertyOf ui.grid.class:GridOptions
6364        * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
6365        * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED.
6366        */
6367       baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
6368   
6369       /**
6370        * @ngdoc boolean
6371        * @name minimumColumnSize
6372        * @propertyOf ui.grid.class:GridOptions
6373        * @description Columns can't be smaller than this, defaults to 10 pixels
6374        */
6375       baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
6376   
6377       /**
6378        * @ngdoc function
6379        * @name rowEquality
6380        * @methodOf ui.grid.class:GridOptions
6381        * @description By default, rows are compared using object equality.  This option can be overridden
6382        * to compare on any data item property or function
6383        * @param {object} entityA First Data Item to compare
6384        * @param {object} entityB Second Data Item to compare
6385        */
6386       baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
6387         return entityA === entityB;
6388       };
6389   
6390       /**
6391        * @ngdoc string
6392        * @name headerTemplate
6393        * @propertyOf ui.grid.class:GridOptions
6394        * @description Null by default. When provided, this setting uses a custom header
6395        * template, rather than the default template. Can be set to either the name of a template file:
6396        * <pre>  $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
6397        * inline html 
6398        * <pre>  $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
6399        * or the id of a precompiled template (TBD how to use this).  
6400        * </br>Refer to the custom header tutorial for more information.
6401        * If you want no header at all, you can set to an empty div:
6402        * <pre>  $scope.gridOptions.headerTemplate = '<div></div>';</pre>
6403        * 
6404        * If you want to only have a static header, then you can set to static content.  If
6405        * you want to tailor the existing column headers, then you should look at the
6406        * current 'ui-grid-header.html' template in github as your starting point.
6407        * 
6408        */
6409       baseOptions.headerTemplate = baseOptions.headerTemplate || null;
6410   
6411       /**
6412        * @ngdoc string
6413        * @name footerTemplate
6414        * @propertyOf ui.grid.class:GridOptions
6415        * @description (optional) Null by default. When provided, this setting uses a custom footer
6416        * template. Can be set to either the name of a template file 'footer_template.html', inline html
6417        * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
6418        * of a precompiled template (TBD how to use this).  Refer to the custom footer tutorial for more information.
6419        */
6420       baseOptions.footerTemplate = baseOptions.footerTemplate || null;
6421   
6422       /**
6423        * @ngdoc string
6424        * @name rowTemplate
6425        * @propertyOf ui.grid.class:GridOptions
6426        * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a 
6427        * custom row template.  Can be set to either the name of a template file:
6428        * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
6429        * inline html 
6430        * <pre>  $scope.gridOptions.rowTemplate = '<div style="background-color: aquamarine" ng-click="getExternalScopes().fnOne(row)" ng-repeat="col in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ui-grid-cell></div>';</pre>
6431        * or the id of a precompiled template (TBD how to use this) can be provided.  
6432        * </br>Refer to the custom row template tutorial for more information.
6433        */
6434       baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
6435       
6436       return baseOptions;
6437     }     
6438   };
6439
6440
6441 }]);
6442
6443 })();
6444
6445 (function(){
6446
6447 angular.module('ui.grid')
6448   
6449   /**
6450    * @ngdoc function
6451    * @name ui.grid.class:GridRenderContainer
6452    * @description The grid has render containers, allowing the ability to have pinned columns.  If the grid
6453    * is right-to-left then there may be a right render container, if left-to-right then there may 
6454    * be a left render container.  There is always a body render container.
6455    * @param {string} name The name of the render container ('body', 'left', or 'right')
6456    * @param {Grid} grid the grid the render container is in
6457    * @param {object} options the render container options
6458    */
6459 .factory('GridRenderContainer', ['gridUtil', function(gridUtil) {
6460   function GridRenderContainer(name, grid, options) {
6461     var self = this;
6462
6463     // if (gridUtil.type(grid) !== 'Grid') {
6464     //   throw new Error('Grid argument is not a Grid object');
6465     // }
6466
6467     self.name = name;
6468
6469     self.grid = grid;
6470     
6471     // self.rowCache = [];
6472     // self.columnCache = [];
6473
6474     self.visibleRowCache = [];
6475     self.visibleColumnCache = [];
6476
6477     self.renderedRows = [];
6478     self.renderedColumns = [];
6479
6480     self.prevScrollTop = 0;
6481     self.prevScrolltopPercentage = 0;
6482     self.prevRowScrollIndex = 0;
6483
6484     self.prevScrollLeft = 0;
6485     self.prevScrollleftPercentage = 0;
6486     self.prevColumnScrollIndex = 0;
6487
6488     self.columnStyles = "";
6489
6490     self.viewportAdjusters = [];
6491
6492     if (options && angular.isObject(options)) {
6493       angular.extend(self, options);
6494     }
6495
6496     grid.registerStyleComputation({
6497       priority: 5,
6498       func: function () {
6499         return self.columnStyles;
6500       }
6501     });
6502   }
6503
6504   // GridRenderContainer.prototype.addRenderable = function addRenderable(renderable) {
6505   //   this.renderables.push(renderable);
6506   // };
6507
6508   GridRenderContainer.prototype.reset = function reset() {
6509     // this.rowCache.length = 0;
6510     // this.columnCache.length = 0;
6511
6512     this.visibleColumnCache.length = 0;
6513     this.visibleRowCache.length = 0;
6514
6515     this.renderedRows.length = 0;
6516     this.renderedColumns.length = 0;
6517   };
6518
6519   // TODO(c0bra): calculate size?? Should this be in a stackable directive?
6520
6521   GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
6522     var self = this;
6523     var minRows = 0;
6524     var rowAddedHeight = 0;
6525     var viewPortHeight = self.getViewportHeight();
6526     for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
6527       rowAddedHeight += self.visibleRowCache[i].height;
6528       minRows++;
6529     }
6530     return minRows;
6531   };
6532
6533   GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
6534     var self = this;
6535     var viewportWidth = this.getViewportWidth();
6536
6537     var min = 0;
6538     var totalWidth = 0;
6539     // self.columns.forEach(function(col, i) {
6540     for (var i = 0; i < self.visibleColumnCache.length; i++) {
6541       var col = self.visibleColumnCache[i];
6542
6543       if (totalWidth < viewportWidth) {
6544         totalWidth += col.drawnWidth ? col.drawnWidth : 0;
6545         min++;
6546       }
6547       else {
6548         var currWidth = 0;
6549         for (var j = i; j >= i - min; j--) {
6550           currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
6551         }
6552         if (currWidth < viewportWidth) {
6553           min++;
6554         }
6555       }
6556     }
6557
6558     return min;
6559   };
6560
6561   GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
6562     return this.visibleRowCache.length;
6563   };
6564
6565   /**
6566    * @ngdoc function
6567    * @name registerViewportAdjuster
6568    * @methodOf ui.grid.class:GridRenderContainer
6569    * @description Registers an adjuster to the render container's available width or height.  Adjusters are used
6570    * to tell the render container that there is something else consuming space, and to adjust it's size
6571    * appropriately.  
6572    * @param {function} func the adjuster function we want to register
6573    */
6574
6575   GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
6576     this.viewportAdjusters.push(func);
6577   };
6578
6579   /**
6580    * @ngdoc function
6581    * @name removeViewportAdjuster
6582    * @methodOf ui.grid.class:GridRenderContainer
6583    * @description Removes an adjuster, should be used when your element is destroyed
6584    * @param {function} func the adjuster function we want to remove
6585    */
6586   GridRenderContainer.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
6587     var idx = this.viewportAdjusters.indexOf(func);
6588
6589     if (typeof(idx) !== 'undefined' && idx !== undefined) {
6590       this.viewportAdjusters.splice(idx, 1);
6591     }
6592   };
6593
6594   /**
6595    * @ngdoc function
6596    * @name getViewportAdjustment
6597    * @methodOf ui.grid.class:GridRenderContainer
6598    * @description Gets the adjustment based on the viewportAdjusters.  
6599    * @returns {object} a hash of { height: x, width: y }.  Usually the values will be negative
6600    */
6601   GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
6602     var self = this;
6603
6604     var adjustment = { height: 0, width: 0 };
6605
6606     self.viewportAdjusters.forEach(function (func) {
6607       adjustment = func.call(this, adjustment);
6608     });
6609
6610     return adjustment;
6611   };
6612
6613   GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
6614     var self = this;
6615
6616     var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
6617
6618     var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
6619
6620     // Account for native horizontal scrollbar, if present
6621     if (typeof(self.horizontalScrollbarHeight) !== 'undefined' && self.horizontalScrollbarHeight !== undefined && self.horizontalScrollbarHeight > 0) {
6622       viewPortHeight = viewPortHeight - self.horizontalScrollbarHeight;
6623     }
6624
6625     var adjustment = self.getViewportAdjustment();
6626     
6627     viewPortHeight = viewPortHeight + adjustment.height;
6628
6629     return viewPortHeight;
6630   };
6631
6632   GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
6633     var self = this;
6634
6635     var viewPortWidth = self.grid.gridWidth;
6636
6637     if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6638       viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
6639     }
6640
6641     var adjustment = self.getViewportAdjustment();
6642     
6643     viewPortWidth = viewPortWidth + adjustment.width;
6644
6645     return viewPortWidth;
6646   };
6647
6648   GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
6649     var self = this;
6650
6651     var viewPortWidth = this.getViewportWidth();
6652
6653     if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6654       viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
6655     }
6656
6657     // var adjustment = self.getViewportAdjustment();
6658     // viewPortWidth = viewPortWidth + adjustment.width;
6659
6660     return viewPortWidth;
6661   };
6662
6663   GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
6664     var self = this;
6665
6666     var ret =  0;
6667
6668     self.visibleRowCache.forEach(function(row){
6669       ret += row.height;
6670     });
6671
6672     if (typeof(self.grid.horizontalScrollbarHeight) !== 'undefined' && self.grid.horizontalScrollbarHeight !== undefined && self.grid.horizontalScrollbarHeight > 0) {
6673       ret = ret - self.grid.horizontalScrollbarHeight;
6674     }
6675
6676     return ret;
6677   };
6678
6679   GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
6680     var self = this;
6681
6682     var ret = self.canvasWidth;
6683
6684     if (typeof(self.verticalScrollbarWidth) !== 'undefined' && self.verticalScrollbarWidth !== undefined && self.verticalScrollbarWidth > 0) {
6685       ret = ret - self.verticalScrollbarWidth;
6686     }
6687
6688     return ret;
6689   };
6690
6691   GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
6692     this.renderedRows.length = newRows.length;
6693     for (var i = 0; i < newRows.length; i++) {
6694       this.renderedRows[i] = newRows[i];
6695     }
6696   };
6697
6698   GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
6699     var self = this;
6700
6701     // OLD:
6702     this.renderedColumns.length = newColumns.length;
6703     for (var i = 0; i < newColumns.length; i++) {
6704       this.renderedColumns[i] = newColumns[i];
6705     }
6706     
6707     this.updateColumnOffset();
6708   };
6709
6710   GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
6711     // Calculate the width of the columns on the left side that are no longer rendered.
6712     //  That will be the offset for the columns as we scroll horizontally.
6713     var hiddenColumnsWidth = 0;
6714     for (var i = 0; i < this.currentFirstColumn; i++) {
6715       hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
6716     }
6717
6718     this.columnOffset = hiddenColumnsWidth;
6719   };
6720
6721   GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
6722     if (this.prevScrollTop === scrollTop && !force) {
6723       return;
6724     }
6725
6726     if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
6727       scrollTop = (this.getCanvasHeight() - this.getCanvasWidth()) * scrollPercentage;
6728     }
6729
6730     this.adjustRows(scrollTop, scrollPercentage);
6731
6732     this.prevScrollTop = scrollTop;
6733     this.prevScrolltopPercentage = scrollPercentage;
6734
6735     this.grid.queueRefresh();
6736   };
6737
6738   GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
6739     if (this.prevScrollLeft === scrollLeft && !force) {
6740       return;
6741     }
6742
6743     if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
6744       scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
6745     }
6746
6747     this.adjustColumns(scrollLeft, scrollPercentage);
6748
6749     this.prevScrollLeft = scrollLeft;
6750     this.prevScrollleftPercentage = scrollPercentage;
6751
6752     this.grid.queueRefresh();
6753   };
6754
6755   GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage) {
6756     var self = this;
6757
6758     var minRows = self.minRowsToRender();
6759
6760     var rowCache = self.visibleRowCache;
6761
6762     var maxRowIndex = rowCache.length - minRows;
6763     self.maxRowIndex = maxRowIndex;
6764
6765     var curRowIndex = self.prevRowScrollIndex;
6766
6767     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
6768     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
6769       scrollPercentage = scrollTop / self.getCanvasHeight();
6770     }
6771     
6772     var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
6773
6774     // Define a max row index that we can't scroll past
6775     if (rowIndex > maxRowIndex) {
6776       rowIndex = maxRowIndex;
6777     }
6778     
6779     var newRange = [];
6780     if (rowCache.length > self.grid.options.virtualizationThreshold) {
6781       // Have we hit the threshold going down?
6782       if (self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
6783         return;
6784       }
6785       //Have we hit the threshold going up?
6786       if (self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
6787         return;
6788       }
6789
6790       var rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
6791       var rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
6792
6793       newRange = [rangeStart, rangeEnd];
6794     }
6795     else {
6796       var maxLen = self.visibleRowCache.length;
6797       newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
6798     }
6799
6800     self.updateViewableRowRange(newRange);
6801
6802     self.prevRowScrollIndex = rowIndex;
6803   };
6804
6805   GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
6806     var self = this;
6807
6808     var minCols = self.minColumnsToRender();
6809
6810     var columnCache = self.visibleColumnCache;
6811     var maxColumnIndex = columnCache.length - minCols;
6812
6813     // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
6814     if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
6815       scrollPercentage = scrollLeft / self.getCanvasWidth();
6816     }
6817
6818     var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
6819
6820     // Define a max row index that we can't scroll past
6821     if (colIndex > maxColumnIndex) {
6822       colIndex = maxColumnIndex;
6823     }
6824     
6825     var newRange = [];
6826     if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
6827       // Have we hit the threshold going down?
6828       if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
6829         return;
6830       }
6831       //Have we hit the threshold going up?
6832       if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
6833         return;
6834       }
6835
6836       var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
6837       var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
6838
6839       newRange = [rangeStart, rangeEnd];
6840     }
6841     else {
6842       var maxLen = self.visibleColumnCache.length;
6843
6844       newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
6845     }
6846     
6847     self.updateViewableColumnRange(newRange);
6848
6849     self.prevColumnScrollIndex = colIndex;
6850   };
6851
6852   // Method for updating the visible rows
6853   GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
6854     // Slice out the range of rows from the data
6855     // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
6856     var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
6857
6858     // Define the top-most rendered row
6859     this.currentTopRow = renderedRange[0];
6860
6861     // TODO(c0bra): make this method!
6862     this.setRenderedRows(rowArr);
6863   };
6864
6865   // Method for updating the visible columns
6866   GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
6867     // Slice out the range of rows from the data
6868     // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
6869     var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
6870
6871     // Define the left-most rendered columns
6872     this.currentFirstColumn = renderedRange[0];
6873
6874     this.setRenderedColumns(columnArr);
6875   };
6876
6877   GridRenderContainer.prototype.rowStyle = function (index) {
6878     var self = this;
6879
6880     var styles = {};
6881     
6882     if (index === 0 && self.currentTopRow !== 0) {
6883       // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
6884       var hiddenRowWidth = (self.currentTopRow) * self.grid.options.rowHeight;
6885
6886       // return { 'margin-top': hiddenRowWidth + 'px' };
6887       styles['margin-top'] = hiddenRowWidth + 'px';
6888     }
6889
6890     if (self.currentFirstColumn !== 0) {
6891       if (self.grid.isRTL()) {
6892         styles['margin-right'] = self.columnOffset + 'px';
6893       }
6894       else {
6895         styles['margin-left'] = self.columnOffset + 'px';
6896       }
6897     }
6898
6899     return styles;
6900   };
6901
6902   GridRenderContainer.prototype.columnStyle = function (index) {
6903     var self = this;
6904     
6905     if (index === 0 && self.currentFirstColumn !== 0) {
6906       var offset = self.columnOffset;
6907
6908       if (self.grid.isRTL()) {
6909         return { 'margin-right': offset + 'px' };
6910       }
6911       else {
6912         return { 'margin-left': offset + 'px' };
6913       }
6914     }
6915
6916     return null;
6917   };
6918
6919   GridRenderContainer.prototype.updateColumnWidths = function () {
6920     var self = this;
6921
6922     var asterisksArray = [],
6923         percentArray = [],
6924         manualArray = [],
6925         asteriskNum = 0,
6926         totalWidth = 0;
6927
6928     // Get the width of the viewport
6929     var availableWidth = self.getViewportWidth();
6930
6931     if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6932       availableWidth = availableWidth + self.grid.verticalScrollbarWidth;
6933     }
6934
6935     // The total number of columns
6936     // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
6937     // var equalWidth = availableWidth / equalWidthColumnCount;
6938
6939     // The last column we processed
6940     var lastColumn;
6941
6942     var manualWidthSum = 0;
6943
6944     var canvasWidth = 0;
6945
6946     var ret = '';
6947
6948
6949     // uiGridCtrl.grid.columns.forEach(function(column, i) {
6950
6951     var columnCache = self.visibleColumnCache;
6952
6953     columnCache.forEach(function(column, i) {
6954       // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + i + ' { width: ' + equalWidth + 'px; left: ' + left + 'px; }';
6955       //var colWidth = (typeof(c.width) !== 'undefined' && c.width !== undefined) ? c.width : equalWidth;
6956
6957       // Skip hidden columns
6958       if (!column.visible) { return; }
6959
6960       var colWidth,
6961           isPercent = false;
6962
6963       if (!angular.isNumber(column.width)) {
6964         isPercent = isNaN(column.width) && gridUtil.endsWith(column.width, "%");
6965       }
6966
6967       if (angular.isString(column.width) && column.width.indexOf('*') !== -1) { //  we need to save it until the end to do the calulations on the remaining width.
6968         asteriskNum = parseInt(asteriskNum + column.width.length, 10);
6969         
6970         asterisksArray.push(column);
6971       }
6972       else if (isPercent) { // If the width is a percentage, save it until the very last.
6973         percentArray.push(column);
6974       }
6975       else if (angular.isNumber(column.width)) {
6976         manualWidthSum = parseInt(manualWidthSum + column.width, 10);
6977         
6978         canvasWidth = parseInt(canvasWidth, 10) + parseInt(column.width, 10);
6979
6980         column.drawnWidth = column.width;
6981       }
6982     });
6983
6984     // Get the remaining width (available width subtracted by the manual widths sum)
6985     var remainingWidth = availableWidth - manualWidthSum;
6986
6987     var i, column, colWidth;
6988
6989     if (percentArray.length > 0) {
6990       // Pre-process to make sure they're all within any min/max values
6991       for (i = 0; i < percentArray.length; i++) {
6992         column = percentArray[i];
6993
6994         var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
6995
6996         colWidth = parseInt(percent * remainingWidth, 10);
6997
6998         if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
6999           colWidth = column.colDef.minWidth;
7000
7001           remainingWidth = remainingWidth - colWidth;
7002
7003           canvasWidth += colWidth;
7004           column.drawnWidth = colWidth;
7005
7006           // Remove this element from the percent array so it's not processed below
7007           percentArray.splice(i, 1);
7008         }
7009         else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
7010           colWidth = column.colDef.maxWidth;
7011
7012           remainingWidth = remainingWidth - colWidth;
7013
7014           canvasWidth += colWidth;
7015           column.drawnWidth = colWidth;
7016
7017           // Remove this element from the percent array so it's not processed below
7018           percentArray.splice(i, 1);
7019         }
7020       }
7021
7022       percentArray.forEach(function(column) {
7023         var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
7024         var colWidth = parseInt(percent * remainingWidth, 10);
7025
7026         canvasWidth += colWidth;
7027
7028         column.drawnWidth = colWidth;
7029       });
7030     }
7031
7032     if (asterisksArray.length > 0) {
7033       var asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
7034
7035        // Pre-process to make sure they're all within any min/max values
7036       for (i = 0; i < asterisksArray.length; i++) {
7037         column = asterisksArray[i];
7038
7039         colWidth = parseInt(asteriskVal * column.width.length, 10);
7040
7041         if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
7042           colWidth = column.colDef.minWidth;
7043
7044           remainingWidth = remainingWidth - colWidth;
7045           asteriskNum--;
7046
7047           canvasWidth += colWidth;
7048           column.drawnWidth = colWidth;
7049
7050           lastColumn = column;
7051
7052           // Remove this element from the percent array so it's not processed below
7053           asterisksArray.splice(i, 1);
7054         }
7055         else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
7056           colWidth = column.colDef.maxWidth;
7057
7058           remainingWidth = remainingWidth - colWidth;
7059           asteriskNum--;
7060
7061           canvasWidth += colWidth;
7062           column.drawnWidth = colWidth;
7063
7064           // Remove this element from the percent array so it's not processed below
7065           asterisksArray.splice(i, 1);
7066         }
7067       }
7068
7069       // Redo the asterisk value, as we may have removed columns due to width constraints
7070       asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
7071
7072       asterisksArray.forEach(function(column) {
7073         var colWidth = parseInt(asteriskVal * column.width.length, 10);
7074
7075         canvasWidth += colWidth;
7076
7077         column.drawnWidth = colWidth;
7078       });
7079     }
7080
7081     // If the grid width didn't divide evenly into the column widths and we have pixels left over, dole them out to the columns one by one to make everything fit
7082     var leftoverWidth = availableWidth - parseInt(canvasWidth, 10);
7083
7084     if (leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
7085       var variableColumn = false;
7086       // uiGridCtrl.grid.columns.forEach(function(col) {
7087       columnCache.forEach(function(col) {
7088         if (col.width && !angular.isNumber(col.width)) {
7089           variableColumn = true;
7090         }
7091       });
7092
7093       if (variableColumn) {
7094         var remFn = function (column) {
7095           if (leftoverWidth > 0) {
7096             column.drawnWidth = column.drawnWidth + 1;
7097             canvasWidth = canvasWidth + 1;
7098             leftoverWidth--;
7099           }
7100         };
7101         while (leftoverWidth > 0) {
7102           columnCache.forEach(remFn);
7103         }
7104       }
7105     }
7106
7107     if (canvasWidth < availableWidth) {
7108       canvasWidth = availableWidth;
7109     }
7110
7111     // Build the CSS
7112     columnCache.forEach(function (column) {
7113       ret = ret + column.getColClassDefinition();
7114     });
7115
7116     // Add the vertical scrollbar width back in to the canvas width, it's taken out in getCanvasWidth
7117     if (self.grid.verticalScrollbarWidth) {
7118       canvasWidth = canvasWidth + self.grid.verticalScrollbarWidth;
7119     }
7120     // canvasWidth = canvasWidth + 1;
7121
7122     self.canvasWidth = parseInt(canvasWidth, 10);
7123
7124     // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
7125     // return ret;
7126
7127     // Set this render container's column styles so they can be used in style computation
7128     this.columnStyles = ret;
7129   };
7130
7131   return GridRenderContainer;
7132 }]);
7133
7134 })();
7135
7136 (function(){
7137
7138 angular.module('ui.grid')
7139 .factory('GridRow', ['gridUtil', function(gridUtil) {
7140
7141    /**
7142    * @ngdoc function
7143    * @name ui.grid.class:GridRow
7144    * @description GridRow is the viewModel for one logical row on the grid.  A grid Row is not necessarily a one-to-one
7145    * relation to gridOptions.data.
7146    * @param {object} entity the array item from GridOptions.data
7147    * @param {number} index the current position of the row in the array
7148    * @param {Grid} reference to the parent grid
7149    */
7150   function GridRow(entity, index, grid) {
7151
7152      /**
7153       *  @ngdoc object
7154       *  @name grid
7155       *  @propertyOf  ui.grid.class:GridRow
7156       *  @description A reference back to the grid
7157       */
7158      this.grid = grid;
7159
7160      /**
7161       *  @ngdoc object
7162       *  @name entity
7163       *  @propertyOf  ui.grid.class:GridRow
7164       *  @description A reference to an item in gridOptions.data[]
7165       */
7166     this.entity = entity;
7167
7168      /**
7169       *  @ngdoc object
7170       *  @name uid
7171       *  @propertyOf  ui.grid.class:GridRow
7172       *  @description  UniqueId of row
7173       */
7174      this.uid = gridUtil.nextUid();
7175
7176      /**
7177       *  @ngdoc object
7178       *  @name visible
7179       *  @propertyOf  ui.grid.class:GridRow
7180       *  @description If true, the row will be rendered
7181       */
7182     // Default to true
7183     this.visible = true;
7184
7185   /**
7186     *  @ngdoc object
7187     *  @name height
7188     *  @propertyOf  ui.grid.class:GridRow
7189     *  @description height of each individual row
7190     */
7191     this.height = grid.options.rowHeight;
7192   }
7193
7194   /**
7195    * @ngdoc function
7196    * @name getQualifiedColField
7197    * @methodOf ui.grid.class:GridRow
7198    * @description returns the qualified field name as it exists on scope
7199    * ie: row.entity.fieldA
7200    * @param {GridCol} col column instance
7201    * @returns {string} resulting name that can be evaluated on scope
7202    */
7203   GridRow.prototype.getQualifiedColField = function(col) {
7204     return 'row.' + this.getEntityQualifiedColField(col);
7205   };
7206
7207     /**
7208      * @ngdoc function
7209      * @name getEntityQualifiedColField
7210      * @methodOf ui.grid.class:GridRow
7211      * @description returns the qualified field name minus the row path
7212      * ie: entity.fieldA
7213      * @param {GridCol} col column instance
7214      * @returns {string} resulting name that can be evaluated against a row
7215      */
7216   GridRow.prototype.getEntityQualifiedColField = function(col) {
7217     return gridUtil.preEval('entity.' + col.field);
7218   };
7219   
7220   
7221   /**
7222    * @ngdoc function
7223    * @name setRowInvisible
7224    * @methodOf  ui.grid.class:GridRow
7225    * @description Sets an override on the row that forces it to always
7226    * be invisible, and if the row is currently visible then marks it
7227    * as invisible and refreshes the grid.  Emits the rowsVisibleChanged
7228    * event if it changed the row visibility
7229    * @param {GridRow} row row to force invisible, needs to be a GridRow,
7230    * which can be found from your data entity using grid.findRow
7231    */
7232   GridRow.prototype.setRowInvisible = function (row) {
7233     if (row !== null) {
7234       row.forceInvisible = true;
7235       
7236       if ( row.visible ){
7237         row.visible = false;
7238         row.grid.refresh();
7239         row.grid.api.core.raise.rowsVisibleChanged();
7240       }
7241     }        
7242   };
7243
7244   /**
7245    * @ngdoc function
7246    * @name clearRowInvisible
7247    * @methodOf ui.grid.class:GridRow
7248    * @description Clears any override on the row visibility, returning it 
7249    * to normal visibility calculations.  If the row is currently invisible
7250    * then sets it to visible and calls refresh and emits the rowsVisibleChanged
7251    * event
7252    * TODO: if filter in action, then is this right?
7253    * @param {GridRow} row row clear force invisible, needs to be a GridRow,
7254    * which can be found from your data entity using grid.findRow
7255    */
7256   GridRow.prototype.clearRowInvisible = function (row) {
7257     if (row !== null) {
7258       row.forceInvisible = false;
7259       
7260       if ( !row.visible ){
7261         row.visible = true;
7262         row.grid.refresh();
7263         row.grid.api.core.raise.rowsVisibleChanged();
7264       }
7265     }        
7266   };
7267
7268   return GridRow;
7269 }]);
7270
7271 })();
7272 (function () {
7273   'use strict';
7274   /**
7275    *  @ngdoc object
7276    *  @name ui.grid.service:gridClassFactory
7277    *
7278    *  @description factory to return dom specific instances of a grid
7279    *
7280    */
7281   angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
7282     function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
7283
7284       var service = {
7285         /**
7286          * @ngdoc method
7287          * @name createGrid
7288          * @methodOf ui.grid.service:gridClassFactory
7289          * @description Creates a new grid instance. Each instance will have a unique id
7290          * @param {object} options An object map of options to pass into the created grid instance.
7291          * @returns {Grid} grid
7292          */
7293         createGrid : function(options) {
7294           options = (typeof(options) !== 'undefined') ? options : {};
7295           options.id = gridUtil.newId();
7296           var grid = new Grid(options);
7297
7298           // NOTE/TODO: rowTemplate should always be defined...
7299           if (grid.options.rowTemplate) {
7300             var rowTemplateFnPromise = $q.defer();
7301             grid.getRowTemplateFn = rowTemplateFnPromise.promise;
7302             
7303             gridUtil.getTemplate(grid.options.rowTemplate)
7304               .then(
7305                 function (template) {
7306                   var rowTemplateFn = $compile(template);
7307                   rowTemplateFnPromise.resolve(rowTemplateFn);
7308                 },
7309                 function (res) {
7310                   // Todo handle response error here?
7311                   throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
7312                 });
7313           }
7314
7315           grid.registerColumnBuilder(service.defaultColumnBuilder);
7316
7317           // Reset all rows to visible initially
7318           grid.registerRowsProcessor(function allRowsVisible(rows) {
7319             rows.forEach(function (row) {
7320               row.visible = !row.forceInvisible;
7321             });
7322
7323             return rows;
7324           });
7325
7326           grid.registerColumnsProcessor(function allColumnsVisible(columns) {
7327             columns.forEach(function (column) {
7328               column.visible = true;
7329             });
7330
7331             return columns;
7332           });
7333
7334           grid.registerColumnsProcessor(function(renderableColumns) {
7335               renderableColumns.forEach(function (column) {
7336                   if (column.colDef.visible === false) {
7337                       column.visible = false;
7338                   }
7339               });
7340
7341               return renderableColumns;
7342           });
7343
7344
7345
7346           if (grid.options.enableFiltering) {
7347             grid.registerRowsProcessor(grid.searchRows);
7348           }
7349
7350           // Register the default row processor, it sorts rows by selected columns
7351           if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
7352             grid.registerRowsProcessor(grid.options.externalSort);
7353           }
7354           else {
7355             grid.registerRowsProcessor(grid.sortByColumn);
7356           }
7357
7358           return grid;
7359         },
7360
7361         /**
7362          * @ngdoc function
7363          * @name defaultColumnBuilder
7364          * @methodOf ui.grid.service:gridClassFactory
7365          * @description Processes designTime column definitions and applies them to col for the
7366          *              core grid features
7367          * @param {object} colDef reference to column definition
7368          * @param {GridColumn} col reference to gridCol
7369          * @param {object} gridOptions reference to grid options
7370          */
7371         defaultColumnBuilder: function (colDef, col, gridOptions) {
7372
7373           var templateGetPromises = [];
7374
7375           /**
7376            * @ngdoc property
7377            * @name headerCellTemplate
7378            * @propertyOf ui.grid.class:GridOptions.columnDef
7379            * @description a custom template for the header for this column.  The default
7380            * is ui-grid/uiGridHeaderCell
7381            *
7382            */
7383           if (!colDef.headerCellTemplate) {
7384             col.providedHeaderCellTemplate = 'ui-grid/uiGridHeaderCell';
7385           } else {
7386             col.providedHeaderCellTemplate = colDef.headerCellTemplate;
7387           }
7388
7389           /**
7390            * @ngdoc property
7391            * @name cellTemplate
7392            * @propertyOf ui.grid.class:GridOptions.columnDef
7393            * @description a custom template for each cell in this column.  The default
7394            * is ui-grid/uiGridCell.  If you are using the cellNav feature, this template
7395            * must contain a div that can receive focus.
7396            *
7397            */
7398           if (!colDef.cellTemplate) {
7399             col.providedCellTemplate = 'ui-grid/uiGridCell';
7400           } else {
7401             col.providedCellTemplate = colDef.cellTemplate;
7402           }
7403
7404           col.cellTemplatePromise = gridUtil.getTemplate(col.providedCellTemplate);
7405           templateGetPromises.push(col.cellTemplatePromise
7406             .then(
7407               function (template) {
7408                 col.cellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.cellFilter ? "|" + col.cellFilter : "");
7409               },
7410               function (res) {
7411                 throw new Error("Couldn't fetch/use colDef.cellTemplate '" + colDef.cellTemplate + "'");
7412               })
7413           );
7414
7415           templateGetPromises.push(gridUtil.getTemplate(col.providedHeaderCellTemplate)
7416               .then(
7417               function (template) {
7418                 col.headerCellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.headerCellFilter ? "|" + col.headerCellFilter : "");
7419               },
7420               function (res) {
7421                 throw new Error("Couldn't fetch/use colDef.headerCellTemplate '" + colDef.headerCellTemplate + "'");
7422               })
7423           );
7424
7425           // Create a promise for the compiled element function
7426           col.compiledElementFnDefer = $q.defer();
7427
7428           return $q.all(templateGetPromises);
7429         }
7430
7431       };
7432
7433       //class definitions (moved to separate factories)
7434
7435       return service;
7436     }]);
7437
7438 })();
7439 (function() {
7440
7441 var module = angular.module('ui.grid');
7442
7443 function escapeRegExp(str) {
7444   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
7445 }
7446
7447 function QuickCache() {
7448   var c = function(get, set) {
7449     // Return the cached value of 'get' if it's stored
7450     if (get && c.cache[get]) {
7451       return c.cache[get];
7452     }
7453     // Otherwise set it and return it
7454     else if (get && set) {
7455       c.cache[get] = set;
7456       return c.cache[get];
7457     }
7458     else {
7459       return undefined;
7460     }
7461   };
7462
7463   c.cache = {};
7464
7465   c.clear = function () {
7466     c.cache = {};
7467   };
7468
7469   return c;
7470 }
7471
7472 /**
7473  *  @ngdoc service
7474  *  @name ui.grid.service:rowSearcher
7475  *
7476  *  @description Service for searching/filtering rows based on column value conditions.
7477  */
7478 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
7479   var defaultCondition = uiGridConstants.filter.STARTS_WITH;
7480
7481   var rowSearcher = {};
7482
7483   // rowSearcher.searchColumn = function searchColumn(condition, item) {
7484   //   var result;
7485
7486   //   var col = self.fieldMap[condition.columnDisplay];
7487
7488   //   if (!col) {
7489   //       return false;
7490   //   }
7491   //   var sp = col.cellFilter.split(':');
7492   //   var filter = col.cellFilter ? $filter(sp[0]) : null;
7493   //   var value = item[condition.column] || item[col.field.split('.')[0]];
7494   //   if (value === null || value === undefined) {
7495   //       return false;
7496   //   }
7497   //   if (typeof filter === "function") {
7498   //       var filterResults = filter(typeof value === "object" ? evalObject(value, col.field) : value, sp[1]).toString();
7499   //       result = condition.regex.test(filterResults);
7500   //   }
7501   //   else {
7502   //       result = condition.regex.test(typeof value === "object" ? evalObject(value, col.field).toString() : value.toString());
7503   //   }
7504   //   if (result) {
7505   //       return true;
7506   //   }
7507   //   return false;
7508   // };
7509
7510   /**
7511    * @ngdoc function
7512    * @name getTerm
7513    * @methodOf ui.grid.service:rowSearcher
7514    * @description Get the term from a filter
7515    * Trims leading and trailing whitespace
7516    * @param {object} filter object to use
7517    * @returns {object} Parsed term
7518    */
7519   rowSearcher.getTerm = function getTerm(filter) {
7520     if (typeof(filter.term) === 'undefined') { return filter.term; }
7521     
7522     var term = filter.term;
7523
7524     // Strip leading and trailing whitespace if the term is a string
7525     if (typeof(term) === 'string') {
7526       term = term.trim();
7527     }
7528
7529     return term;
7530   };
7531
7532   /**
7533    * @ngdoc function
7534    * @name stripTerm
7535    * @methodOf ui.grid.service:rowSearcher
7536    * @description Remove leading and trailing asterisk (*) from the filter's term
7537    * @param {object} filter object to use
7538    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
7539    */
7540   rowSearcher.stripTerm = function stripTerm(filter) {
7541     var term = rowSearcher.getTerm(filter);
7542
7543     if (typeof(term) === 'string') {
7544       return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
7545     }
7546     else {
7547       return term;
7548     }
7549   };
7550
7551   /**
7552    * @ngdoc function
7553    * @name guessCondition
7554    * @methodOf ui.grid.service:rowSearcher
7555    * @description Guess the condition for a filter based on its term
7556    * <br>
7557    * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
7558    * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
7559    * @param {object} filter object to use
7560    * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
7561    */
7562   rowSearcher.guessCondition = function guessCondition(filter) {
7563     if (typeof(filter.term) === 'undefined' || !filter.term) {
7564       return defaultCondition;
7565     }
7566
7567     var term = rowSearcher.getTerm(filter);
7568     
7569     // Term starts with and ends with a *, use 'contains' condition
7570     // if (/^\*[\s\S]+?\*$/.test(term)) {
7571     //   return uiGridConstants.filter.CONTAINS;
7572     // }
7573     // // Term starts with a *, use 'ends with' condition
7574     // else if (/^\*/.test(term)) {
7575     //   return uiGridConstants.filter.ENDS_WITH;
7576     // }
7577     // // Term ends with a *, use 'starts with' condition
7578     // else if (/\*$/.test(term)) {
7579     //   return uiGridConstants.filter.STARTS_WITH;
7580     // }
7581     // // Default to default condition
7582     // else {
7583     //   return defaultCondition;
7584     // }
7585
7586     // If the term has *s then turn it into a regex
7587     if (/\*/.test(term)) {
7588       var regexpFlags = '';
7589       if (!filter.flags || !filter.flags.caseSensitive) {
7590         regexpFlags += 'i';
7591       }
7592
7593       var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
7594       return new RegExp('^' + reText + '$', regexpFlags);
7595     }
7596     // Otherwise default to default condition
7597     else {
7598       return defaultCondition;
7599     }
7600   };
7601
7602   rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, termCache, i, filter) {
7603     // Cache typeof condition
7604     var conditionType = typeof(filter.condition);
7605
7606     // Default to CONTAINS condition
7607     if (conditionType === 'undefined' || !filter.condition) {
7608       filter.condition = uiGridConstants.filter.CONTAINS;
7609     }
7610
7611     // Term to search for.
7612     var term = rowSearcher.stripTerm(filter);
7613
7614     if (term === null || term === undefined || term === '') {
7615       return true;
7616     }
7617
7618     // Get the column value for this row
7619     var value = grid.getCellValue(row, column);
7620
7621     var regexpFlags = '';
7622     if (!filter.flags || !filter.flags.caseSensitive) {
7623       regexpFlags += 'i';
7624     }
7625
7626     var cacheId = column.field + i;
7627
7628     // If the filter's condition is a RegExp, then use it
7629     if (filter.condition instanceof RegExp) {
7630       if (!filter.condition.test(value)) {
7631         return false;
7632       }
7633     }
7634     // If the filter's condition is a function, run it
7635     else if (conditionType === 'function') {
7636       return filter.condition(term, value, row, column);
7637     }
7638     else if (filter.condition === uiGridConstants.filter.STARTS_WITH) {
7639       var startswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp('^' + term, regexpFlags));
7640
7641       if (!startswithRE.test(value)) {
7642         return false;
7643       }
7644     }
7645     else if (filter.condition === uiGridConstants.filter.ENDS_WITH) {
7646       var endswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term + '$', regexpFlags));
7647
7648       if (!endswithRE.test(value)) {
7649         return false;
7650       }
7651     }
7652     else if (filter.condition === uiGridConstants.filter.CONTAINS) {
7653       var containsRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term, regexpFlags));
7654
7655       if (!containsRE.test(value)) {
7656         return false;
7657       }
7658     }
7659     else if (filter.condition === uiGridConstants.filter.EXACT) {
7660       var exactRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId,  new RegExp('^' + term + '$', regexpFlags));
7661
7662       if (!exactRE.test(value)) {
7663         return false;
7664       }
7665     }
7666     else if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
7667       if (value <= term) {
7668         return false;
7669       }
7670     }
7671     else if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
7672       if (value < term) {
7673         return false;
7674       }
7675     }
7676     else if (filter.condition === uiGridConstants.filter.LESS_THAN) {
7677       if (value >= term) {
7678         return false;
7679       }
7680     }
7681     else if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
7682       if (value > term) {
7683         return false;
7684       }
7685     }
7686     else if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
7687       if (!angular.equals(value, term)) {
7688         return false;
7689       }
7690     }
7691
7692     return true;
7693   };
7694
7695   /**
7696    * @ngdoc boolean
7697    * @name useExternalFiltering
7698    * @propertyOf ui.grid.class:GridOptions
7699    * @description False by default. When enabled, this setting suppresses the internal filtering.
7700    * All UI logic will still operate, allowing filter conditions to be set and modified.
7701    * 
7702    * The external filter logic can listen for the `filterChange` event, which fires whenever
7703    * a filter has been adjusted.
7704    */
7705   /**
7706    * @ngdoc function
7707    * @name searchColumn
7708    * @methodOf ui.grid.service:rowSearcher
7709    * @description Process filters on a given column against a given row. If the row meets the conditions on all the filters, return true.
7710    * @param {Grid} grid Grid to search in
7711    * @param {GridRow} row Row to search on
7712    * @param {GridCol} column Column with the filters to use
7713    * @returns {boolean} Whether the column matches or not.
7714    */
7715   rowSearcher.searchColumn = function searchColumn(grid, row, column, termCache) {
7716     var filters = [];
7717
7718     if (grid.options.useExternalFiltering) {
7719       return true;
7720     }
7721     
7722     if (typeof(column.filters) !== 'undefined' && column.filters && column.filters.length > 0) {
7723       filters = column.filters;
7724     } else {
7725       // If filters array is not there, assume no filters for this column. 
7726       // This array should have been built in GridColumn::updateColumnDef.
7727       return true;
7728     }
7729     
7730     for (var i in filters) {
7731       var filter = filters[i];
7732
7733       /*
7734         filter: {
7735           term: 'blah', // Search term to search for, could be a string, integer, etc.
7736           condition: uiGridConstants.filter.CONTAINS // Type of match to do. Defaults to CONTAINS (i.e. looking in a string), but could be EXACT, GREATER_THAN, etc.
7737           flags: { // Flags for the conditions
7738             caseSensitive: false // Case-sensitivity defaults to false
7739           }
7740         }
7741       */
7742      
7743       // Check for when no condition is supplied. In this case, guess the condition
7744       // to use based on the filter's term. Cache this result.
7745       if (!filter.condition) {
7746         // Cache custom conditions, building the RegExp takes time
7747         var conditionCacheId = 'cond-' + column.field + '-' + filter.term;
7748         var condition = termCache(conditionCacheId) ? termCache(conditionCacheId) : termCache(conditionCacheId, rowSearcher.guessCondition(filter));
7749
7750         // Create a surrogate filter so as not to change
7751         // the actual columnDef.filters.
7752         filter = {
7753           // Copy over the search term
7754           term: filter.term,
7755           // Use the guessed condition
7756           condition: condition,
7757           // Set flags, using passed flags if present
7758           flags: angular.extend({
7759             caseSensitive: false
7760           }, filter.flags)
7761         };
7762       }
7763
7764       var ret = rowSearcher.runColumnFilter(grid, row, column, termCache, i, filter);
7765       if (!ret) {
7766         return false;
7767       }
7768     }
7769
7770     return true;
7771     // }
7772     // else {
7773     //   // No filter conditions, default to true
7774     //   return true;
7775     // }
7776   };
7777
7778   /**
7779    * @ngdoc function
7780    * @name search
7781    * @methodOf ui.grid.service:rowSearcher
7782    * @description Run a search across
7783    * @param {Grid} grid Grid instance to search inside
7784    * @param {Array[GridRow]} rows GridRows to filter
7785    * @param {Array[GridColumn]} columns GridColumns with filters to process
7786    */
7787   rowSearcher.search = function search(grid, rows, columns) {
7788     // Don't do anything if we weren't passed any rows
7789     if (!rows) {
7790       return;
7791     }
7792
7793     // Create a term cache
7794     var termCache = new QuickCache();
7795
7796     // Build filtered column list
7797     var filterCols = [];
7798     columns.forEach(function (col) {
7799       if (typeof(col.filters) !== 'undefined' && col.filters.length > 0) {
7800         filterCols.push(col);
7801       }
7802       else if (typeof(col.filter) !== 'undefined' && col.filter && typeof(col.filter.term) !== 'undefined' && col.filter.term) {
7803         filterCols.push(col);
7804       }
7805     });
7806     
7807     if (filterCols.length > 0) {
7808       filterCols.forEach(function foreachFilterCol(col) {
7809         rows.forEach(function foreachRow(row) {
7810           if (row.forceInvisible || !rowSearcher.searchColumn(grid, row, col, termCache)) {
7811             row.visible = false;
7812           }
7813         });
7814       });
7815
7816       if (grid.api.core.raise.rowsVisibleChanged) {
7817         grid.api.core.raise.rowsVisibleChanged();
7818       }
7819
7820       // rows.forEach(function (row) {
7821       //   var matchesAllColumns = true;
7822
7823       //   for (var i in filterCols) {
7824       //     var col = filterCols[i];
7825
7826       //     if (!rowSearcher.searchColumn(grid, row, col, termCache)) {
7827       //       matchesAllColumns = false;
7828
7829       //       // Stop processing other terms
7830       //       break;
7831       //     }
7832       //   }
7833
7834       //   // Row doesn't match all the terms, don't display it
7835       //   if (!matchesAllColumns) {
7836       //     row.visible = false;
7837       //   }
7838       //   else {
7839       //     row.visible = true;
7840       //   }
7841       // });
7842     }
7843
7844     // Reset the term cache
7845     termCache.clear();
7846
7847     return rows;
7848   };
7849
7850   return rowSearcher;
7851 }]);
7852
7853 })();
7854 (function() {
7855
7856 var module = angular.module('ui.grid');
7857
7858 /**
7859  * @ngdoc object
7860  * @name ui.grid.class:RowSorter
7861  * @description RowSorter provides the default sorting mechanisms, 
7862  * including guessing column types and applying appropriate sort 
7863  * algorithms
7864  * 
7865  */ 
7866
7867 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
7868   var currencyRegexStr = 
7869     '(' +
7870     uiGridConstants.CURRENCY_SYMBOLS
7871       .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
7872       .join('|') + // Join all the symbols together with |s
7873     ')?';
7874
7875   // /^[-+]?[£$¤¥]?[\d,.]+%?$/
7876   var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
7877
7878   var rowSorter = {
7879     // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
7880     //   this takes a piece of data from the cell and tries to determine its type and what sorting
7881     //   function to use for it
7882     colSortFnCache: []
7883   };
7884
7885
7886   /**
7887    * @ngdoc method
7888    * @methodOf ui.grid.class:RowSorter
7889    * @name guessSortFn
7890    * @description Assigns a sort function to use based on the itemType in the column
7891    * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'.  And
7892    * error will be thrown for any other type.
7893    * @returns {function} a sort function that will sort that type
7894    */
7895   rowSorter.guessSortFn = function guessSortFn(itemType) {
7896     switch (itemType) {
7897       case "number":
7898         return rowSorter.sortNumber;
7899       case "boolean":
7900         return rowSorter.sortBool;
7901       case "string":
7902         return rowSorter.sortAlpha;
7903       case "date":
7904         return rowSorter.sortDate;
7905       case "object":
7906         return rowSorter.basicSort;
7907       default:
7908         throw new Error('No sorting function found for type:' + itemType);
7909     }
7910   };
7911
7912
7913   /**
7914    * @ngdoc method
7915    * @methodOf ui.grid.class:RowSorter
7916    * @name handleNulls
7917    * @description Sorts nulls and undefined to the bottom (top when
7918    * descending).  Called by each of the internal sorters before
7919    * attempting to sort.  Note that this method is available on the core api
7920    * via gridApi.core.sortHandleNulls
7921    * @param {object} a sort value a
7922    * @param {object} b sort value b
7923    * @returns {number} null if there were no nulls/undefineds, otherwise returns
7924    * a sort value that should be passed back from the sort function
7925    */
7926   rowSorter.handleNulls = function handleNulls(a, b) {
7927     // We want to allow zero values and false values to be evaluated in the sort function
7928     if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
7929       // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
7930       if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
7931         return 0;
7932       }
7933       else if (!a && a !== 0 && a !== false) {
7934         return 1;
7935       }
7936       else if (!b && b !== 0 && b !== false) {
7937         return -1;
7938       }
7939     }
7940     return null;
7941   };
7942
7943
7944   /**
7945    * @ngdoc method
7946    * @methodOf ui.grid.class:RowSorter
7947    * @name basicSort
7948    * @description Sorts any values that provide the < method, including strings
7949    * or numbers.  Handles nulls and undefined through calling handleNulls 
7950    * @param {object} a sort value a
7951    * @param {object} b sort value b
7952    * @returns {number} normal sort function, returns -ve, 0, +ve
7953    */
7954   rowSorter.basicSort = function basicSort(a, b) {
7955     var nulls = rowSorter.handleNulls(a, b);
7956     if ( nulls !== null ){
7957       return nulls;
7958     } else {
7959       if (a === b) {
7960         return 0;
7961       }
7962       if (a < b) {
7963         return -1;
7964       }
7965       return 1;
7966     }
7967   };
7968
7969
7970   /**
7971    * @ngdoc method
7972    * @methodOf ui.grid.class:RowSorter
7973    * @name sortNumber
7974    * @description Sorts numerical values.  Handles nulls and undefined through calling handleNulls 
7975    * @param {object} a sort value a
7976    * @param {object} b sort value b
7977    * @returns {number} normal sort function, returns -ve, 0, +ve
7978    */
7979   rowSorter.sortNumber = function sortNumber(a, b) {
7980     var nulls = rowSorter.handleNulls(a, b);
7981     if ( nulls !== null ){
7982       return nulls;
7983     } else {
7984       return a - b;
7985     }
7986   };
7987
7988
7989   /**
7990    * @ngdoc method
7991    * @methodOf ui.grid.class:RowSorter
7992    * @name sortNumberStr
7993    * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).  
7994    * Handles nulls and undefined through calling handleNulls 
7995    * @param {object} a sort value a
7996    * @param {object} b sort value b
7997    * @returns {number} normal sort function, returns -ve, 0, +ve
7998    */
7999   rowSorter.sortNumberStr = function sortNumberStr(a, b) {
8000     var nulls = rowSorter.handleNulls(a, b);
8001     if ( nulls !== null ){
8002       return nulls;
8003     } else {
8004       var numA, // The parsed number form of 'a'
8005           numB, // The parsed number form of 'b'
8006           badA = false,
8007           badB = false;
8008   
8009       // Try to parse 'a' to a float
8010       numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
8011   
8012       // If 'a' couldn't be parsed to float, flag it as bad
8013       if (isNaN(numA)) {
8014           badA = true;
8015       }
8016   
8017       // Try to parse 'b' to a float
8018       numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
8019   
8020       // If 'b' couldn't be parsed to float, flag it as bad
8021       if (isNaN(numB)) {
8022           badB = true;
8023       }
8024   
8025       // We want bad ones to get pushed to the bottom... which effectively is "greater than"
8026       if (badA && badB) {
8027           return 0;
8028       }
8029   
8030       if (badA) {
8031           return 1;
8032       }
8033   
8034       if (badB) {
8035           return -1;
8036       }
8037   
8038       return numA - numB;
8039     }
8040   };
8041
8042
8043   /**
8044    * @ngdoc method
8045    * @methodOf ui.grid.class:RowSorter
8046    * @name sortAlpha
8047    * @description Sorts string values. Handles nulls and undefined through calling handleNulls 
8048    * @param {object} a sort value a
8049    * @param {object} b sort value b
8050    * @returns {number} normal sort function, returns -ve, 0, +ve
8051    */
8052   rowSorter.sortAlpha = function sortAlpha(a, b) {
8053     var nulls = rowSorter.handleNulls(a, b);
8054     if ( nulls !== null ){
8055       return nulls;
8056     } else {
8057       var strA = a.toLowerCase(),
8058           strB = b.toLowerCase();
8059   
8060       return strA === strB ? 0 : (strA < strB ? -1 : 1);
8061     }
8062   };
8063
8064
8065   /**
8066    * @ngdoc method
8067    * @methodOf ui.grid.class:RowSorter
8068    * @name sortDate
8069    * @description Sorts date values. Handles nulls and undefined through calling handleNulls 
8070    * @param {object} a sort value a
8071    * @param {object} b sort value b
8072    * @returns {number} normal sort function, returns -ve, 0, +ve
8073    */
8074   rowSorter.sortDate = function sortDate(a, b) {
8075     var nulls = rowSorter.handleNulls(a, b);
8076     if ( nulls !== null ){
8077       return nulls;
8078     } else {
8079       var timeA = a.getTime(),
8080           timeB = b.getTime();
8081   
8082       return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
8083     }
8084   };
8085
8086
8087   /**
8088    * @ngdoc method
8089    * @methodOf ui.grid.class:RowSorter
8090    * @name sortBool
8091    * @description Sorts boolean values, true is considered larger than false. 
8092    * Handles nulls and undefined through calling handleNulls 
8093    * @param {object} a sort value a
8094    * @param {object} b sort value b
8095    * @returns {number} normal sort function, returns -ve, 0, +ve
8096    */
8097   rowSorter.sortBool = function sortBool(a, b) {
8098     var nulls = rowSorter.handleNulls(a, b);
8099     if ( nulls !== null ){
8100       return nulls;
8101     } else {
8102       if (a && b) {
8103         return 0;
8104       }
8105   
8106       if (!a && !b) {
8107         return 0;
8108       }
8109       else {
8110         return a ? 1 : -1;
8111       }
8112     }
8113   };
8114
8115
8116   /**
8117    * @ngdoc method
8118    * @methodOf ui.grid.class:RowSorter
8119    * @name getSortFn
8120    * @description Get the sort function for the column.  Looks first in 
8121    * rowSorter.colSortFnCache using the column name, failing that it
8122    * looks at col.sortingAlgorithm (and puts it in the cache), failing that
8123    * it guesses the sort algorithm based on the data type.
8124    * 
8125    * The cache currently seems a bit pointless, as none of the work we do is
8126    * processor intensive enough to need caching.  Presumably in future we might
8127    * inspect the row data itself to guess the sort function, and in that case
8128    * it would make sense to have a cache, the infrastructure is in place to allow
8129    * that.
8130    * 
8131    * @param {Grid} grid the grid to consider
8132    * @param {GridCol} col the column to find a function for
8133    * @param {array} rows an array of grid rows.  Currently unused, but presumably in future
8134    * we might inspect the rows themselves to decide what sort of data might be there
8135    * @returns {function} the sort function chosen for the column
8136    */
8137   rowSorter.getSortFn = function getSortFn(grid, col, rows) {
8138     var sortFn, item;
8139
8140     // See if we already figured out what to use to sort the column and have it in the cache
8141     if (rowSorter.colSortFnCache[col.colDef.name]) {
8142       sortFn = rowSorter.colSortFnCache[col.colDef.name];
8143     }
8144     // If the column has its OWN sorting algorithm, use that
8145     else if (col.sortingAlgorithm !== undefined) {
8146       sortFn = col.sortingAlgorithm;
8147       rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
8148     }
8149     // Try and guess what sort function to use
8150     else {
8151       // Guess the sort function
8152       sortFn = rowSorter.guessSortFn(col.colDef.type);
8153
8154       // If we found a sort function, cache it
8155       if (sortFn) {
8156         rowSorter.colSortFnCache[col.colDef.name] = sortFn;
8157       }
8158       else {
8159         // We assign the alpha sort because anything that is null/undefined will never get passed to
8160         // the actual sorting function. It will get caught in our null check and returned to be sorted
8161         // down to the bottom
8162         sortFn = rowSorter.sortAlpha;
8163       }
8164     }
8165
8166     return sortFn;
8167   };
8168
8169
8170
8171   /**
8172    * @ngdoc method
8173    * @methodOf ui.grid.class:RowSorter
8174    * @name prioritySort
8175    * @description Used where multiple columns are present in the sort criteria,
8176    * we determine which column should take precedence in the sort by sorting
8177    * the columns based on their sort.priority
8178    * 
8179    * @param {gridColumn} a column a
8180    * @param {gridColumn} b column b
8181    * @returns {number} normal sort function, returns -ve, 0, +ve
8182    */
8183   rowSorter.prioritySort = function (a, b) {
8184     // Both columns have a sort priority
8185     if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
8186       // A is higher priority
8187       if (a.sort.priority < b.sort.priority) {
8188         return -1;
8189       }
8190       // Equal
8191       else if (a.sort.priority === b.sort.priority) {
8192         return 0;
8193       }
8194       // B is higher
8195       else {
8196         return 1;
8197       }
8198     }
8199     // Only A has a priority
8200     else if (a.sort.priority || a.sort.priority === 0) {
8201       return -1;
8202     }
8203     // Only B has a priority
8204     else if (b.sort.priority || b.sort.priority === 0) {
8205       return 1;
8206     }
8207     // Neither has a priority
8208     else {
8209       return 0;
8210     }
8211   };
8212
8213
8214   /**
8215    * @ngdoc object
8216    * @name useExternalSorting
8217    * @propertyOf ui.grid.class:GridOptions
8218    * @description Prevents the internal sorting from executing.  Events will
8219    * still be fired when the sort changes, and the sort information on
8220    * the columns will be updated, allowing an external sorter (for example,
8221    * server sorting) to be implemented.  Defaults to false. 
8222    * 
8223    */
8224   /**
8225    * @ngdoc method
8226    * @methodOf ui.grid.class:RowSorter
8227    * @name sort
8228    * @description sorts the grid 
8229    * @param {Object} grid the grid itself
8230    * @param {Object} rows the rows to be sorted
8231    * @param {Object} columns the columns in which to look
8232    * for sort criteria
8233    */
8234   rowSorter.sort = function rowSorterSort(grid, rows, columns) {
8235     // first make sure we are even supposed to do work
8236     if (!rows) {
8237       return;
8238     }
8239     
8240     if (grid.options.useExternalSorting){
8241       return rows;
8242     }
8243
8244     // Build the list of columns to sort by
8245     var sortCols = [];
8246     columns.forEach(function (col) {
8247       if (col.sort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
8248         sortCols.push(col);
8249       }
8250     });
8251
8252     // Sort the "sort columns" by their sort priority
8253     sortCols = sortCols.sort(rowSorter.prioritySort);
8254
8255     // Now rows to sort by, maintain original order
8256     if (sortCols.length === 0) {
8257       return rows;
8258     }
8259     
8260     // Re-usable variables
8261     var col, direction;
8262
8263     // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
8264     // var d = data.slice(0);
8265     var r = rows.slice(0);
8266
8267     // Now actually sort the data
8268     return rows.sort(function rowSortFn(rowA, rowB) {
8269       var tem = 0,
8270           idx = 0,
8271           sortFn;
8272
8273       while (tem === 0 && idx < sortCols.length) {
8274         // grab the metadata for the rest of the logic
8275         col = sortCols[idx];
8276         direction = sortCols[idx].sort.direction;
8277
8278         sortFn = rowSorter.getSortFn(grid, col, r);
8279         
8280         var propA = grid.getCellValue(rowA, col);
8281         var propB = grid.getCellValue(rowB, col);
8282
8283         tem = sortFn(propA, propB);
8284
8285         idx++;
8286       }
8287
8288       // Made it this far, we don't have to worry about null & undefined
8289       if (direction === uiGridConstants.ASC) {
8290         return tem;
8291       } else {
8292         return 0 - tem;
8293       }
8294     });
8295   };
8296
8297   return rowSorter;
8298 }]);
8299
8300 })();
8301 (function() {
8302
8303 var module = angular.module('ui.grid');
8304
8305 function getStyles (elem) {
8306   var e = elem;
8307   if (typeof(e.length) !== 'undefined' && e.length) {
8308     e = elem[0];
8309   }
8310
8311   return e.ownerDocument.defaultView.getComputedStyle(e, null);
8312 }
8313
8314 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
8315     // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
8316     // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
8317     rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
8318     cssShow = { position: "absolute", visibility: "hidden", display: "block" };
8319
8320 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
8321   var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
8322           // If we already have the right measurement, avoid augmentation
8323           4 :
8324           // Otherwise initialize for horizontal or vertical properties
8325           name === 'width' ? 1 : 0,
8326
8327           val = 0;
8328
8329   var sides = ['Top', 'Right', 'Bottom', 'Left'];
8330   
8331   for ( ; i < 4; i += 2 ) {
8332     var side = sides[i];
8333     // dump('side', side);
8334
8335     // both box models exclude margin, so add it if we want it
8336     if ( extra === 'margin' ) {
8337       var marg = parseFloat(styles[extra + side]);
8338       if (!isNaN(marg)) {
8339         val += marg;
8340       }
8341     }
8342     // dump('val1', val);
8343
8344     if ( isBorderBox ) {
8345       // border-box includes padding, so remove it if we want content
8346       if ( extra === 'content' ) {
8347         var padd = parseFloat(styles['padding' + side]);
8348         if (!isNaN(padd)) {
8349           val -= padd;
8350           // dump('val2', val);
8351         }
8352       }
8353
8354       // at this point, extra isn't border nor margin, so remove border
8355       if ( extra !== 'margin' ) {
8356         var bordermarg = parseFloat(styles['border' + side + 'Width']);
8357         if (!isNaN(bordermarg)) {
8358           val -= bordermarg;
8359           // dump('val3', val);
8360         }
8361       }
8362     }
8363     else {
8364       // at this point, extra isn't content, so add padding
8365       var nocontentPad = parseFloat(styles['padding' + side]);
8366       if (!isNaN(nocontentPad)) {
8367         val += nocontentPad;
8368         // dump('val4', val);
8369       }
8370
8371       // at this point, extra isn't content nor padding, so add border
8372       if ( extra !== 'padding') {
8373         var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
8374         if (!isNaN(nocontentnopad)) {
8375           val += nocontentnopad;
8376           // dump('val5', val);
8377         }
8378       }
8379     }
8380   }
8381
8382   // dump('augVal', val);
8383
8384   return val;
8385 }
8386
8387 function getWidthOrHeight( elem, name, extra ) {
8388   // Start with offset property, which is equivalent to the border-box value
8389   var valueIsBorderBox = true,
8390           val,
8391           styles = getStyles(elem),
8392           isBorderBox = styles['boxSizing'] === 'border-box';
8393
8394   // some non-html elements return undefined for offsetWidth, so check for null/undefined
8395   // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
8396   // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
8397   if ( val <= 0 || val == null ) {
8398     // Fall back to computed then uncomputed css if necessary
8399     val = styles[name];
8400     if ( val < 0 || val == null ) {
8401       val = elem.style[ name ];
8402     }
8403
8404     // Computed unit is not pixels. Stop here and return.
8405     if ( rnumnonpx.test(val) ) {
8406       return val;
8407     }
8408
8409     // we need the check for style in case a browser which returns unreliable values
8410     // for getComputedStyle silently falls back to the reliable elem.style
8411     valueIsBorderBox = isBorderBox &&
8412             ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
8413
8414     // Normalize "", auto, and prepare for extra
8415     val = parseFloat( val ) || 0;
8416   }
8417
8418   // use the active box-sizing model to add/subtract irrelevant styles
8419   var ret = ( val +
8420     augmentWidthOrHeight(
8421       elem,
8422       name,
8423       extra || ( isBorderBox ? "border" : "content" ),
8424       valueIsBorderBox,
8425       styles
8426     )
8427   );
8428
8429   // dump('ret', ret, val);
8430   return ret;
8431 }
8432
8433 var uid = ['0', '0', '0'];
8434 var uidPrefix = 'uiGrid-';
8435
8436 /**
8437  *  @ngdoc service
8438  *  @name ui.grid.service:GridUtil
8439  *  
8440  *  @description Grid utility functions
8441  */
8442 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$injector', '$q', '$interpolate', 'uiGridConstants',
8443   function ($log, $window, $document, $http, $templateCache, $timeout, $injector, $q, $interpolate, uiGridConstants) {
8444   var s = {
8445
8446     getStyles: getStyles,
8447
8448     /**
8449      * @ngdoc method
8450      * @name createBoundedWrapper
8451      * @methodOf ui.grid.service:GridUtil
8452      *
8453      * @param {object} Object to bind 'this' to
8454      * @param {method} Method to bind
8455      * @returns {Function} The wrapper that performs the binding
8456      *
8457      * @description
8458      * Binds given method to given object.
8459      *
8460      * By means of a wrapper, ensures that ``method`` is always bound to
8461      * ``object`` regardless of its calling environment.
8462      * Iow, inside ``method``, ``this`` always points to ``object``.
8463      *
8464      * See http://alistapart.com/article/getoutbindingsituations
8465      *
8466      */
8467     createBoundedWrapper: function(object, method) {
8468         return function() {
8469             return method.apply(object, arguments);
8470         };
8471     },
8472
8473
8474     /**
8475      * @ngdoc method
8476      * @name readableColumnName
8477      * @methodOf ui.grid.service:GridUtil
8478      *
8479      * @param {string} columnName Column name as a string
8480      * @returns {string} Column name appropriately capitalized and split apart
8481      *
8482        @example
8483        <example module="app">
8484         <file name="app.js">
8485           var app = angular.module('app', ['ui.grid']);
8486
8487           app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
8488             $scope.name = 'firstName';
8489             $scope.columnName = function(name) {
8490               return gridUtil.readableColumnName(name);
8491             };
8492           }]);
8493         </file>
8494         <file name="index.html">
8495           <div ng-controller="MainCtrl">
8496             <strong>Column name:</strong> <input ng-model="name" />
8497             <br>
8498             <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
8499           </div>
8500         </file>
8501       </example>
8502      */
8503     readableColumnName: function (columnName) {
8504       // Convert underscores to spaces
8505       if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
8506
8507       if (typeof(columnName) !== 'string') {
8508         columnName = String(columnName);
8509       }
8510
8511       return columnName.replace(/_+/g, ' ')
8512         // Replace a completely all-capsed word with a first-letter-capitalized version
8513         .replace(/^[A-Z]+$/, function (match) {
8514           return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
8515         })
8516         // Capitalize the first letter of words
8517         .replace(/(\w+)/g, function (match) {
8518           return angular.uppercase(match.charAt(0)) + match.slice(1);
8519         })
8520         // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
8521         // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
8522         // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
8523         .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
8524     },
8525
8526     /**
8527      * @ngdoc method
8528      * @name getColumnsFromData
8529      * @methodOf ui.grid.service:GridUtil
8530      * @description Return a list of column names, given a data set
8531      *
8532      * @param {string} data Data array for grid
8533      * @returns {Object} Column definitions with field accessor and column name
8534      *
8535      * @example
8536        <pre>
8537          var data = [
8538            { firstName: 'Bob', lastName: 'Jones' },
8539            { firstName: 'Frank', lastName: 'Smith' }
8540          ];
8541
8542          var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
8543
8544          columnDefs == [
8545           {
8546             field: 'firstName',
8547             name: 'First Name'
8548           },
8549           {
8550             field: 'lastName',
8551             name: 'Last Name'
8552           }
8553          ];
8554        </pre>
8555      */
8556     getColumnsFromData: function (data, excludeProperties) {
8557       var columnDefs = [];
8558
8559       if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
8560       if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
8561
8562       var item = data[0];
8563       
8564       angular.forEach(item,function (prop, propName) {
8565         if ( excludeProperties.indexOf(propName) === -1){
8566           columnDefs.push({
8567             name: propName
8568           });
8569         }
8570       });
8571
8572       return columnDefs;
8573     },
8574
8575     /**
8576      * @ngdoc method
8577      * @name newId
8578      * @methodOf ui.grid.service:GridUtil
8579      * @description Return a unique ID string
8580      *
8581      * @returns {string} Unique string
8582      *
8583      * @example
8584        <pre>
8585         var id = GridUtil.newId();
8586
8587         # 1387305700482;
8588        </pre>
8589      */
8590     newId: (function() {
8591       var seedId = new Date().getTime();
8592       return function() {
8593           return seedId += 1;
8594       };
8595     })(),
8596
8597
8598     /**
8599      * @ngdoc method
8600      * @name getTemplate
8601      * @methodOf ui.grid.service:GridUtil
8602      * @description Get's template from cache / element / url
8603      *
8604      * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
8605      *   an jQuery/Angualr element, or a promise that returns the template contents to use.
8606      * @returns {object} a promise resolving to template contents
8607      *
8608      * @example
8609      <pre>
8610      GridUtil.getTemplate(url).then(function (contents) {
8611           alert(contents);
8612         })
8613      </pre>
8614      */
8615     getTemplate: function (template) {
8616       // Try to fetch the template out of the templateCache
8617       if ($templateCache.get(template)) {
8618         return s.postProcessTemplate($templateCache.get(template));
8619       }
8620
8621       // See if the template is itself a promise
8622       if (template.hasOwnProperty('then')) {
8623         return template.then(s.postProcessTemplate);
8624       }
8625
8626       // If the template is an element, return the element
8627       try {
8628         if (angular.element(template).length > 0) {
8629           return $q.when(template).then(s.postProcessTemplate);
8630         }
8631       }
8632       catch (err){
8633         //do nothing; not valid html
8634       }
8635
8636       s.logDebug('fetching url', template);
8637
8638       // Default to trying to fetch the template as a url with $http
8639       return $http({ method: 'GET', url: template})
8640         .then(
8641           function (result) {
8642             var templateHtml = result.data.trim();
8643             //put in templateCache for next call
8644             $templateCache.put(template, templateHtml);
8645             return templateHtml;
8646           },
8647           function (err) {
8648             throw new Error("Could not get template " + template + ": " + err);
8649           }
8650         )
8651         .then(s.postProcessTemplate);
8652     },
8653
8654     // 
8655     postProcessTemplate: function (template) {
8656       var startSym = $interpolate.startSymbol(),
8657           endSym = $interpolate.endSymbol();
8658
8659       // If either of the interpolation symbols have been changed, we need to alter this template
8660       if (startSym !== '{{' || endSym !== '}}') {
8661         template = template.replace(/\{\{/g, startSym);
8662         template = template.replace(/\}\}/g, endSym);
8663       }
8664
8665       return $q.when(template);
8666     },
8667
8668     /**
8669      * @ngdoc method
8670      * @name guessType
8671      * @methodOf ui.grid.service:GridUtil
8672      * @description guesses the type of an argument
8673      *
8674      * @param {string/number/bool/object} item variable to examine
8675      * @returns {string} one of the following
8676      * 'string'
8677      * 'boolean'
8678      * 'number'
8679      * 'date'
8680      * 'object'
8681      */
8682     guessType : function (item) {
8683       var itemType = typeof(item);
8684
8685       // Check for numbers and booleans
8686       switch (itemType) {
8687         case "number":
8688         case "boolean":
8689         case "string":
8690           return itemType;
8691         default:
8692           if (angular.isDate(item)) {
8693             return "date";
8694           }
8695           return "object";
8696       }
8697     },
8698
8699
8700   /**
8701     * @ngdoc method
8702     * @name elementWidth
8703     * @methodOf ui.grid.service:GridUtil
8704     *
8705     * @param {element} element DOM element
8706     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
8707     *
8708     * @returns {number} Element width in pixels, accounting for any borders, etc.
8709     */
8710     elementWidth: function (elem) {
8711       
8712     },
8713
8714     /**
8715     * @ngdoc method
8716     * @name elementHeight
8717     * @methodOf ui.grid.service:GridUtil
8718     *
8719     * @param {element} element DOM element
8720     * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
8721     *
8722     * @returns {number} Element height in pixels, accounting for any borders, etc.
8723     */
8724     elementHeight: function (elem) {
8725       
8726     },
8727
8728     // Thanks to http://stackoverflow.com/a/13382873/888165
8729     getScrollbarWidth: function() {
8730         var outer = document.createElement("div");
8731         outer.style.visibility = "hidden";
8732         outer.style.width = "100px";
8733         outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
8734
8735         document.body.appendChild(outer);
8736
8737         var widthNoScroll = outer.offsetWidth;
8738         // force scrollbars
8739         outer.style.overflow = "scroll";
8740
8741         // add innerdiv
8742         var inner = document.createElement("div");
8743         inner.style.width = "100%";
8744         outer.appendChild(inner);        
8745
8746         var widthWithScroll = inner.offsetWidth;
8747
8748         // remove divs
8749         outer.parentNode.removeChild(outer);
8750
8751         return widthNoScroll - widthWithScroll;
8752     },
8753
8754     swap: function( elem, options, callback, args ) {
8755       var ret, name,
8756               old = {};
8757
8758       // Remember the old values, and insert the new ones
8759       for ( name in options ) {
8760         old[ name ] = elem.style[ name ];
8761         elem.style[ name ] = options[ name ];
8762       }
8763
8764       ret = callback.apply( elem, args || [] );
8765
8766       // Revert the old values
8767       for ( name in options ) {
8768         elem.style[ name ] = old[ name ];
8769       }
8770
8771       return ret;
8772     },
8773
8774     fakeElement: function( elem, options, callback, args ) {
8775       var ret, name,
8776           newElement = angular.element(elem).clone()[0];
8777
8778       for ( name in options ) {
8779         newElement.style[ name ] = options[ name ];
8780       }
8781
8782       angular.element(document.body).append(newElement);
8783
8784       ret = callback.call( newElement, newElement );
8785
8786       angular.element(newElement).remove();
8787
8788       return ret;
8789     },
8790
8791     /**
8792     * @ngdoc method
8793     * @name normalizeWheelEvent
8794     * @methodOf ui.grid.service:GridUtil
8795     *
8796     * @param {event} event A mouse wheel event
8797     *
8798     * @returns {event} A normalized event
8799     *
8800     * @description
8801     * Given an event from this list:
8802     *
8803     * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
8804     *
8805     * "normalize" it
8806     * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
8807     */
8808     normalizeWheelEvent: function (event) {
8809       // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
8810       // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
8811       var lowestDelta, lowestDeltaXY;
8812       
8813       var orgEvent   = event || window.event,
8814           args       = [].slice.call(arguments, 1),
8815           delta      = 0,
8816           deltaX     = 0,
8817           deltaY     = 0,
8818           absDelta   = 0,
8819           absDeltaXY = 0,
8820           fn;
8821
8822       // event = $.event.fix(orgEvent);
8823       // event.type = 'mousewheel';
8824
8825       // NOTE: jQuery masks the event and stores it in the event as originalEvent
8826       if (orgEvent.originalEvent) {
8827         orgEvent = orgEvent.originalEvent;
8828       }
8829
8830       // Old school scrollwheel delta
8831       if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
8832       if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
8833
8834       // At a minimum, setup the deltaY to be delta
8835       deltaY = delta;
8836
8837       // Firefox < 17 related to DOMMouseScroll event
8838       if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
8839           deltaY = 0;
8840           deltaX = delta * -1;
8841       }
8842
8843       // New school wheel delta (wheel event)
8844       if ( orgEvent.deltaY ) {
8845           deltaY = orgEvent.deltaY * -1;
8846           delta  = deltaY;
8847       }
8848       if ( orgEvent.deltaX ) {
8849           deltaX = orgEvent.deltaX;
8850           delta  = deltaX * -1;
8851       }
8852
8853       // Webkit
8854       if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
8855       if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
8856
8857       // Look for lowest delta to normalize the delta values
8858       absDelta = Math.abs(delta);
8859       if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
8860       absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
8861       if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
8862
8863       // Get a whole value for the deltas
8864       fn     = delta > 0 ? 'floor' : 'ceil';
8865       delta  = Math[fn](delta  / lowestDelta);
8866       deltaX = Math[fn](deltaX / lowestDeltaXY);
8867       deltaY = Math[fn](deltaY / lowestDeltaXY);
8868
8869       return {
8870         delta: delta,
8871         deltaX: deltaX,
8872         deltaY: deltaY
8873       };
8874     },
8875
8876     // Stolen from Modernizr
8877     // TODO: make this, and everythign that flows from it, robust
8878     //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
8879     isTouchEnabled: function() {
8880       var bool;
8881
8882       if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
8883         bool = true;
8884       }
8885
8886       return bool;
8887     },
8888
8889     isNullOrUndefined: function(obj) {
8890       if (obj === undefined || obj === null) {
8891         return true;
8892       }
8893       return false;
8894     },
8895
8896     endsWith: function(str, suffix) {
8897       if (!str || !suffix || typeof str !== "string") {
8898         return false;
8899       }
8900       return str.indexOf(suffix, str.length - suffix.length) !== -1;
8901     },
8902
8903     arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
8904         var found = false;
8905         angular.forEach(array, function (object) {
8906             if (object[propertyName] === propertyValue) {
8907                 found = true;
8908             }
8909         });
8910         return found;
8911     },
8912
8913     // Shim requestAnimationFrame
8914     requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
8915                            $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
8916                            function(fn) {
8917                              return $timeout(fn, 10, false);
8918                            },
8919
8920     numericAndNullSort: function (a, b) {
8921       if (a === null) { return 1; }
8922       if (b === null) { return -1; }
8923       if (a === null && b === null) { return 0; }
8924       return a - b;
8925     },
8926
8927     // Disable ngAnimate animations on an element
8928     disableAnimations: function (element) {
8929       var $animate;
8930       try {
8931         $animate = $injector.get('$animate');
8932         $animate.enabled(false, element);
8933       }
8934       catch (e) {}
8935     },
8936
8937     enableAnimations: function (element) {
8938       var $animate;
8939       try {
8940         $animate = $injector.get('$animate');
8941         $animate.enabled(true, element);
8942         return $animate;
8943       }
8944       catch (e) {}
8945     },
8946
8947     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
8948     nextUid: function nextUid() {
8949       var index = uid.length;
8950       var digit;
8951
8952       while (index) {
8953         index--;
8954         digit = uid[index].charCodeAt(0);
8955         if (digit === 57 /*'9'*/) {
8956           uid[index] = 'A';
8957           return uidPrefix + uid.join('');
8958         }
8959         if (digit === 90  /*'Z'*/) {
8960           uid[index] = '0';
8961         } else {
8962           uid[index] = String.fromCharCode(digit + 1);
8963           return uidPrefix + uid.join('');
8964         }
8965       }
8966       uid.unshift('0');
8967
8968       return uidPrefix + uid.join('');
8969     },
8970
8971     // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
8972     hashKey: function hashKey(obj) {
8973       var objType = typeof obj,
8974           key;
8975
8976       if (objType === 'object' && obj !== null) {
8977         if (typeof (key = obj.$$hashKey) === 'function') {
8978           // must invoke on object to keep the right this
8979           key = obj.$$hashKey();
8980         }
8981         else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
8982           key = obj.$$hashKey;
8983         }
8984         else if (key === undefined) {
8985           key = obj.$$hashKey = s.nextUid();
8986         }
8987       }
8988       else {
8989         key = obj;
8990       }
8991
8992       return objType + ':' + key;
8993     },
8994
8995     resetUids: function () {
8996       uid = ['0', '0', '0'];
8997     },
8998     
8999     /**
9000      * @ngdoc method
9001      * @methodOf ui.grid.service:GridUtil
9002      * @name logError
9003      * @description wraps the $log method, allowing us to choose different
9004      * treatment within ui-grid if we so desired.  At present we only log
9005      * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
9006      * @param {string} logMessage message to be logged to the console
9007      * 
9008      */
9009     logError: function( logMessage ){
9010       if ( uiGridConstants.LOG_ERROR_MESSAGES ){
9011         $log.error( logMessage );
9012       }
9013     },
9014
9015     /**
9016      * @ngdoc method
9017      * @methodOf ui.grid.service:GridUtil
9018      * @name logWarn
9019      * @description wraps the $log method, allowing us to choose different
9020      * treatment within ui-grid if we so desired.  At present we only log
9021      * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
9022      * @param {string} logMessage message to be logged to the console
9023      * 
9024      */
9025     logWarn: function( logMessage ){
9026       if ( uiGridConstants.LOG_WARN_MESSAGES ){
9027         $log.warn( logMessage );
9028       }
9029     },
9030
9031     /**
9032      * @ngdoc method
9033      * @methodOf ui.grid.service:GridUtil
9034      * @name logDebug
9035      * @description wraps the $log method, allowing us to choose different
9036      * treatment within ui-grid if we so desired.  At present we only log
9037      * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
9038      * 
9039      */
9040     logDebug: function() {
9041       if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
9042         $log.debug.apply($log, arguments);
9043       }
9044     }
9045
9046   };
9047
9048   ['width', 'height'].forEach(function (name) {
9049     var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
9050     s['element' + capsName] = function (elem, extra) {
9051       var e = elem;
9052       if (e && typeof(e.length) !== 'undefined' && e.length) {
9053         e = elem[0];
9054       }
9055
9056       if (e) {
9057         var styles = getStyles(e);
9058         return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
9059                   s.fakeElement(e, cssShow, function(newElm) {
9060                     return getWidthOrHeight( newElm, name, extra );
9061                   }) :
9062                   getWidthOrHeight( e, name, extra );
9063       }
9064       else {
9065         return null;
9066       }
9067     };
9068
9069     s['outerElement' + capsName] = function (elem, margin) {
9070       return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
9071     };
9072   });
9073
9074   // http://stackoverflow.com/a/24107550/888165
9075   s.closestElm = function closestElm(el, selector) {
9076     if (typeof(el.length) !== 'undefined' && el.length) {
9077       el = el[0];
9078     }
9079
9080     var matchesFn;
9081
9082     // find vendor prefix
9083     ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
9084         if (typeof document.body[fn] === 'function') {
9085             matchesFn = fn;
9086             return true;
9087         }
9088         return false;
9089     });
9090
9091     // traverse parents
9092     var parent;
9093     while (el !== null) {
9094       parent = el.parentElement;
9095       if (parent !== null && parent[matchesFn](selector)) {
9096           return parent;
9097       }
9098       el = parent;
9099     }
9100
9101     return null;
9102   };
9103
9104   s.type = function (obj) {
9105     var text = Function.prototype.toString.call(obj.constructor);
9106     return text.match(/function (.*?)\(/)[1];
9107   };
9108
9109   s.getBorderSize = function getBorderSize(elem, borderType) {
9110     if (typeof(elem.length) !== 'undefined' && elem.length) {
9111       elem = elem[0];
9112     }
9113
9114     var styles = getStyles(elem);
9115
9116     // If a specific border is supplied, like 'top', read the 'borderTop' style property
9117     if (borderType) {
9118       borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
9119     }
9120     else {
9121       borderType = 'border';
9122     }
9123
9124     borderType += 'Width';
9125
9126     var val = parseInt(styles[borderType], 10);
9127
9128     if (isNaN(val)) {
9129       return 0;
9130     }
9131     else {
9132       return val;
9133     }
9134   };
9135
9136   // http://stackoverflow.com/a/22948274/888165
9137   // TODO: Opera? Mobile?
9138   s.detectBrowser = function detectBrowser() {
9139     var userAgent = $window.navigator.userAgent;
9140
9141     var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
9142
9143     for (var key in browsers) {
9144       if (browsers[key].test(userAgent)) {
9145         return key;
9146       }
9147     }
9148
9149     return 'unknown';
9150   };
9151
9152   /**
9153     * @ngdoc method
9154     * @name normalizeScrollLeft
9155     * @methodOf ui.grid.service:GridUtil
9156     *
9157     * @param {element} element The element to get the `scrollLeft` from.
9158     *
9159     * @returns {int} A normalized scrollLeft value for the current browser.
9160     *
9161     * @description
9162     * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
9163     */
9164   s.normalizeScrollLeft = function normalizeScrollLeft(element) {
9165     if (typeof(element.length) !== 'undefined' && element.length) {
9166       element = element[0];
9167     }
9168
9169     var browser = s.detectBrowser();
9170
9171     var scrollLeft = element.scrollLeft;
9172     
9173     var dir = s.getStyles(element)['direction'];
9174
9175     // IE stays normal in RTL
9176     if (browser === 'ie') {
9177       return scrollLeft;
9178     }
9179     // Chrome doesn't alter the scrollLeft value. So with RTL on a 400px-wide grid, the right-most position will still be 400 and the left-most will still be 0;
9180     else if (browser === 'chrome') {
9181       if (dir === 'rtl') {
9182         // Get the max scroll for the element
9183         var maxScrollLeft = element.scrollWidth - element.clientWidth;
9184
9185         // Subtract the current scroll amount from the max scroll
9186         return maxScrollLeft - scrollLeft;
9187       }
9188       else {
9189         return scrollLeft;
9190       }
9191     }
9192     // Firefox goes negative!
9193     else if (browser === 'firefox') {
9194       return Math.abs(scrollLeft);
9195     }
9196     else {
9197       // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
9198       return scrollLeft;
9199     }
9200   };
9201
9202   /**
9203   * @ngdoc method
9204   * @name normalizeScrollLeft
9205   * @methodOf ui.grid.service:GridUtil
9206   *
9207   * @param {element} element The element to normalize the `scrollLeft` value for
9208   * @param {int} scrollLeft The `scrollLeft` value to denormalize.
9209   *
9210   * @returns {int} A normalized scrollLeft value for the current browser.
9211   *
9212   * @description
9213   * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
9214   */
9215   s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft) {
9216     if (typeof(element.length) !== 'undefined' && element.length) {
9217       element = element[0];
9218     }
9219
9220     var browser = s.detectBrowser();
9221
9222     var dir = s.getStyles(element)['direction'];
9223
9224     // IE stays normal in RTL
9225     if (browser === 'ie') {
9226       return scrollLeft;
9227     }
9228     // Chrome doesn't alter the scrollLeft value. So with RTL on a 400px-wide grid, the right-most position will still be 400 and the left-most will still be 0;
9229     else if (browser === 'chrome') {
9230       if (dir === 'rtl') {
9231         // Get the max scroll for the element
9232         var maxScrollLeft = element.scrollWidth - element.clientWidth;
9233
9234         // Subtract the current scroll amount from the max scroll
9235         return maxScrollLeft - scrollLeft;
9236       }
9237       else {
9238         return scrollLeft;
9239       }
9240     }
9241     // Firefox goes negative!
9242     else if (browser === 'firefox') {
9243       if (dir === 'rtl') {
9244         return scrollLeft * -1;
9245       }
9246       else {
9247         return scrollLeft;
9248       }
9249     }
9250     else {
9251       // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
9252       return scrollLeft;
9253     }
9254   };
9255
9256     /**
9257      * @ngdoc method
9258      * @name preEval
9259      * @methodOf ui.grid.service:GridUtil
9260      *
9261      * @param {string} path Path to evaluate
9262      *
9263      * @returns {string} A path that is normalized.
9264      *
9265      * @description
9266      * Takes a field path and converts it to bracket notation to allow for special characters in path
9267      * @example
9268      * <pre>
9269      * gridUtil.preEval('property') == 'property'
9270      * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
9271      * </pre>
9272      */
9273   s.preEval = function (path) {
9274     var m = uiGridConstants.BRACKET_REGEXP.exec(path);
9275     if (m) {
9276       return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
9277     } else {
9278       path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
9279       var parts = path.split(uiGridConstants.DOT_REGEXP);
9280       var preparsed = [parts.shift()];    // first item must be var notation, thus skip
9281       angular.forEach(parts, function (part) {
9282         preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
9283       });
9284       return preparsed.join('[\'');
9285     }
9286   };
9287
9288   /**
9289    * @ngdoc method
9290    * @name debounce
9291    * @methodOf ui.grid.service:GridUtil
9292    *
9293    * @param {function} func function to debounce
9294    * @param {number} wait milliseconds to delay
9295    * @param {bool} immediate execute before delay
9296    *
9297    * @returns {function} A function that can be executed as debounced function
9298    *
9299    * @description
9300    * Copied from https://github.com/shahata/angular-debounce
9301    * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
9302    * @example
9303    * <pre>
9304    * var debouncedFunc =  gridUtil.debounce(function(){alert('debounced');}, 500);
9305    * debouncedFunc();
9306    * debouncedFunc();
9307    * debouncedFunc();
9308    * </pre>
9309    */
9310   s.debounce =  function (func, wait, immediate) {
9311     var timeout, args, context, result;
9312     function debounce() {
9313       /* jshint validthis:true */
9314       context = this;
9315       args = arguments;
9316       var later = function () {
9317         timeout = null;
9318         if (!immediate) {
9319           result = func.apply(context, args);
9320         }
9321       };
9322       var callNow = immediate && !timeout;
9323       if (timeout) {
9324         $timeout.cancel(timeout);
9325       }
9326       timeout = $timeout(later, wait);
9327       if (callNow) {
9328         result = func.apply(context, args);
9329       }
9330       return result;
9331     }
9332     debounce.cancel = function () {
9333       $timeout.cancel(timeout);
9334       timeout = null;
9335     };
9336     return debounce;
9337   };
9338
9339   /**
9340    * @ngdoc method
9341    * @name throttle
9342    * @methodOf ui.grid.service:GridUtil
9343    *
9344    * @param {function} func function to throttle
9345    * @param {number} wait milliseconds to delay after first trigger
9346    * @param {Object} params to use in throttle.
9347    *
9348    * @returns {function} A function that can be executed as throttled function
9349    *
9350    * @description
9351    * Adapted from debounce function (above)
9352    * Potential keys for Params Object are:
9353    *    trailing (bool) - whether to trigger after throttle time ends if called multiple times
9354    * @example
9355    * <pre>
9356    * var throttledFunc =  gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
9357    * throttledFunc(); //=> logs throttled
9358    * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
9359    * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
9360    * </pre>
9361    */
9362   s.throttle = function(func, wait, options){
9363     options = options || {};
9364     var lastCall = 0, queued = null, context, args;
9365
9366     function runFunc(endDate){
9367       lastCall = +new Date();
9368       func.apply(context, args);
9369       $timeout(function(){ queued = null; }, 0);
9370     }
9371
9372     return function(){
9373       /* jshint validthis:true */
9374       context = this;
9375       args = arguments;
9376       if (queued === null){
9377         var sinceLast = +new Date() - lastCall;
9378         if (sinceLast > wait){
9379           runFunc();
9380         }
9381         else if (options.trailing){
9382           queued = $timeout(runFunc, wait - sinceLast);
9383         }
9384       }
9385     };
9386   };
9387
9388   return s;
9389 }]);
9390
9391 // Add 'px' to the end of a number string if it doesn't have it already
9392 module.filter('px', function() {
9393   return function(str) {
9394     if (str.match(/^[\d\.]+$/)) {
9395       return str + 'px';
9396     }
9397     else {
9398       return str;
9399     }
9400   };
9401 });
9402
9403 })();
9404
9405 (function(){
9406   angular.module('ui.grid').config(['$provide', function($provide) {
9407     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9408       $delegate.add('da', {
9409         aggregate:{
9410           label: 'artikler'
9411         },
9412         groupPanel:{
9413           description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
9414         },
9415         search:{
9416           placeholder: 'Søg...',
9417           showingItems: 'Viste rækker:',
9418           selectedItems: 'Valgte rækker:',
9419           totalItems: 'Rækker totalt:',
9420           size: 'Side størrelse:',
9421           first: 'Første side',
9422           next: 'Næste side',
9423           previous: 'Forrige side',
9424           last: 'Sidste side'
9425         },
9426         menu:{
9427           text: 'Vælg kolonner:'
9428         },
9429         column: {
9430           hide: 'Skjul kolonne'
9431         },
9432         aggregation: {
9433           count: 'samlede rækker: ',
9434           sum: 'smalede: ',
9435           avg: 'gns: ',
9436           min: 'min: ',
9437           max: 'max: '
9438         },
9439         gridMenu: {
9440           columns: 'Columns:',
9441           importerTitle: 'Import file',
9442           exporterAllAsCsv: 'Export all data as csv',
9443           exporterVisibleAsCsv: 'Export visible data as csv',
9444           exporterSelectedAsCsv: 'Export selected data as csv',
9445           exporterAllAsPdf: 'Export all data as pdf',
9446           exporterVisibleAsPdf: 'Export visible data as pdf',
9447           exporterSelectedAsPdf: 'Export selected data as pdf'
9448         },
9449         importer: {
9450           noHeaders: 'Column names were unable to be derived, does the file have a header?',
9451           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9452           invalidCsv: 'File was unable to be processed, is it valid CSV?',
9453           invalidJson: 'File was unable to be processed, is it valid Json?',
9454           jsonNotArray: 'Imported json file must contain an array, aborting.'
9455         }
9456       });
9457       return $delegate;
9458     }]);
9459   }]);
9460 })();
9461 (function () {
9462   angular.module('ui.grid').config(['$provide', function ($provide) {
9463     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
9464       $delegate.add('de', {
9465         aggregate: {
9466           label: 'eintrag'
9467         },
9468         groupPanel: {
9469           description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
9470         },
9471         search: {
9472           placeholder: 'Suche...',
9473           showingItems: 'Zeige Einträge:',
9474           selectedItems: 'Ausgewählte Einträge:',
9475           totalItems: 'Einträge gesamt:',
9476           size: 'Einträge pro Seite:',
9477           first: 'Erste Seite',
9478           next: 'Nächste Seite',
9479           previous: 'Vorherige Seite',
9480           last: 'Letzte Seite'
9481         },
9482         menu: {
9483           text: 'Spalten auswählen:'
9484         },
9485         sort: {
9486           ascending: 'aufsteigend sortieren',
9487           descending: 'absteigend sortieren',
9488           remove: 'Sortierung zurücksetzen'
9489         },
9490         column: {
9491           hide: 'Spalte ausblenden'
9492         },
9493         aggregation: {
9494           count: 'Zeilen insgesamt: ',
9495           sum: 'gesamt: ',
9496           avg: 'Durchschnitt: ',
9497           min: 'min: ',
9498           max: 'max: '
9499         },
9500         gridMenu: {
9501           columns: 'Spalten:',
9502           importerTitle: 'Datei importieren',
9503           exporterAllAsCsv: 'Alle Daten als CSV exportieren',
9504           exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
9505           exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
9506           exporterAllAsPdf: 'Alle Daten als PDF exportieren',
9507           exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
9508           exporterSelectedAsPdf: 'markierte Daten als CSV exportieren'
9509         },
9510         importer: {
9511           noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
9512           noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
9513           invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
9514           invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
9515           jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
9516         }
9517       });
9518       return $delegate;
9519     }]);
9520   }]);
9521 })();
9522
9523 (function () {
9524   angular.module('ui.grid').config(['$provide', function($provide) {
9525     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9526       $delegate.add('en', {
9527         aggregate: {
9528           label: 'items'
9529         },
9530         groupPanel: {
9531           description: 'Drag a column header here and drop it to group by that column.'
9532         },
9533         search: {
9534           placeholder: 'Search...',
9535           showingItems: 'Showing Items:',
9536           selectedItems: 'Selected Items:',
9537           totalItems: 'Total Items:',
9538           size: 'Page Size:',
9539           first: 'First Page',
9540           next: 'Next Page',
9541           previous: 'Previous Page',
9542           last: 'Last Page'
9543         },
9544         menu: {
9545           text: 'Choose Columns:'
9546         },
9547         sort: {
9548           ascending: 'Sort Ascending',
9549           descending: 'Sort Descending',
9550           remove: 'Remove Sort'
9551         },
9552         column: {
9553           hide: 'Hide Column'
9554         },
9555         aggregation: {
9556           count: 'total rows: ',
9557           sum: 'total: ',
9558           avg: 'avg: ',
9559           min: 'min: ',
9560           max: 'max: '
9561         },
9562         pinning: {
9563          pinLeft: 'Pin Left',
9564           pinRight: 'Pin Right',
9565           unpin: 'Unpin'
9566         },
9567         gridMenu: {
9568           columns: 'Columns:',
9569           importerTitle: 'Import file',
9570           exporterAllAsCsv: 'Export all data as csv',
9571           exporterVisibleAsCsv: 'Export visible data as csv',
9572           exporterSelectedAsCsv: 'Export selected data as csv',
9573           exporterAllAsPdf: 'Export all data as pdf',
9574           exporterVisibleAsPdf: 'Export visible data as pdf',
9575           exporterSelectedAsPdf: 'Export selected data as pdf'
9576         },
9577         importer: {
9578           noHeaders: 'Column names were unable to be derived, does the file have a header?',
9579           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9580           invalidCsv: 'File was unable to be processed, is it valid CSV?',
9581           invalidJson: 'File was unable to be processed, is it valid Json?',
9582           jsonNotArray: 'Imported json file must contain an array, aborting.'
9583         },
9584         paging: {
9585           sizes: 'items per page',
9586           totalItems: 'items'
9587         }
9588       });
9589       return $delegate;
9590     }]);
9591   }]);
9592 })();
9593 (function () {
9594   angular.module('ui.grid').config(['$provide', function($provide) {
9595     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9596       $delegate.add('es', {
9597         aggregate: {
9598           label: 'Artículos'
9599         },
9600         groupPanel: {
9601           description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
9602         },
9603         search: {
9604           placeholder: 'Buscar...',
9605           showingItems: 'Artículos Mostrados:',
9606           selectedItems: 'Artículos Seleccionados:',
9607           totalItems: 'Artículos Totales:',
9608           size: 'Tamaño de Página:',
9609           first: 'Primera Página',
9610           next: 'Página Siguiente',
9611           previous: 'Página Anterior',
9612           last: 'Última Página'
9613         },
9614         menu: {
9615           text: 'Elegir columnas:'
9616         },
9617         sort: {
9618           ascending: 'Orden Ascendente',
9619           descending: 'Orden Descendente',
9620           remove: 'Sin Ordenar'
9621         },
9622         column: {
9623           hide: 'Ocultar la columna'
9624         },
9625         aggregation: {
9626           count: 'filas totales: ',
9627           sum: 'total: ',
9628           avg: 'media: ',
9629           min: 'min: ',
9630           max: 'max: '
9631         },
9632         pinning: {
9633           pinLeft: 'Fijar a la Izquierda',
9634           pinRight: 'Fijar a la Derecha',
9635           unpin: 'Quitar Fijación'
9636         },
9637         gridMenu: {
9638           columns: 'Columnas:',
9639           importerTitle: 'Importar archivo',
9640           exporterAllAsCsv: 'Exportar todo como csv',
9641           exporterVisibleAsCsv: 'Exportar vista como csv',
9642           exporterSelectedAsCsv: 'Exportar selección como csv',
9643           exporterAllAsPdf: 'Exportar todo como pdf',
9644           exporterVisibleAsPdf: 'Exportar vista como pdf',
9645           exporterSelectedAsPdf: 'Exportar selección como pdf'
9646         },
9647         importer: {
9648           noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
9649           noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
9650           invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
9651           invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
9652           jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
9653         }
9654       });
9655       return $delegate;
9656     }]);
9657 }]);
9658 })();
9659
9660 (function () {
9661   angular.module('ui.grid').config(['$provide', function($provide) {
9662     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9663       $delegate.add('fa', {
9664         aggregate: {
9665           label: 'موردها'
9666         },
9667         groupPanel: {
9668           description: 'یک عنوان ستون اینجا را بردار و به گروهی از آن ستون بیانداز.'
9669         },
9670         search: {
9671           placeholder: 'جستجو...',
9672           showingItems: 'نمایش موردها:',
9673           selectedItems: 'موردهای انتخاب\u200cشده:',
9674           totalItems: 'همهٔ موردها:',
9675           size: 'اندازهٔ صفحه:',
9676           first: 'صفحهٔ اول',
9677           next: 'صفحهٔ بعد',
9678           previous: 'صفحهٔ قبل',
9679           last: 'آخرین صفحه'
9680         },
9681         menu: {
9682           text: 'انتخاب ستون\u200cها:'
9683         },
9684         column: {
9685           hide: 'ستون پنهان کن'
9686         },
9687         aggregation: {
9688           count: 'total rows: ',
9689           sum: 'total: ',
9690           avg: 'avg: ',
9691           min: 'min: ',
9692           max: 'max: '
9693         },
9694         gridMenu: {
9695           columns: 'Columns:',
9696           importerTitle: 'Import file',
9697           exporterAllAsCsv: 'Export all data as csv',
9698           exporterVisibleAsCsv: 'Export visible data as csv',
9699           exporterSelectedAsCsv: 'Export selected data as csv',
9700           exporterAllAsPdf: 'Export all data as pdf',
9701           exporterVisibleAsPdf: 'Export visible data as pdf',
9702           exporterSelectedAsPdf: 'Export selected data as pdf'
9703         },
9704         importer: {
9705           noHeaders: 'Column names were unable to be derived, does the file have a header?',
9706           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9707           invalidCsv: 'File was unable to be processed, is it valid CSV?',
9708           invalidJson: 'File was unable to be processed, is it valid Json?',
9709           jsonNotArray: 'Imported json file must contain an array, aborting.'
9710         }
9711       });
9712       return $delegate;
9713     }]);
9714 }]);
9715 })();
9716 (function () {
9717   angular.module('ui.grid').config(['$provide', function($provide) {
9718     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9719       $delegate.add('fi', {
9720         aggregate: {
9721           label: 'rivit'
9722         },
9723         groupPanel: {
9724           description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
9725         },
9726         search: {
9727           placeholder: 'Hae...',
9728           showingItems: 'Näytetään rivejä:',
9729           selectedItems: 'Valitut rivit:',
9730           totalItems: 'Rivejä yht.:',
9731           size: 'Näytä:',
9732           first: 'Ensimmäinen sivu',
9733           next: 'Seuraava sivu',
9734           previous: 'Edellinen sivu',
9735           last: 'Viimeinen sivu'
9736         },
9737         menu: {
9738           text: 'Valitse sarakkeet:'
9739         },
9740         sort: {
9741           ascending: 'Järjestä nouseva',
9742           descending: 'Järjestä laskeva',
9743           remove: 'Poista järjestys'
9744         },
9745         column: {
9746           hide: 'Piilota sarake'
9747         },
9748         aggregation: {
9749           count: 'Rivejä yht.: ',
9750           sum: 'Summa: ',
9751           avg: 'K.a.: ',
9752           min: 'Min: ',
9753           max: 'Max: '
9754         },
9755         pinning: {
9756          pinLeft: 'Lukitse vasemmalle',
9757           pinRight: 'Lukitse oikealle',
9758           unpin: 'Poista lukitus'
9759         },
9760         gridMenu: {
9761           columns: 'Sarakkeet:',
9762           importerTitle: 'Tuo tiedosto',
9763           exporterAllAsCsv: 'Vie tiedot csv-muodossa',
9764           exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
9765           exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
9766           exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
9767           exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
9768           exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa'
9769         },
9770         importer: {
9771           noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
9772           noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
9773           invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
9774           invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
9775           jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
9776         }
9777       });
9778       return $delegate;
9779     }]);
9780   }]);
9781 })();
9782
9783 (function () {
9784   angular.module('ui.grid').config(['$provide', function($provide) {
9785     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9786       $delegate.add('fr', {
9787         aggregate: {
9788           label: 'articles'
9789         },
9790         groupPanel: {
9791           description: 'Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.'
9792         },
9793         search: {
9794           placeholder: 'Recherche...',
9795           showingItems: 'Articles Affichage des:',
9796           selectedItems: 'Éléments Articles:',
9797           totalItems: 'Nombre total d\'articles:',
9798           size: 'Taille de page:',
9799           first: 'Première page',
9800           next: 'Page Suivante',
9801           previous: 'Page précédente',
9802           last: 'Dernière page'
9803         },
9804         menu: {
9805           text: 'Choisir des colonnes:'
9806         },
9807         sort: {
9808           ascending: 'Trier par ordre croissant',
9809           descending: 'Trier par ordre décroissant',
9810           remove: 'Enlever le tri'
9811         },
9812         column: {
9813           hide: 'Cacher la colonne'
9814         },
9815         aggregation: {
9816           count: 'total lignes: ',
9817           sum: 'total: ',
9818           avg: 'moy: ',
9819           min: 'min: ',
9820           max: 'max: '
9821         },
9822         pinning: {
9823           pinLeft: 'Épingler à gauche',
9824           pinRight: 'Épingler à droite',
9825           unpin: 'Détacher'
9826         },
9827         gridMenu: {
9828           columns: 'Colonnes:',
9829           importerTitle: 'Importer un fichier',
9830           exporterAllAsCsv: 'Exporter toutes les données en CSV',
9831           exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
9832           exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
9833           exporterAllAsPdf: 'Exporter toutes les données en PDF',
9834           exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
9835           exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF'
9836         },
9837         importer: {
9838           noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il un en-tête ?',
9839           noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
9840           invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
9841           invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
9842           jsonNotArray: 'Le fichier JSON importé doit contenir un tableau. Abandon.'
9843         }
9844       });
9845       return $delegate;
9846     }]);
9847 }]);
9848 })();
9849 (function () {
9850   angular.module('ui.grid').config(['$provide', function ($provide) {
9851     $provide.decorator('i18nService', ['$delegate', function ($delegate) {
9852       $delegate.add('he', {
9853         aggregate: {
9854           label: 'items'
9855         },
9856         groupPanel: {
9857           description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
9858         },
9859         search: {
9860           placeholder: 'חפש...',
9861           showingItems: 'מציג:',
9862           selectedItems: 'סה"כ נבחרו:',
9863           totalItems: 'סה"כ רשומות:',
9864           size: 'תוצאות בדף:',
9865           first: 'דף ראשון',
9866           next: 'דף הבא',
9867           previous: 'דף קודם',
9868           last: 'דף אחרון'
9869         },
9870         menu: {
9871           text: 'בחר עמודות:'
9872         },
9873         sort: {
9874           ascending: 'סדר עולה',
9875           descending: 'סדר יורד',
9876           remove: 'בטל'
9877         },
9878         column: {
9879           hide: 'טור הסתר'
9880         },
9881         aggregation: {
9882           count: 'total rows: ',
9883           sum: 'total: ',
9884           avg: 'avg: ',
9885           min: 'min: ',
9886           max: 'max: '
9887         },
9888         gridMenu: {
9889           columns: 'Columns:',
9890           importerTitle: 'Import file',
9891           exporterAllAsCsv: 'Export all data as csv',
9892           exporterVisibleAsCsv: 'Export visible data as csv',
9893           exporterSelectedAsCsv: 'Export selected data as csv',
9894           exporterAllAsPdf: 'Export all data as pdf',
9895           exporterVisibleAsPdf: 'Export visible data as pdf',
9896           exporterSelectedAsPdf: 'Export selected data as pdf'
9897         },
9898         importer: {
9899           noHeaders: 'Column names were unable to be derived, does the file have a header?',
9900           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9901           invalidCsv: 'File was unable to be processed, is it valid CSV?',
9902           invalidJson: 'File was unable to be processed, is it valid Json?',
9903           jsonNotArray: 'Imported json file must contain an array, aborting.'
9904         }
9905       });
9906       return $delegate;
9907     }]);
9908   }]);
9909 })();
9910 (function () {
9911   angular.module('ui.grid').config(['$provide', function($provide) {
9912     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9913       $delegate.add('it', {
9914         aggregate: {
9915           label: 'elementi'
9916         },
9917         groupPanel: {
9918           description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
9919         },
9920         search: {
9921           placeholder: 'Ricerca...',
9922           showingItems: 'Mostra:',
9923           selectedItems: 'Selezionati:',
9924           totalItems: 'Totali:',
9925           size: 'Tot Pagine:',
9926           first: 'Prima',
9927           next: 'Prossima',
9928           previous: 'Precedente',
9929           last: 'Ultima'
9930         },
9931         menu: {
9932           text: 'Scegli le colonne:'
9933         },
9934         sort: {
9935           ascending: 'Asc.',
9936           descending: 'Desc.',
9937           remove: 'Annulla ordinamento'
9938         },
9939         column: {
9940           hide: 'Nascondi'
9941         },
9942         aggregation: {
9943           count: 'righe totali: ',
9944           sum: 'tot: ',
9945           avg: 'media: ',
9946           min: 'minimo: ',
9947           max: 'massimo: '
9948         },
9949         pinning: {
9950          pinLeft: 'Blocca a sx',
9951           pinRight: 'Blocca a dx',
9952           unpin: 'Blocca in alto'
9953         },
9954         gridMenu: {
9955           columns: 'Colonne:',
9956           importerTitle: 'Importa',
9957           exporterAllAsCsv: 'Esporta tutti i dati in CSV',
9958           exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
9959           exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
9960           exporterAllAsPdf: 'Esporta tutti i dati in PDF',
9961           exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
9962           exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF'
9963         },
9964         importer: {
9965           noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
9966           noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
9967           invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
9968           invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
9969           jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
9970         }
9971       });
9972       return $delegate;
9973     }]);
9974   }]);
9975 })();
9976 (function () {
9977   angular.module('ui.grid').config(['$provide', function($provide) {
9978     $provide.decorator('i18nService', ['$delegate', function($delegate) {
9979       $delegate.add('nl', {
9980         aggregate: {
9981           label: 'items'
9982         },
9983         groupPanel: {
9984           description: 'Sleep hier een kolomnaam heen om op te groeperen.'
9985         },
9986         search: {
9987           placeholder: 'Zoeken...',
9988           showingItems: 'Getoonde items:',
9989           selectedItems: 'Geselecteerde items:',
9990           totalItems: 'Totaal aantal items:',
9991           size: 'Items per pagina:',
9992           first: 'Eerste pagina',
9993           next: 'Volgende pagina',
9994           previous: 'Vorige pagina',
9995           last: 'Laatste pagina'
9996         },
9997         menu: {
9998           text: 'Kies kolommen:'
9999         },
10000         sort: {
10001           ascending: 'Sorteer oplopend',
10002           descending: 'Sorteer aflopend',
10003           remove: 'Verwijder sortering'
10004         },
10005         column: {
10006           hide: 'Verberg kolom'
10007         },
10008         aggregation: {
10009           count: 'Aantal rijen: ',
10010           sum: 'Som: ',
10011           avg: 'Gemiddelde: ',
10012           min: 'Min: ',
10013           max: 'Max: '
10014         },
10015         pinning: {
10016           pinLeft: 'Zet links vast',
10017           pinRight: 'Zet rechts vast',
10018           unpin: 'Maak los'
10019         },
10020         gridMenu: {
10021           columns: 'Kolommen:',
10022           importerTitle: 'Importeer bestand',
10023           exporterAllAsCsv: 'Exporteer alle data als csv',
10024           exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
10025           exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
10026           exporterAllAsPdf: 'Exporteer alle data als pdf',
10027           exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
10028           exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf'
10029         },
10030         importer: {
10031           noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
10032           noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
10033           invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
10034           invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
10035           jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
10036         }
10037       });
10038       return $delegate;
10039     }]);
10040   }]);
10041 })();
10042 (function () {
10043   angular.module('ui.grid').config(['$provide', function($provide) {
10044     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10045       $delegate.add('pt-br', {
10046         aggregate: {
10047           label: 'itens'
10048         },
10049         groupPanel: {
10050           description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
10051         },
10052         search: {
10053           placeholder: 'Procurar...',
10054           showingItems: 'Mostrando os Itens:',
10055           selectedItems: 'Items Selecionados:',
10056           totalItems: 'Total de Itens:',
10057           size: 'Tamanho da Página:',
10058           first: 'Primeira Página',
10059           next: 'Próxima Página',
10060           previous: 'Página Anterior',
10061           last: 'Última Página'
10062         },
10063         menu: {
10064           text: 'Selecione as colunas:'
10065         },
10066         sort: {
10067           ascending: 'Ordenar Ascendente',
10068           descending: 'Ordenar Descendente',
10069           remove: 'Remover Ordenação'
10070         },
10071         column: {
10072           hide: 'Esconder coluna'
10073         },
10074         aggregation: {
10075           count: 'total de linhas: ',
10076           sum: 'total: ',
10077           avg: 'med: ',
10078           min: 'min: ',
10079           max: 'max: '
10080         },
10081         pinning: {
10082           pinLeft: 'Fixar Esquerda',
10083           pinRight: 'Fixar Direita',
10084           unpin: 'Desprender'
10085         },
10086         gridMenu: {
10087           columns: 'Colunas:',
10088           exporterAllAsCsv: 'Exportar todos os dados como csv',
10089           exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
10090           exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
10091           exporterAllAsPdf: 'Exportar todos os dados como pdf',
10092           exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
10093           exporterSelectedAsPdf: 'Exportar dados selecionados como pdf'
10094         },
10095         importer: {
10096           noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
10097           noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
10098           invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
10099           invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
10100           jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
10101         }
10102       });
10103       return $delegate;
10104     }]);
10105 }]);
10106 })();
10107 (function () {
10108   angular.module('ui.grid').config(['$provide', function($provide) {
10109     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10110       $delegate.add('ru', {
10111         aggregate: {
10112           label: 'элементы'
10113         },
10114         groupPanel: {
10115           description: 'Для группировки по столбцу перетащите сюда его название.'
10116         },
10117         search: {
10118           placeholder: 'Поиск...',
10119           showingItems: 'Показать элементы:',
10120           selectedItems: 'Выбранные элементы:',
10121           totalItems: 'Всего элементов:',
10122           size: 'Размер страницы:',
10123           first: 'Первая страница',
10124           next: 'Следующая страница',
10125           previous: 'Предыдущая страница',
10126           last: 'Последняя страница'
10127         },
10128         menu: {
10129           text: 'Выбрать столбцы:'
10130         },
10131         sort: {
10132           ascending: 'По возрастанию',
10133           descending: 'По убыванию',
10134           remove: 'Убрать сортировку'
10135         },
10136         column: {
10137           hide: 'спрятать столбец'
10138         },
10139         aggregation: {
10140           count: 'total rows: ',
10141           sum: 'total: ',
10142           avg: 'avg: ',
10143           min: 'min: ',
10144           max: 'max: '
10145         },
10146         gridMenu: {
10147           columns: 'Columns:',
10148           importerTitle: 'Import file',
10149           exporterAllAsCsv: 'Export all data as csv',
10150           exporterVisibleAsCsv: 'Export visible data as csv',
10151           exporterSelectedAsCsv: 'Export selected data as csv',
10152           exporterAllAsPdf: 'Export all data as pdf',
10153           exporterVisibleAsPdf: 'Export visible data as pdf',
10154           exporterSelectedAsPdf: 'Export selected data as pdf'
10155         },
10156         importer: {
10157           noHeaders: 'Column names were unable to be derived, does the file have a header?',
10158           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
10159           invalidCsv: 'File was unable to be processed, is it valid CSV?',
10160           invalidJson: 'File was unable to be processed, is it valid Json?',
10161           jsonNotArray: 'Imported json file must contain an array, aborting.'
10162         }
10163       });
10164       return $delegate;
10165     }]);
10166   }]);
10167 })();
10168 (function () {
10169   angular.module('ui.grid').config(['$provide', function($provide) {
10170     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10171       $delegate.add('sk', {
10172         aggregate: {
10173           label: 'items'
10174         },
10175         groupPanel: {
10176           description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
10177         },
10178         search: {
10179           placeholder: 'Hľadaj...',
10180           showingItems: 'Zobrazujem položky:',
10181           selectedItems: 'Vybraté položky:',
10182           totalItems: 'Počet položiek:',
10183           size: 'Počet:',
10184           first: 'Prvá strana',
10185           next: 'Ďalšia strana',
10186           previous: 'Predchádzajúca strana',
10187           last: 'Posledná strana'
10188         },
10189         menu: {
10190           text: 'Vyberte stĺpce:'
10191         },
10192         sort: {
10193           ascending: 'Zotriediť vzostupne',
10194           descending: 'Zotriediť zostupne',
10195           remove: 'Vymazať triedenie'
10196         },
10197         aggregation: {
10198           count: 'total rows: ',
10199           sum: 'total: ',
10200           avg: 'avg: ',
10201           min: 'min: ',
10202           max: 'max: '
10203         },
10204         gridMenu: {
10205           columns: 'Columns:',
10206           importerTitle: 'Import file',
10207           exporterAllAsCsv: 'Export all data as csv',
10208           exporterVisibleAsCsv: 'Export visible data as csv',
10209           exporterSelectedAsCsv: 'Export selected data as csv',
10210           exporterAllAsPdf: 'Export all data as pdf',
10211           exporterVisibleAsPdf: 'Export visible data as pdf',
10212           exporterSelectedAsPdf: 'Export selected data as pdf'
10213         },
10214         importer: {
10215           noHeaders: 'Column names were unable to be derived, does the file have a header?',
10216           noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
10217           invalidCsv: 'File was unable to be processed, is it valid CSV?',
10218           invalidJson: 'File was unable to be processed, is it valid Json?',
10219           jsonNotArray: 'Imported json file must contain an array, aborting.'
10220         }
10221       });
10222       return $delegate;
10223     }]);
10224   }]);
10225 })();
10226
10227 (function () {
10228   angular.module('ui.grid').config(['$provide', function($provide) {
10229     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10230       $delegate.add('sv', {
10231         aggregate: {
10232           label: 'Artiklar'
10233         },
10234         groupPanel: {
10235           description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
10236         },
10237         search: {
10238           placeholder: 'Sök...',
10239           showingItems: 'Visar artiklar:',
10240           selectedItems: 'Valda artiklar:',
10241           totalItems: 'Antal artiklar:',
10242           size: 'Sidstorlek:',
10243           first: 'Första sidan',
10244           next: 'Nästa sida',
10245           previous: 'Föregående sida',
10246           last: 'Sista sidan'
10247         },
10248         menu: {
10249           text: 'Välj kolumner:'
10250         },
10251         sort: {
10252           ascending: 'Sortera stigande',
10253           descending: 'Sortera fallande',
10254           remove: 'Inaktivera sortering'
10255         },
10256         column: {
10257           hide: 'Göm kolumn'
10258         },
10259         aggregation: {
10260           count: 'Antal rader: ',
10261           sum: 'Summa: ',
10262           avg: 'Genomsnitt: ',
10263           min: 'Min: ',
10264           max: 'Max: '
10265         },
10266         pinning: {
10267           pinLeft: 'Fäst vänster',
10268           pinRight: 'Fäst höger',
10269           unpin: 'Lösgör'
10270         },
10271         gridMenu: {
10272           columns: 'Kolumner:',
10273           importerTitle: 'Importera fil',
10274           exporterAllAsCsv: 'Exportera all data som CSV',
10275           exporterVisibleAsCsv: 'Exportera synlig data som CSV',
10276           exporterSelectedAsCsv: 'Exportera markerad data som CSV',
10277           exporterAllAsPdf: 'Exportera all data som PDF',
10278           exporterVisibleAsPdf: 'Exportera synlig data som PDF',
10279           exporterSelectedAsPdf: 'Exportera markerad data som PDF'
10280         },
10281         importer: {
10282           noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
10283           noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
10284           invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
10285           invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
10286           jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
10287         },
10288         paging: {
10289           sizes: 'Artiklar per sida',
10290           totalItems: 'Artiklar'
10291         }
10292       });
10293       return $delegate;
10294     }]);
10295   }]);
10296 })();
10297
10298 /**
10299  * @ngdoc overview
10300  * @name ui.grid.i18n
10301  * @description
10302  *
10303  *  # ui.grid.i18n
10304  * This module provides i18n functions to ui.grid and any application that wants to use it
10305
10306  *
10307  * <div doc-module-components="ui.grid.i18n"></div>
10308  */
10309
10310 (function () {
10311   var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
10312   var FILTER_ALIASES = ['t', 'uiTranslate'];
10313
10314   var module = angular.module('ui.grid.i18n');
10315
10316
10317   /**
10318    *  @ngdoc object
10319    *  @name ui.grid.i18n.constant:i18nConstants
10320    *
10321    *  @description constants available in i18n module
10322    */
10323   module.constant('i18nConstants', {
10324     MISSING: '[MISSING]',
10325     UPDATE_EVENT: '$uiI18n',
10326
10327     LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
10328     // default to english
10329     DEFAULT_LANG: 'en'
10330   });
10331
10332 //    module.config(['$provide', function($provide) {
10333 //        $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
10334
10335   /**
10336    *  @ngdoc service
10337    *  @name ui.grid.i18n.service:i18nService
10338    *
10339    *  @description Services for i18n
10340    */
10341   module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
10342     function ($log, i18nConstants, $rootScope) {
10343
10344       var langCache = {
10345         _langs: {},
10346         current: null,
10347         get: function (lang) {
10348           return this._langs[lang.toLowerCase()];
10349         },
10350         add: function (lang, strings) {
10351           var lower = lang.toLowerCase();
10352           if (!this._langs[lower]) {
10353             this._langs[lower] = {};
10354           }
10355           angular.extend(this._langs[lower], strings);
10356         },
10357         getAllLangs: function () {
10358           var langs = [];
10359           if (!this._langs) {
10360             return langs;
10361           }
10362
10363           for (var key in this._langs) {
10364             langs.push(key);
10365           }
10366
10367           return langs;
10368         },
10369         setCurrent: function (lang) {
10370           this.current = lang.toLowerCase();
10371         },
10372         getCurrentLang: function () {
10373           return this.current;
10374         }
10375       };
10376
10377       var service = {
10378
10379         /**
10380          * @ngdoc service
10381          * @name add
10382          * @methodOf ui.grid.i18n.service:i18nService
10383          * @description  Adds the languages and strings to the cache. Decorate this service to
10384          * add more translation strings
10385          * @param {string} lang language to add
10386          * @param {object} stringMaps of strings to add grouped by property names
10387          * @example
10388          * <pre>
10389          *      i18nService.add('en', {
10390          *         aggregate: {
10391          *                 label1: 'items',
10392          *                 label2: 'some more items'
10393          *                 }
10394          *         },
10395          *         groupPanel: {
10396          *              description: 'Drag a column header here and drop it to group by that column.'
10397          *           }
10398          *      }
10399          * </pre>
10400          */
10401         add: function (langs, stringMaps) {
10402           if (typeof(langs) === 'object') {
10403             angular.forEach(langs, function (lang) {
10404               if (lang) {
10405                 langCache.add(lang, stringMaps);
10406               }
10407             });
10408           } else {
10409             langCache.add(langs, stringMaps);
10410           }
10411         },
10412
10413         /**
10414          * @ngdoc service
10415          * @name getAllLangs
10416          * @methodOf ui.grid.i18n.service:i18nService
10417          * @description  return all currently loaded languages
10418          * @returns {array} string
10419          */
10420         getAllLangs: function () {
10421           return langCache.getAllLangs();
10422         },
10423
10424         /**
10425          * @ngdoc service
10426          * @name get
10427          * @methodOf ui.grid.i18n.service:i18nService
10428          * @description  return all currently loaded languages
10429          * @param {string} lang to return.  If not specified, returns current language
10430          * @returns {object} the translation string maps for the language
10431          */
10432         get: function (lang) {
10433           var language = lang ? lang : service.getCurrentLang();
10434           return langCache.get(language);
10435         },
10436
10437         /**
10438          * @ngdoc service
10439          * @name getSafeText
10440          * @methodOf ui.grid.i18n.service:i18nService
10441          * @description  returns the text specified in the path or a Missing text if text is not found
10442          * @param {string} path property path to use for retrieving text from string map
10443          * @param {string} lang to return.  If not specified, returns current language
10444          * @returns {object} the translation for the path
10445          * @example
10446          * <pre>
10447          * i18nService.getSafeText('sort.ascending')
10448          * </pre>
10449          */
10450         getSafeText: function (path, lang) {
10451           var language = lang ? lang : service.getCurrentLang();
10452           var trans = langCache.get(language);
10453
10454           if (!trans) {
10455             return i18nConstants.MISSING;
10456           }
10457
10458           var paths = path.split('.');
10459           var current = trans;
10460
10461           for (var i = 0; i < paths.length; ++i) {
10462             if (current[paths[i]] === undefined || current[paths[i]] === null) {
10463               return i18nConstants.MISSING;
10464             } else {
10465               current = current[paths[i]];
10466             }
10467           }
10468
10469           return current;
10470
10471         },
10472
10473         /**
10474          * @ngdoc service
10475          * @name setCurrentLang
10476          * @methodOf ui.grid.i18n.service:i18nService
10477          * @description sets the current language to use in the application
10478          * $broadcasts the Update_Event on the $rootScope
10479          * @param {string} lang to set
10480          * @example
10481          * <pre>
10482          * i18nService.setCurrentLang('fr');
10483          * </pre>
10484          */
10485
10486         setCurrentLang: function (lang) {
10487           if (lang) {
10488             langCache.setCurrent(lang);
10489             $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
10490           }
10491         },
10492
10493         /**
10494          * @ngdoc service
10495          * @name getCurrentLang
10496          * @methodOf ui.grid.i18n.service:i18nService
10497          * @description returns the current language used in the application
10498          */
10499         getCurrentLang: function () {
10500           var lang = langCache.getCurrentLang();
10501           if (!lang) {
10502             lang = i18nConstants.DEFAULT_LANG;
10503             langCache.setCurrent(lang);
10504           }
10505           return lang;
10506         }
10507
10508       };
10509
10510       return service;
10511
10512     }]);
10513
10514   var localeDirective = function (i18nService, i18nConstants) {
10515     return {
10516       compile: function () {
10517         return {
10518           pre: function ($scope, $elm, $attrs) {
10519             var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
10520             // check for watchable property
10521             var lang = $scope.$eval($attrs[alias]);
10522             if (lang) {
10523               $scope.$watch($attrs[alias], function () {
10524                 i18nService.setCurrentLang(lang);
10525               });
10526             } else if ($attrs.$$observers) {
10527               $attrs.$observe(alias, function () {
10528                 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
10529               });
10530             }
10531           }
10532         };
10533       }
10534     };
10535   };
10536
10537   module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
10538
10539   // directive syntax
10540   var uitDirective = function ($parse, i18nService, i18nConstants) {
10541     return {
10542       restrict: 'EA',
10543       compile: function () {
10544         return {
10545           pre: function ($scope, $elm, $attrs) {
10546             var alias1 = DIRECTIVE_ALIASES[0],
10547               alias2 = DIRECTIVE_ALIASES[1];
10548             var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
10549             var missing = i18nConstants.MISSING + token;
10550             var observer;
10551             if ($attrs.$$observers) {
10552               var prop = $attrs[alias1] ? alias1 : alias2;
10553               observer = $attrs.$observe(prop, function (result) {
10554                 if (result) {
10555                   $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
10556                 }
10557               });
10558             }
10559             var getter = $parse(token);
10560             var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
10561               if (observer) {
10562                 observer($attrs[alias1] || $attrs[alias2]);
10563               } else {
10564                 // set text based on i18n current language
10565                 $elm.html(getter(i18nService.get()) || missing);
10566               }
10567             });
10568             $scope.$on('$destroy', listener);
10569
10570             $elm.html(getter(i18nService.get()) || missing);
10571           }
10572         };
10573       }
10574     };
10575   };
10576
10577   angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
10578     module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
10579   } );
10580
10581   // optional filter syntax
10582   var uitFilter = function ($parse, i18nService, i18nConstants) {
10583     return function (data) {
10584       var getter = $parse(data);
10585       // set text based on i18n current language
10586       return getter(i18nService.get()) || i18nConstants.MISSING + data;
10587     };
10588   };
10589
10590   angular.forEach( FILTER_ALIASES, function ( alias ) {
10591     module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
10592   } );
10593
10594
10595 })();
10596 (function() {
10597   angular.module('ui.grid').config(['$provide', function($provide) {
10598     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10599       $delegate.add('zh-cn', {
10600         aggregate: {
10601           label: '行'
10602         },
10603         groupPanel: {
10604           description: '拖曳表头到此处进行分组'
10605         },
10606         search: {
10607           placeholder: '查找',
10608           showingItems: '已显示行数:',
10609           selectedItems: '已选择行数:',
10610           totalItems: '总行数:',
10611           size: '每页显示行数:',
10612           first: '首页',
10613           next: '下一页',
10614           previous: '上一页',
10615           last: '末页'
10616         },
10617         menu: {
10618           text: '选择列:'
10619         },
10620         sort: {
10621           ascending: '升序',
10622           descending: '降序',
10623           remove: '取消排序'
10624         },
10625         column: {
10626           hide: '隐藏列'
10627         },
10628         aggregation: {
10629           count: '计数:',
10630           sum: '求和:',
10631           avg: '均值:',
10632           min: '最小值:',
10633           max: '最大值:'
10634         },
10635         pinning: {
10636           pinLeft: '左侧固定',
10637           pinRight: '右侧固定',
10638           unpin: '取消固定'
10639         },
10640         gridMenu: {
10641           columns: '列:',
10642           importerTitle: '导入文件',
10643           exporterAllAsCsv: '导出全部数据到CSV',
10644           exporterVisibleAsCsv: '导出可见数据到CSV',
10645           exporterSelectedAsCsv: '导出已选数据到CSV',
10646           exporterAllAsPdf: '导出全部数据到PDF',
10647           exporterVisibleAsPdf: '导出可见数据到PDF',
10648           exporterSelectedAsPdf: '导出已选数据到PDF'
10649         },
10650         importer: {
10651           noHeaders: '无法获取列名,确定文件包含表头?',
10652           noObjects: '无法获取数据,确定文件包含数据?',
10653           invalidCsv: '无法处理文件,确定是合法的CSV文件?',
10654           invalidJson: '无法处理文件,确定是合法的JSON文件?',
10655           jsonNotArray: '导入的文件不是JSON数组!'
10656         }
10657       });
10658       return $delegate;
10659     }]);
10660   }]);
10661 })();
10662
10663 (function() {
10664   angular.module('ui.grid').config(['$provide', function($provide) {
10665     $provide.decorator('i18nService', ['$delegate', function($delegate) {
10666       $delegate.add('zh-tw', {
10667         aggregate: {
10668           label: '行'
10669         },
10670         groupPanel: {
10671           description: '拖曳表頭到此處進行分組'
10672         },
10673         search: {
10674           placeholder: '查找',
10675           showingItems: '已顯示行數:',
10676           selectedItems: '已選擇行數:',
10677           totalItems: '總行數:',
10678           size: '每頁顯示行數:',
10679           first: '首頁',
10680           next: '下壹頁',
10681           previous: '上壹頁',
10682           last: '末頁'
10683         },
10684         menu: {
10685           text: '選擇列:'
10686         },
10687         sort: {
10688           ascending: '升序',
10689           descending: '降序',
10690           remove: '取消排序'
10691         },
10692         column: {
10693           hide: '隱藏列'
10694         },
10695         aggregation: {
10696           count: '計數:',
10697           sum: '求和:',
10698           avg: '均值:',
10699           min: '最小值:',
10700           max: '最大值:'
10701         },
10702         pinning: {
10703           pinLeft: '左側固定',
10704           pinRight: '右側固定',
10705           unpin: '取消固定'
10706         },
10707         gridMenu: {
10708           columns: '列:',
10709           importerTitle: '導入文件',
10710           exporterAllAsCsv: '導出全部數據到CSV',
10711           exporterVisibleAsCsv: '導出可見數據到CSV',
10712           exporterSelectedAsCsv: '導出已選數據到CSV',
10713           exporterAllAsPdf: '導出全部數據到PDF',
10714           exporterVisibleAsPdf: '導出可見數據到PDF',
10715           exporterSelectedAsPdf: '導出已選數據到PDF'
10716         },
10717         importer: {
10718           noHeaders: '無法獲取列名,確定文件包含表頭?',
10719           noObjects: '無法獲取數據,確定文件包含數據?',
10720           invalidCsv: '無法處理文件,確定是合法的CSV文件?',
10721           invalidJson: '無法處理文件,確定是合法的JSON文件?',
10722           jsonNotArray: '導入的文件不是JSON數組!'
10723         }
10724       });
10725       return $delegate;
10726     }]);
10727   }]);
10728 })();
10729
10730 (function() {
10731   'use strict';
10732   /**
10733    *  @ngdoc overview
10734    *  @name ui.grid.autoResize
10735    *
10736    *  @description 
10737    *
10738    *  #ui.grid.autoResize
10739    *  This module provides auto-resizing functionality to ui-grid
10740    *
10741    */
10742   var module = angular.module('ui.grid.autoResize', ['ui.grid']);
10743   
10744
10745   module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
10746     return {
10747       require: 'uiGrid',
10748       scope: false,
10749       link: function ($scope, $elm, $attrs, uiGridCtrl) {
10750         var prevGridWidth, prevGridHeight;
10751
10752         function getDimensions() {
10753           prevGridHeight = gridUtil.elementHeight($elm);
10754           prevGridWidth = gridUtil.elementWidth($elm);
10755         }
10756
10757         // Initialize the dimensions
10758         getDimensions();
10759
10760         var canceler;
10761         function startTimeout() {
10762           $timeout.cancel(canceler);
10763
10764           canceler = $timeout(function () {
10765             var newGridHeight = gridUtil.elementHeight($elm);
10766             var newGridWidth = gridUtil.elementWidth($elm);
10767
10768             if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
10769               uiGridCtrl.grid.gridHeight = newGridHeight;
10770               uiGridCtrl.grid.gridWidth = newGridWidth;
10771
10772               uiGridCtrl.grid.refresh()
10773                 .then(function () {
10774                   getDimensions();
10775
10776                   startTimeout();
10777                 });
10778             }
10779             else {
10780               startTimeout();
10781             }
10782           }, 250);
10783         }
10784
10785         startTimeout();
10786
10787         $scope.$on('$destroy', function() {
10788           $timeout.cancel(canceler);
10789         });
10790       }
10791     };
10792   }]);
10793 })();
10794 (function () {
10795   'use strict';
10796   var module = angular.module('ui.grid.cellNav', ['ui.grid']);
10797
10798   function RowCol(row, col) {
10799     this.row = row;
10800     this.col = col;
10801   }
10802
10803   /**
10804    *  @ngdoc object
10805    *  @name ui.grid.cellNav.constant:uiGridCellNavConstants
10806    *
10807    *  @description constants available in cellNav
10808    */
10809   module.constant('uiGridCellNavConstants', {
10810     FEATURE_NAME: 'gridCellNav',
10811     CELL_NAV_EVENT: 'cellNav',
10812     direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3},
10813     EVENT_TYPE: {
10814       KEYDOWN: 0,
10815       CLICK: 1
10816     }
10817   });
10818
10819
10820   module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q',
10821     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q) {
10822       /**
10823        *  @ngdoc object
10824        *  @name ui.grid.cellNav.object:CellNav
10825        *  @description returns a CellNav prototype function
10826        *  @param {object} rowContainer container for rows
10827        *  @param {object} colContainer parent column container
10828        *  @param {object} leftColContainer column container to the left of parent
10829        *  @param {object} rightColContainer column container to the right of parent
10830        */
10831       var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
10832         this.rows = rowContainer.visibleRowCache;
10833         this.columns = colContainer.visibleColumnCache;
10834         this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
10835         this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
10836       };
10837
10838       /** returns focusable columns of all containers */
10839       UiGridCellNav.prototype.getFocusableCols = function () {
10840         var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
10841
10842         return allColumns.filter(function (col) {
10843           return col.colDef.allowCellFocus;
10844         });
10845       };
10846
10847       UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
10848         switch (direction) {
10849           case uiGridCellNavConstants.direction.LEFT:
10850             return this.getRowColLeft(curRow, curCol);
10851           case uiGridCellNavConstants.direction.RIGHT:
10852             return this.getRowColRight(curRow, curCol);
10853           case uiGridCellNavConstants.direction.UP:
10854             return this.getRowColUp(curRow, curCol);
10855           case uiGridCellNavConstants.direction.DOWN:
10856             return this.getRowColDown(curRow, curCol);
10857         }
10858
10859       };
10860
10861       UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
10862         var focusableCols = this.getFocusableCols();
10863         var curColIndex = focusableCols.indexOf(curCol);
10864         var curRowIndex = this.rows.indexOf(curRow);
10865
10866         //could not find column in focusable Columns so set it to 1
10867         if (curColIndex === -1) {
10868           curColIndex = 1;
10869         }
10870
10871         var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
10872
10873         //get column to left
10874         if (nextColIndex > curColIndex) {
10875           if (curRowIndex === 0) {
10876             return new RowCol(curRow, focusableCols[nextColIndex]); //return same row
10877           }
10878           else {
10879             //up one row and far right column
10880             return new RowCol(this.rows[curRowIndex - 1], focusableCols[nextColIndex]);
10881           }
10882         }
10883         else {
10884           return new RowCol(curRow, focusableCols[nextColIndex]);
10885         }
10886       };
10887
10888       UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
10889         var focusableCols = this.getFocusableCols();
10890         var curColIndex = focusableCols.indexOf(curCol);
10891         var curRowIndex = this.rows.indexOf(curRow);
10892
10893         //could not find column in focusable Columns so set it to 0
10894         if (curColIndex === -1) {
10895           curColIndex = 0;
10896         }
10897         var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
10898
10899         if (nextColIndex < curColIndex) {
10900           if (curRowIndex === this.rows.length - 1) {
10901             return new RowCol(curRow, focusableCols[nextColIndex]); //return same row
10902           }
10903           else {
10904             //down one row and far left column
10905             return new RowCol(this.rows[curRowIndex + 1], focusableCols[nextColIndex]);
10906           }
10907         }
10908         else {
10909           return new RowCol(curRow, focusableCols[nextColIndex]);
10910         }
10911       };
10912
10913       UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
10914         var focusableCols = this.getFocusableCols();
10915         var curColIndex = focusableCols.indexOf(curCol);
10916         var curRowIndex = this.rows.indexOf(curRow);
10917
10918         //could not find column in focusable Columns so set it to 0
10919         if (curColIndex === -1) {
10920           curColIndex = 0;
10921         }
10922
10923         if (curRowIndex === this.rows.length - 1) {
10924           return new RowCol(curRow, focusableCols[curColIndex]); //return same row
10925         }
10926         else {
10927           //down one row
10928           return new RowCol(this.rows[curRowIndex + 1], focusableCols[curColIndex]);
10929         }
10930       };
10931
10932       UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
10933         var focusableCols = this.getFocusableCols();
10934         var curColIndex = focusableCols.indexOf(curCol);
10935         var curRowIndex = this.rows.indexOf(curRow);
10936
10937         //could not find column in focusable Columns so set it to 0
10938         if (curColIndex === -1) {
10939           curColIndex = 0;
10940         }
10941
10942         if (curRowIndex === 0) {
10943           return new RowCol(curRow, focusableCols[curColIndex]); //return same row
10944         }
10945         else {
10946           //up one row
10947           return new RowCol(this.rows[curRowIndex - 1], focusableCols[curColIndex]);
10948         }
10949       };
10950
10951       return UiGridCellNav;
10952     }]);
10953
10954   /**
10955    *  @ngdoc service
10956    *  @name ui.grid.cellNav.service:uiGridCellNavService
10957    *
10958    *  @description Services for cell navigation features. If you don't like the key maps we use,
10959    *  or the direction cells navigation, override with a service decorator (see angular docs)
10960    */
10961   module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory',
10962     function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav) {
10963
10964       var service = {
10965
10966         initializeGrid: function (grid) {
10967           grid.registerColumnBuilder(service.cellNavColumnBuilder);
10968
10969           //create variables for state
10970           grid.cellNav = {};
10971           grid.cellNav.lastRowCol = null;
10972
10973           /**
10974            *  @ngdoc object
10975            *  @name ui.grid.cellNav.api:PublicApi
10976            *
10977            *  @description Public Api for cellNav feature
10978            */
10979           var publicApi = {
10980             events: {
10981               cellNav: {
10982                 /**
10983                  * @ngdoc event
10984                  * @name navigate
10985                  * @eventOf  ui.grid.cellNav.api:PublicApi
10986                  * @description raised when the active cell is changed
10987                  * <pre>
10988                  *      gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
10989                  * </pre>
10990                  * @param {object} newRowCol new position
10991                  * @param {object} oldRowCol old position
10992                  */
10993                 navigate: function (newRowCol, oldRowCol) {
10994                 }
10995               }
10996             },
10997             methods: {
10998               cellNav: {
10999                 /**
11000                  * @ngdoc function
11001                  * @name scrollTo
11002                  * @methodOf  ui.grid.cellNav.api:PublicApi
11003                  * @description brings the specified row and column into view
11004                  * @param {Grid} grid the grid you'd like to act upon, usually available
11005                  * from gridApi.grid
11006                  * @param {object} $scope a scope we can broadcast events from
11007                  * @param {object} rowEntity gridOptions.data[] array instance to make visible
11008                  * @param {object} colDef to make visible
11009                  */
11010                 scrollTo: function (grid, $scope, rowEntity, colDef) {
11011                   service.scrollTo(grid, $scope, rowEntity, colDef);
11012                 },
11013
11014                 /**
11015                  * @ngdoc function
11016                  * @name getFocusedCell
11017                  * @methodOf  ui.grid.cellNav.api:PublicApi
11018                  * @description returns the current (or last if Grid does not have focus) focused row and column
11019                  * <br> value is null if no selection has occurred
11020                  */
11021                 getFocusedCell: function () {
11022                   return grid.cellNav.lastRowCol;
11023                 }
11024               }
11025             }
11026           };
11027
11028           grid.api.registerEventsFromObject(publicApi.events);
11029
11030           grid.api.registerMethodsFromObject(publicApi.methods);
11031
11032         },
11033
11034         /**
11035          * @ngdoc service
11036          * @name decorateRenderContainers
11037          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11038          * @description  decorates grid renderContainers with cellNav functions
11039          */
11040         decorateRenderContainers: function (grid) {
11041
11042           var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
11043           var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
11044
11045           if (leftContainer !== null) {
11046             grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
11047           }
11048           if (rightContainer !== null) {
11049             grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
11050           }
11051
11052           grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
11053         },
11054
11055         /**
11056          * @ngdoc service
11057          * @name getDirection
11058          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11059          * @description  determines which direction to for a given keyDown event
11060          * @returns {uiGridCellNavConstants.direction} direction
11061          */
11062         getDirection: function (evt) {
11063           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
11064             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
11065             return uiGridCellNavConstants.direction.LEFT;
11066           }
11067           if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
11068             evt.keyCode === uiGridConstants.keymap.TAB) {
11069             return uiGridCellNavConstants.direction.RIGHT;
11070           }
11071
11072           if (evt.keyCode === uiGridConstants.keymap.UP ||
11073             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey)) {
11074             return uiGridCellNavConstants.direction.UP;
11075           }
11076
11077           if (evt.keyCode === uiGridConstants.keymap.DOWN ||
11078             evt.keyCode === uiGridConstants.keymap.ENTER) {
11079             return uiGridCellNavConstants.direction.DOWN;
11080           }
11081
11082           return null;
11083         },
11084
11085         /**
11086          * @ngdoc service
11087          * @name cellNavColumnBuilder
11088          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11089          * @description columnBuilder function that adds cell navigation properties to grid column
11090          * @returns {promise} promise that will load any needed templates when resolved
11091          */
11092         cellNavColumnBuilder: function (colDef, col, gridOptions) {
11093           var promises = [];
11094
11095           /**
11096            *  @ngdoc object
11097            *  @name ui.grid.cellNav.api:ColumnDef
11098            *
11099            *  @description Column Definitions for cellNav feature, these are available to be
11100            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11101            */
11102
11103           /**
11104            *  @ngdoc object
11105            *  @name allowCellFocus
11106            *  @propertyOf  ui.grid.cellNav.api:ColumnDef
11107            *  @description Enable focus on a cell.
11108            *  <br/>Defaults to true
11109            */
11110           colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
11111
11112           return $q.all(promises);
11113         },
11114
11115         /**
11116          * @ngdoc method
11117          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11118          * @name scrollTo
11119          * @description Scroll the grid such that the specified
11120          * row and column is in view
11121          * @param {Grid} grid the grid you'd like to act upon, usually available
11122          * from gridApi.grid
11123          * @param {object} $scope a scope we can broadcast events from
11124          * @param {object} rowEntity gridOptions.data[] array instance to make visible
11125          * @param {object} colDef to make visible
11126          */
11127         scrollTo: function (grid, $scope, rowEntity, colDef) {
11128           var gridRow = null, gridCol = null;
11129
11130           if (rowEntity !== null) {
11131             gridRow = grid.getRow(rowEntity);
11132           }
11133
11134           if (colDef !== null) {
11135             gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
11136           }
11137           this.scrollToInternal(grid, $scope, gridRow, gridCol);
11138         },
11139
11140         /**
11141          * @ngdoc method
11142          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11143          * @name scrollToInternal
11144          * @description Like scrollTo, but takes gridRow and gridCol.
11145          * In calculating the scroll height we have to deal with wanting
11146          * 0% for the first row, and 100% for the last row.  Normal maths
11147          * for a 10 row list would return 1/10 = 10% for the first row, so 
11148          * we need to tweak the numbers to add an extra 10% somewhere.  The
11149          * formula if we're trying to get to row 0 in a 10 row list (assuming our
11150          * index is zero based, so the last row is row 9) is:
11151          * <pre>
11152          *   0 + 0 / 10 = 0%
11153          * </pre>
11154          * 
11155          * To get to row 9 (i.e. the last row) in the same list, we want to 
11156          * go to:
11157          * <pre>
11158          *  ( 9 + 1 ) / 10 = 100%
11159          * </pre>
11160          * So we need to apportion one whole row within the overall grid scroll, 
11161          * the formula is:
11162          * <pre>
11163          *   ( index + ( index / (total rows - 1) ) / total rows
11164          * </pre>
11165          * @param {Grid} grid the grid you'd like to act upon, usually available
11166          * from gridApi.grid
11167          * @param {object} $scope a scope we can broadcast events from
11168          * @param {GridRow} gridRow row to make visible
11169          * @param {GridCol} gridCol column to make visible
11170          */
11171         scrollToInternal: function (grid, $scope, gridRow, gridCol) {
11172           var args = {};
11173
11174           if (gridRow !== null) {
11175             var seekRowIndex = grid.renderContainers.body.visibleRowCache.indexOf(gridRow);
11176             var totalRows = grid.renderContainers.body.visibleRowCache.length;
11177             var percentage = ( seekRowIndex + ( seekRowIndex / ( totalRows - 1 ) ) ) / totalRows;
11178             args.y = { percentage:  percentage  };
11179           }
11180
11181           if (gridCol !== null) {
11182             args.x = { percentage: this.getLeftWidth(grid, gridCol) / this.getLeftWidth(grid, grid.renderContainers.body.visibleColumnCache[grid.renderContainers.body.visibleColumnCache.length - 1] ) };
11183           }
11184
11185           if (args.y || args.x) {
11186             $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
11187           }
11188         },
11189
11190         /**
11191          * @ngdoc method
11192          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11193          * @name scrollToIfNecessary
11194          * @description Scrolls the grid to make a certain row and column combo visible,
11195          *   in the case that it is not completely visible on the screen already.
11196          * @param {Grid} grid the grid you'd like to act upon, usually available
11197          * from gridApi.grid
11198          * @param {object} $scope a scope we can broadcast events from
11199          * @param {GridRow} gridRow row to make visible
11200          * @param {GridCol} gridCol column to make visible
11201          */
11202         scrollToIfNecessary: function (grid, $scope, gridRow, gridCol) {
11203           var args = {};
11204
11205           // Alias the visible row and column caches 
11206           var visRowCache = grid.renderContainers.body.visibleRowCache;
11207           var visColCache = grid.renderContainers.body.visibleColumnCache;
11208
11209           /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
11210
11211           // 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
11212           var topBound = grid.renderContainers.body.prevScrollTop + grid.headerHeight;
11213
11214           // Don't the let top boundary be less than 0
11215           topBound = (topBound < 0) ? 0 : topBound;
11216
11217           // The left boundary is the current X scroll position
11218           var leftBound = grid.renderContainers.body.prevScrollLeft;
11219
11220           // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
11221           //   Basically this is the viewport height added on to the scroll position
11222           var bottomBound = grid.renderContainers.body.prevScrollTop + grid.gridHeight - grid.headerHeight;
11223
11224           // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
11225           if (grid.horizontalScrollbarHeight) {
11226             bottomBound = bottomBound - grid.horizontalScrollbarHeight;
11227           }
11228
11229           // The right position is the current X scroll position minus the grid width
11230           var rightBound = grid.renderContainers.body.prevScrollLeft + grid.gridWidth;
11231
11232           // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
11233           if (grid.verticalScrollbarWidth) {
11234             rightBound = rightBound - grid.verticalScrollbarWidth;
11235           }
11236
11237           // We were given a row to scroll to
11238           if (gridRow !== null) {
11239             // This is the index of the row we want to scroll to, within the list of rows that can be visible
11240             var seekRowIndex = visRowCache.indexOf(gridRow);
11241             
11242             // Total vertical scroll length of the grid
11243             var scrollLength = (grid.renderContainers.body.getCanvasHeight() - grid.renderContainers.body.getViewportHeight());
11244
11245             // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
11246             if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
11247               scrollLength = scrollLength + grid.horizontalScrollbarHeight;
11248             }
11249
11250             // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
11251             var pixelsToSeeRow = ((seekRowIndex + 1) * grid.options.rowHeight);
11252
11253             // Don't let the pixels required to see the row be less than zero
11254             pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
11255
11256             var scrollPixels, percentage;
11257
11258             // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the grid...
11259             if (pixelsToSeeRow < topBound) {
11260               // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
11261               //   to get the full position we need
11262               scrollPixels = grid.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
11263
11264               // Turn the scroll position into a percentage and make it an argument for a scroll event
11265               percentage = scrollPixels / scrollLength;
11266               args.y = { percentage: percentage  };
11267             }
11268             // 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 grid...
11269             else if (pixelsToSeeRow > bottomBound) {
11270               // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
11271               //   to get the full position we need
11272               scrollPixels = pixelsToSeeRow - bottomBound + grid.renderContainers.body.prevScrollTop;
11273
11274               // Turn the scroll position into a percentage and make it an argument for a scroll event
11275               percentage = scrollPixels / scrollLength;
11276               args.y = { percentage: percentage  };
11277             }
11278           }
11279
11280           // We were given a column to scroll to
11281           if (gridCol !== null) {
11282             // This is the index of the row we want to scroll to, within the list of rows that can be visible
11283             var seekColumnIndex = visColCache.indexOf(gridCol);
11284             
11285             // Total vertical scroll length of the grid
11286             var horizScrollLength = (grid.renderContainers.body.getCanvasWidth() - grid.renderContainers.body.getViewportWidth());
11287
11288             // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
11289             // if (grid.verticalScrollbarWidth && grid.verticalScrollbarWidth > 0) {
11290             //   horizScrollLength = horizScrollLength + grid.verticalScrollbarWidth;
11291             // }
11292
11293             // This is the minimum amount of pixels we need to scroll vertical in order to see this column
11294             var columnLeftEdge = 0;
11295             for (var i = 0; i < seekColumnIndex; i++) {
11296               var col = visColCache[i];
11297               columnLeftEdge += col.drawnWidth;
11298             }
11299             columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
11300
11301             var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
11302
11303             // Don't let the pixels required to see the column be less than zero
11304             columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
11305
11306             var horizScrollPixels, horizPercentage;
11307
11308             // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the grid...
11309             if (columnLeftEdge < leftBound) {
11310               // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
11311               //   to get the full position we need
11312               horizScrollPixels = grid.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
11313
11314               // Turn the scroll position into a percentage and make it an argument for a scroll event
11315               horizPercentage = horizScrollPixels / horizScrollLength;
11316               horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
11317               args.x = { percentage: horizPercentage  };
11318             }
11319             // 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 grid...
11320             else if (columnRightEdge > rightBound) {
11321               // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
11322               //   to get the full position we need
11323               horizScrollPixels = columnRightEdge - rightBound + grid.renderContainers.body.prevScrollLeft;
11324
11325               // Turn the scroll position into a percentage and make it an argument for a scroll event
11326               horizPercentage = horizScrollPixels / horizScrollLength;
11327               horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
11328               args.x = { percentage: horizPercentage  };
11329             }
11330           }
11331
11332           // If we need to scroll on either the x or y axes, fire a scroll event
11333           if (args.y || args.x) {
11334             $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
11335           }
11336         },
11337
11338         /**
11339          * @ngdoc method
11340          * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11341          * @name getLeftWidth
11342          * @description Get the current drawn width of the columns in the
11343          * grid up to the numbered column, and add an apportionment for the
11344          * column that we're on.  So if we are on column 0, we want to scroll
11345          * 0% (i.e. exclude this column from calc).  If we're on the last column
11346          * we want to scroll to 100% (i.e. include this column in the calc). So
11347          * we include (thisColIndex / totalNumberCols) % of this column width
11348          * @param {Grid} grid the grid you'd like to act upon, usually available
11349          * from gridApi.grid
11350          * @param {gridCol} upToCol the column to total up to and including
11351          */
11352         getLeftWidth: function (grid, upToCol) {
11353           var width = 0;
11354
11355           if (!upToCol) {
11356             return width;
11357           }
11358           
11359           var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
11360           
11361           // total column widths up-to but not including the passed in column
11362           grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
11363             if ( index < lastIndex ){
11364               width += col.drawnWidth;  
11365             }
11366           });
11367           
11368           // pro-rata the final column based on % of total columns.
11369           var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
11370           width += upToCol.drawnWidth * percentage;
11371            
11372           return width;
11373         }
11374       };
11375
11376       return service;
11377     }]);
11378
11379   /**
11380    *  @ngdoc directive
11381    *  @name ui.grid.cellNav.directive:uiCellNav
11382    *  @element div
11383    *  @restrict EA
11384    *
11385    *  @description Adds cell navigation features to the grid columns
11386    *
11387    *  @example
11388    <example module="app">
11389    <file name="app.js">
11390    var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
11391
11392    app.controller('MainCtrl', ['$scope', function ($scope) {
11393       $scope.data = [
11394         { name: 'Bob', title: 'CEO' },
11395             { name: 'Frank', title: 'Lowly Developer' }
11396       ];
11397
11398       $scope.columnDefs = [
11399         {name: 'name'},
11400         {name: 'title'}
11401       ];
11402     }]);
11403    </file>
11404    <file name="index.html">
11405    <div ng-controller="MainCtrl">
11406    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
11407    </div>
11408    </file>
11409    </example>
11410    */
11411   module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants',
11412     function (gridUtil, uiGridCellNavService, uiGridCellNavConstants) {
11413       return {
11414         replace: true,
11415         priority: -150,
11416         require: '^uiGrid',
11417         scope: false,
11418         compile: function () {
11419           return {
11420             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11421
11422               var grid = uiGridCtrl.grid;
11423               uiGridCellNavService.initializeGrid(grid);
11424
11425               uiGridCtrl.cellNav = {};
11426
11427               uiGridCtrl.cellNav.focusCell = function (row, col) {
11428                 uiGridCtrl.cellNav.broadcastCellNav({ row: row, col: col });
11429               };
11430
11431               //  gridUtil.logDebug('uiGridEdit preLink');
11432               uiGridCtrl.cellNav.broadcastCellNav = function (newRowCol) {
11433                 $scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol);
11434                 uiGridCtrl.cellNav.broadcastFocus(newRowCol);
11435               };
11436
11437               uiGridCtrl.cellNav.broadcastFocus = function (rowCol) {
11438                 var row = rowCol.row,
11439                     col = rowCol.col;
11440
11441                 if (grid.cellNav.lastRowCol === null || (grid.cellNav.lastRowCol.row !== row || grid.cellNav.lastRowCol.col !== col)) {
11442                   var newRowCol = new RowCol(row, col);
11443                   grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
11444                   grid.cellNav.lastRowCol = newRowCol;
11445                 }
11446               };
11447
11448               uiGridCtrl.cellNav.handleKeyDown = function (evt) {
11449                 var direction = uiGridCellNavService.getDirection(evt);
11450                 if (direction === null) {
11451                   return true;
11452                 }
11453
11454                 var containerId = 'body';
11455                 if (evt.uiGridTargetRenderContainerId) {
11456                   containerId = evt.uiGridTargetRenderContainerId;
11457                 }
11458
11459                 // Get the last-focused row+col combo
11460                 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
11461                 if (lastRowCol) {
11462                   // Figure out which new row+combo we're navigating to
11463                   var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
11464
11465                   rowCol.eventType = uiGridCellNavConstants.EVENT_TYPE.KEYDOWN;
11466
11467                   // Broadcast the navigation
11468                   uiGridCtrl.cellNav.broadcastCellNav(rowCol);
11469
11470                   // Scroll to the new cell, if it's not completely visible within the render container's viewport
11471                   uiGridCellNavService.scrollToIfNecessary(grid, $scope, rowCol.row, rowCol.col);
11472
11473                   evt.stopPropagation();
11474                   evt.preventDefault();
11475
11476                   return false;
11477                 }
11478               };
11479             },
11480             post: function ($scope, $elm, $attrs, uiGridCtrl) {
11481             }
11482           };
11483         }
11484       };
11485     }]);
11486
11487   module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants',
11488     function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants) {
11489       return {
11490         replace: true,
11491         priority: -99999, //this needs to run very last
11492         require: ['^uiGrid', 'uiGridRenderContainer'],
11493         scope: false,
11494         compile: function () {
11495           return {
11496             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11497             },
11498             post: function ($scope, $elm, $attrs, controllers) {
11499               var uiGridCtrl = controllers[0],
11500                   renderContainerCtrl = controllers[1];
11501
11502               var containerId = renderContainerCtrl.containerId;
11503
11504               var grid = uiGridCtrl.grid;
11505
11506               // Needs to run last after all renderContainers are built
11507               uiGridCellNavService.decorateRenderContainers(grid);
11508
11509               // Let the render container be focus-able
11510               $elm.attr("tabindex", -1);
11511
11512               // Bind to keydown events in the render container
11513               $elm.on('keydown', function (evt) {
11514                 evt.uiGridTargetRenderContainerId = containerId;
11515                 return uiGridCtrl.cellNav.handleKeyDown(evt);
11516               });
11517
11518               // When there's a scroll event we need to make sure to re-focus the right row, because the cell contents may have changed
11519               $scope.$on(uiGridConstants.events.GRID_SCROLL, function (evt, args) {
11520                 // Skip if there's no currently-focused cell
11521                 if (uiGridCtrl.grid.api.cellNav.getFocusedCell() == null) {
11522                   return;
11523                 }
11524
11525                 // We have to wrap in TWO timeouts so that we run AFTER the scroll event is resolved.
11526                 $timeout(function () {
11527                   $timeout(function () {
11528                     // Get the last row+col combo
11529                     var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
11530
11531                     // If the body element becomes active, re-focus on the render container so we can capture cellNav events again.
11532                     //   NOTE: this happens when we navigate LET from the left-most cell (RIGHT from the right-most) and have to re-render a new
11533                     //   set of cells. The cell element we are navigating to doesn't exist and focus gets lost. This will re-capture it, imperfectly...
11534                     if ($document.activeElement === $document.body) {
11535                       $elm[0].focus();
11536                     }
11537
11538                     // Re-broadcast a cellNav event so we re-focus the right cell
11539                     uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
11540                   });
11541                 });
11542               });
11543             }
11544           };
11545         }
11546       };
11547     }]);
11548
11549   /**
11550    *  @ngdoc directive
11551    *  @name ui.grid.cellNav.directive:uiGridCell
11552    *  @element div
11553    *  @restrict A
11554    *  @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
11555    */
11556   module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants',
11557     function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants) {
11558       return {
11559         priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
11560         restrict: 'A',
11561         require: '^uiGrid',
11562         scope: false,
11563         link: function ($scope, $elm, $attrs, uiGridCtrl) {
11564           if (!$scope.col.colDef.allowCellFocus) {
11565             return;
11566           }
11567
11568           setTabEnabled();
11569           
11570           // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
11571           $elm.find('div').on('click', function (evt) {
11572             uiGridCtrl.cellNav.broadcastCellNav(new RowCol($scope.row, $scope.col));
11573
11574             evt.stopPropagation();
11575           });
11576
11577           // This event is fired for all cells.  If the cell matches, then focus is set
11578           $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol) {
11579             if (rowCol.row === $scope.row &&
11580               rowCol.col === $scope.col) {
11581               setFocused();
11582
11583               // This cellNav event came from a keydown event so we can safely refocus
11584               if (rowCol.hasOwnProperty('eventType') && rowCol.eventType === uiGridCellNavConstants.EVENT_TYPE.KEYDOWN) {
11585                 $elm.find('div')[0].focus();
11586               }
11587             }
11588             else {
11589               clearFocus();
11590             }
11591           });
11592
11593           function setTabEnabled() {
11594             $elm.find('div').attr("tabindex", -1);
11595           }
11596
11597           function setFocused() {
11598             var div = $elm.find('div');
11599             div.addClass('ui-grid-cell-focus');
11600           }
11601
11602           function clearFocus() {
11603             var div = $elm.find('div');
11604             div.removeClass('ui-grid-cell-focus');
11605           }
11606
11607           $scope.$on('$destroy', function () {
11608             $elm.find('div').off('click');
11609           });
11610         }
11611       };
11612     }]);
11613
11614 })();
11615 (function () {
11616   'use strict';
11617
11618   /**
11619    * @ngdoc overview
11620    * @name ui.grid.edit
11621    * @description
11622    *
11623    *  # ui.grid.edit
11624    * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
11625    * a keyboard.
11626    * <br/>
11627    * <br/>
11628    * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
11629    * user to key data and then tab, arrow, or enter to the cells beside or below.
11630    *
11631    * <div doc-module-components="ui.grid.edit"></div>
11632    */
11633
11634   var module = angular.module('ui.grid.edit', ['ui.grid']);
11635
11636   /**
11637    *  @ngdoc object
11638    *  @name ui.grid.edit.constant:uiGridEditConstants
11639    *
11640    *  @description constants available in edit module
11641    */
11642   module.constant('uiGridEditConstants', {
11643     EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
11644     //must be lowercase because template bulder converts to lower
11645     EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
11646     events: {
11647       BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
11648       END_CELL_EDIT: 'uiGridEventEndCellEdit',
11649       CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
11650     }
11651   });
11652
11653   /**
11654    *  @ngdoc service
11655    *  @name ui.grid.edit.service:uiGridEditService
11656    *
11657    *  @description Services for editing features
11658    */
11659   module.service('uiGridEditService', ['$q', '$templateCache', 'uiGridConstants', 'gridUtil',
11660     function ($q, $templateCache, uiGridConstants, gridUtil) {
11661
11662       var service = {
11663
11664         initializeGrid: function (grid) {
11665
11666           service.defaultGridOptions(grid.options);
11667
11668           grid.registerColumnBuilder(service.editColumnBuilder);
11669
11670           /**
11671            *  @ngdoc object
11672            *  @name ui.grid.edit.api:PublicApi
11673            *
11674            *  @description Public Api for edit feature
11675            */
11676           var publicApi = {
11677             events: {
11678               edit: {
11679                 /**
11680                  * @ngdoc event
11681                  * @name afterCellEdit
11682                  * @eventOf  ui.grid.edit.api:PublicApi
11683                  * @description raised when cell editing is complete
11684                  * <pre>
11685                  *      gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
11686                  * </pre>
11687                  * @param {object} rowEntity the options.data element that was edited
11688                  * @param {object} colDef the column that was edited
11689                  * @param {object} newValue new value
11690                  * @param {object} oldValue old value
11691                  */
11692                 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
11693                 },
11694                 /**
11695                  * @ngdoc event
11696                  * @name beginCellEdit
11697                  * @eventOf  ui.grid.edit.api:PublicApi
11698                  * @description raised when cell editing starts on a cell
11699                  * <pre>
11700                  *      gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
11701                  * </pre>
11702                  * @param {object} rowEntity the options.data element that was edited
11703                  * @param {object} colDef the column that was edited
11704                  */
11705                 beginCellEdit: function (rowEntity, colDef) {
11706                 },
11707                 /**
11708                  * @ngdoc event
11709                  * @name cancelCellEdit
11710                  * @eventOf  ui.grid.edit.api:PublicApi
11711                  * @description raised when cell editing is cancelled on a cell
11712                  * <pre>
11713                  *      gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
11714                  * </pre>
11715                  * @param {object} rowEntity the options.data element that was edited
11716                  * @param {object} colDef the column that was edited
11717                  */
11718                 cancelCellEdit: function (rowEntity, colDef) {
11719                 }                
11720               }
11721             },
11722             methods: {
11723               edit: { }
11724             }
11725           };
11726
11727           grid.api.registerEventsFromObject(publicApi.events);
11728           //grid.api.registerMethodsFromObject(publicApi.methods);
11729
11730         },
11731
11732         defaultGridOptions: function (gridOptions) {
11733
11734           /**
11735            *  @ngdoc object
11736            *  @name ui.grid.edit.api:GridOptions
11737            *
11738            *  @description Options for configuring the edit feature, these are available to be  
11739            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
11740            */
11741
11742           /**
11743            *  @ngdoc object
11744            *  @name enableCellEdit
11745            *  @propertyOf  ui.grid.edit.api:GridOptions
11746            *  @description If defined, sets the default value for the editable flag on each individual colDefs 
11747            *  if their individual enableCellEdit configuration is not defined. Defaults to undefined.  
11748            */
11749
11750           /**
11751            *  @ngdoc object
11752            *  @name cellEditableCondition
11753            *  @propertyOf  ui.grid.edit.api:GridOptions
11754            *  @description If specified, either a value or function to be used by all columns before editing.  
11755            *  If falsy, then editing of cell is not allowed.
11756            *  @example
11757            *  <pre>
11758            *  function($scope){
11759            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
11760            *    return true;
11761            *  }
11762            *  </pre>
11763            */
11764           gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
11765
11766           /**
11767            *  @ngdoc object
11768            *  @name editableCellTemplate
11769            *  @propertyOf  ui.grid.edit.api:GridOptions
11770            *  @description If specified, cellTemplate to use as the editor for all columns.  
11771            *  <br/> defaults to 'ui-grid/cellTextEditor'
11772            */
11773
11774           /**
11775            *  @ngdoc object
11776            *  @name enableCellEditOnFocus
11777            *  @propertyOf  ui.grid.edit.api:GridOptions
11778            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
11779            *  <br/>_requires cellNav feature and the edit feature to be enabled_
11780            */
11781             //enableCellEditOnFocus can only be used if cellnav module is used
11782           gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
11783         },
11784
11785         /**
11786          * @ngdoc service
11787          * @name editColumnBuilder
11788          * @methodOf ui.grid.edit.service:uiGridEditService
11789          * @description columnBuilder function that adds edit properties to grid column
11790          * @returns {promise} promise that will load any needed templates when resolved
11791          */
11792         editColumnBuilder: function (colDef, col, gridOptions) {
11793
11794           var promises = [];
11795
11796           /**
11797            *  @ngdoc object
11798            *  @name ui.grid.edit.api:ColumnDef
11799            *
11800            *  @description Column Definition for edit feature, these are available to be 
11801            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11802            */
11803
11804           /**
11805            *  @ngdoc object
11806            *  @name enableCellEdit
11807            *  @propertyOf  ui.grid.edit.api:ColumnDef
11808            *  @description enable editing on column
11809            */
11810           colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
11811             (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
11812
11813           /**
11814            *  @ngdoc object
11815            *  @name cellEditableCondition
11816            *  @propertyOf  ui.grid.edit.api:ColumnDef
11817            *  @description If specified, either a value or function evaluated before editing cell.  If falsy, then editing of cell is not allowed.
11818            *  @example 
11819            *  <pre>
11820            *  function($scope){
11821            *    //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
11822            *    return true;
11823            *  }
11824            *  </pre>
11825            */
11826           colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition :  colDef.cellEditableCondition;
11827
11828           /**
11829            *  @ngdoc object
11830            *  @name editableCellTemplate
11831            *  @propertyOf  ui.grid.edit.api:ColumnDef
11832            *  @description cell template to be used when editing this column. Can be Url or text template
11833            *  <br/>Defaults to gridOptions.editableCellTemplate
11834            */
11835           if (colDef.enableCellEdit) {
11836             colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
11837
11838             promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
11839               .then(
11840               function (template) {
11841                 col.editableCellTemplate = template;
11842               },
11843               function (res) {
11844                 // Todo handle response error here?
11845                 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
11846               }));
11847           }
11848
11849           /**
11850            *  @ngdoc object
11851            *  @name enableCellEditOnFocus
11852            *  @propertyOf  ui.grid.edit.api:ColumnDef
11853            *  @requires ui.grid.cellNav
11854            *  @description If true, then editor is invoked as soon as cell receives focus. Default false.
11855            *  <br>_requires both the cellNav feature and the edit feature to be enabled_
11856            */
11857             //enableCellEditOnFocus can only be used if cellnav module is used
11858           colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
11859
11860           return $q.all(promises);
11861         },
11862
11863         /**
11864          * @ngdoc service
11865          * @name isStartEditKey
11866          * @methodOf ui.grid.edit.service:uiGridEditService
11867          * @description  Determines if a keypress should start editing.  Decorate this service to override with your
11868          * own key events.  See service decorator in angular docs.
11869          * @param {Event} evt keydown event
11870          * @returns {boolean} true if an edit should start
11871          */
11872         isStartEditKey: function (evt) {
11873           if (evt.keyCode === uiGridConstants.keymap.LEFT ||
11874             (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
11875
11876             evt.keyCode === uiGridConstants.keymap.RIGHT ||
11877             evt.keyCode === uiGridConstants.keymap.TAB ||
11878
11879             evt.keyCode === uiGridConstants.keymap.UP ||
11880             (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
11881
11882             evt.keyCode === uiGridConstants.keymap.DOWN ||
11883             evt.keyCode === uiGridConstants.keymap.ENTER) {
11884             return false;
11885
11886           }
11887           return true;
11888         }
11889
11890
11891       };
11892
11893       return service;
11894
11895     }]);
11896
11897   /**
11898    *  @ngdoc directive
11899    *  @name ui.grid.edit.directive:uiGridEdit
11900    *  @element div
11901    *  @restrict A
11902    *
11903    *  @description Adds editing features to the ui-grid directive.
11904    *
11905    *  @example
11906    <example module="app">
11907    <file name="app.js">
11908    var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
11909
11910    app.controller('MainCtrl', ['$scope', function ($scope) {
11911       $scope.data = [
11912         { name: 'Bob', title: 'CEO' },
11913             { name: 'Frank', title: 'Lowly Developer' }
11914       ];
11915
11916       $scope.columnDefs = [
11917         {name: 'name', enableCellEdit: true},
11918         {name: 'title', enableCellEdit: true}
11919       ];
11920     }]);
11921    </file>
11922    <file name="index.html">
11923    <div ng-controller="MainCtrl">
11924    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
11925    </div>
11926    </file>
11927    </example>
11928    */
11929   module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
11930     return {
11931       replace: true,
11932       priority: 0,
11933       require: '^uiGrid',
11934       scope: false,
11935       compile: function () {
11936         return {
11937           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11938             uiGridEditService.initializeGrid(uiGridCtrl.grid);
11939           },
11940           post: function ($scope, $elm, $attrs, uiGridCtrl) {
11941           }
11942         };
11943       }
11944     };
11945   }]);
11946
11947   /**
11948    *  @ngdoc directive
11949    *  @name ui.grid.edit.directive:uiGridCell
11950    *  @element div
11951    *  @restrict A
11952    *
11953    *  @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
11954    *  Editing Actions.
11955    *
11956    *  Binds edit start events to the uiGridCell element.  When the events fire, the gridCell element is appended
11957    *  with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
11958    *
11959    *  The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
11960    *  and do the initial steps needed to edit the cell (setfocus on input element, etc).
11961    *
11962    *  When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
11963    *  it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
11964    *
11965    *  If editableCellTemplate recognizes that the editing has been cancelled (esc key)
11966    *  it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event.  The original value
11967    *  will be set back on the model by the uiGridCell directive.
11968    *
11969    *  Events that invoke editing:
11970    *    - dblclick
11971    *    - F2 keydown (when using cell selection)
11972    *
11973    *  Events that end editing:
11974    *    - Dependent on the specific editableCellTemplate
11975    *    - Standards should be blur and enter keydown
11976    *
11977    *  Events that cancel editing:
11978    *    - Dependent on the specific editableCellTemplate
11979    *    - Standards should be Esc keydown
11980    *
11981    *  Grid Events that end editing:
11982    *    - uiGridConstants.events.GRID_SCROLL
11983    *
11984    */
11985   module.directive('uiGridCell',
11986     ['$compile', '$injector', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService',
11987       function ($compile, $injector, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService) {
11988         return {
11989           priority: -100, // run after default uiGridCell directive
11990           restrict: 'A',
11991           scope: false,
11992           require: '?^uiGrid',
11993           link: function ($scope, $elm, $attrs, uiGridCtrl) {
11994             if (!$scope.col.colDef.enableCellEdit) {
11995               return;
11996             }
11997
11998             var html;
11999             var origCellValue;
12000             var inEdit = false;
12001             var isFocusedBeforeEdit = false;
12002             var cellModel;
12003
12004             registerBeginEditEvents();
12005
12006             function registerBeginEditEvents() {
12007               $elm.on('dblclick', beginEdit);
12008               $elm.on('keydown', beginEditKeyDown);
12009               if ($scope.col.colDef.enableCellEditOnFocus) {
12010                 $elm.find('div').on('focus', beginEditFocus);
12011               }
12012             }
12013
12014             function cancelBeginEditEvents() {
12015               $elm.off('dblclick', beginEdit);
12016               $elm.off('keydown', beginEditKeyDown);
12017               if ($scope.col.colDef.enableCellEditOnFocus) {
12018                 $elm.find('div').off('focus', beginEditFocus);
12019               }
12020             }
12021
12022             function beginEditFocus(evt) {
12023               // gridUtil.logDebug('begin edit');
12024               if (uiGridCtrl && uiGridCtrl.cellNav) {
12025                 // NOTE(c0bra): This is causing a loop where focusCell causes beginEditFocus to be called....
12026                 uiGridCtrl.cellNav.focusCell($scope.row, $scope.col);
12027               }
12028
12029               evt.stopPropagation();
12030               beginEdit();
12031             }
12032
12033             // If the cellNagv module is installed and we can get the uiGridCellNavConstants value injected,
12034             //   then if the column has enableCellEditOnFocus set to true, we need to listen for cellNav events
12035             //   to this cell and start editing when the "focus" reaches us
12036             try {
12037               var uiGridCellNavConstants = $injector.get('uiGridCellNavConstants');
12038
12039               if ($scope.col.colDef.enableCellEditOnFocus) {
12040                 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol) {
12041                   if (rowCol.row === $scope.row && rowCol.col === $scope.col) {
12042                     beginEdit();
12043                   }
12044                   else {
12045                     endEdit();
12046                   }
12047                 });
12048               }
12049             }
12050             catch (e) {}
12051
12052             function beginEditKeyDown(evt) {
12053               if (uiGridEditService.isStartEditKey(evt)) {
12054                 beginEdit();
12055               }
12056             }
12057
12058             function shouldEdit(col, row) {
12059               return !row.isSaving && 
12060                 ( angular.isFunction(col.colDef.cellEditableCondition) ?
12061                     col.colDef.cellEditableCondition($scope) :
12062                     col.colDef.cellEditableCondition );
12063             }
12064
12065
12066             /**
12067              *  @ngdoc property
12068              *  @name editDropdownOptionsArray
12069              *  @propertyOf ui.grid.edit.api:ColumnDef
12070              *  @description an array of values in the format
12071              *  [ {id: xxx, value: xxx} ], which is populated
12072              *  into the edit dropdown
12073              * 
12074              */
12075             /**
12076              *  @ngdoc property
12077              *  @name editDropdownIdLabel
12078              *  @propertyOf ui.grid.edit.api:ColumnDef
12079              *  @description the label for the "id" field
12080              *  in the editDropdownOptionsArray.  Defaults
12081              *  to 'id'
12082              *  @example
12083              *  <pre>
12084              *    $scope.gridOptions = { 
12085              *      columnDefs: [
12086              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', 
12087              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12088              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
12089              *      ],
12090              *  </pre>
12091              * 
12092              */
12093             /**
12094              *  @ngdoc property
12095              *  @name editDropdownValueLabel
12096              *  @propertyOf ui.grid.edit.api:ColumnDef
12097              *  @description the label for the "value" field
12098              *  in the editDropdownOptionsArray.  Defaults
12099              *  to 'value'
12100              *  @example
12101              *  <pre>
12102              *    $scope.gridOptions = { 
12103              *      columnDefs: [
12104              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', 
12105              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12106              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
12107              *      ],
12108              *  </pre>
12109              * 
12110              */
12111             /**
12112              *  @ngdoc property
12113              *  @name editDropdownFilter
12114              *  @propertyOf ui.grid.edit.api:ColumnDef
12115              *  @description A filter that you would like to apply to the values in the options list
12116              *  of the dropdown.  For example if you were using angular-translate you might set this
12117              *  to `'translate'`
12118              *  @example
12119              *  <pre>
12120              *    $scope.gridOptions = { 
12121              *      columnDefs: [
12122              *        {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', 
12123              *          editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12124              *          editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
12125              *      ],
12126              *  </pre>
12127              * 
12128              */
12129             function beginEdit() {
12130               // If we are already editing, then just skip this so we don't try editing twice...
12131               if (inEdit) {
12132                 return;
12133               }
12134
12135               if (!shouldEdit($scope.col, $scope.row)) {
12136                 return;
12137               }
12138
12139               cellModel = $parse($scope.row.getQualifiedColField($scope.col));
12140               //get original value from the cell
12141               origCellValue = cellModel($scope);
12142
12143               html = $scope.col.editableCellTemplate;
12144               html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
12145               
12146               var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : ''; 
12147               html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
12148
12149               $scope.inputType = 'text';
12150               switch ($scope.col.colDef.type){
12151                 case 'boolean':
12152                   $scope.inputType = 'checkbox';
12153                   break;
12154                 case 'number':
12155                   $scope.inputType = 'number';
12156                   break;
12157                 case 'date':
12158                   $scope.inputType = 'date';
12159                   break;
12160               }
12161               
12162               $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
12163               $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';  
12164               $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';  
12165
12166               var cellElement;
12167               $scope.$apply(function () {
12168                 inEdit = true;
12169                 cancelBeginEditEvents();
12170                 var cellElement = angular.element(html);
12171                 $elm.append(cellElement);
12172                 $compile(cellElement)($scope.$new());
12173                 var gridCellContentsEl = angular.element($elm.children()[0]);
12174                 isFocusedBeforeEdit = gridCellContentsEl.hasClass('ui-grid-cell-focus');
12175                 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
12176               });
12177
12178               //stop editing when grid is scrolled
12179               var deregOnGridScroll = $scope.$on(uiGridConstants.events.GRID_SCROLL, function () {
12180                 endEdit(true);
12181                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
12182                 deregOnGridScroll();
12183               });
12184
12185               //end editing
12186               var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function (evt, retainFocus) {
12187                 endEdit(retainFocus);
12188                 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
12189                 deregOnEndCellEdit();
12190               });
12191
12192               //cancel editing
12193               var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
12194                 cancelEdit();
12195                 deregOnCancelCellEdit();
12196               });
12197
12198               $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT);
12199               $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef);
12200             }
12201
12202             function endEdit(retainFocus) {
12203               if (!inEdit) {
12204                 return;
12205               }
12206               var gridCellContentsEl = angular.element($elm.children()[0]);
12207               //remove edit element
12208               angular.element($elm.children()[1]).remove();
12209               gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
12210               if (retainFocus && isFocusedBeforeEdit) {
12211                 gridCellContentsEl[0].focus();
12212               }
12213               isFocusedBeforeEdit = false;
12214               inEdit = false;
12215               registerBeginEditEvents();
12216               $scope.grid.api.core.notifyDataChange( $scope.grid, uiGridConstants.dataChange.EDIT );
12217             }
12218
12219             function cancelEdit() {
12220               if (!inEdit) {
12221                 return;
12222               }
12223               cellModel.assign($scope, origCellValue);
12224               $scope.$apply();
12225
12226               $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
12227               endEdit(true);
12228             }
12229
12230           }
12231         };
12232       }]);
12233
12234   /**
12235    *  @ngdoc directive
12236    *  @name ui.grid.edit.directive:uiGridEditor
12237    *  @element div
12238    *  @restrict A
12239    *
12240    *  @description input editor directive for editable fields.
12241    *  Provides EndEdit and CancelEdit events
12242    *
12243    *  Events that end editing:
12244    *     blur and enter keydown
12245    *
12246    *  Events that cancel editing:
12247    *    - Esc keydown
12248    *
12249    */
12250   module.directive('uiGridEditor',
12251     ['uiGridConstants', 'uiGridEditConstants',
12252       function (uiGridConstants, uiGridEditConstants) {
12253         return {
12254           scope: true,
12255           require: ['?^uiGrid', '?^uiGridRenderContainer'],
12256           compile: function () {
12257             return {
12258               pre: function ($scope, $elm, $attrs) {
12259
12260               },
12261               post: function ($scope, $elm, $attrs, controllers) {
12262                 var uiGridCtrl, renderContainerCtrl;
12263                 if (controllers[0]) { uiGridCtrl = controllers[0]; }
12264                 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
12265
12266                 //set focus at start of edit
12267                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
12268                   $elm[0].focus();
12269                   $elm[0].select();
12270                   $elm.on('blur', function (evt) {
12271                     $scope.stopEdit(evt);
12272                   });
12273                 });
12274
12275                
12276                $scope.deepEdit = false;
12277                
12278                $scope.stopEdit = function (evt) {
12279                   if ($scope.inputForm && !$scope.inputForm.$valid) {
12280                     evt.stopPropagation();
12281                     $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12282                   }
12283                   else {
12284                     $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
12285                   }
12286                   $scope.deepEdit = false;
12287                 };
12288
12289                 $elm.on('click', function (evt) {
12290                   $scope.deepEdit = true;
12291                 });
12292
12293                 $elm.on('keydown', function (evt) {
12294                   switch (evt.keyCode) {
12295                     case uiGridConstants.keymap.ESC:
12296                       evt.stopPropagation();
12297                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12298                       break;
12299                     case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
12300                       $scope.stopEdit(evt);
12301                       break;
12302                     case uiGridConstants.keymap.TAB:
12303                       $scope.stopEdit(evt);
12304                       break;
12305                   }
12306
12307                   if ($scope.deepEdit) {
12308                     switch (evt.keyCode) {
12309                       case uiGridConstants.keymap.LEFT:
12310                         evt.stopPropagation();
12311                         break;
12312                       case uiGridConstants.keymap.RIGHT:
12313                         evt.stopPropagation();
12314                         break;
12315                       case uiGridConstants.keymap.UP:
12316                         evt.stopPropagation();
12317                         break;
12318                       case uiGridConstants.keymap.DOWN:
12319                         evt.stopPropagation();
12320                         break;
12321                     }
12322                   }
12323                   // Pass the keydown event off to the cellNav service, if it exists
12324                   else if (uiGridCtrl && uiGridCtrl.hasOwnProperty('cellNav') && renderContainerCtrl) {
12325                     evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
12326                     uiGridCtrl.cellNav.handleKeyDown(evt);
12327                   }
12328
12329                   return true;
12330                 });
12331               }
12332             };
12333           }
12334         };
12335       }]);
12336
12337   /**
12338    *  @ngdoc directive
12339    *  @name ui.grid.edit.directive:input
12340    *  @element input
12341    *  @restrict E
12342    *
12343    *  @description directive to provide binding between input[date] value and ng-model for angular 1.2
12344    *  It is similar to input[date] directive of angular 1.3
12345    *
12346    *  Supported date format for input is 'yyyy-MM-dd'
12347    *  The directive will set the $valid property of input element and the enclosing form to false if
12348    *  model is invalid date or value of input is entered wrong.
12349    *
12350    */
12351     module.directive('input', ['$filter', function ($filter) {
12352       function parseDateString(dateString) {
12353         if (typeof(dateString) === 'undefined' || dateString === '') {
12354           return null;
12355         }
12356         var parts = dateString.split('-');
12357         if (parts.length !== 3) {
12358           return null;
12359         }
12360         var year = parseInt(parts[0], 10);
12361         var month = parseInt(parts[1], 10);
12362         var day = parseInt(parts[2], 10);
12363
12364         if (month < 1 || year < 1 || day < 1) {
12365           return null;
12366         }
12367         return new Date(year, (month - 1), day);
12368       }
12369       return {
12370         restrict: 'E',
12371         require: '?ngModel',
12372         link: function (scope, element, attrs, ngModel) {
12373
12374           if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
12375
12376             ngModel.$formatters.push(function (modelValue) {
12377               ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
12378               return $filter('date')(modelValue, 'yyyy-MM-dd');
12379             });
12380
12381             ngModel.$parsers.push(function (viewValue) {
12382               if (viewValue && viewValue.length > 0) {
12383                 var dateValue = parseDateString(viewValue);
12384                 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
12385                 return dateValue;
12386               }
12387               else {
12388                 ngModel.$setValidity(null, true);
12389                 return null;
12390               }
12391             });
12392           }
12393         }
12394       };
12395     }]);
12396     
12397     
12398   /**
12399    *  @ngdoc directive
12400    *  @name ui.grid.edit.directive:uiGridEditDropdown
12401    *  @element div
12402    *  @restrict A
12403    *
12404    *  @description dropdown editor for editable fields.
12405    *  Provides EndEdit and CancelEdit events
12406    *
12407    *  Events that end editing:
12408    *     blur and enter keydown, and any left/right nav
12409    *
12410    *  Events that cancel editing:
12411    *    - Esc keydown
12412    *
12413    */
12414   module.directive('uiGridEditDropdown',
12415     ['uiGridConstants', 'uiGridEditConstants',
12416       function (uiGridConstants, uiGridEditConstants) {
12417         return {
12418           scope: true,
12419           compile: function () {
12420             return {
12421               pre: function ($scope, $elm, $attrs) {
12422
12423               },
12424               post: function ($scope, $elm, $attrs) {
12425
12426                 //set focus at start of edit
12427                 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
12428                   $elm[0].focus();
12429                   $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
12430                   $elm.on('blur', function (evt) {
12431                     $scope.stopEdit(evt);
12432                   });
12433                 });
12434
12435                
12436                 $scope.stopEdit = function (evt) {
12437                   // no need to validate a dropdown - invalid values shouldn't be
12438                   // available in the list
12439                   $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
12440                 };
12441
12442                 $elm.on('keydown', function (evt) {
12443                   switch (evt.keyCode) {
12444                     case uiGridConstants.keymap.ESC:
12445                       evt.stopPropagation();
12446                       $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12447                       break;
12448                     case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
12449                       $scope.stopEdit(evt);
12450                       break;
12451                     case uiGridConstants.keymap.LEFT:
12452                       $scope.stopEdit(evt);
12453                       break;
12454                     case uiGridConstants.keymap.RIGHT:
12455                       $scope.stopEdit(evt);
12456                       break;
12457                     case uiGridConstants.keymap.UP:
12458                       evt.stopPropagation();
12459                       break;
12460                     case uiGridConstants.keymap.DOWN:
12461                       evt.stopPropagation();
12462                       break;
12463                     case uiGridConstants.keymap.TAB:
12464                       $scope.stopEdit(evt);
12465                       break;
12466                   }
12467                   return true;
12468                 });
12469               }
12470             };
12471           }
12472         };
12473       }]);    
12474
12475 })();
12476
12477 (function () {
12478   'use strict';
12479
12480   /**
12481    * @ngdoc overview
12482    * @name ui.grid.expandable
12483    * @description
12484    *
12485    *  # ui.grid.expandable
12486    * This module provides the ability to create subgrids with the ability to expand a row
12487    * to show the subgrid.
12488    *
12489    * <div doc-module-components="ui.grid.expandable"></div>
12490    */
12491   var module = angular.module('ui.grid.expandable', ['ui.grid']);
12492
12493   /**
12494    *  @ngdoc service
12495    *  @name ui.grid.edit.service:uiGridExpandableService
12496    *
12497    *  @description Services for the expandable grid
12498    */
12499   module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
12500     var service = {
12501       initializeGrid: function (grid) {
12502         
12503         /**
12504          *  @ngdoc object
12505          *  @name enableExpandable
12506          *  @propertyOf  ui.grid.expandable.api:GridOptions
12507          *  @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
12508          *  within your application, or in specific modes on _this_ grid. Defaults to true.  
12509          *  @example
12510          *  <pre>
12511          *    $scope.gridOptions = {
12512          *      enableExpandable: false
12513          *    }
12514          *  </pre>  
12515          */
12516         grid.options.enableExpandable = grid.options.enableExpandable !== false;
12517         
12518         /**
12519          *  @ngdoc object
12520          *  @name expandableRowHeight
12521          *  @propertyOf  ui.grid.expandable.api:GridOptions
12522          *  @description Height in pixels of the expanded subgrid.  Defaults to
12523          *  150
12524          *  @example
12525          *  <pre>
12526          *    $scope.gridOptions = {
12527          *      expandableRowHeight: 150
12528          *    }
12529          *  </pre>  
12530          */
12531         grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
12532
12533         /**
12534          *  @ngdoc object
12535          *  @name expandableRowTemplate
12536          *  @propertyOf  ui.grid.expandable.api:GridOptions
12537          *  @description Mandatory. The template for your expanded row
12538          *  @example
12539          *  <pre>
12540          *    $scope.gridOptions = {
12541          *      expandableRowTemplate: 'expandableRowTemplate.html'
12542          *    }
12543          *  </pre>  
12544          */
12545         if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
12546           gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
12547           grid.options.enableExpandable = false;
12548         }
12549
12550         /**
12551          *  @ngdoc object
12552          *  @name ui.grid.expandable.api:PublicApi
12553          *
12554          *  @description Public Api for expandable feature
12555          */
12556         /**
12557          *  @ngdoc object
12558          *  @name ui.grid.expandable.api:GridOptions
12559          *
12560          *  @description Options for configuring the expandable feature, these are available to be  
12561          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12562          */
12563
12564         var publicApi = {
12565           events: {
12566             expandable: {
12567               /**
12568                * @ngdoc event
12569                * @name rowExpandedStateChanged
12570                * @eventOf  ui.grid.expandable.api:PublicApi
12571                * @description raised when cell editing is complete
12572                * <pre>
12573                *      gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
12574                * </pre>
12575                * @param {GridRow} row the row that was expanded
12576                */
12577               rowExpandedStateChanged: function (scope, row) {
12578               }
12579             }
12580           },
12581           
12582           methods: {
12583             expandable: {
12584               /**
12585                * @ngdoc method
12586                * @name toggleRowExpansion
12587                * @methodOf  ui.grid.expandable.api:PublicApi
12588                * @description Toggle a specific row
12589                * <pre>
12590                *      gridApi.expandable.toggleRowExpansion(rowEntity);
12591                * </pre>
12592                * @param {object} rowEntity the data entity for the row you want to expand
12593                */              
12594               toggleRowExpansion: function (rowEntity) {
12595                 var row = grid.getRow(rowEntity);
12596                 if (row !== null) {
12597                   service.toggleRowExpansion(grid, row);
12598                 }
12599               },
12600
12601               /**
12602                * @ngdoc method
12603                * @name expandAllRows
12604                * @methodOf  ui.grid.expandable.api:PublicApi
12605                * @description Expand all subgrids.
12606                * <pre>
12607                *      gridApi.expandable.expandAllRows();
12608                * </pre>
12609                */              
12610               expandAllRows: function() {
12611                 service.expandAllRows(grid);
12612               },
12613
12614               /**
12615                * @ngdoc method
12616                * @name collapseAllRows
12617                * @methodOf  ui.grid.expandable.api:PublicApi
12618                * @description Collapse all subgrids.
12619                * <pre>
12620                *      gridApi.expandable.collapseAllRows();
12621                * </pre>
12622                */              
12623               collapseAllRows: function() {
12624                 service.collapseAllRows(grid);
12625               }
12626             }
12627           }
12628         };
12629         grid.api.registerEventsFromObject(publicApi.events);
12630         grid.api.registerMethodsFromObject(publicApi.methods);
12631       },
12632       
12633       toggleRowExpansion: function (grid, row) {
12634         row.isExpanded = !row.isExpanded;
12635
12636         if (row.isExpanded) {
12637           row.height = row.grid.options.rowHeight + grid.options.expandableRowHeight; 
12638         }
12639         else {
12640           row.height = row.grid.options.rowHeight;
12641         }
12642
12643         grid.api.expandable.raise.rowExpandedStateChanged(row);
12644       },
12645       
12646       expandAllRows: function(grid, $scope) {
12647         angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
12648           if (!row.isExpanded) {
12649             service.toggleRowExpansion(grid, row);
12650           }
12651         });
12652         grid.refresh();
12653       },
12654       
12655       collapseAllRows: function(grid) {
12656         angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
12657           if (row.isExpanded) {
12658             service.toggleRowExpansion(grid, row);
12659           }
12660         });
12661         grid.refresh();
12662       }
12663     };
12664     return service;
12665   }]);
12666
12667   /**
12668    *  @ngdoc object
12669    *  @name enableExpandableRowHeader
12670    *  @propertyOf  ui.grid.expandable.api:GridOptions
12671    *  @description Show a rowHeader to provide the expandable buttons.  If set to false then implies
12672    *  you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
12673    *  @example
12674    *  <pre>
12675    *    $scope.gridOptions = {
12676    *      enableExpandableRowHeader: false
12677    *    }
12678    *  </pre>  
12679    */
12680   module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
12681     function (uiGridExpandableService, $templateCache) {
12682       return {
12683         replace: true,
12684         priority: 0,
12685         require: '^uiGrid',
12686         scope: false,
12687         compile: function () {
12688           return {
12689             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12690               if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
12691                 var expandableRowHeaderColDef = {name: 'expandableButtons', width: 40};
12692                 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
12693                 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
12694               }
12695               uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
12696             },
12697             post: function ($scope, $elm, $attrs, uiGridCtrl) {
12698             }
12699           };
12700         }
12701       };
12702     }]);
12703
12704   module.directive('uiGridExpandableRow',
12705   ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
12706     function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
12707
12708       return {
12709         replace: false,
12710         priority: 0,
12711         scope: false,
12712
12713         compile: function () {
12714           return {
12715             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12716               gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
12717                 function (template) {
12718                   if ($scope.grid.options.expandableRowScope) {
12719                     var expandableRowScope = $scope.grid.options.expandableRowScope;
12720                     for (var property in expandableRowScope) {
12721                       if (expandableRowScope.hasOwnProperty(property)) {
12722                         $scope[property] = expandableRowScope[property];
12723                       }
12724                     }
12725                   }
12726                   var expandedRowElement = $compile(template)($scope);
12727                   $elm.append(expandedRowElement);
12728                   $scope.row.expandedRendered = true;
12729               });
12730             },
12731
12732             post: function ($scope, $elm, $attrs, uiGridCtrl) {
12733               $scope.$on('$destroy', function() {
12734                 $scope.row.expandedRendered = false;
12735               });
12736             }
12737           };
12738         }
12739       };
12740     }]);
12741
12742   module.directive('uiGridRow',
12743     ['$compile', 'gridUtil', '$templateCache',
12744       function ($compile, gridUtil, $templateCache) {
12745         return {
12746           priority: -200,
12747           scope: false,
12748           compile: function ($elm, $attrs) {
12749             return {
12750               pre: function ($scope, $elm, $attrs, controllers) {
12751
12752                 $scope.expandableRow = {};
12753
12754                 $scope.expandableRow.shouldRenderExpand = function () {
12755                   var ret = $scope.colContainer.name === 'body' &&  $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
12756                   return ret;
12757                 };
12758
12759                 $scope.expandableRow.shouldRenderFiller = function () {
12760                   var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
12761                   return ret;
12762                 };
12763
12764                   function updateRowContainerWidth() {
12765                       var grid = $scope.grid;
12766                       var colWidth = 0;
12767                       angular.forEach(grid.columns, function (column) {
12768                           if (column.renderContainer === 'left') {
12769                             colWidth += column.width;
12770                           }
12771                       });
12772                       colWidth = Math.floor(colWidth);
12773                       return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
12774                           ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
12775                           ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
12776                   }
12777
12778                   if ($scope.colContainer.name === 'left') {
12779                       $scope.grid.registerStyleComputation({
12780                           priority: 15,
12781                           func: updateRowContainerWidth
12782                       });
12783                   }
12784
12785               },
12786               post: function ($scope, $elm, $attrs, controllers) {
12787               }
12788             };
12789           }
12790         };
12791       }]);
12792
12793   module.directive('uiGridViewport',
12794     ['$compile', 'gridUtil', '$templateCache',
12795       function ($compile, gridUtil, $templateCache) {
12796         return {
12797           priority: -200,
12798           scope: false,
12799           compile: function ($elm, $attrs) {
12800             var rowRepeatDiv = angular.element($elm.children().children()[0]);
12801             var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
12802             var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
12803             rowRepeatDiv.append(expandedRowElement);
12804             rowRepeatDiv.append(expandedRowFillerElement);
12805             return {
12806               pre: function ($scope, $elm, $attrs, controllers) {
12807               },
12808               post: function ($scope, $elm, $attrs, controllers) {
12809               }
12810             };
12811           }
12812         };
12813       }]);
12814
12815 })();
12816
12817 /* global console */
12818
12819 (function () {
12820   'use strict';
12821
12822   /**
12823    * @ngdoc overview
12824    * @name ui.grid.exporter
12825    * @description
12826    *
12827    *  # ui.grid.exporter
12828    * This module provides the ability to exporter data from the grid.  
12829    * 
12830    * Data can be exported in a range of formats, and all data, visible 
12831    * data, or selected rows can be exported, with all columns or visible
12832    * columns.
12833    * 
12834    * No UI is provided, the caller should provide their own UI/buttons 
12835    * as appropriate, or enable the gridMenu
12836    * 
12837    * <br/>
12838    * <br/>
12839    *
12840    * <div doc-module-components="ui.grid.exporter"></div>
12841    */
12842
12843   var module = angular.module('ui.grid.exporter', ['ui.grid']);
12844
12845   /**
12846    *  @ngdoc object
12847    *  @name ui.grid.exporter.constant:uiGridExporterConstants
12848    *
12849    *  @description constants available in exporter module
12850    */
12851   /**
12852    * @ngdoc property
12853    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12854    * @name ALL
12855    * @description export all data, including data not visible.  Can
12856    * be set for either rowTypes or colTypes
12857    */
12858   /**
12859    * @ngdoc property
12860    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12861    * @name VISIBLE
12862    * @description export only visible data, including data not visible.  Can
12863    * be set for either rowTypes or colTypes
12864    */
12865   /**
12866    * @ngdoc property
12867    * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12868    * @name SELECTED
12869    * @description export all data, including data not visible.  Can
12870    * be set only for rowTypes, selection of only some columns is 
12871    * not supported
12872    */
12873   module.constant('uiGridExporterConstants', {
12874     featureName: 'exporter',
12875     ALL: 'all',
12876     VISIBLE: 'visible',
12877     SELECTED: 'selected',
12878     CSV_CONTENT: 'CSV_CONTENT',
12879     LINK_LABEL: 'LINK_LABEL',
12880     BUTTON_LABEL: 'BUTTON_LABEL'
12881   });
12882
12883   /**
12884    *  @ngdoc service
12885    *  @name ui.grid.exporter.service:uiGridExporterService
12886    *
12887    *  @description Services for exporter feature
12888    */
12889   module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'uiGridSelectionConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
12890     function ($q, uiGridExporterConstants, uiGridSelectionConstants, gridUtil, $compile, $interval, i18nService) {
12891
12892       var service = {
12893
12894         initializeGrid: function (grid) {
12895
12896           //add feature namespace and any properties to grid for needed state
12897           grid.exporter = {};
12898           this.defaultGridOptions(grid.options);
12899
12900           /**
12901            *  @ngdoc object
12902            *  @name ui.grid.exporter.api:PublicApi
12903            *
12904            *  @description Public Api for exporter feature
12905            */
12906           var publicApi = {
12907             events: {
12908               exporter: {
12909               }
12910             },
12911             methods: {
12912               exporter: {
12913                 /**
12914                  * @ngdoc function
12915                  * @name csvExport
12916                  * @methodOf  ui.grid.exporter.api:PublicApi
12917                  * @description Exports rows from the grid in csv format, 
12918                  * the data exported is selected based on the provided options
12919                  * @param {string} rowTypes which rows to export, valid values are
12920                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
12921                  * uiGridExporterConstants.SELECTED
12922                  * @param {string} colTypes which columns to export, valid values are
12923                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
12924                  * @param {element} $elm (Optional) A UI element into which the
12925                  * resulting download link will be placed. 
12926                  */
12927                 csvExport: function (rowTypes, colTypes, $elm) {
12928                   service.csvExport(grid, rowTypes, colTypes, $elm);
12929                 },
12930                 /**
12931                  * @ngdoc function
12932                  * @name pdfExport
12933                  * @methodOf  ui.grid.exporter.api:PublicApi
12934                  * @description Exports rows from the grid in pdf format, 
12935                  * the data exported is selected based on the provided options
12936                  * Note that this function has a dependency on pdfMake, all
12937                  * going well this has been installed for you.
12938                  * The resulting pdf opens in a new browser window.
12939                  * @param {string} rowTypes which rows to export, valid values are
12940                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
12941                  * uiGridExporterConstants.SELECTED
12942                  * @param {string} colTypes which columns to export, valid values are
12943                  * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
12944                  */
12945                 pdfExport: function (rowTypes, colTypes) {
12946                   service.pdfExport(grid, rowTypes, colTypes);
12947                 }
12948               }
12949             }
12950           };
12951
12952           grid.api.registerEventsFromObject(publicApi.events);
12953
12954           grid.api.registerMethodsFromObject(publicApi.methods);
12955           
12956           if (grid.api.core.addToGridMenu){
12957             service.addToMenu( grid );
12958           } else {
12959             // order of registration is not guaranteed, register in a little while
12960             $interval( function() {
12961               if (grid.api.core.addToGridMenu){
12962                 service.addToMenu( grid );
12963               }              
12964             }, 100, 1);
12965           }
12966
12967         },
12968
12969         defaultGridOptions: function (gridOptions) {
12970           //default option to true unless it was explicitly set to false
12971           /**
12972            * @ngdoc object
12973            * @name ui.grid.exporter.api:GridOptions
12974            *
12975            * @description GridOptions for selection feature, these are available to be  
12976            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12977            */
12978           /**
12979            * @ngdoc object
12980            * @name ui.grid.exporter.api:GridOptions.columnDef
12981            * @description ColumnDef settings for exporter
12982            */
12983           /**
12984            * @ngdoc object
12985            * @name exporterSuppressMenu
12986            * @propertyOf  ui.grid.exporter.api:GridOptions
12987            * @description Don't show the export menu button, implying the user
12988            * will roll their own UI for calling the exporter
12989            * <br/>Defaults to false
12990            */
12991           gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
12992           /**
12993            * @ngdoc object
12994            * @name exporterLinkTemplate
12995            * @propertyOf  ui.grid.exporter.api:GridOptions
12996            * @description A custom template to use for the resulting
12997            * link (for csv export)
12998            * <br/>Defaults to ui-grid/csvLink
12999            */
13000           gridOptions.exporterLinkTemplate = gridOptions.exporterLinkTemplate ? gridOptions.exporterLinkTemplate : 'ui-grid/csvLink';
13001           /**
13002            * @ngdoc object
13003            * @name exporterHeaderTemplate
13004            * @propertyOf  ui.grid.exporter.api:GridOptions
13005            * @description A custom template to use for the header
13006            * section, containing the button and csv download link.  Not
13007            * needed if you've set suppressButton and are providing a custom
13008            * $elm into which the download link will go.
13009            * <br/>Defaults to ui-grid/exporterHeader
13010            */
13011           gridOptions.exporterHeaderTemplate = gridOptions.exporterHeaderTemplate ? gridOptions.exporterHeaderTemplate : 'ui-grid/exporterHeader';
13012           /**
13013            * @ngdoc object
13014            * @name exporterLinkLabel
13015            * @propertyOf  ui.grid.exporter.api:GridOptions
13016            * @description The text to show on the CSV download
13017            * link
13018            * <br/>Defaults to 'Download CSV'
13019            */
13020           gridOptions.exporterLinkLabel = gridOptions.exporterLinkLabel ? gridOptions.exporterLinkLabel : 'Download CSV';
13021           /**
13022            * @ngdoc object
13023            * @name exporterMenuLabel
13024            * @propertyOf  ui.grid.exporter.api:GridOptions
13025            * @description The text to show on the exporter menu button
13026            * link
13027            * <br/>Defaults to 'Export'
13028            */
13029           gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
13030           /**
13031            * @ngdoc object
13032            * @name exporterSuppressColumns
13033            * @propertyOf  ui.grid.exporter.api:GridOptions
13034            * @description Columns that should not be exported.  The selectionRowHeader is already automatically
13035            * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
13036            * output then add it in this list.  You should provide an array of column names.
13037            * <br/>Defaults to: []
13038            * <pre>
13039            *   gridOptions.exporterSuppressColumns = [ 'buttons' ];
13040            * </pre>
13041            */
13042           gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
13043           /**
13044            * @ngdoc object
13045            * @name exporterCsvColumnSeparator
13046            * @propertyOf  ui.grid.exporter.api:GridOptions
13047            * @description The character to use as column separator
13048            * link
13049            * <br/>Defaults to ','
13050            */
13051           gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
13052           /**
13053            * @ngdoc object
13054            * @name exporterPdfDefaultStyle
13055            * @propertyOf  ui.grid.exporter.api:GridOptions
13056            * @description The default style in pdfMake format
13057            * <br/>Defaults to:
13058            * <pre>
13059            *   {
13060            *     fontSize: 11
13061            *   }
13062            * </pre>
13063            */
13064           gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
13065           /**
13066            * @ngdoc object
13067            * @name exporterPdfTableStyle
13068            * @propertyOf  ui.grid.exporter.api:GridOptions
13069            * @description The table style in pdfMake format
13070            * <br/>Defaults to:
13071            * <pre>
13072            *   {
13073            *     margin: [0, 5, 0, 15]
13074            *   }
13075            * </pre>
13076            */
13077           gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
13078           /**
13079            * @ngdoc object
13080            * @name exporterPdfTableHeaderStyle
13081            * @propertyOf  ui.grid.exporter.api:GridOptions
13082            * @description The tableHeader style in pdfMake format
13083            * <br/>Defaults to:
13084            * <pre>
13085            *   {
13086            *     bold: true,
13087            *     fontSize: 12,
13088            *     color: 'black'
13089            *   }
13090            * </pre>
13091            */
13092           gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
13093           /**
13094            * @ngdoc object
13095            * @name exporterPdfHeader
13096            * @propertyOf  ui.grid.exporter.api:GridOptions
13097            * @description The header section for pdf exports.  Can be
13098            * simple text:
13099            * <pre>
13100            *   gridOptions.exporterPdfHeader = 'My Header';
13101            * </pre>
13102            * Can be a more complex object in pdfMake format:
13103            * <pre>
13104            *   gridOptions.exporterPdfHeader = {
13105            *     columns: [
13106            *       'Left part',
13107            *       { text: 'Right part', alignment: 'right' }
13108            *     ]
13109            *   };
13110            * </pre>
13111            * Or can be a function, allowing page numbers and the like
13112            * <pre>
13113            *   gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
13114            * </pre>
13115            */
13116           gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
13117           /**
13118            * @ngdoc object
13119            * @name exporterPdfFooter
13120            * @propertyOf  ui.grid.exporter.api:GridOptions
13121            * @description The header section for pdf exports.  Can be
13122            * simple text:
13123            * <pre>
13124            *   gridOptions.exporterPdfFooter = 'My Footer';
13125            * </pre>
13126            * Can be a more complex object in pdfMake format:
13127            * <pre>
13128            *   gridOptions.exporterPdfFooter = {
13129            *     columns: [
13130            *       'Left part',
13131            *       { text: 'Right part', alignment: 'right' }
13132            *     ]
13133            *   };
13134            * </pre>
13135            * Or can be a function, allowing page numbers and the like
13136            * <pre>
13137            *   gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
13138            * </pre>
13139            */
13140           gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
13141           /**
13142            * @ngdoc object
13143            * @name exporterPdfOrientation
13144            * @propertyOf  ui.grid.exporter.api:GridOptions
13145            * @description The orientation, should be a valid pdfMake value,
13146            * 'landscape' or 'portrait'
13147            * <br/>Defaults to landscape
13148            */
13149           gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
13150           /**
13151            * @ngdoc object
13152            * @name exporterPdfPageSize
13153            * @propertyOf  ui.grid.exporter.api:GridOptions
13154            * @description The orientation, should be a valid pdfMake
13155            * paper size, usually 'A4' or 'LETTER'
13156            * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
13157            * <br/>Defaults to A4
13158            */
13159           gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
13160           /**
13161            * @ngdoc object
13162            * @name exporterPdfMaxGridWidth
13163            * @propertyOf  ui.grid.exporter.api:GridOptions
13164            * @description The maxium grid width - the current grid width 
13165            * will be scaled to match this, with any fixed width columns
13166            * being adjusted accordingly.
13167            * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER 
13168            */
13169           gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
13170           /**
13171            * @ngdoc object
13172            * @name exporterPdfTableLayout
13173            * @propertyOf  ui.grid.exporter.api:GridOptions
13174            * @description A tableLayout in pdfMake format, 
13175            * controls gridlines and the like.  We use the default
13176            * layout usually.
13177            * <br/>Defaults to null, which means no layout 
13178            */
13179
13180           /**
13181            * @ngdoc object
13182            * @name exporterMenuCsv
13183            * @propertyOf  ui.grid.exporter.api:GridOptions
13184            * @description Add csv export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
13185            */
13186           gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
13187
13188           /**
13189            * @ngdoc object
13190            * @name exporterMenuPdf
13191            * @propertyOf  ui.grid.exporter.api:GridOptions
13192            * @description Add pdf export menu items to the ui-grid grid menu, if it's present.  Defaults to true.
13193            */
13194           gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
13195           
13196           /**
13197            * @ngdoc object
13198            * @name exporterPdfCustomFormatter
13199            * @propertyOf  ui.grid.exporter.api:GridOptions
13200            * @description A custom callback routine that changes the pdf document, adding any
13201            * custom styling or content that is supported by pdfMake.  Takes in the complete docDefinition, and
13202            * must return an updated docDefinition ready for pdfMake.
13203            * @example
13204            * In this example we add a style to the style array, so that we can use it in our
13205            * footer definition.
13206            * <pre>
13207            *   gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
13208            *     docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
13209            *     return docDefinition;
13210            *   }
13211            * 
13212            *   gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
13213            * </pre>
13214            */
13215           gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
13216           
13217           /**
13218            * @ngdoc object
13219            * @name exporterHeaderFilter
13220            * @propertyOf  ui.grid.exporter.api:GridOptions
13221            * @description A function to apply to the header displayNames before exporting.  Useful for internationalisation,
13222            * for example if you were using angular-translate you'd set this to `$translate.instant`.  Note that this
13223            * call must be synchronous, it cannot be a call that returns a promise.
13224            * @example
13225            * <pre>
13226            *   gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + displayName; };
13227            * </pre>
13228            * OR
13229            * <pre>
13230            *   gridOptions.exporterHeaderFilter = $translate.instant;
13231            * </pre>
13232            */
13233
13234           /**
13235            * @ngdoc function
13236            * @name exporterFieldCallback
13237            * @propertyOf  ui.grid.exporter.api:GridOptions
13238            * @description A function to call for each field before exporting it.  Allows 
13239            * massaging of raw data into a display format, for example if you have applied 
13240            * filters to convert codes into decodes, or you require
13241            * a specific date format in the exported content.
13242            * 
13243            * The method is called once for each field exported, and provides the grid, the
13244            * gridCol and the GridRow for you to use as context in massaging the data.
13245            * 
13246            * Note that the format of the passed in value is along the lines of:
13247            * <pre>
13248            *   { value: 'cellValue', alignment: 'left' }
13249            * </pre>
13250            * 
13251            * Your returned value needs to follow that format.
13252            * 
13253            * @param {Grid} grid provides the grid in case you have need of it
13254            * @param {GridRow} row the row from which the data comes
13255            * @param {GridCol} col the column from which the data comes
13256            * @param {object} value the value for your massaging
13257            * @returns {object} you must return the massaged value ready for exporting
13258            * 
13259            * @example
13260            * <pre>
13261            *   gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
13262            *     if ( col.name === 'status' ){
13263            *       value = { value: decodeStatus( value ) };
13264            *     }
13265            *     return value;
13266            *   }
13267            * </pre>
13268            */
13269           gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
13270         },
13271
13272
13273         /**
13274          * @ngdoc function
13275          * @name addToMenu
13276          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13277          * @description Adds export items to the grid menu,
13278          * allowing the user to select export options 
13279          * @param {Grid} grid the grid from which data should be exported
13280          */
13281         addToMenu: function ( grid ) {
13282           grid.api.core.addToGridMenu( grid, [
13283             {
13284               title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
13285               action: function ($event) {
13286                 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
13287               },
13288               shown: function() {
13289                 return this.grid.options.exporterMenuCsv; 
13290               }
13291             },
13292             {
13293               title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
13294               action: function ($event) {
13295                 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
13296               },
13297               shown: function() {
13298                 return this.grid.options.exporterMenuCsv; 
13299               }
13300             },
13301             {
13302               title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
13303               action: function ($event) {
13304                 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
13305               },
13306               shown: function() {
13307                 return this.grid.options.exporterMenuCsv &&
13308                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 ); 
13309               }
13310             },
13311             {
13312               title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
13313               action: function ($event) {
13314                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
13315               },
13316               shown: function() {
13317                 return this.grid.options.exporterMenuPdf; 
13318               }
13319             },
13320             {
13321               title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
13322               action: function ($event) {
13323                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
13324               },
13325               shown: function() {
13326                 return this.grid.options.exporterMenuPdf; 
13327               }
13328             },
13329             {
13330               title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
13331               action: function ($event) {
13332                 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
13333               },
13334               shown: function() {
13335                 return this.grid.options.exporterMenuPdf &&
13336                        ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 ); 
13337               }
13338             }
13339           ]);
13340         },
13341         
13342
13343         /**
13344          * @ngdoc object
13345          * @name exporterCsvLinkElement
13346          * @propertyOf  ui.grid.exporter.api:GridOptions
13347          * @description The element that the csv link should be placed into.
13348          * Mandatory if using the native UI.
13349          */
13350         /**
13351          * @ngdoc function
13352          * @name csvExport
13353          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13354          * @description Exports rows from the grid in csv format, 
13355          * the data exported is selected based on the provided options
13356          * @param {Grid} grid the grid from which data should be exported
13357          * @param {string} rowTypes which rows to export, valid values are
13358          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13359          * uiGridExporterConstants.SELECTED
13360          * @param {string} colTypes which columns to export, valid values are
13361          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13362          * uiGridExporterConstants.SELECTED
13363          * @param {element} $elm (Optional) A UI element into which the
13364          * resulting download link will be placed. 
13365          */
13366         csvExport: function (grid, rowTypes, colTypes, $elm) {
13367           var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
13368           var exportData = this.getData(grid, rowTypes, colTypes);
13369           var csvContent = this.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
13370           
13371           if ( !$elm && grid.options.exporterCsvLinkElement ){
13372             $elm = grid.options.exporterCsvLinkElement;
13373           }
13374           
13375           if ( $elm ){
13376             this.renderCsvLink(grid, csvContent, $elm);
13377           } else {
13378             gridUtil.logError( 'Exporter asked to export as csv, but no element provided.  Perhaps you should set gridOptions.exporterCsvLinkElement?')
13379 ;          }
13380         },
13381         
13382         
13383         /**
13384          * @ngdoc function
13385          * @name getColumnHeaders
13386          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13387          * @description Gets the column headers from the grid to use
13388          * as a title row for the exported file, all headers have 
13389          * headerCellFilters applied as appropriate.
13390          * 
13391          * Column headers are an array of objects, each object has
13392          * name, displayName, width and align attributes.  Only name is
13393          * used for csv, all attributes are used for pdf.
13394          * 
13395          * @param {Grid} grid the grid from which data should be exported
13396          * @param {string} colTypes which columns to export, valid values are
13397          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13398          * uiGridExporterConstants.SELECTED
13399          */
13400         getColumnHeaders: function (grid, colTypes) {
13401           var headers = [];
13402           angular.forEach(grid.columns, function( gridCol, index ) {
13403             if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) && 
13404                  gridCol.name !== uiGridSelectionConstants.selectionRowHeaderColName &&
13405                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
13406               headers.push({
13407                 name: gridCol.field,
13408                 displayName: grid.options.exporterHeaderFilter ? grid.options.exporterHeaderFilter(gridCol.displayName) : gridCol.displayName,
13409                 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
13410                 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
13411               });
13412             }
13413           });
13414           
13415           return headers;
13416         },
13417         
13418         
13419         /** 
13420          * @ngdoc property
13421          * @propertyOf ui.grid.exporter.api:GridOptions.columnDef
13422          * @name exporterPdfAlign
13423          * @description the alignment you'd like for this specific column when
13424          * exported into a pdf.  Can be 'left', 'right', 'center' or any other
13425          * valid pdfMake alignment option.
13426          */
13427         /**
13428          * @ngdoc function
13429          * @name getData
13430          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13431          * @description Gets data from the grid based on the provided options,
13432          * all cells have cellFilters applied as appropriate
13433          * @param {Grid} grid the grid from which data should be exported
13434          * @param {string} rowTypes which rows to export, valid values are
13435          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13436          * uiGridExporterConstants.SELECTED
13437          * @param {string} colTypes which columns to export, valid values are
13438          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13439          * uiGridExporterConstants.SELECTED
13440          */
13441         getData: function (grid, rowTypes, colTypes) {
13442           var data = [];
13443           
13444           var rows;
13445           
13446           switch ( rowTypes ) {
13447             case uiGridExporterConstants.ALL:
13448               rows = grid.rows; 
13449               break;
13450             case uiGridExporterConstants.VISIBLE:
13451               rows = grid.getVisibleRows();
13452               break;
13453             case uiGridExporterConstants.SELECTED:
13454               if ( grid.api.selection ){
13455                 rows = grid.api.selection.getSelectedGridRows();
13456               } else {
13457                 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
13458               }
13459               break;
13460           }
13461           
13462           angular.forEach(rows, function( row, index ) {
13463
13464             var extractedRow = [];
13465             angular.forEach(grid.columns, function( gridCol, index ) {
13466             if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) && 
13467                  gridCol.name !== uiGridSelectionConstants.selectionRowHeaderColName &&
13468                  grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
13469                 var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, grid.getCellValue( row, gridCol ) ) };
13470                 if ( gridCol.colDef.exporterPdfAlign ) {
13471                   extractedField.alignment = gridCol.colDef.exporterPdfAlign;                 
13472                 }
13473                 extractedRow.push(extractedField);
13474               }
13475             });
13476             
13477             data.push(extractedRow);
13478           });
13479           
13480           return data;
13481         },
13482
13483
13484         /**
13485          * @ngdoc function
13486          * @name formatAsCSV
13487          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13488          * @description Formats the column headers and data as a CSV, 
13489          * and sends that data to the user
13490          * @param {array} exportColumnHeaders an array of column headers, 
13491          * where each header is an object with name, width and maybe alignment
13492          * @param {array} exportData an array of rows, where each row is
13493          * an array of column data
13494          * @returns {string} csv the formatted csv as a string
13495          */
13496         formatAsCsv: function (exportColumnHeaders, exportData, separator) {
13497           var self = this;
13498           
13499           var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
13500           
13501           var csv = self.formatRowAsCsv(this, separator)(bareHeaders) + '\n';
13502           
13503           csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
13504           
13505           return csv;
13506         },
13507
13508         /**
13509          * @ngdoc function
13510          * @name formatRowAsCsv
13511          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13512          * @description Renders a single field as a csv field, including
13513          * quotes around the value
13514          * @param {exporterService} exporter pass in exporter 
13515          * @param {array} row the row to be turned into a csv string
13516          * @returns {string} a csv-ified version of the row
13517          */
13518         formatRowAsCsv: function (exporter, separator) {
13519           return function (row) {
13520             return row.map(exporter.formatFieldAsCsv).join(separator);
13521           };
13522         },
13523         
13524         /**
13525          * @ngdoc function
13526          * @name formatFieldAsCsv
13527          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13528          * @description Renders a single field as a csv field, including
13529          * quotes around the value
13530          * @param {field} field the field to be turned into a csv string,
13531          * may be of any type
13532          * @returns {string} a csv-ified version of the field
13533          */
13534         formatFieldAsCsv: function (field) {
13535           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
13536             return '';
13537           }
13538           if (typeof(field.value) === 'number') {
13539             return field.value;
13540           }
13541           if (typeof(field.value) === 'boolean') {
13542             return (field.value ? 'TRUE' : 'FALSE') ;
13543           }
13544           if (typeof(field.value) === 'string') {
13545             return '"' + field.value.replace(/"/g,'""') + '"';
13546           }
13547
13548           return JSON.stringify(field.value);        
13549         },
13550
13551         /**
13552          * @ngdoc function
13553          * @name renderCsvLink
13554          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13555          * @description Creates a download link with the csv content, 
13556          * putting it into the default exporter element, or into the element
13557          * passed in if provided
13558          * @param {Grid} grid the grid from which data should be exported
13559          * @param {string} csvContent the csv content that we'd like to 
13560          * make available as a download link
13561          * @param {element} $elm (Optional) A UI element into which the
13562          * resulting download link will be placed.  If not provided, the
13563          * link is put into the default exporter element. 
13564          */
13565         renderCsvLink: function (grid, csvContent, $elm) {
13566           var targetElm = $elm ? $elm : angular.element( grid.exporter.gridElm[0].querySelectorAll('.ui-grid-exporter-csv-link') );
13567           if ( angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')) ) {
13568             angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')).remove();
13569           }
13570           
13571           var linkTemplate = gridUtil.getTemplate(grid.options.exporterLinkTemplate)
13572           .then(function (contents) {
13573
13574               var template = angular.element(contents);
13575
13576               template.children("a").html(
13577                   template.children("a").html().replace(
13578                       uiGridExporterConstants.LINK_LABEL, grid.options.exporterLinkLabel));
13579
13580               template.children("a").attr("href", 
13581                   template.children("a").attr("href").replace(
13582                       uiGridExporterConstants.CSV_CONTENT, encodeURIComponent(csvContent)));
13583             
13584             var newElm = $compile(template)(grid.exporter.$scope);
13585             targetElm.append(newElm);
13586           });
13587           
13588         },
13589         
13590         /**
13591          * @ngdoc function
13592          * @name pdfExport
13593          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13594          * @description Exports rows from the grid in pdf format, 
13595          * the data exported is selected based on the provided options.
13596          * Note that this function has a dependency on pdfMake, which must
13597          * be installed.  The resulting pdf opens in a new
13598          * browser window.
13599          * @param {Grid} grid the grid from which data should be exported
13600          * @param {string} rowTypes which rows to export, valid values are
13601          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13602          * uiGridExporterConstants.SELECTED
13603          * @param {string} colTypes which columns to export, valid values are
13604          * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13605          * uiGridExporterConstants.SELECTED
13606          */
13607         pdfExport: function (grid, rowTypes, colTypes) {
13608           var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
13609           var exportData = this.getData(grid, rowTypes, colTypes);
13610           var docDefinition = this.prepareAsPdf(grid, exportColumnHeaders, exportData);
13611           
13612           pdfMake.createPdf(docDefinition).open();
13613         },
13614         
13615         
13616         /**
13617          * @ngdoc function
13618          * @name renderAsPdf
13619          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13620          * @description Renders the data into a pdf, and opens that pdf.
13621          * 
13622          * @param {Grid} grid the grid from which data should be exported
13623          * @param {array} exportColumnHeaders an array of column headers, 
13624          * where each header is an object with name, width and maybe alignment
13625          * @param {array} exportData an array of rows, where each row is
13626          * an array of column data
13627          * @returns {object} a pdfMake format document definition, ready 
13628          * for generation
13629          */        
13630         prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
13631           var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
13632           
13633           var headerColumns = exportColumnHeaders.map( function( header ) {
13634             return { text: header.displayName, style: 'tableHeader' }; 
13635           });
13636           
13637           var stringData = exportData.map(this.formatRowAsPdf(this));
13638           
13639           var allData = [headerColumns].concat(stringData);
13640           
13641           var docDefinition = {
13642             pageOrientation: grid.options.exporterPdfOrientation,
13643             pageSize: grid.options.exporterPdfPageSize,
13644             content: [{
13645               style: 'tableStyle',
13646               table: {
13647                 headerRows: 1,
13648                 widths: headerWidths,
13649                 body: allData 
13650               }
13651             }],
13652             styles: {
13653               tableStyle: grid.options.exporterPdfTableStyle,
13654               tableHeader: grid.options.exporterPdfTableHeaderStyle
13655             },
13656             defaultStyle: grid.options.exporterPdfDefaultStyle
13657           };
13658           
13659           if ( grid.options.exporterPdfLayout ){
13660             docDefinition.layout = grid.options.exporterPdfLayout;
13661           }
13662           
13663           if ( grid.options.exporterPdfHeader ){
13664             docDefinition.content.unshift( grid.options.exporterPdfHeader );
13665           }
13666           
13667           if ( grid.options.exporterPdfFooter ){
13668             docDefinition.content.push( grid.options.exporterPdfFooter );
13669           }
13670           
13671           if ( grid.options.exporterPdfCustomFormatter ){
13672             docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
13673           }
13674           return docDefinition;
13675           
13676         },
13677         
13678                 
13679         /**
13680          * @ngdoc function
13681          * @name calculatePdfHeaderWidths
13682          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13683          * @description Determines the column widths base on the 
13684          * widths we got from the grid.  If the column is drawn
13685          * then we have a drawnWidth.  If the column is not visible
13686          * then we have '*', 'x%' or a width.  When columns are
13687          * not visible they don't contribute to the overall gridWidth,
13688          * so we need to adjust to allow for extra columns
13689          * 
13690          * Our basic heuristic is to take the current gridWidth, plus 
13691          * numeric columns and call this the base gridwidth.
13692          * 
13693          * To that we add 100 for any '*' column, and x% of the base gridWidth
13694          * for any column that is a %
13695          *  
13696          * @param {Grid} grid the grid from which data should be exported
13697          * @param {object} exportHeaders array of header information 
13698          * @returns {object} an array of header widths
13699          */
13700         calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
13701           var baseGridWidth = 0;
13702           angular.forEach(exportHeaders, function(value){
13703             if (typeof(value.width) === 'number'){
13704               baseGridWidth += value.width;
13705             }
13706           });
13707           
13708           var extraColumns = 0;
13709           angular.forEach(exportHeaders, function(value){
13710             if (value.width === '*'){
13711               extraColumns += 100;
13712             }
13713             if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
13714               var percent = parseInt(value.width.match(/(\d)*%/)[0]);
13715               
13716               value.width = baseGridWidth * percent / 100;
13717               extraColumns += value.width;
13718             }
13719           });
13720           
13721           var gridWidth = baseGridWidth + extraColumns;
13722           
13723           return exportHeaders.map(function( header ) {
13724             return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
13725           });
13726           
13727         },
13728         
13729         /**
13730          * @ngdoc function
13731          * @name formatRowAsPdf
13732          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13733          * @description Renders a row in a format consumable by PDF,
13734          * mainly meaning casting everything to a string
13735          * @param {exporterService} exporter pass in exporter 
13736          * @param {array} row the row to be turned into a csv string
13737          * @returns {string} a csv-ified version of the row
13738          */
13739         formatRowAsPdf: function ( exporter ) {
13740           return function( row ) {
13741             return row.map(exporter.formatFieldAsPdfString);
13742           };
13743         },
13744         
13745         
13746         /**
13747          * @ngdoc function
13748          * @name formatFieldAsCsv
13749          * @methodOf  ui.grid.exporter.service:uiGridExporterService
13750          * @description Renders a single field as a pdf-able field, which
13751          * is different from a csv field only in that strings don't have quotes
13752          * around them
13753          * @param {field} field the field to be turned into a pdf string,
13754          * may be of any type
13755          * @returns {string} a string-ified version of the field
13756          */
13757         formatFieldAsPdfString: function (field) {
13758           var returnVal;
13759           if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
13760             returnVal = '';
13761           } else if (typeof(field.value) === 'number') {
13762             returnVal = field.value.toString();
13763           } else if (typeof(field.value) === 'boolean') {
13764             returnVal = (field.value ? 'TRUE' : 'FALSE') ;
13765           } else if (typeof(field.value) === 'string') {
13766             returnVal = field.value.replace(/"/g,'""');
13767           } else {
13768             returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');        
13769           }
13770           
13771           if (field.alignment && typeof(field.alignment) === 'string' ){
13772             returnVal = { text: returnVal, alignment: field.alignment };
13773           }
13774           
13775           return returnVal;
13776         }
13777       };
13778
13779       return service;
13780
13781     }
13782   ]);
13783
13784   /**
13785    *  @ngdoc directive
13786    *  @name ui.grid.exporter.directive:uiGridExporter
13787    *  @element div
13788    *  @restrict A
13789    *
13790    *  @description Adds exporter features to grid
13791    *
13792    *  @example
13793    <example module="app">
13794    <file name="app.js">
13795    var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
13796
13797    app.controller('MainCtrl', ['$scope', function ($scope) {
13798       $scope.data = [
13799         { name: 'Bob', title: 'CEO' },
13800             { name: 'Frank', title: 'Lowly Developer' }
13801       ];
13802
13803       $scope.gridOptions = {
13804         enableGridMenu: true,
13805         exporterMenuCsv: false,
13806         columnDefs: [
13807           {name: 'name', enableCellEdit: true},
13808           {name: 'title', enableCellEdit: true}
13809         ],
13810         data: $scope.data
13811       };
13812     }]);
13813    </file>
13814    <file name="index.html">
13815    <div ng-controller="MainCtrl">
13816    <div ui-grid="gridOptions" ui-grid-exporter></div>
13817    </div>
13818    </file>
13819    </example>
13820    */
13821   module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
13822     function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
13823       return {
13824         replace: true,
13825         priority: 0,
13826         require: '^uiGrid',
13827         scope: false,
13828         link: function ($scope, $elm, $attrs, uiGridCtrl) {
13829           uiGridExporterService.initializeGrid(uiGridCtrl.grid);
13830           uiGridCtrl.grid.exporter.$scope = $scope;
13831         }
13832       };
13833     }
13834   ]);
13835 })();
13836
13837 (function () {
13838   'use strict';
13839
13840   /**
13841    * @ngdoc overview
13842    * @name ui.grid.importer
13843    * @description
13844    *
13845    *  # ui.grid.importer
13846    * This module provides the ability to import data into the grid. It
13847    * uses the column defs to work out which data belongs in which column, 
13848    * and creates entities from a configured class (typically a $resource).
13849    * 
13850    * If the rowEdit feature is enabled, it also calls save on those newly 
13851    * created objects, and then displays any errors in the imported data.  
13852    * 
13853    * Currently the importer imports only CSV and json files, although provision has been
13854    * made to process other file formats, and these can be added over time.  
13855    * 
13856    * For json files, the properties within each object in the json must match the column names
13857    * (to put it another way, the importer doesn't process the json, it just copies the objects
13858    * within the json into a new instance of the specified object type)
13859    * 
13860    * For CSV import, the default column identification relies on each column in the
13861    * header row matching a column.name or column.displayName. Optionally, a column identification 
13862    * callback can be used.  This allows matching using other attributes, which is particularly
13863    * useful if your application has internationalised column headings (i.e. the headings that 
13864    * the user sees don't match the column names).
13865    * 
13866    * The importer makes use of the grid menu as the UI for requesting an
13867    * import. 
13868    *
13869    * <div ui-grid-importer></div>
13870    */
13871
13872   var module = angular.module('ui.grid.importer', ['ui.grid']);
13873
13874   /**
13875    *  @ngdoc object
13876    *  @name ui.grid.importer.constant:uiGridImporterConstants
13877    *
13878    *  @description constants available in importer module
13879    */
13880
13881   module.constant('uiGridImporterConstants', {
13882     featureName: 'importer'
13883   });
13884
13885   /**
13886    *  @ngdoc service
13887    *  @name ui.grid.importer.service:uiGridImporterService
13888    *
13889    *  @description Services for importer feature
13890    */
13891   module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
13892     function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
13893
13894       var service = {
13895
13896         initializeGrid: function ($scope, grid) {
13897
13898           //add feature namespace and any properties to grid for needed state
13899           grid.importer = {
13900             $scope: $scope 
13901           };
13902           
13903           this.defaultGridOptions(grid.options);
13904
13905           /**
13906            *  @ngdoc object
13907            *  @name ui.grid.importer.api:PublicApi
13908            *
13909            *  @description Public Api for importer feature
13910            */
13911           var publicApi = {
13912             events: {
13913               importer: {
13914               }
13915             },
13916             methods: {
13917               importer: {
13918                 /**
13919                  * @ngdoc function
13920                  * @name importFile
13921                  * @methodOf  ui.grid.importer.api:PublicApi
13922                  * @description Imports a file into the grid using the file object 
13923                  * provided.  Bypasses the grid menu
13924                  * @param {Grid} grid the grid we're importing into
13925                  * @param {File} fileObject the file we want to import, as a javascript
13926                  * File object
13927                  */
13928                 importFile: function ( grid, fileObject ) {
13929                   service.importFile( grid, fileObject );
13930                 }
13931               }
13932             }
13933           };
13934
13935           grid.api.registerEventsFromObject(publicApi.events);
13936
13937           grid.api.registerMethodsFromObject(publicApi.methods);
13938
13939           if ( grid.options.enableImporter && grid.options.importerShowMenu ){
13940             if ( grid.api.core.addToGridMenu ){
13941               service.addToMenu( grid );
13942             } else {
13943               // order of registration is not guaranteed, register in a little while
13944               $interval( function() {
13945                 if (grid.api.core.addToGridMenu){
13946                   service.addToMenu( grid );
13947                 }             
13948               }, 100, 1);
13949             }
13950           }
13951         },
13952         
13953
13954         defaultGridOptions: function (gridOptions) {
13955           //default option to true unless it was explicitly set to false
13956           /**
13957            * @ngdoc object
13958            * @name ui.grid.importer.api:GridOptions
13959            *
13960            * @description GridOptions for importer feature, these are available to be  
13961            * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
13962            */
13963
13964           /**
13965            * @ngdoc property
13966            * @propertyOf ui.grid.importer.api:GridOptions
13967            * @name enableImporter
13968            * @description Whether or not importer is enabled.  Automatically set
13969            * to false if the user's browser does not support the required fileApi.
13970            * Otherwise defaults to true.
13971            * 
13972            */
13973           if (gridOptions.enableImporter  || gridOptions.enableImporter === undefined) {
13974             if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
13975               gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
13976               gridOptions.enableImporter = false;
13977             } else {
13978               gridOptions.enableImporter = true;
13979             }
13980           } else {
13981             gridOptions.enableImporter = false;
13982           }
13983           
13984           /**
13985            * @ngdoc method
13986            * @name importerProcessHeaders
13987            * @methodOf ui.grid.importer.api:GridOptions
13988            * @description A callback function that will process headers using custom
13989            * logic.  Set this callback function if the headers that your user will provide in their 
13990            * import file don't necessarily match the grid header or field names.  This might commonly
13991            * occur where your application is internationalised, and therefore the field names
13992            * that the user recognises are in a different language than the field names that
13993            * ui-grid knows about.
13994            * 
13995            * Defaults to the internal `processHeaders` method, which seeks to match using both
13996            * displayName and column.name.  Any non-matching columns are discarded.
13997            * 
13998            * Your callback routine should respond by processing the header array, and returning an array
13999            * of matching column names.  A null value in any given position means "don't import this column"
14000            * 
14001            * <pre>
14002            *      gridOptions.importerProcessHeaders: function( headerArray ) {
14003            *        var myHeaderColumns = [];
14004            *        var thisCol;
14005            *        headerArray.forEach( function( value, index ) {
14006            *          thisCol = mySpecialLookupFunction( value );
14007            *          myHeaderColumns.push( thisCol.name ); 
14008            *        });
14009            *        
14010            *        return myHeaderCols;
14011            *      })
14012            * </pre>
14013            * @param {Grid} grid the grid we're importing into
14014            * @param {array} headerArray an array of the text from the first row of the csv file,
14015            * which you need to match to column.names
14016            * @returns {array} array of matching column names, in the same order as the headerArray
14017            * 
14018            */
14019           gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
14020
14021           /**
14022            * @ngdoc method
14023            * @name importerHeaderFilter
14024            * @methodOf ui.grid.importer.api:GridOptions
14025            * @description A callback function that will filter (usually translate) a single
14026            * header.  Used when you want to match the passed in column names to the column
14027            * displayName after the header filter.
14028            * 
14029            * Your callback routine needs to return the filtered header value. 
14030            * <pre>
14031            *      gridOptions.importerHeaderFilter: function( displayName ) {
14032            *        return $translate.instant( displayName );
14033            *      })
14034            * </pre>
14035            * 
14036            * or:
14037            * <pre>
14038            *      gridOptions.importerHeaderFilter: $translate.instant
14039            * </pre>
14040            * @param {string} displayName the displayName that we'd like to translate
14041            * @returns {string} the translated name
14042            * 
14043            */
14044           gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
14045
14046           /**
14047            * @ngdoc method
14048            * @name importerErrorCallback
14049            * @methodOf ui.grid.importer.api:GridOptions
14050            * @description A callback function that provides custom error handling, rather
14051            * than the standard grid behaviour of an alert box and a console message.  You 
14052            * might use this to internationalise the console log messages, or to write to a 
14053            * custom logging routine that returned errors to the server.
14054            * 
14055            * <pre>
14056            *      gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
14057            *        myUserDisplayRoutine( errorKey );
14058            *        myLoggingRoutine( consoleMessage, context );
14059            *      })
14060            * </pre>
14061            * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
14062            * in some way
14063            * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders, 
14064            * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
14065            * @param {string} consoleMessage the English console message that importer would have written
14066            * @param {object} context the context data that importer would have appended to that console message,
14067            * often the file content itself or the element that is in error
14068            * 
14069            */
14070           if ( !gridOptions.importerErrorCallback ||  typeof(gridOptions.importerErrorCallback) !== 'function' ){
14071             delete gridOptions.importerErrorCallback;  
14072           }
14073
14074           /**
14075            * @ngdoc method
14076            * @name importerDataAddCallback
14077            * @methodOf ui.grid.importer.api:GridOptions
14078            * @description A mandatory callback function that adds data to the source data array.  The grid
14079            * generally doesn't add rows to the source data array, it is tidier to handle this through a user
14080            * callback.
14081            * 
14082            * <pre>
14083            *      gridOptions.importerDataAddCallback: function( grid, newObjects ) {
14084            *        $scope.myData = $scope.myData.concat( newObjects );
14085            *      })
14086            * </pre>
14087            * @param {Grid} grid the grid we're importing into, may be useful in some way
14088            * @param {array} newObjects an array of new objects that you should add to your data
14089            * 
14090            */
14091           if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
14092             gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
14093             gridOptions.enableImporter = false;
14094           }
14095                     
14096           /**
14097            * @ngdoc object
14098            * @name importerNewObject
14099            * @propertyOf  ui.grid.importer.api:GridOptions
14100            * @description An object on which we call `new` to create each new row before inserting it into
14101            * the data array.  Typically this would be a $resource entity, which means that if you're using 
14102            * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
14103            * 
14104            * Defaults to a vanilla javascript object
14105            * 
14106            * @example
14107            * <pre>
14108            *   gridOptions.importerNewObject = MyRes;
14109            * </pre>
14110            * 
14111            */
14112
14113           /**
14114            * @ngdoc property
14115            * @propertyOf ui.grid.importer.api:GridOptions
14116            * @name importerShowMenu
14117            * @description Whether or not to show an item in the grid menu.  Defaults to true.
14118            * 
14119            */
14120           gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
14121           
14122           /**
14123            * @ngdoc method
14124            * @methodOf ui.grid.importer.api:GridOptions
14125            * @name importerObjectCallback
14126            * @description A callback that massages the data for each object.  For example,
14127            * you might have data stored as a code value, but display the decode.  This callback
14128            * can be used to change the decoded value back into a code.  Defaults to doing nothing.
14129            * @param {Grid} grid in case you need it
14130            * @param {object} newObject the new object as importer has created it, modify it
14131            * then return the modified version
14132            * @returns {object} the modified object
14133            * @example
14134            * <pre>
14135            *   gridOptions.importerObjectCallback = function ( grid, newObject ) {
14136            *     switch newObject.status {
14137            *       case 'Active':
14138            *         newObject.status = 1;
14139            *         break;
14140            *       case 'Inactive':
14141            *         newObject.status = 2;
14142            *         break;
14143            *     }
14144            *     return newObject;
14145            *   };
14146            * </pre>
14147            */
14148           gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
14149         },
14150
14151
14152         /**
14153          * @ngdoc function
14154          * @name addToMenu
14155          * @methodOf  ui.grid.importer.service:uiGridImporterService
14156          * @description Adds import menu item to the grid menu,
14157          * allowing the user to request import of a file 
14158          * @param {Grid} grid the grid into which data should be imported
14159          */
14160         addToMenu: function ( grid ) {
14161           grid.api.core.addToGridMenu( grid, [
14162             {
14163               title: i18nService.getSafeText('gridMenu.importerTitle')
14164             },
14165             {
14166               templateUrl: 'ui-grid/importerMenuItemContainer',
14167               action: function ($event) {
14168                 this.grid.api.importer.importAFile( grid );
14169               }
14170             }
14171           ]);
14172         },
14173         
14174         
14175         /**
14176          * @ngdoc function
14177          * @name importThisFile
14178          * @methodOf ui.grid.importer.service:uiGridImporterService
14179          * @description Imports the provided file into the grid using the file object 
14180          * provided.  Bypasses the grid menu
14181          * @param {Grid} grid the grid we're importing into
14182          * @param {File} fileObject the file we want to import, as returned from the File
14183          * javascript object
14184          */
14185         importThisFile: function ( grid, fileObject ) {
14186           if (!fileObject){
14187             gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
14188             return;
14189           }
14190           
14191           var reader = new FileReader();
14192           
14193           switch ( fileObject.type ){
14194             case 'application/json':
14195               reader.onload = service.importJsonClosure( grid );
14196               break;
14197             default:
14198               reader.onload = service.importCsvClosure( grid );
14199               break;
14200           }
14201           
14202           reader.readAsText( fileObject );
14203         },
14204         
14205         
14206         /**
14207          * @ngdoc function
14208          * @name importJson
14209          * @methodOf ui.grid.importer.service:uiGridImporterService
14210          * @description Creates a function that imports a json file into the grid.
14211          * The json data is imported into new objects of type `gridOptions.importerNewObject`,
14212          * and ift he rowEdit feature is enabled the rows are marked as dirty
14213          * @param {Grid} grid the grid we want to import into
14214          * @param {FileObject} importFile the file that we want to import, as 
14215          * a FileObject
14216          */
14217         importJsonClosure: function( grid ) {
14218           return function( importFile ){
14219             var newObjects = [];
14220             var newObject;
14221             
14222             angular.forEach( service.parseJson( grid, importFile ), function( value, index ) {
14223               newObject = service.newObject( grid );
14224               angular.extend( newObject, value );
14225               newObject = grid.options.importerObjectCallback( grid, newObject );
14226               newObjects.push( newObject );
14227             });
14228             
14229             service.addObjects( grid, newObjects );
14230             
14231           };
14232         },
14233
14234
14235         /**
14236          * @ngdoc function
14237          * @name parseJson
14238          * @methodOf ui.grid.importer.service:uiGridImporterService
14239          * @description Parses a json file, returns the parsed data.
14240          * Displays an error if file doesn't parse
14241          * @param {Grid} grid the grid that we want to import into 
14242          * @param {FileObject} importFile the file that we want to import, as 
14243          * a FileObject
14244          * @returns {array} array of objects from the imported json
14245          */
14246         parseJson: function( grid, importFile ){
14247           var loadedObjects;
14248           try {
14249             loadedObjects = JSON.parse( importFile.target.result );
14250           } catch (e) {
14251             service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
14252             return;
14253           }
14254           
14255           if ( !Array.isArray( loadedObjects ) ){
14256             service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
14257             return [];
14258           } else {
14259             return loadedObjects;
14260           }
14261         },
14262         
14263         
14264         
14265         /**
14266          * @ngdoc function
14267          * @name importCsvClosure
14268          * @methodOf ui.grid.importer.service:uiGridImporterService
14269          * @description Creates a function that imports a csv file into the grid
14270          * (allowing it to be used in the reader.onload event)
14271          * @param {Grid} grid the grid that we want to import into 
14272          * @param {FileObject} importFile the file that we want to import, as 
14273          * a file object
14274          */
14275         importCsvClosure: function( grid ) {
14276           return function( importFile ){
14277             var importArray = service.parseCsv( importFile );
14278             if ( !importArray || importArray.length < 1 ){ 
14279               service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
14280               return; 
14281             }
14282             
14283             var newObjects = service.createCsvObjects( grid, importArray );
14284             if ( !newObjects || newObjects.length === 0 ){
14285               service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
14286               return;
14287             }
14288             
14289             service.addObjects( grid, newObjects );
14290           };
14291         },
14292         
14293         
14294         /**
14295          * @ngdoc function
14296          * @name parseCsv
14297          * @methodOf ui.grid.importer.service:uiGridImporterService
14298          * @description Parses a csv file into an array of arrays, with the first
14299          * array being the headers, and the remaining arrays being the data.
14300          * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js, 
14301          * which is noted as being under the MIT license.  The code is modified to pass the jscs yoda condition
14302          * checker
14303          * @param {FileObject} importFile the file that we want to import, as a 
14304          * file object
14305          */
14306         parseCsv: function( importFile ) {
14307           var csv = importFile.target.result;
14308           
14309           // use the CSV-JS library to parse
14310           return CSV.parse(csv);
14311         },
14312         
14313
14314         /**
14315          * @ngdoc function
14316          * @name createCsvObjects
14317          * @methodOf ui.grid.importer.service:uiGridImporterService
14318          * @description Converts an array of arrays (representing the csv file)
14319          * into a set of objects.  Uses the provided `gridOptions.importerNewObject`
14320          * to create the objects, and maps the header row into the individual columns 
14321          * using either `gridOptions.importerProcessHeaders`, or by using a native method
14322          * of matching to either the displayName, column name or column field of
14323          * the columns in the column defs.  The resulting objects will have attributes
14324          * that are named based on the column.field or column.name, in that order.
14325          * @param {Grid} grid the grid that we want to import into 
14326          * @param {FileObject} importFile the file that we want to import, as a 
14327          * file object
14328          */
14329         createCsvObjects: function( grid, importArray ){
14330           // pull off header row and turn into headers
14331           var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
14332           if ( !headerMapping || headerMapping.length === 0 ){
14333             service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
14334             return [];
14335           }
14336           
14337           var newObjects = [];
14338           var newObject;
14339           angular.forEach( importArray, function( row, index ) {
14340             newObject = service.newObject( grid );
14341             angular.forEach( row, function( field, index ){
14342               if ( headerMapping[index] !== null ){
14343                 newObject[ headerMapping[index] ] = field;
14344               }
14345             });
14346             newObject = grid.options.importerObjectCallback( grid, newObject );
14347             newObjects.push( newObject );
14348           });
14349           
14350           return newObjects;
14351         },
14352         
14353         
14354         /**
14355          * @ngdoc function
14356          * @name processHeaders
14357          * @methodOf ui.grid.importer.service:uiGridImporterService
14358          * @description Determines the columns that the header row from
14359          * a csv (or other) file represents.
14360          * @param {Grid} grid the grid we're importing into
14361          * @param {array} headerRow the header row that we wish to match against
14362          * the column definitions
14363          * @returns {array} an array of the attribute names that should be used
14364          * for that column, based on matching the headers or creating the headers
14365          * 
14366          */
14367         processHeaders: function( grid, headerRow ) {
14368           var headers = [];
14369           if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
14370             // we are going to create new columnDefs for all these columns, so just remove
14371             // spaces from the names to create fields
14372             angular.forEach( headerRow, function( value, index ) {
14373               headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
14374             });
14375             return headers;
14376           } else {
14377             var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
14378             angular.forEach( headerRow, function( value, index ) {
14379               if ( lookupHash[value] ) {
14380                 headers.push( lookupHash[value] );
14381               } else if ( lookupHash[ value.toLowerCase() ] ) {
14382                 headers.push( lookupHash[ value.toLowerCase() ] );
14383               } else {
14384                 headers.push( null );
14385               }
14386             });
14387             return headers;
14388           }
14389         },
14390         
14391         
14392         /**
14393          * @name flattenColumnDefs
14394          * @methodOf ui.grid.importer.service:uiGridImporterService
14395          * @description Runs through the column defs and creates a hash of
14396          * the displayName, name and field, and of each of those values forced to lower case,
14397          * with each pointing to the field or name
14398          * (whichever is present).  Used to lookup column headers and decide what 
14399          * attribute name to give to the resulting field. 
14400          * @param {Grid} grid the grid we're importing into
14401          * @param {array} columnDefs the columnDefs that we should flatten
14402          * @returns {hash} the flattened version of the column def information, allowing
14403          * us to look up a value by `flattenedHash[ headerValue ]`
14404          */
14405         flattenColumnDefs: function( grid, columnDefs ){
14406           var flattenedHash = {};
14407           angular.forEach( columnDefs, function( columnDef, index) {
14408             if ( columnDef.name ){
14409               flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
14410               flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
14411             }
14412             
14413             if ( columnDef.field ){
14414               flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
14415               flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
14416             }
14417             
14418             if ( columnDef.displayName ){
14419               flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
14420               flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
14421             }
14422             
14423             if ( columnDef.displayName && grid.options.importerHeaderFilter ){
14424               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
14425               flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
14426             }
14427           });
14428           
14429           return flattenedHash;
14430         },
14431         
14432         
14433         /**
14434          * @ngdoc function
14435          * @name addObjects
14436          * @methodOf ui.grid.importer.service:uiGridImporterService
14437          * @description Inserts our new objects into the grid data, and
14438          * sets the rows to dirty if the rowEdit feature is being used
14439          * 
14440          * Does this by registering a watch on dataChanges, which essentially
14441          * is waiting on the result of the grid data watch, and downstream processing.
14442          * 
14443          * When the callback is called, it deregisters itself - we don't want to run
14444          * again next time data is added.
14445          * 
14446          * If we never get called, we deregister on destroy.
14447          * 
14448          * @param {Grid} grid the grid we're importing into
14449          * @param {array} newObjects the objects we want to insert into the grid data
14450          * @returns {object} the new object
14451          */
14452         addObjects: function( grid, newObjects, $scope ){
14453           if ( grid.api.rowEdit ){
14454             var callbackId = grid.registerDataChangeCallback( function() {
14455               grid.api.rowEdit.setRowsDirty( grid, newObjects );
14456               grid.deregisterDataChangeCallback( callbackId );
14457             }, [uiGridConstants.dataChange.ROW] );
14458             
14459             var deregisterClosure = function() {
14460               grid.deregisterDataChangeCallback( callbackId );
14461             };
14462   
14463             grid.importer.$scope.$on( '$destroy', deregisterClosure );
14464           }
14465
14466           grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
14467           
14468         },
14469         
14470         
14471         /**
14472          * @ngdoc function
14473          * @name newObject
14474          * @methodOf ui.grid.importer.service:uiGridImporterService
14475          * @description Makes a new object based on `gridOptions.importerNewObject`,
14476          * or based on an empty object if not present
14477          * @param {Grid} grid the grid we're importing into
14478          * @returns {object} the new object
14479          */
14480         newObject: function( grid ){
14481           if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
14482             return new grid.options.importerNewObject();
14483           } else {
14484             return {};
14485           }
14486         },
14487         
14488         
14489         /**
14490          * @ngdoc function
14491          * @name alertError
14492          * @methodOf ui.grid.importer.service:uiGridImporterService
14493          * @description Provides an internationalised user alert for the failure,
14494          * and logs a console message including diagnostic content.
14495          * Optionally, if the the `gridOptions.importerErrorCallback` routine
14496          * is defined, then calls that instead, allowing user specified error routines
14497          * @param {Grid} grid the grid we're importing into
14498          * @param {array} headerRow the header row that we wish to match against
14499          * the column definitions
14500          */
14501         alertError: function( grid, alertI18nToken, consoleMessage, context ){
14502           if ( grid.options.importerErrorCallback ){
14503             grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
14504           } else {
14505             $window.alert(i18nService.getSafeText( alertI18nToken )); 
14506             gridUtil.logError(consoleMessage + context ); 
14507           }
14508         }
14509       };
14510
14511       return service;
14512
14513     }
14514   ]);
14515
14516   /**
14517    *  @ngdoc directive
14518    *  @name ui.grid.importer.directive:uiGridImporter
14519    *  @element div
14520    *  @restrict A
14521    *
14522    *  @description Adds importer features to grid
14523    *
14524    */
14525   module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
14526     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
14527       return {
14528         replace: true,
14529         priority: 0,
14530         require: '^uiGrid',
14531         scope: false,
14532         link: function ($scope, $elm, $attrs, uiGridCtrl) {
14533           uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
14534         }
14535       };
14536     }
14537   ]);
14538   
14539   /**
14540    *  @ngdoc directive
14541    *  @name ui.grid.importer.directive:uiGridImporterMenuItem
14542    *  @element div
14543    *  @restrict A
14544    *
14545    *  @description Handles the processing from the importer menu item - once a file is
14546    *  selected
14547    *
14548    */
14549   module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
14550     function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
14551       return {
14552         replace: true,
14553         priority: 0,
14554         require: '^uiGrid',
14555         scope: false,
14556         templateUrl: 'ui-grid/importerMenuItem',
14557         link: function ($scope, $elm, $attrs, uiGridCtrl) {
14558           var handleFileSelect = function( event ){
14559             if (event.srcElement.files.length === 1) {
14560               var fileObject = event.srcElement.files[0];
14561               uiGridImporterService.importThisFile( grid, fileObject );
14562               event.srcElement.form.reset();
14563             }
14564           };
14565
14566           var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
14567           var grid = uiGridCtrl.grid;
14568           
14569           if ( fileChooser.length !== 1 ){
14570             gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
14571           } else {
14572             fileChooser[0].addEventListener('change', handleFileSelect, false);  // TODO: why the false on the end?  Google  
14573           }
14574         }
14575       };
14576     }
14577   ]);  
14578 })();
14579 (function() {
14580   'use strict';
14581   /**
14582    *  @ngdoc overview
14583    *  @name ui.grid.infiniteScroll
14584    *
14585    *  @description
14586    *
14587    *   #ui.grid.infiniteScroll
14588    * This module provides infinite scroll functionality to ui-grid
14589    *
14590    */
14591   var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
14592   /**
14593    *  @ngdoc service
14594    *  @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14595    *
14596    *  @description Service for infinite scroll features
14597    */
14598   module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', function (gridUtil, $compile, $timeout) {
14599
14600     var service = {
14601
14602       /**
14603        * @ngdoc function
14604        * @name initializeGrid
14605        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14606        * @description This method register events and methods into grid public API
14607        */
14608
14609       initializeGrid: function(grid) {
14610         service.defaultGridOptions(grid.options);
14611
14612         /**
14613          *  @ngdoc object
14614          *  @name ui.grid.infiniteScroll.api:PublicAPI
14615          *
14616          *  @description Public API for infinite scroll feature
14617          */
14618         var publicApi = {
14619           events: {
14620             infiniteScroll: {
14621
14622               /**
14623                * @ngdoc event
14624                * @name needLoadMoreData
14625                * @eventOf ui.grid.infiniteScroll.api:PublicAPI
14626                * @description This event fires when scroll reached bottom percentage of grid
14627                * and needs to load data
14628                */
14629
14630               needLoadMoreData: function ($scope, fn) {
14631               }
14632             }
14633           },
14634           methods: {
14635             infiniteScroll: {
14636
14637               /**
14638                * @ngdoc function
14639                * @name dataLoaded
14640                * @methodOf ui.grid.infiniteScroll.api:PublicAPI
14641                * @description This function is used as a promise when data finished loading.
14642                * See infinite_scroll ngdoc for example of usage
14643                */
14644
14645               dataLoaded: function() {
14646                 grid.options.loadTimout = false;
14647               }
14648             }
14649           }
14650         };
14651         grid.options.loadTimout = false;
14652         grid.api.registerEventsFromObject(publicApi.events);
14653         grid.api.registerMethodsFromObject(publicApi.methods);
14654       },
14655       defaultGridOptions: function (gridOptions) {
14656         //default option to true unless it was explicitly set to false
14657         /**
14658          *  @ngdoc object
14659          *  @name ui.grid.infiniteScroll.api:GridOptions
14660          *
14661          *  @description GridOptions for infinite scroll feature, these are available to be
14662          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14663          */
14664
14665         /**
14666          *  @ngdoc object
14667          *  @name enableInfiniteScroll
14668          *  @propertyOf  ui.grid.infiniteScroll.api:GridOptions
14669          *  @description Enable infinite scrolling for this grid
14670          *  <br/>Defaults to true
14671          */
14672         gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
14673       },
14674
14675
14676       /**
14677        * @ngdoc function
14678        * @name loadData
14679        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14680        * @description This function fires 'needLoadMoreData' event
14681        */
14682
14683       loadData: function (grid) {
14684                 grid.options.loadTimout = true;
14685         grid.api.infiniteScroll.raise.needLoadMoreData();        
14686       },
14687
14688       /**
14689        * @ngdoc function
14690        * @name checkScroll
14691        * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14692        * @description This function checks scroll position inside grid and
14693        * calls 'loadData' function when scroll reaches 'infiniteScrollPercentage'
14694        */
14695
14696       checkScroll: function(grid, scrollTop) {
14697
14698         /* Take infiniteScrollPercentage value or use 20% as default */
14699         var infiniteScrollPercentage = grid.options.infiniteScrollPercentage ? grid.options.infiniteScrollPercentage : 20;
14700
14701         if (!grid.options.loadTimout && scrollTop <= infiniteScrollPercentage) {
14702           this.loadData(grid);
14703           return true;
14704         }
14705         return false;
14706       }
14707       /**
14708        * @ngdoc property
14709        * @name infiniteScrollPercentage
14710        * @propertyOf ui.grid.class:GridOptions
14711        * @description This setting controls at what percentage of the scroll more data
14712        * is requested by the infinite scroll
14713        */
14714     };
14715     return service;
14716   }]);
14717   /**
14718    *  @ngdoc directive
14719    *  @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
14720    *  @element div
14721    *  @restrict A
14722    *
14723    *  @description Adds infinite scroll features to grid
14724    *
14725    *  @example
14726    <example module="app">
14727    <file name="app.js">
14728    var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
14729
14730    app.controller('MainCtrl', ['$scope', function ($scope) {
14731       $scope.data = [
14732         { name: 'Alex', car: 'Toyota' },
14733             { name: 'Sam', car: 'Lexus' }
14734       ];
14735
14736       $scope.columnDefs = [
14737         {name: 'name'},
14738         {name: 'car'}
14739       ];
14740     }]);
14741    </file>
14742    <file name="index.html">
14743    <div ng-controller="MainCtrl">
14744    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
14745    </div>
14746    </file>
14747    </example>
14748    */
14749
14750   module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
14751     function (uiGridInfiniteScrollService) {
14752       return {
14753         priority: -200,
14754         scope: false,
14755         require: '^uiGrid',
14756         compile: function($scope, $elm, $attr){
14757           return {
14758             pre: function($scope, $elm, $attr, uiGridCtrl) {
14759               uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid);
14760             },
14761             post: function($scope, $elm, $attr) {
14762             }
14763           };
14764         }
14765       };
14766     }]);
14767
14768   module.directive('uiGridViewport',
14769     ['$compile', 'gridUtil', 'uiGridInfiniteScrollService', 'uiGridConstants',
14770       function ($compile, gridUtil, uiGridInfiniteScrollService, uiGridConstants) {
14771         return {
14772           priority: -200,
14773           scope: false,
14774           link: function ($scope, $elm, $attr){
14775             if ($scope.grid.options.enableInfiniteScroll) {
14776               $scope.$on(uiGridConstants.events.GRID_SCROLL, function (evt, args) {
14777                 if (args.y) {
14778                   var percentage = 100 - (args.y.percentage * 100);
14779                   uiGridInfiniteScrollService.checkScroll($scope.grid, percentage);
14780                 }
14781               });
14782             }
14783           }
14784         };
14785       }]);
14786 })();
14787 (function () {
14788   'use strict';
14789
14790   /**
14791    * @ngdoc overview
14792    * @name ui.grid.moveColumns
14793    * @description
14794    * # ui.grid.moveColumns
14795    * This module provides column moving capability to ui.grid. It enables to change the position of columns.
14796    * <div doc-module-components="ui.grid.moveColumns"></div>
14797    */
14798   var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
14799
14800   /**
14801    *  @ngdoc service
14802    *  @name ui.grid.moveColumns.service:uiGridMoveColumnService
14803    *  @description Service for column moving feature.
14804    */
14805   module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', function ($q, $timeout, $log) {
14806
14807     var service = {
14808       initializeGrid: function (grid) {
14809         var self = this;
14810         this.registerPublicApi(grid);
14811         this.defaultGridOptions(grid.options);
14812         grid.registerColumnBuilder(self.movableColumnBuilder);
14813       },
14814       registerPublicApi: function (grid) {
14815         var self = this;
14816         /**
14817          *  @ngdoc object
14818          *  @name ui.grid.moveColumns.api:PublicApi
14819          *  @description Public Api for column moving feature.
14820          */
14821         var publicApi = {
14822           events: {
14823             /**
14824              * @ngdoc event
14825              * @name columnPositionChanged
14826              * @eventOf  ui.grid.moveColumns.api:PublicApi
14827              * @description raised when column is moved
14828              * <pre>
14829              *      gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
14830              * </pre>
14831              * @param {object} colDef the column that was moved
14832              * @param {integer} originalPosition of the column
14833              * @param {integer} finalPosition of the column
14834              */
14835             colMovable: {
14836               columnPositionChanged: function (colDef, originalPosition, newPosition) {
14837               }
14838             }
14839           },
14840           methods: {
14841             /**
14842              * @ngdoc method
14843              * @name moveColumn
14844              * @methodOf  ui.grid.moveColumns.api:PublicApi
14845              * @description Method can be used to change column position.
14846              * <pre>
14847              *      gridApi.colMovable.on.moveColumn(oldPosition, newPosition)
14848              * </pre>
14849              * @param {integer} originalPosition of the column
14850              * @param {integer} finalPosition of the column
14851              */
14852             colMovable: {
14853               moveColumn: function (originalPosition, finalPosition) {
14854                 self.redrawColumnAtPosition(grid, originalPosition, finalPosition);
14855               }
14856             }
14857           }
14858         };
14859         grid.api.registerEventsFromObject(publicApi.events);
14860         grid.api.registerMethodsFromObject(publicApi.methods);
14861       },
14862       defaultGridOptions: function (gridOptions) {
14863         /**
14864          *  @ngdoc object
14865          *  @name ui.grid.moveColumns.api:GridOptions
14866          *
14867          *  @description Options for configuring the move column feature, these are available to be
14868          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14869          */
14870         /**
14871          *  @ngdoc object
14872          *  @name enableColumnMoving
14873          *  @propertyOf  ui.grid.moveColumns.api:GridOptions
14874          *  @description If defined, sets the default value for the colMovable flag on each individual colDefs
14875          *  if their individual enableColumnMoving configuration is not defined. Defaults to true.
14876          */
14877         gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
14878       },
14879       movableColumnBuilder: function (colDef, col, gridOptions) {
14880         var promises = [];
14881         /**
14882          *  @ngdoc object
14883          *  @name ui.grid.moveColumns.api:ColumnDef
14884          *
14885          *  @description Column Definition for move column feature, these are available to be
14886          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
14887          */
14888         /**
14889          *  @ngdoc object
14890          *  @name enableColumnMoving
14891          *  @propertyOf  ui.grid.moveColumns.api:ColumnDef
14892          *  @description Enable column moving for the column.
14893          */
14894         colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
14895           : colDef.enableColumnMoving;
14896         return $q.all(promises);
14897       },
14898       redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
14899         var columns = grid.columns;
14900
14901         //Function to find column position for a render index, ths is needed to take care of
14902         // invisible columns and row headers
14903         var findPositionForRenderIndex = function (index) {
14904           var position = index;
14905           for (var i = 0; i <= position; i++) {
14906             if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false)) {
14907               position++;
14908             }
14909           }
14910           return position;
14911         };
14912
14913         originalPosition = findPositionForRenderIndex(originalPosition);
14914         newPosition = findPositionForRenderIndex(newPosition);
14915         var originalColumn = columns[originalPosition];
14916         if (originalColumn.colDef.enableColumnMoving) {
14917           if (originalPosition > newPosition) {
14918             for (var i1 = originalPosition; i1 > newPosition; i1--) {
14919               columns[i1] = columns[i1 - 1];
14920             }
14921           }
14922           else if (newPosition > originalPosition) {
14923             for (var i2 = originalPosition; i2 < newPosition; i2++) {
14924               columns[i2] = columns[i2 + 1];
14925             }
14926           }
14927           columns[newPosition] = originalColumn;
14928           $timeout(function () {
14929             grid.refresh();
14930             grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
14931           });
14932         }
14933       }
14934     };
14935     return service;
14936   }]);
14937
14938   /**
14939    *  @ngdoc directive
14940    *  @name ui.grid.moveColumns.directive:uiGridMoveColumns
14941    *  @element div
14942    *  @restrict A
14943    *  @description Adds column moving features to the ui-grid directive.
14944    *  @example
14945    <example module="app">
14946    <file name="app.js">
14947    var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
14948    app.controller('MainCtrl', ['$scope', function ($scope) {
14949         $scope.data = [
14950           { name: 'Bob', title: 'CEO', age: 45 },
14951           { name: 'Frank', title: 'Lowly Developer', age: 25 },
14952           { name: 'Jenny', title: 'Highly Developer', age: 35 }
14953         ];
14954         $scope.columnDefs = [
14955           {name: 'name'},
14956           {name: 'title'},
14957           {name: 'age'}
14958         ];
14959       }]);
14960    </file>
14961    <file name="main.css">
14962    .grid {
14963       width: 100%;
14964       height: 150px;
14965     }
14966    </file>
14967    <file name="index.html">
14968    <div ng-controller="MainCtrl">
14969    <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
14970    </div>
14971    </file>
14972    </example>
14973    */
14974   module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
14975     return {
14976       replace: true,
14977       priority: 0,
14978       require: '^uiGrid',
14979       scope: false,
14980       compile: function () {
14981         return {
14982           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14983             uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
14984           },
14985           post: function ($scope, $elm, $attrs, uiGridCtrl) {
14986           }
14987         };
14988       }
14989     };
14990   }]);
14991
14992   /**
14993    *  @ngdoc directive
14994    *  @name ui.grid.moveColumns.directive:uiGridHeaderCell
14995    *  @element div
14996    *  @restrict A
14997    *
14998    *  @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
14999    *
15000    *  On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
15001    *  In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
15002    *  On mouseUp event column is repositioned at position where mouse is released and coned header cell is removed.
15003    *
15004    *  Events that invoke cloning of header cell:
15005    *    - mousedown
15006    *
15007    *  Events that invoke movement of cloned header cell:
15008    *    - mousemove
15009    *
15010    *  Events that invoke repositioning of column:
15011    *    - mouseup
15012    */
15013   module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document',
15014     function ($q, gridUtil, uiGridMoveColumnService, $document) {
15015       return {
15016         priority: -10,
15017         require: '^uiGrid',
15018         compile: function () {
15019           return {
15020             post: function ($scope, $elm, $attrs, uiGridCtrl) {
15021               if ($scope.col.colDef.enableColumnMoving) {
15022
15023                 var mouseDownHandler = function (evt) {
15024                   if (evt.target.className !== 'ui-grid-icon-angle-down' && evt.target.tagName !== 'I') {
15025
15026                     //Cloning header cell and appending to current header cell.
15027                     var movingElm = $elm.clone();
15028                     $elm.append(movingElm);
15029
15030                     //Left of cloned element should be aligned to original header cell.
15031                     movingElm.addClass('movingColumn');
15032                     var movingElementStyles = {};
15033                     var gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
15034                     var elmLeft = $elm[0].getBoundingClientRect().left;
15035                     movingElementStyles.left = (elmLeft - gridLeft) + 'px';
15036                     var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
15037                     var elmRight = $elm[0].getBoundingClientRect().right;
15038                     var reducedWidth;
15039                     if (elmRight > gridRight) {
15040                       reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
15041                       movingElementStyles.width = reducedWidth + 'px';
15042                     }
15043                     //movingElementStyles.visibility = 'hidden';
15044                     movingElm.css(movingElementStyles);
15045
15046                     //Setting some variables required for calculations.
15047                     var previousMouseX = evt.pageX;
15048                     var totalMouseMovement = 0;
15049                     var rightMoveLimit = gridLeft + $scope.grid.getViewportWidth() - $scope.grid.verticalScrollbarWidth;
15050
15051                     //Clone element should move horizontally with mouse.
15052                     var mouseMoveHandler = function (evt) {
15053                       uiGridCtrl.fireEvent('hide-menu');
15054                       var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
15055                       var currentElmRight = movingElm[0].getBoundingClientRect().right;
15056                       var changeValue = evt.pageX - previousMouseX;
15057                       var newElementLeft;
15058                       if (gridUtil.detectBrowser() === 'ie') {
15059                         newElementLeft = currentElmLeft + changeValue;
15060                       }
15061                       else {
15062                         newElementLeft = currentElmLeft - gridLeft + changeValue;
15063                       }
15064                       newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
15065                       if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
15066                         movingElm.css({visibility: 'visible', 'left': newElementLeft + 'px'});
15067                       }
15068                       else {
15069                         changeValue *= 5;
15070                         uiGridCtrl.fireScrollingEvent({ x: { pixels: changeValue * 2.5} });
15071                       }
15072                       totalMouseMovement += changeValue;
15073                       previousMouseX = evt.pageX;
15074                       if (reducedWidth < $scope.col.drawnWidth) {
15075                         reducedWidth += Math.abs(changeValue);
15076                         movingElm.css({'width': reducedWidth + 'px'});
15077                       }
15078                     };
15079
15080                     // On scope destroy, remove the mouse event handlers from the document body
15081                     $scope.$on('$destroy', function () {
15082                       $document.off('mousemove', mouseMoveHandler);
15083                       $document.off('mouseup', mouseUpHandler);
15084                     });
15085
15086                     $document.on('mousemove', mouseMoveHandler);
15087                     var mouseUpHandler = function (evt) {
15088                       var renderIndexDefer = $q.defer();
15089
15090                       var renderIndex;
15091                       $attrs.$observe('renderIndex', function (n, o) {
15092                         renderIndex = $scope.$eval(n);
15093                         renderIndexDefer.resolve();
15094                       });
15095
15096                       renderIndexDefer.promise.then(function () {
15097
15098                         //Remove the cloned element on mouse up.
15099                         if (movingElm) {
15100                           movingElm.remove();
15101                         }
15102
15103                         var renderedColumns = $scope.grid.renderContainers['body'].renderedColumns;
15104
15105                         //This method will calculate the number of columns hidden in lift due to scroll
15106                         //renderContainer.prevColumnScrollIndex could also have been used but this is more accurate
15107                         var scrolledColumnCount = 0;
15108                         var columns = $scope.grid.columns;
15109                         for (var i = 0; i < columns.length; i++) {
15110                           if (columns[i].colDef.name !== renderedColumns[0].colDef.name) {
15111                             scrolledColumnCount++;
15112                           }
15113                           else {
15114                             break;
15115                           }
15116                         }
15117
15118                         //Case where column should be moved to a position on its left
15119                         if (totalMouseMovement < 0) {
15120                           var totalColumnsLeftWidth = 0;
15121                           for (var il = renderIndex - 1; il >= 0; il--) {
15122                             totalColumnsLeftWidth += renderedColumns[il].drawnWidth;
15123                             if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
15124                               uiGridMoveColumnService.redrawColumnAtPosition
15125                               ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + il + 1);
15126                               break;
15127                             }
15128                           }
15129                           //Case where column should be moved to beginning of the grid.
15130                           if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
15131                             uiGridMoveColumnService.redrawColumnAtPosition
15132                             ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + 0);
15133                           }
15134                         }
15135                         //Case where column should be moved to a position on its right
15136                         else if (totalMouseMovement > 0) {
15137                           var totalColumnsRightWidth = 0;
15138                           for (var ir = renderIndex + 1; ir < renderedColumns.length; ir++) {
15139                             totalColumnsRightWidth += renderedColumns[ir].drawnWidth;
15140                             if (totalColumnsRightWidth > totalMouseMovement) {
15141                               uiGridMoveColumnService.redrawColumnAtPosition
15142                               ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + ir - 1);
15143                               break;
15144                             }
15145                           }
15146                           //Case where column should be moved to end of the grid.
15147                           if (totalColumnsRightWidth < totalMouseMovement) {
15148                             uiGridMoveColumnService.redrawColumnAtPosition
15149                             ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + renderedColumns.length - 1);
15150                           }
15151                         }
15152                         else if (totalMouseMovement === 0) {
15153                           if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
15154                             //sort the current column
15155                             var add = false;
15156                             if (evt.shiftKey) {
15157                               add = true;
15158                             }
15159
15160                             // Sort this column then rebuild the grid's rows
15161                             uiGridCtrl.grid.sortColumn($scope.col, add)
15162                               .then(function () {
15163                                 if (uiGridCtrl.columnMenuScope) {
15164                                   uiGridCtrl.columnMenuScope.hideMenu();
15165                                 }
15166                                 uiGridCtrl.grid.refresh();
15167                               });
15168                           }
15169                         }
15170
15171                         $document.off('mousemove', mouseMoveHandler);
15172                         $document.off('mouseup', mouseUpHandler);
15173                       });
15174                     };
15175
15176                     $document.on('mouseup', mouseUpHandler);
15177                   }
15178                 };
15179
15180                 $elm.on('mousedown', mouseDownHandler);
15181               }
15182             }
15183           };
15184         }
15185       };
15186     }]);
15187 })();
15188
15189 (function () {
15190   'use strict';
15191
15192   /**
15193    * @ngdoc overview
15194    * @name ui.grid.pagination
15195    *
15196    * @description
15197    *
15198    * #ui.grid.pagination
15199    * This module provides pagination support to ui-grid
15200    */
15201   var module = angular.module('ui.grid.pagination', ['ui.grid']);
15202
15203   /**
15204    * @ngdoc service
15205    * @name ui.grid.pagination.service:uiGridPaginationService
15206    *
15207    * @description Service for the pagination feature
15208    */
15209   module.service('uiGridPaginationService', function () {
15210     var service = {
15211
15212       /**
15213        * @ngdoc method
15214        * @name initializeGrid
15215        * @methodOf ui.grid.pagination.service:uiGridPaginationService
15216        * @description Attaches the service to a certain grid
15217        * @param {Grid} grid The grid we want to work with
15218        */
15219       initializeGrid: function (grid) {
15220         service.defaultGridOptions(grid.options);
15221         grid.pagination = {page: 1, totalPages: 1};
15222
15223         /**
15224          * @ngdoc object
15225          * @name ui.grid.pagination.api:PublicAPI
15226          *
15227          * @description Public API for the pagination feature
15228          */
15229         var publicApi = {
15230           methods: {
15231             pagination: {
15232               /**
15233                * @ngdoc method
15234                * @name getPage
15235                * @methodOf ui.grid.pagination.api:PublicAPI
15236                * @description Returns the number of the current page
15237                */
15238               getPage: function () {
15239                 return grid.pagination.page;
15240               },
15241               /**
15242                * @ngdoc method
15243                * @name getTotalPages
15244                * @methodOf ui.grid.pagination.api:PublicAPI
15245                * @description Returns the total number of pages
15246                */
15247               getTotalPages: function () {
15248                 return grid.pagination.totalPages;
15249               },
15250               /**
15251                * @ngdoc method
15252                * @name nextPage
15253                * @methodOf ui.grid.pagination.api:PublicAPI
15254                * @description Moves to the next page, if possible
15255                */
15256               nextPage: function () {
15257                 grid.pagination.page++;
15258                 grid.refresh();
15259               },
15260               /**
15261                * @ngdoc method
15262                * @name previousPage
15263                * @methodOf ui.grid.pagination.api:PublicAPI
15264                * @description Moves to the previous page, if we're not on the first page
15265                */
15266               previousPage: function () {
15267                 grid.pagination.page = Math.max(1, grid.pagination.page - 1);
15268                 grid.refresh();
15269               },
15270               seek: function (page) {
15271                 if (!angular.isNumber(page) || page < 1) {
15272                   throw 'Invalid page number: ' + page;
15273                 }
15274
15275                 grid.pagination.page = page;
15276                 grid.refresh();
15277               }
15278             }
15279           }
15280         };
15281         grid.api.registerMethodsFromObject(publicApi.methods);
15282         grid.registerRowsProcessor(function (renderableRows) {
15283           if (!grid.options.enablePagination) {
15284             return renderableRows;
15285           }
15286           grid.pagination.totalPages = Math.max(
15287             1,
15288             Math.ceil(renderableRows.length / grid.options.rowsPerPage)
15289           );
15290
15291           var firstRow = (grid.pagination.page - 1) * grid.options.rowsPerPage;
15292           if (firstRow >= renderableRows.length) {
15293             grid.pagination.page = grid.pagination.totalPages;
15294             firstRow = (grid.pagination.page - 1) * grid.options.rowsPerPage;
15295           }
15296
15297           return renderableRows.slice(
15298             firstRow,
15299             firstRow + grid.options.rowsPerPage
15300           );
15301         });
15302       },
15303
15304       defaultGridOptions: function (gridOptions) {
15305         /**
15306          *  @ngdoc object
15307          *  @name ui.grid.pagination.api:GridOptions
15308          *
15309          *  @description GridOptions for the pagination feature, these are available to be
15310          *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15311          */
15312
15313         /**
15314          *  @ngdoc object
15315          *  @name enablePagination
15316          *  @propertyOf  ui.grid.pagination.api:GridOptions
15317          *  @description Enable pagination for this grid
15318          *  <br/>Defaults to true
15319          */
15320         gridOptions.enablePagination = gridOptions.enablePagination !== false;
15321
15322         /**
15323          *  @ngdoc object
15324          *  @name rowsPerPage
15325          *  @propertyOf  ui.grid.pagination.api:GridOptions
15326          *  @description The number of rows that should be displayed per page
15327          *  <br/>Defaults to 10
15328          */
15329         gridOptions.rowsPerPage = angular.isNumber(gridOptions.rowsPerPage) ? gridOptions.rowsPerPage : 10;
15330       }
15331     };
15332
15333     return service;
15334   });
15335
15336   /**
15337    * @ngdoc directive
15338    * @name ui.grid.pagination.directive:uiGridPagination
15339    * @element div
15340    * @restrict A
15341    *
15342    * @description Adds pagination support to a grid.
15343    */
15344   module.directive('uiGridPagination', ['uiGridPaginationService', function (uiGridPaginationService) {
15345     return {
15346       priority: -400,
15347       scope: false,
15348       require: '^uiGrid',
15349       link: {
15350         pre: function (scope, element, attrs, uiGridCtrl) {
15351           uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
15352         }
15353       }
15354     };
15355   }]);
15356 })();
15357
15358 (function() {
15359   'use strict';
15360   /**
15361    * @ngdoc overview
15362    * @name ui.grid.paging
15363    *
15364    * @description
15365    *
15366    * #ui.grid.paging
15367    * This module provides paging support to ui-grid
15368    */
15369    
15370   var module = angular.module('ui.grid.paging', ['ui.grid']);
15371
15372   /**
15373    * @ngdoc service
15374    * @name ui.grid.paging.service:uiGridPagingService
15375    *
15376    * @description Service for the paging feature
15377    */
15378   module.service('uiGridPagingService', ['gridUtil', 
15379     function (gridUtil) {
15380       var service = {
15381       /**
15382        * @ngdoc method
15383        * @name initializeGrid
15384        * @methodOf ui.grid.paging.service:uiGridPagingService
15385        * @description Attaches the service to a certain grid
15386        * @param {Grid} grid The grid we want to work with
15387        */
15388         initializeGrid: function (grid) {
15389           service.defaultGridOptions(grid.options);
15390
15391           /**
15392           * @ngdoc object
15393           * @name ui.grid.paging.api:PublicAPI
15394           *
15395           * @description Public API for the paging feature
15396           */
15397           var publicApi = {
15398             events: {
15399               paging: {
15400               /**
15401                * @ngdoc event
15402                * @name pagingChanged
15403                * @eventOf ui.grid.paging.api:PublicAPI
15404                * @description This event fires when the pageSize or currentPage changes
15405                * @param {currentPage} requested page number
15406                * @param {pageSize} requested page size 
15407                */
15408                 pagingChanged: function (currentPage, pageSize) { }
15409               }
15410             },
15411             methods: {
15412               paging: {
15413               }
15414             }
15415           };
15416
15417           grid.api.registerEventsFromObject(publicApi.events);
15418           grid.api.registerMethodsFromObject(publicApi.methods);
15419           grid.registerRowsProcessor(function (renderableRows) {
15420             if (grid.options.useExternalPaging || !grid.options.enablePaging) {
15421               return renderableRows;
15422             }
15423             //client side paging
15424             var pageSize = parseInt(grid.options.pagingPageSize, 10);
15425             var currentPage = parseInt(grid.options.pagingCurrentPage, 10);
15426
15427             var firstRow = (currentPage - 1) * pageSize;
15428             return renderableRows.slice(firstRow, firstRow + pageSize);
15429           });
15430
15431         },
15432         defaultGridOptions: function (gridOptions) {
15433           /**
15434            * @ngdoc property
15435            * @name enablePaging
15436            * @propertyOf ui.grid.class:GridOptions
15437            * @description Enables paging, defaults to true
15438            */
15439           gridOptions.enablePaging = gridOptions.enablePaging !== false;
15440           /**
15441            * @ngdoc property
15442            * @name useExternalPaging
15443            * @propertyOf ui.grid.class:GridOptions
15444            * @description Disables client side paging. When true, handle the pagingChanged event and set data and totalItems
15445            * defaults to false
15446            */           
15447           gridOptions.useExternalPaging = gridOptions.useExternalPaging === true;
15448           /**
15449            * @ngdoc property
15450            * @name totalItems
15451            * @propertyOf ui.grid.class:GridOptions
15452            * @description Total number of items, set automatically when client side paging, needs set by user for server side paging
15453            */
15454           if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
15455             gridOptions.totalItems = 0;
15456           }
15457           /**
15458            * @ngdoc property
15459            * @name pagingPageSizes
15460            * @propertyOf ui.grid.class:GridOptions
15461            * @description Array of page sizes
15462            * defaults to [250, 500, 1000]
15463            */
15464           if (gridUtil.isNullOrUndefined(gridOptions.pagingPageSizes)) {
15465             gridOptions.pagingPageSizes = [250, 500, 1000];
15466           }
15467           /**
15468            * @ngdoc property
15469            * @name pagingPageSize
15470            * @propertyOf ui.grid.class:GridOptions
15471            * @description Page size
15472            * defaults to the first item in pagingPageSizes, or 0 if pagingPageSizes is empty
15473            */
15474           if (gridUtil.isNullOrUndefined(gridOptions.pagingPageSize)) {
15475             if (gridOptions.pagingPageSizes.length > 0) {
15476               gridOptions.pagingPageSize = gridOptions.pagingPageSizes[0];
15477             } else {              
15478               gridOptions.pagingPageSize = 0;
15479             }
15480           }
15481           /**
15482            * @ngdoc property
15483            * @name pagingCurrentPage
15484            * @propertyOf ui.grid.class:GridOptions
15485            * @description Current page number
15486            * default 1
15487            */
15488           if (gridUtil.isNullOrUndefined(gridOptions.pagingCurrentPage)) {
15489             gridOptions.pagingCurrentPage = 1;
15490           }
15491         },
15492         /**
15493          * @ngdoc method
15494          * @methodOf ui.grid.paging.service:uiGridPagingService
15495          * @name uiGridPagingService
15496          * @description  Raises pagingChanged and calls refresh for client side paging
15497          * @param {grid} the grid for which the paging changed
15498          * @param {currentPage} requested page number
15499          * @param {pageSize} requested page size 
15500          */
15501         onPagingChanged: function (grid, currentPage, pageSize) {
15502             grid.api.paging.raise.pagingChanged(currentPage, pageSize);
15503             if (!grid.options.useExternalPaging) {
15504               grid.refresh(); //client side paging
15505             }
15506         }
15507       };
15508       
15509       return service;
15510     }
15511   ]);
15512   /**
15513    *  @ngdoc directive
15514    *  @name ui.grid.paging.directive:uiGridPaging
15515    *  @element div
15516    *  @restrict A
15517    *
15518    *  @description Adds paging features to grid
15519    *  @example
15520    <example module="app">
15521    <file name="app.js">
15522    var app = angular.module('app', ['ui.grid', 'ui.grid.paging']);
15523
15524    app.controller('MainCtrl', ['$scope', function ($scope) {
15525       $scope.data = [
15526         { name: 'Alex', car: 'Toyota' },
15527         { name: 'Sam', car: 'Lexus' },
15528         { name: 'Joe', car: 'Dodge' },
15529         { name: 'Bob', car: 'Buick' },
15530         { name: 'Cindy', car: 'Ford' },
15531         { name: 'Brian', car: 'Audi' },
15532         { name: 'Malcom', car: 'Mercedes Benz' },
15533         { name: 'Dave', car: 'Ford' },
15534         { name: 'Stacey', car: 'Audi' },
15535         { name: 'Amy', car: 'Acura' },
15536         { name: 'Scott', car: 'Toyota' },
15537         { name: 'Ryan', car: 'BMW' },
15538       ];
15539
15540       $scope.gridOptions = {
15541         data: 'data',
15542         pagingPageSizes: [5, 10, 25],
15543         pagingPageSize: 5,
15544         columnDefs: [
15545           {name: 'name'},
15546           {name: 'car'}
15547         ];
15548        }
15549     }]);
15550    </file>
15551    <file name="index.html">
15552    <div ng-controller="MainCtrl">
15553    <div ui-grid="gridOptions" ui-grid-paging></div>
15554    </div>
15555    </file>
15556    </example>
15557    */
15558   module.directive('uiGridPaging', ['gridUtil', 'uiGridPagingService', 
15559     function (gridUtil, uiGridPagingService) {
15560     /**
15561      * @ngdoc property
15562      * @name pagingTemplate
15563      * @propertyOf ui.grid.class:GridOptions
15564      * @description a custom template for the pager.  The default
15565      * is ui-grid/ui-grid-paging
15566      */
15567       var defaultTemplate = 'ui-grid/ui-grid-paging';
15568
15569       return {
15570         priority: -200,
15571         scope: false,
15572         require: 'uiGrid',
15573         compile: function ($scope, $elm, $attr, uiGridCtrl) {
15574           return {
15575             pre: function ($scope, $elm, $attr, uiGridCtrl) {
15576
15577               uiGridPagingService.initializeGrid(uiGridCtrl.grid);
15578
15579               var pagingTemplate = uiGridCtrl.grid.options.pagingTemplate || defaultTemplate;
15580               gridUtil.getTemplate(pagingTemplate)
15581                 .then(function (contents) {
15582                   var template = angular.element(contents);
15583                   $elm.append(template);
15584                   uiGridCtrl.innerCompile(template);
15585                 });
15586             },
15587             post: function ($scope, $elm, $attr, uiGridCtrl) {
15588             }
15589           };
15590         }
15591       };
15592     }
15593   ]);
15594
15595   /**
15596    *  @ngdoc directive
15597    *  @name ui.grid.paging.directive:uiGridPager
15598    *  @element div
15599    *
15600    *  @description Panel for handling paging
15601    */
15602   module.directive('uiGridPager', ['uiGridPagingService', 'uiGridConstants', 'gridUtil', 'i18nService',
15603     function (uiGridPagingService, uiGridConstants, gridUtil, i18nService) {
15604       return {
15605         priority: -200,
15606         scope: true,
15607         require: '^uiGrid',
15608         link: function ($scope, $elm, $attr, uiGridCtrl) {
15609
15610           $scope.sizesLabel = i18nService.getSafeText('paging.sizes');
15611           $scope.totalItemsLabel = i18nService.getSafeText('paging.totalItems');
15612           
15613           var options = $scope.grid.options;
15614           
15615           uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
15616             adjustment.height = adjustment.height - gridUtil.elementHeight($elm);
15617             return adjustment;
15618           });
15619           
15620           uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
15621             if (!grid.options.useExternalPaging) {
15622               grid.options.totalItems = grid.rows.length;
15623             }
15624           }, [uiGridConstants.dataChange.ROW]);
15625
15626           var setShowing = function () {
15627             $scope.showingLow = ((options.pagingCurrentPage - 1) * options.pagingPageSize) + 1;
15628             $scope.showingHigh = Math.min(options.pagingCurrentPage * options.pagingPageSize, options.totalItems);
15629           };
15630
15631           var getMaxPages = function () {
15632             return (options.totalItems === 0) ? 1 : Math.ceil(options.totalItems / options.pagingPageSize);
15633           };
15634
15635           var deregT = $scope.$watch('grid.options.totalItems + grid.options.pagingPageSize', function () {
15636               $scope.currentMaxPages = getMaxPages();
15637               setShowing();
15638             }
15639           );
15640
15641           var deregP = $scope.$watch('grid.options.pagingCurrentPage + grid.options.pagingPageSize', function (newValues, oldValues) {
15642               if (newValues === oldValues) { 
15643                 return; 
15644               }
15645
15646               if (!angular.isNumber(options.pagingCurrentPage) || options.pagingCurrentPage < 1) {
15647                 options.pagingCurrentPage = 1;
15648                 return;
15649               }
15650
15651               if (options.totalItems > 0 && options.pagingCurrentPage > getMaxPages()) {
15652                 options.pagingCurrentPage = getMaxPages();
15653                 return;
15654               }
15655
15656               setShowing();
15657               uiGridPagingService.onPagingChanged($scope.grid, options.pagingCurrentPage, options.pagingPageSize);
15658             }
15659           );
15660
15661           $scope.$on('$destroy', function() {
15662             deregT();
15663             deregP();
15664           });
15665
15666           $scope.pageForward = function () {
15667             if (options.totalItems > 0) {
15668               options.pagingCurrentPage = Math.min(options.pagingCurrentPage + 1, $scope.currentMaxPages);
15669             } else {
15670               options.pagingCurrentPage++;
15671             }
15672           };
15673
15674           $scope.pageBackward = function () {
15675             options.pagingCurrentPage = Math.max(options.pagingCurrentPage - 1, 1);
15676           };
15677
15678           $scope.pageToFirst = function () {
15679             options.pagingCurrentPage = 1;
15680           };
15681
15682           $scope.pageToLast = function () {
15683             options.pagingCurrentPage = $scope.currentMaxPages;
15684           };
15685
15686           $scope.cantPageForward = function () {
15687             if (options.totalItems > 0) {
15688               return options.pagingCurrentPage >= $scope.currentMaxPages;
15689             } else {
15690               return options.data.length < 1;
15691             }
15692           };
15693           
15694           $scope.cantPageToLast = function () {
15695             if (options.totalItems > 0) {
15696               return $scope.cantPageForward();
15697             } else {
15698               return true;
15699             }
15700           };
15701           
15702           $scope.cantPageBackward = function () {
15703             return options.pagingCurrentPage <= 1;
15704           };
15705         }
15706       };
15707     }
15708   ]);
15709 })();
15710 (function () {
15711   'use strict';
15712
15713   /**
15714    * @ngdoc overview
15715    * @name ui.grid.pinning
15716    * @description
15717    *
15718    *  # ui.grid.pinning
15719    * This module provides column pinning to the end user via menu options in the column header
15720    * <br/>
15721    * <br/>
15722    *
15723    * <div doc-module-components="ui.grid.pinning"></div>
15724    */
15725
15726   var module = angular.module('ui.grid.pinning', ['ui.grid']);
15727
15728   module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', function (gridUtil, GridRenderContainer, i18nService) {
15729     var service = {
15730
15731       initializeGrid: function (grid) {
15732         service.defaultGridOptions(grid.options);
15733
15734         // Register a column builder to add new menu items for pinning left and right
15735         grid.registerColumnBuilder(service.pinningColumnBuilder);
15736       },
15737
15738       defaultGridOptions: function (gridOptions) {
15739         //default option to true unless it was explicitly set to false
15740         /**
15741          *  @ngdoc object
15742          *  @name ui.grid.pinning.api:GridOptions
15743          *
15744          *  @description GridOptions for pinning feature, these are available to be  
15745            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15746          */
15747
15748         /**
15749          *  @ngdoc object
15750          *  @name enableRowSelection
15751          *  @propertyOf  ui.grid.pinning.api:GridOptions
15752          *  @description Enable pinning for the entire grid.  
15753          *  <br/>Defaults to true
15754          */
15755         gridOptions.enablePinning = gridOptions.enablePinning !== false;
15756
15757       },
15758
15759       pinningColumnBuilder: function (colDef, col, gridOptions) {
15760         //default to true unless gridOptions or colDef is explicitly false
15761
15762         /**
15763          *  @ngdoc object
15764          *  @name ui.grid.pinning.api:ColumnDef
15765          *
15766          *  @description ColumnDef for pinning feature, these are available to be 
15767          *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15768          */
15769
15770         /**
15771          *  @ngdoc object
15772          *  @name enablePinning
15773          *  @propertyOf  ui.grid.pinning.api:ColumnDef
15774          *  @description Enable pinning for the individual column.  
15775          *  <br/>Defaults to true
15776          */
15777         colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
15778
15779
15780         /**
15781          *  @ngdoc object
15782          *  @name pinnedLeft
15783          *  @propertyOf  ui.grid.pinning.api:ColumnDef
15784          *  @description Column is pinned left when grid is rendered
15785          *  <br/>Defaults to false
15786          */
15787
15788         /**
15789          *  @ngdoc object
15790          *  @name pinnedRight
15791          *  @propertyOf  ui.grid.pinning.api:ColumnDef
15792          *  @description Column is pinned right when grid is rendered
15793          *  <br/>Defaults to false
15794          */
15795         if (colDef.pinnedLeft) {
15796           if (col.width === '*') {
15797             // Need to refresh so the width can be calculated.
15798             col.grid.refresh()
15799                 .then(function () {
15800                     col.renderContainer = 'left';
15801                     // Need to calculate the width. If col.drawnWidth is used instead then the width
15802                     // will be 100% if it's the first column, 50% if it's the second etc.
15803                     col.width = col.grid.canvasWidth / col.grid.columns.length;
15804                     col.grid.createLeftContainer();
15805             });
15806           }
15807           else {
15808             col.renderContainer = 'left';
15809             col.grid.createLeftContainer();
15810           }
15811         }
15812         else if (colDef.pinnedRight) {
15813             if (col.width === '*') {
15814                 // Need to refresh so the width can be calculated.
15815                 col.grid.refresh()
15816                     .then(function () {
15817                         col.renderContainer = 'right';
15818                         // Need to calculate the width. If col.drawnWidth is used instead then the width
15819                         // will be 100% if it's the first column, 50% if it's the second etc.
15820                         col.width = col.grid.canvasWidth / col.grid.columns.length;
15821                         col.grid.createRightContainer();
15822                     });
15823             }
15824             else {
15825                 col.renderContainer = 'right';
15826                 col.grid.createRightContainer();
15827             }
15828         }
15829
15830         if (!colDef.enablePinning) {
15831           return;
15832         }
15833
15834         var pinColumnLeftAction = {
15835           name: 'ui.grid.pinning.pinLeft',
15836           title: i18nService.get().pinning.pinLeft,
15837           icon: 'ui-grid-icon-left-open',
15838           shown: function () {
15839             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
15840           },
15841           action: function () {
15842             this.context.col.renderContainer = 'left';
15843             this.context.col.width = this.context.col.drawnWidth;
15844             this.context.col.grid.createLeftContainer();
15845
15846             // Need to call refresh twice; once to move our column over to the new render container and then
15847             //   a second time to update the grid viewport dimensions with our adjustments
15848             col.grid.refresh()
15849               .then(function () {
15850                 col.grid.refresh();
15851               });
15852           }
15853         };
15854
15855         var pinColumnRightAction = {
15856           name: 'ui.grid.pinning.pinRight',
15857           title: i18nService.get().pinning.pinRight,
15858           icon: 'ui-grid-icon-right-open',
15859           shown: function () {
15860             return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
15861           },
15862           action: function () {
15863             this.context.col.renderContainer = 'right';
15864             this.context.col.width = this.context.col.drawnWidth;
15865             this.context.col.grid.createRightContainer();
15866
15867
15868             // Need to call refresh twice; once to move our column over to the new render container and then
15869             //   a second time to update the grid viewport dimensions with our adjustments
15870             col.grid.refresh()
15871               .then(function () {
15872                 col.grid.refresh();
15873               });
15874           }
15875         };
15876
15877         var removePinAction = {
15878           name: 'ui.grid.pinning.unpin',
15879           title: i18nService.get().pinning.unpin,
15880           icon: 'ui-grid-icon-cancel',
15881           shown: function () {
15882             return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
15883           },
15884           action: function () {
15885             this.context.col.renderContainer = null;
15886
15887             // Need to call refresh twice; once to move our column over to the new render container and then
15888             //   a second time to update the grid viewport dimensions with our adjustments
15889             col.grid.refresh()
15890               .then(function () {
15891                 col.grid.refresh();
15892               });
15893           }
15894         };
15895
15896         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
15897           col.menuItems.push(pinColumnLeftAction);
15898         }
15899         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
15900           col.menuItems.push(pinColumnRightAction);
15901         }
15902         if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
15903           col.menuItems.push(removePinAction);
15904         }
15905       }
15906     };
15907
15908     return service;
15909   }]);
15910
15911   module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
15912     function (gridUtil, uiGridPinningService) {
15913       return {
15914         require: 'uiGrid',
15915         scope: false,
15916         compile: function () {
15917           return {
15918             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15919               uiGridPinningService.initializeGrid(uiGridCtrl.grid);
15920             },
15921             post: function ($scope, $elm, $attrs, uiGridCtrl) {
15922             }
15923           };
15924         }
15925       };
15926     }]);
15927
15928
15929 })();
15930
15931 (function(){
15932   'use strict';
15933
15934   var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
15935
15936   module.constant('columnBounds', {
15937     minWidth: 35
15938   });
15939
15940
15941   module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
15942     function (gridUtil, $q, $timeout) {
15943
15944       var service = {
15945         defaultGridOptions: function(gridOptions){
15946           //default option to true unless it was explicitly set to false
15947           /**
15948            *  @ngdoc object
15949            *  @name ui.grid.resizeColumns.api:GridOptions
15950            *
15951            *  @description GridOptions for resizeColumns feature, these are available to be  
15952            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15953            */
15954
15955           /**
15956            *  @ngdoc object
15957            *  @name enableColumnResizing
15958            *  @propertyOf  ui.grid.resizeColumns.api:GridOptions
15959            *  @description Enable column resizing on the entire grid 
15960            *  <br/>Defaults to true
15961            */
15962           gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
15963
15964           //legacy support
15965           //use old name if it is explicitly false
15966           if (gridOptions.enableColumnResize === false){
15967             gridOptions.enableColumnResizing = false;
15968           }
15969         },
15970
15971         colResizerColumnBuilder: function (colDef, col, gridOptions) {
15972
15973           var promises = [];
15974           /**
15975            *  @ngdoc object
15976            *  @name ui.grid.resizeColumns.api:ColumnDef
15977            *
15978            *  @description ColumnDef for resizeColumns feature, these are available to be 
15979            *  set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15980            */
15981
15982           /**
15983            *  @ngdoc object
15984            *  @name enableColumnResizing
15985            *  @propertyOf  ui.grid.resizeColumns.api:ColumnDef
15986            *  @description Enable column resizing on an individual column
15987            *  <br/>Defaults to GridOptions.enableColumnResizing
15988            */
15989           //default to true unless gridOptions or colDef is explicitly false
15990           colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
15991
15992
15993           //legacy support of old option name
15994           if (colDef.enableColumnResize === false){
15995             colDef.enableColumnResizing = false;
15996           }
15997
15998           return $q.all(promises);
15999         },
16000         
16001         registerPublicApi: function (grid) {
16002             /**
16003              *  @ngdoc object
16004              *  @name ui.grid.resizeColumns.api:PublicApi
16005              *  @description Public Api for column resize feature.
16006              */
16007             var publicApi = {
16008               events: {
16009                 /**
16010                  * @ngdoc event
16011                  * @name columnSizeChanged
16012                  * @eventOf  ui.grid.resizeColumns.api:PublicApi
16013                  * @description raised when column is resized
16014                  * <pre>
16015                  *      gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
16016                  * </pre>
16017                  * @param {object} colDef the column that was resized
16018                  * @param {integer} delta of the column size change
16019                  */
16020                 colResizable: {
16021                   columnSizeChanged: function (colDef, deltaChange) {
16022                   }
16023                 }
16024               }
16025             };
16026             grid.api.registerEventsFromObject(publicApi.events);
16027         },
16028         
16029         fireColumnSizeChanged: function (grid, colDef, deltaChange) {
16030           $timeout(function () {
16031             grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
16032           });
16033         }
16034       };
16035
16036       return service;
16037
16038     }]);
16039
16040
16041   /**
16042    * @ngdoc directive
16043    * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
16044    * @element div
16045    * @restrict A
16046    * @description
16047    * 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
16048    * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
16049    *
16050    * @example
16051    <doc:example module="app">
16052    <doc:source>
16053    <script>
16054    var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
16055
16056    app.controller('MainCtrl', ['$scope', function ($scope) {
16057           $scope.gridOpts = {
16058             data: [
16059               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
16060               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
16061               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
16062               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
16063             ]
16064           };
16065         }]);
16066    </script>
16067
16068    <div ng-controller="MainCtrl">
16069    <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
16070    </div>
16071    </doc:source>
16072    <doc:scenario>
16073
16074    </doc:scenario>
16075    </doc:example>
16076    */
16077   module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
16078     return {
16079       replace: true,
16080       priority: 0,
16081       require: '^uiGrid',
16082       scope: false,
16083       compile: function () {
16084         return {
16085           pre: function ($scope, $elm, $attrs, uiGridCtrl) {            
16086             uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
16087             uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
16088             uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
16089           },
16090           post: function ($scope, $elm, $attrs, uiGridCtrl) {
16091           }
16092         };
16093       }
16094     };
16095   }]);
16096
16097   // Extend the uiGridHeaderCell directive
16098   module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', function (gridUtil, $templateCache, $compile, $q) {
16099     return {
16100       // Run after the original uiGridHeaderCell
16101       priority: -10,
16102       require: '^uiGrid',
16103       // scope: false,
16104       compile: function() {
16105         return {
16106           post: function ($scope, $elm, $attrs, uiGridCtrl) {
16107            if (uiGridCtrl.grid.options.enableColumnResizing) {
16108               var renderIndexDefer = $q.defer();
16109
16110               $attrs.$observe('renderIndex', function (n, o) {
16111                 $scope.renderIndex = $scope.$eval(n);
16112
16113                 renderIndexDefer.resolve();
16114               });
16115
16116               renderIndexDefer.promise.then(function() {
16117                 var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
16118
16119                 var resizerLeft = angular.element(columnResizerElm).clone();
16120                 var resizerRight = angular.element(columnResizerElm).clone();
16121
16122                 resizerLeft.attr('position', 'left');
16123                 resizerRight.attr('position', 'right');
16124
16125                 var col = $scope.col;
16126                 var renderContainer = col.getRenderContainer();
16127
16128
16129                 // Get the column to the left of this one
16130                 var otherCol = renderContainer.renderedColumns[$scope.renderIndex - 1];
16131
16132                 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
16133                 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
16134                   $elm.prepend(resizerLeft);
16135                   $compile(resizerLeft)($scope);
16136                 }
16137                 
16138                 // Don't append the right resizer if this column has resizing disabled
16139                 if ($scope.col.colDef.enableColumnResizing !== false) {
16140                   $elm.append(resizerRight);
16141                   $compile(resizerRight)($scope);
16142                 }
16143               });
16144             }
16145           }
16146         };
16147       }
16148     };
16149   }]);
16150
16151
16152   
16153   /**
16154    * @ngdoc directive
16155    * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
16156    * @element div
16157    * @restrict A
16158    *
16159    * @description
16160    * Draggable handle that controls column resizing.
16161    * 
16162    * @example
16163    <doc:example module="app">
16164      <doc:source>
16165        <script>
16166         var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
16167
16168         app.controller('MainCtrl', ['$scope', function ($scope) {
16169           $scope.gridOpts = {
16170             enableColumnResizing: true,
16171             data: [
16172               { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
16173               { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
16174               { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
16175               { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
16176             ]
16177           };
16178         }]);
16179        </script>
16180
16181        <div ng-controller="MainCtrl">
16182         <div class="testGrid" ui-grid="gridOpts"></div>
16183        </div>
16184      </doc:source>
16185      <doc:scenario>
16186       // TODO: e2e specs?
16187         // TODO: Obey minWidth and maxWIdth;
16188
16189       // TODO: post-resize a horizontal scroll event should be fired
16190      </doc:scenario>
16191    </doc:example>
16192    */  
16193   module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'columnBounds', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, columnBounds, uiGridResizeColumnsService) {
16194     var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
16195
16196     var resizer = {
16197       priority: 0,
16198       scope: {
16199         col: '=',
16200         position: '@',
16201         renderIndex: '='
16202       },
16203       require: '?^uiGrid',
16204       link: function ($scope, $elm, $attrs, uiGridCtrl) {
16205         var startX = 0,
16206             x = 0,
16207             gridLeft = 0,
16208             rtlMultiplier = 1;
16209
16210         //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
16211         if (uiGridCtrl.grid.isRTL()) {
16212           $scope.position = 'left';
16213           rtlMultiplier = -1;
16214         }
16215
16216         if ($scope.position === 'left') {
16217           $elm.addClass('left');
16218         }
16219         else if ($scope.position === 'right') {
16220           $elm.addClass('right');
16221         }
16222
16223         // Resize all the other columns around col
16224         function resizeAroundColumn(col) {
16225           // Get this column's render container
16226           var renderContainer = col.getRenderContainer();
16227
16228           renderContainer.visibleColumnCache.forEach(function (column) {
16229             // Skip the column we just resized
16230             if (column === col) { return; }
16231             
16232             var colDef = column.colDef;
16233             if (!colDef.width || (angular.isString(colDef.width) && (colDef.width.indexOf('*') !== -1 || colDef.width.indexOf('%') !== -1))) {
16234               column.width = column.drawnWidth;
16235             }
16236           });
16237         }
16238
16239         // Build the columns then refresh the grid canvas
16240         //   takes an argument representing the diff along the X-axis that the resize had
16241         function buildColumnsAndRefresh(xDiff) {
16242           // Build the columns
16243           uiGridCtrl.grid.buildColumns()
16244             .then(function() {
16245               // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
16246               uiGridCtrl.grid.refreshCanvas(true);
16247             });
16248         }
16249
16250         function mousemove(event, args) {
16251           if (event.originalEvent) { event = event.originalEvent; }
16252           event.preventDefault();
16253
16254           x = event.clientX - gridLeft;
16255
16256           if (x < 0) { x = 0; }
16257           else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
16258
16259           // The other column to resize (the one next to this one)
16260           var col = $scope.col;
16261           var renderContainer = col.getRenderContainer();
16262           var otherCol;
16263           if ($scope.position === 'left') {
16264             // Get the column to the left of this one
16265             col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16266             otherCol = $scope.col;
16267           }
16268           else if ($scope.position === 'right') {
16269             otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16270           }
16271
16272           // Don't resize if it's disabled on this column
16273           if (col.colDef.enableColumnResizing === false) {
16274             return;
16275           }
16276
16277           if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
16278             uiGridCtrl.grid.element.addClass('column-resizing');
16279           }
16280
16281           // Get the diff along the X axis
16282           var xDiff = x - startX;
16283
16284           // Get the width that this mouse would give the column
16285           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
16286
16287           // If the new width would be less than the column's allowably minimum width, don't allow it
16288           if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
16289             x = x + (col.colDef.minWidth - newWidth) * rtlMultiplier;
16290           }
16291           else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
16292             x = x + (col.colDef.minWidth - newWidth);
16293           }
16294           else if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
16295             x = x + (col.colDef.maxWidth - newWidth) * rtlMultiplier;
16296           }
16297           
16298           resizeOverlay.css({ left: x + 'px' });
16299
16300           uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
16301         }
16302
16303         function mouseup(event, args) {
16304           if (event.originalEvent) { event = event.originalEvent; }
16305           event.preventDefault();
16306
16307           uiGridCtrl.grid.element.removeClass('column-resizing');
16308
16309           resizeOverlay.remove();
16310
16311           // Resize the column
16312           x = event.clientX - gridLeft;
16313           var xDiff = x - startX;
16314
16315           if (xDiff === 0) {
16316             $document.off('mouseup', mouseup);
16317             $document.off('mousemove', mousemove);
16318             return;
16319           }
16320
16321           // The other column to resize (the one next to this one)
16322           var col = $scope.col;
16323           var renderContainer = col.getRenderContainer();
16324
16325           var otherCol;
16326           if ($scope.position === 'left') {
16327             // Get the column to the left of this one
16328             col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16329             otherCol = $scope.col;
16330           }
16331           else if ($scope.position === 'right') {
16332             otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16333           }
16334
16335           // Don't resize if it's disabled on this column
16336           if (col.colDef.enableColumnResizing === false) {
16337             return;
16338           }
16339
16340           // Get the new width
16341           var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
16342
16343           // If the new width is less than the minimum width, make it the minimum width
16344           if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
16345             newWidth = col.colDef.minWidth;
16346           }
16347           else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
16348             newWidth = columnBounds.minWidth;
16349           }
16350           // 
16351           if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
16352             newWidth = col.colDef.maxWidth;
16353           }
16354           
16355           col.width = newWidth;
16356
16357           // All other columns because fixed to their drawn width, if they aren't already
16358           resizeAroundColumn(col);
16359
16360           buildColumnsAndRefresh(xDiff);
16361
16362           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
16363
16364           $document.off('mouseup', mouseup);
16365           $document.off('mousemove', mousemove);
16366         }
16367
16368         $elm.on('mousedown', function(event, args) {
16369           if (event.originalEvent) { event = event.originalEvent; }
16370           event.stopPropagation();
16371
16372           // Get the left offset of the grid
16373           // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
16374           gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
16375
16376           // Get the starting X position, which is the X coordinate of the click minus the grid's offset
16377           startX = event.clientX - gridLeft;
16378
16379           // Append the resizer overlay
16380           uiGridCtrl.grid.element.append(resizeOverlay);
16381
16382           // Place the resizer overlay at the start position
16383           resizeOverlay.css({ left: startX });
16384
16385           // Add handlers for mouse move and up events
16386           $document.on('mouseup', mouseup);
16387           $document.on('mousemove', mousemove);
16388         });
16389
16390         // On doubleclick, resize to fit all rendered cells
16391         $elm.on('dblclick', function(event, args) {
16392           event.stopPropagation();
16393
16394           var col = $scope.col;
16395           var renderContainer = col.getRenderContainer();
16396
16397           var otherCol, multiplier;
16398
16399           // If we're the left-positioned resizer then we need to resize the column to the left of our column, and not our column itself
16400           if ($scope.position === 'left') {
16401             col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16402             otherCol = $scope.col;
16403             multiplier = 1;
16404           }
16405           else if ($scope.position === 'right') {
16406             otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16407             otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16408             multiplier = -1;
16409           }
16410
16411           // Go through the rendered rows and find out the max size for the data in this column
16412           var maxWidth = 0;
16413           var xDiff = 0;
16414
16415           // Get the parent render container element
16416           var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
16417
16418           // 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
16419           var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
16420           Array.prototype.forEach.call(cells, function (cell) {
16421               // Get the cell width
16422               // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
16423
16424               // Account for the menu button if it exists
16425               var menuButton;
16426               if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
16427                 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
16428               }
16429
16430               gridUtil.fakeElement(cell, {}, function(newElm) {
16431                 // Make the element float since it's a div and can expand to fill its container
16432                 var e = angular.element(newElm);
16433                 e.attr('style', 'float: left');
16434
16435                 var width = gridUtil.elementWidth(e);
16436
16437                 if (menuButton) {
16438                   var menuButtonWidth = gridUtil.elementWidth(menuButton);
16439                   width = width + menuButtonWidth;
16440                 }
16441
16442                 if (width > maxWidth) {
16443                   maxWidth = width;
16444                   xDiff = maxWidth - width;
16445                 }
16446               });
16447             });
16448
16449           // If the new width is less than the minimum width, make it the minimum width
16450           if (col.colDef.minWidth && maxWidth < col.colDef.minWidth) {
16451             maxWidth = col.colDef.minWidth;
16452           }
16453           else if (!col.colDef.minWidth && columnBounds.minWidth && maxWidth < columnBounds.minWidth) {
16454             maxWidth = columnBounds.minWidth;
16455           }
16456           // 
16457           if (col.colDef.maxWidth && maxWidth > col.colDef.maxWidth) {
16458             maxWidth = col.colDef.maxWidth;
16459           }
16460
16461           col.width = parseInt(maxWidth, 10);
16462           
16463           // All other columns because fixed to their drawn width, if they aren't already
16464           resizeAroundColumn(col);
16465
16466           buildColumnsAndRefresh(xDiff);
16467           
16468           uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
16469         });
16470
16471         $elm.on('$destroy', function() {
16472           $elm.off('mousedown');
16473           $elm.off('dblclick');
16474           $document.off('mousemove', mousemove);
16475           $document.off('mouseup', mouseup);
16476         });
16477       }
16478     };
16479
16480     return resizer;
16481   }]);
16482
16483 })();
16484 (function () {
16485   'use strict';
16486
16487   /**
16488    * @ngdoc overview
16489    * @name ui.grid.rowEdit
16490    * @description
16491    *
16492    *  # ui.grid.rowEdit
16493    * This module extends the edit feature to provide tracking and saving of rows
16494    * of data.  The tutorial provides more information on how this feature is best
16495    * used {@link tutorial/205_row_editable here}.
16496    * <br/>
16497    * This feature depends on usage of the ui-grid-edit feature, and also benefits
16498    * from use of ui-grid-cellNav to provide the full spreadsheet-like editing 
16499    * experience
16500    * 
16501    */
16502
16503   var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
16504
16505   /**
16506    *  @ngdoc object
16507    *  @name ui.grid.rowEdit.constant:uiGridRowEditConstants
16508    *
16509    *  @description constants available in row edit module
16510    */
16511   module.constant('uiGridRowEditConstants', {
16512   });
16513
16514   /**
16515    *  @ngdoc service
16516    *  @name ui.grid.rowEdit.service:uiGridRowEditService
16517    *
16518    *  @description Services for row editing features
16519    */
16520   module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil', 
16521     function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
16522
16523       var service = {
16524
16525         initializeGrid: function (scope, grid) {
16526           /**
16527            *  @ngdoc object
16528            *  @name ui.grid.rowEdit.api:PublicApi
16529            *
16530            *  @description Public Api for rowEdit feature
16531            */
16532           
16533           grid.rowEdit = {};
16534           
16535           var publicApi = {
16536             events: {
16537               rowEdit: {
16538                 /**
16539                  * @ngdoc event
16540                  * @eventOf ui.grid.rowEdit.api:PublicApi
16541                  * @name saveRow
16542                  * @description raised when a row is ready for saving.  Once your
16543                  * row has saved you may need to use angular.extend to update the
16544                  * data entity with any changed data from your save (for example, 
16545                  * lock version information if you're using optimistic locking,
16546                  * or last update time/user information).
16547                  * 
16548                  * Your method should call setSavePromise somewhere in the body before
16549                  * returning control.  The feature will then wait, with the gridRow greyed out 
16550                  * whilst this promise is being resolved.
16551                  * 
16552                  * <pre>
16553                  *      gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
16554                  * </pre>
16555                  * and somewhere within the event handler:
16556                  * <pre>
16557                  *      gridApi.rowEdit.setSavePromise( grid, rowEntity, savePromise)
16558                  * </pre>
16559                  * @param {object} rowEntity the options.data element that was edited
16560                  * @returns {promise} Your saveRow method should return a promise, the
16561                  * promise should either be resolved (implying successful save), or 
16562                  * rejected (implying an error).
16563                  */
16564                 saveRow: function (rowEntity) {
16565                 }
16566               }
16567             },
16568             methods: {
16569               rowEdit: {
16570                 /**
16571                  * @ngdoc method
16572                  * @methodOf ui.grid.rowEdit.api:PublicApi
16573                  * @name setSavePromise
16574                  * @description Sets the promise associated with the row save, mandatory that
16575                  * the saveRow event handler calls this method somewhere before returning.
16576                  * <pre>
16577                  *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
16578                  * </pre>
16579                  * @param {object} grid the grid for which dirty rows should be returned
16580                  * @param {object} rowEntity a data row from the grid for which a save has
16581                  * been initiated
16582                  * @param {promise} savePromise the promise that will be resolved when the
16583                  * save is successful, or rejected if the save fails
16584                  * 
16585                  */
16586                 setSavePromise: function (grid, rowEntity, savePromise) {
16587                   service.setSavePromise(grid, rowEntity, savePromise);
16588                 },
16589                 /**
16590                  * @ngdoc method
16591                  * @methodOf ui.grid.rowEdit.api:PublicApi
16592                  * @name getDirtyRows
16593                  * @description Returns all currently dirty rows
16594                  * <pre>
16595                  *      gridApi.rowEdit.getDirtyRows(grid)
16596                  * </pre>
16597                  * @param {object} grid the grid for which dirty rows should be returned
16598                  * @returns {array} An array of gridRows that are currently dirty
16599                  * 
16600                  */
16601                 getDirtyRows: function (grid) {
16602                   return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
16603                 },
16604                 /**
16605                  * @ngdoc method
16606                  * @methodOf ui.grid.rowEdit.api:PublicApi
16607                  * @name getErrorRows
16608                  * @description Returns all currently errored rows
16609                  * <pre>
16610                  *      gridApi.rowEdit.getErrorRows(grid)
16611                  * </pre>
16612                  * @param {object} grid the grid for which errored rows should be returned
16613                  * @returns {array} An array of gridRows that are currently in error
16614                  * 
16615                  */
16616                 getErrorRows: function (grid) {
16617                   return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
16618                 },
16619                 /**
16620                  * @ngdoc method
16621                  * @methodOf ui.grid.rowEdit.api:PublicApi
16622                  * @name flushDirtyRows
16623                  * @description Triggers a save event for all currently dirty rows, could
16624                  * be used where user presses a save button or navigates away from the page
16625                  * <pre>
16626                  *      gridApi.rowEdit.flushDirtyRows(grid)
16627                  * </pre>
16628                  * @param {object} grid the grid for which dirty rows should be flushed
16629                  * @returns {promise} a promise that represents the aggregate of all
16630                  * of the individual save promises - i.e. it will be resolved when all
16631                  * the individual save promises have been resolved.
16632                  * 
16633                  */
16634                 flushDirtyRows: function (grid) {
16635                   return service.flushDirtyRows(grid);
16636                 },
16637                 
16638                 /**
16639                  * @ngdoc method
16640                  * @methodOf ui.grid.rowEdit.api:PublicApi
16641                  * @name setRowsDirty
16642                  * @description Sets each of the rows passed in dataRows
16643                  * to be dirty.  note that if you have only just inserted the
16644                  * rows into your data you will need to wait for a $digest cycle
16645                  * before the gridRows are present - so often you would wrap this
16646                  * call in a $interval or $timeout
16647                  * <pre>
16648                  *      $interval( function() {
16649                  *        gridApi.rowEdit.setRowsDirty(grid, myDataRows);
16650                  *      }, 0, 1);
16651                  * </pre>
16652                  * @param {object} grid the grid for which rows should be set dirty
16653                  * @param {array} dataRows the data entities for which the gridRows
16654                  * should be set dirty.  
16655                  * 
16656                  */
16657                 setRowsDirty: function (grid, dataRows) {
16658                   service.setRowsDirty(grid, dataRows);
16659                 }
16660               }
16661             }
16662           };
16663
16664           grid.api.registerEventsFromObject(publicApi.events);
16665           grid.api.registerMethodsFromObject(publicApi.methods);
16666           
16667           grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
16668             grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
16669             grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
16670             grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
16671             
16672             if ( grid.api.cellNav ) {
16673               grid.api.cellNav.on.navigate( scope, service.navigate );
16674             }              
16675           });
16676
16677         },
16678
16679         defaultGridOptions: function (gridOptions) {
16680
16681           /**
16682            *  @ngdoc object
16683            *  @name ui.grid.rowEdit.api:GridOptions
16684            *
16685            *  @description Options for configuring the rowEdit feature, these are available to be  
16686            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16687            */
16688
16689         },
16690
16691
16692         /**
16693          * @ngdoc method
16694          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16695          * @name saveRow
16696          * @description  Returns a function that saves the specified row from the grid,
16697          * and returns a promise
16698          * @param {object} grid the grid for which dirty rows should be flushed
16699          * @param {GridRow} gridRow the row that should be saved
16700          * @returns {function} the saveRow function returns a function.  That function
16701          * in turn, when called, returns a promise relating to the save callback
16702          */
16703         saveRow: function ( grid, gridRow ) {
16704           var self = this;
16705
16706           return function() {
16707             gridRow.isSaving = true;
16708
16709             var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
16710             
16711             if ( gridRow.rowEditSavePromise ){
16712               gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
16713             } else {
16714               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' );
16715             }
16716             return promise;
16717           };
16718         },
16719         
16720
16721         /**
16722          * @ngdoc method
16723          * @methodOf  ui.grid.rowEdit.service:uiGridRowEditService
16724          * @name setSavePromise
16725          * @description Sets the promise associated with the row save, mandatory that
16726          * the saveRow event handler calls this method somewhere before returning.
16727          * <pre>
16728          *      gridApi.rowEdit.setSavePromise(grid, rowEntity)
16729          * </pre>
16730          * @param {object} grid the grid for which dirty rows should be returned
16731          * @param {object} rowEntity a data row from the grid for which a save has
16732          * been initiated
16733          * @param {promise} savePromise the promise that will be resolved when the
16734          * save is successful, or rejected if the save fails
16735          * 
16736          */
16737         setSavePromise: function (grid, rowEntity, savePromise) {
16738           var gridRow = grid.getRow( rowEntity );
16739           gridRow.rowEditSavePromise = savePromise;
16740         },
16741
16742
16743         /**
16744          * @ngdoc method
16745          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16746          * @name processSuccessPromise
16747          * @description  Returns a function that processes the successful
16748          * resolution of a save promise  
16749          * @param {object} grid the grid for which the promise should be processed
16750          * @param {GridRow} gridRow the row that has been saved
16751          * @returns {function} the success handling function
16752          */
16753         processSuccessPromise: function ( grid, gridRow ) {
16754           var self = this;
16755           
16756           return function() {
16757             delete gridRow.isSaving;
16758             delete gridRow.isDirty;
16759             delete gridRow.isError;
16760             delete gridRow.rowEditSaveTimer;
16761             self.removeRow( grid.rowEdit.errorRows, gridRow );
16762             self.removeRow( grid.rowEdit.dirtyRows, gridRow );
16763           };
16764         },
16765         
16766
16767         /**
16768          * @ngdoc method
16769          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16770          * @name processErrorPromise
16771          * @description  Returns a function that processes the failed
16772          * resolution of a save promise  
16773          * @param {object} grid the grid for which the promise should be processed
16774          * @param {GridRow} gridRow the row that is now in error
16775          * @returns {function} the error handling function
16776          */
16777         processErrorPromise: function ( grid, gridRow ) {
16778           return function() {
16779             delete gridRow.isSaving;
16780             delete gridRow.rowEditSaveTimer;
16781
16782             gridRow.isError = true;
16783             
16784             if (!grid.rowEdit.errorRows){
16785               grid.rowEdit.errorRows = [];
16786             }
16787             if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
16788               grid.rowEdit.errorRows.push( gridRow );
16789             }
16790           };
16791         },
16792         
16793         
16794         /**
16795          * @ngdoc method
16796          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16797          * @name removeRow
16798          * @description  Removes a row from a cache of rows - either
16799          * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows.  If the row
16800          * is not present silently does nothing. 
16801          * @param {array} rowArray the array from which to remove the row
16802          * @param {GridRow} gridRow the row that should be removed
16803          */
16804         removeRow: function( rowArray, removeGridRow ){
16805           angular.forEach( rowArray, function( gridRow, index ){
16806             if ( gridRow.uid === removeGridRow.uid ){
16807               rowArray.splice( index, 1);
16808             }
16809           });
16810         },
16811         
16812         
16813         /**
16814          * @ngdoc method
16815          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16816          * @name isRowPresent
16817          * @description  Checks whether a row is already present
16818          * in the given array 
16819          * @param {array} rowArray the array in which to look for the row
16820          * @param {GridRow} gridRow the row that should be looked for
16821          */
16822         isRowPresent: function( rowArray, removeGridRow ){
16823           var present = false;
16824           angular.forEach( rowArray, function( gridRow, index ){
16825             if ( gridRow.uid === removeGridRow.uid ){
16826               present = true;
16827             }
16828           });
16829           return present;
16830         },
16831
16832         
16833         /**
16834          * @ngdoc method
16835          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16836          * @name flushDirtyRows
16837          * @description Triggers a save event for all currently dirty rows, could
16838          * be used where user presses a save button or navigates away from the page
16839          * <pre>
16840          *      gridApi.rowEdit.flushDirtyRows(grid)
16841          * </pre>
16842          * @param {object} grid the grid for which dirty rows should be flushed
16843          * @returns {promise} a promise that represents the aggregate of all
16844          * of the individual save promises - i.e. it will be resolved when all
16845          * the individual save promises have been resolved.
16846          * 
16847          */
16848         flushDirtyRows: function(grid){
16849           var promises = [];
16850           angular.forEach(grid.rowEdit.dirtyRows, function( gridRow ){
16851             service.saveRow( grid, gridRow )();
16852             promises.push( gridRow.rowEditSavePromise );
16853           });
16854           
16855           return $q.all( promises );
16856         },
16857         
16858         
16859         /**
16860          * @ngdoc method
16861          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16862          * @name endEditCell
16863          * @description Receives an afterCellEdit event from the edit function,
16864          * and sets flags as appropriate.  Only the rowEntity parameter
16865          * is processed, although other params are available.  Grid
16866          * is automatically provided by the gridApi. 
16867          * @param {object} rowEntity the data entity for which the cell
16868          * was edited
16869          */        
16870         endEditCell: function( rowEntity, colDef, newValue, previousValue ){
16871           var grid = this.grid;
16872           var gridRow = grid.getRow( rowEntity );
16873           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
16874
16875           if ( newValue !== previousValue || gridRow.isDirty ){
16876             if ( !grid.rowEdit.dirtyRows ){
16877               grid.rowEdit.dirtyRows = [];
16878             }
16879             
16880             if ( !gridRow.isDirty ){
16881               gridRow.isDirty = true;
16882               grid.rowEdit.dirtyRows.push( gridRow );
16883             }
16884             
16885             delete gridRow.isError;
16886             
16887             service.considerSetTimer( grid, gridRow );
16888           }
16889         },
16890         
16891         
16892         /**
16893          * @ngdoc method
16894          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16895          * @name beginEditCell
16896          * @description Receives a beginCellEdit event from the edit function,
16897          * and cancels any rowEditSaveTimers if present, as the user is still editing
16898          * this row.  Only the rowEntity parameter
16899          * is processed, although other params are available.  Grid
16900          * is automatically provided by the gridApi. 
16901          * @param {object} rowEntity the data entity for which the cell
16902          * editing has commenced
16903          */
16904         beginEditCell: function( rowEntity, colDef ){
16905           var grid = this.grid;
16906           var gridRow = grid.getRow( rowEntity );
16907           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
16908           
16909           service.cancelTimer( grid, gridRow );
16910         },
16911
16912
16913         /**
16914          * @ngdoc method
16915          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16916          * @name cancelEditCell
16917          * @description Receives a cancelCellEdit event from the edit function,
16918          * and if the row was already dirty, restarts the save timer.  If the row
16919          * was not already dirty, then it's not dirty now either and does nothing.
16920          * 
16921          * Only the rowEntity parameter
16922          * is processed, although other params are available.  Grid
16923          * is automatically provided by the gridApi.
16924          *  
16925          * @param {object} rowEntity the data entity for which the cell
16926          * editing was cancelled
16927          */        
16928         cancelEditCell: function( rowEntity, colDef ){
16929           var grid = this.grid;
16930           var gridRow = grid.getRow( rowEntity );
16931           if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
16932           
16933           service.considerSetTimer( grid, gridRow );
16934         },
16935         
16936         
16937         /**
16938          * @ngdoc method
16939          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16940          * @name navigate
16941          * @description cellNav tells us that the selected cell has changed.  If
16942          * the new row had a timer running, then stop it similar to in a beginCellEdit
16943          * call.  If the old row is dirty and not the same as the new row, then 
16944          * start a timer on it.
16945          * @param {object} newRowCol the row and column that were selected
16946          * @param {object} oldRowCol the row and column that was left
16947          * 
16948          */
16949         navigate: function( newRowCol, oldRowCol ){
16950           var grid = this.grid;
16951           if ( newRowCol.row.rowEditSaveTimer ){
16952             service.cancelTimer( grid, newRowCol.row );
16953           }
16954
16955           if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
16956             service.considerSetTimer( grid, oldRowCol.row );
16957           }
16958         },
16959         
16960         
16961         /**
16962          * @ngdoc property
16963          * @propertyOf ui.grid.rowEdit.api:GridOptions
16964          * @name rowEditWaitInterval
16965          * @description How long the grid should wait for another change on this row
16966          * before triggering a save (in milliseconds).  If set to -1, then saves are 
16967          * never triggered by timer (implying that the user will call flushDirtyRows() 
16968          * manually)
16969          * 
16970          * @example
16971          * Setting the wait interval to 4 seconds
16972          * <pre>
16973          *   $scope.gridOptions = { rowEditWaitInterval: 4000 }
16974          * </pre>
16975          * 
16976          */
16977         /**
16978          * @ngdoc method
16979          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16980          * @name considerSetTimer
16981          * @description Consider setting a timer on this row (if it is dirty).  if there is a timer running 
16982          * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is 
16983          * dirty and not currently saving then set a new timer
16984          * @param {object} grid the grid for which we are processing
16985          * @param {GridRow} gridRow the row for which the timer should be adjusted
16986          * 
16987          */
16988         considerSetTimer: function( grid, gridRow ){
16989           service.cancelTimer( grid, gridRow );
16990           
16991           if ( gridRow.isDirty && !gridRow.isSaving ){
16992             if ( grid.options.rowEditWaitInterval !== -1 ){
16993               var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
16994               gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
16995             }
16996           }
16997         },
16998         
16999
17000         /**
17001          * @ngdoc method
17002          * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
17003          * @name cancelTimer
17004          * @description cancel the $interval for any timer running on this row
17005          * then delete the timer itself
17006          * @param {object} grid the grid for which we are processing
17007          * @param {GridRow} gridRow the row for which the timer should be adjusted
17008          * 
17009          */
17010         cancelTimer: function( grid, gridRow ){
17011           if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
17012             $interval.cancel(gridRow.rowEditSaveTimer);
17013             delete gridRow.rowEditSaveTimer;
17014           }
17015         },
17016
17017
17018         /**
17019          * @ngdoc method
17020          * @methodOf ui.grid.rowEdit.api:PublicApi
17021          * @name setRowsDirty
17022          * @description Sets each of the rows passed in dataRows
17023          * to be dirty.  note that if you have only just inserted the
17024          * rows into your data you will need to wait for a $digest cycle
17025          * before the gridRows are present - so often you would wrap this
17026          * call in a $interval or $timeout
17027          * <pre>
17028          *      $interval( function() {
17029          *        gridApi.rowEdit.setRowsDirty(grid, myDataRows);
17030          *      }, 0, 1);
17031          * </pre>
17032          * @param {object} grid the grid for which rows should be set dirty
17033          * @param {array} dataRows the data entities for which the gridRows
17034          * should be set dirty.  
17035          * 
17036          */
17037         setRowsDirty: function( grid, myDataRows ) {
17038           var gridRow;
17039           myDataRows.forEach( function( value, index ){
17040             gridRow = grid.getRow( value );
17041             if ( gridRow ){
17042               if ( !grid.rowEdit.dirtyRows ){
17043                 grid.rowEdit.dirtyRows = [];
17044               }
17045               
17046               if ( !gridRow.isDirty ){
17047                 gridRow.isDirty = true;
17048                 grid.rowEdit.dirtyRows.push( gridRow );
17049               }
17050               
17051               delete gridRow.isError;
17052               
17053               service.considerSetTimer( grid, gridRow );
17054             } else {
17055               gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
17056             }
17057           });
17058         }
17059         
17060         
17061       };
17062
17063       return service;
17064
17065     }]);
17066
17067   /**
17068    *  @ngdoc directive
17069    *  @name ui.grid.rowEdit.directive:uiGridEdit
17070    *  @element div
17071    *  @restrict A
17072    *
17073    *  @description Adds row editing features to the ui-grid-edit directive.
17074    *
17075    */
17076   module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants', 
17077   function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
17078     return {
17079       replace: true,
17080       priority: 0,
17081       require: '^uiGrid',
17082       scope: false,
17083       compile: function () {
17084         return {
17085           pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17086             uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
17087           },
17088           post: function ($scope, $elm, $attrs, uiGridCtrl) {            
17089           }
17090         };
17091       }
17092     };
17093   }]);
17094
17095
17096   /**
17097    *  @ngdoc directive
17098    *  @name ui.grid.rowEdit.directive:uiGridViewport
17099    *  @element div
17100    *
17101    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
17102    *  for the grid row to allow coloring of saving and error rows
17103    */
17104   module.directive('uiGridViewport',
17105     ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
17106       function ($compile, uiGridConstants, gridUtil, $parse) {
17107         return {
17108           priority: -200, // run after default  directive
17109           scope: false,
17110           compile: function ($elm, $attrs) {
17111             var rowRepeatDiv = angular.element($elm.children().children()[0]);
17112             
17113             var existingNgClass = rowRepeatDiv.attr("ng-class");
17114             var newNgClass = '';
17115             if ( existingNgClass ) {
17116               newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
17117             } else {
17118               newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
17119             }
17120             rowRepeatDiv.attr("ng-class", newNgClass);
17121
17122             return {
17123               pre: function ($scope, $elm, $attrs, controllers) {
17124
17125               },
17126               post: function ($scope, $elm, $attrs, controllers) {
17127               }
17128             };
17129           }
17130         };
17131       }]);
17132
17133 })();
17134
17135 (function () {
17136   'use strict';
17137
17138   /**
17139    * @ngdoc overview
17140    * @name ui.grid.selection
17141    * @description
17142    *
17143    *  # ui.grid.selection
17144    * This module provides row selection
17145    * <br/>
17146    * <br/>
17147    *
17148    * <div doc-module-components="ui.grid.selection"></div>
17149    */
17150
17151   var module = angular.module('ui.grid.selection', ['ui.grid']);
17152
17153   /**
17154    *  @ngdoc object
17155    *  @name ui.grid.selection.constant:uiGridSelectionConstants
17156    *
17157    *  @description constants available in selection module
17158    */
17159   module.constant('uiGridSelectionConstants', {
17160     featureName: "selection",
17161     selectionRowHeaderColName: 'selectionRowHeaderCol'
17162   });
17163
17164   /**
17165    *  @ngdoc service
17166    *  @name ui.grid.selection.service:uiGridSelectionService
17167    *
17168    *  @description Services for selection features
17169    */
17170   module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
17171     function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
17172
17173       var service = {
17174
17175         initializeGrid: function (grid) {
17176
17177           //add feature namespace and any properties to grid for needed state
17178           grid.selection = {};
17179           grid.selection.lastSelectedRow = null;
17180           grid.selection.selectAll = false;
17181
17182           service.defaultGridOptions(grid.options);
17183
17184           /**
17185            *  @ngdoc object
17186            *  @name ui.grid.selection.api:PublicApi
17187            *
17188            *  @description Public Api for selection feature
17189            */
17190           var publicApi = {
17191             events: {
17192               selection: {
17193                 /**
17194                  * @ngdoc event
17195                  * @name rowSelectionChanged
17196                  * @eventOf  ui.grid.selection.api:PublicApi
17197                  * @description  is raised after the row.isSelected state is changed
17198                  * @param {GridRow} row the row that was selected/deselected
17199                  */
17200                 rowSelectionChanged: function (scope, row) {
17201                 },
17202                 /**
17203                  * @ngdoc event
17204                  * @name rowSelectionChangedBatch
17205                  * @eventOf  ui.grid.selection.api:PublicApi
17206                  * @description  is raised after the row.isSelected state is changed
17207                  * in bulk, if the `enableSelectionBatchEvent` option is set to true
17208                  * (which it is by default).  This allows more efficient processing 
17209                  * of bulk events.
17210                  * @param {array} rows the rows that were selected/deselected
17211                  */
17212                 rowSelectionChangedBatch: function (scope, rows) {
17213                 }
17214               }
17215             },
17216             methods: {
17217               selection: {
17218                 /**
17219                  * @ngdoc function
17220                  * @name toggleRowSelection
17221                  * @methodOf  ui.grid.selection.api:PublicApi
17222                  * @description Toggles data row as selected or unselected
17223                  * @param {object} rowEntity gridOptions.data[] array instance
17224                  */
17225                 toggleRowSelection: function (rowEntity) {
17226                   var row = grid.getRow(rowEntity);
17227                   if (row !== null) {
17228                     service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17229                   }
17230                 },
17231                 /**
17232                  * @ngdoc function
17233                  * @name selectRow
17234                  * @methodOf  ui.grid.selection.api:PublicApi
17235                  * @description Select the data row
17236                  * @param {object} rowEntity gridOptions.data[] array instance
17237                  */
17238                 selectRow: function (rowEntity) {
17239                   var row = grid.getRow(rowEntity);
17240                   if (row !== null && !row.isSelected) {
17241                     service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17242                   }
17243                 },
17244                 /**
17245                  * @ngdoc function
17246                  * @name selectRowByVisibleIndex
17247                  * @methodOf  ui.grid.selection.api:PublicApi
17248                  * @description Select the specified row by visible index (i.e. if you
17249                  * specify row 0 you'll get the first visible row selected).  In this context
17250                  * visible means of those rows that are theoretically visible (i.e. not filtered),
17251                  * rather than rows currently rendered on the screen.
17252                  * @param {number} index index within the rowsVisible array
17253                  */
17254                 selectRowByVisibleIndex: function ( rowNum ) {
17255                   var row = grid.renderContainers.body.visibleRowCache[rowNum];
17256                   if (row !== null && !row.isSelected) {
17257                     service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17258                   }
17259                 },
17260                 /**
17261                  * @ngdoc function
17262                  * @name unSelectRow
17263                  * @methodOf  ui.grid.selection.api:PublicApi
17264                  * @description UnSelect the data row
17265                  * @param {object} rowEntity gridOptions.data[] array instance
17266                  */
17267                 unSelectRow: function (rowEntity) {
17268                   var row = grid.getRow(rowEntity);
17269                   if (row !== null && row.isSelected) {
17270                     service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17271                   }
17272                 },
17273                 /**
17274                  * @ngdoc function
17275                  * @name selectAllRows
17276                  * @methodOf  ui.grid.selection.api:PublicApi
17277                  * @description Selects all rows.  Does nothing if multiSelect = false
17278                  */
17279                 selectAllRows: function () {
17280                   if (grid.options.multiSelect === false) {
17281                     return;
17282                   }
17283
17284                   var changedRows = [];
17285                   grid.rows.forEach(function (row) {
17286                     if ( !row.isSelected ){
17287                       row.isSelected = true;
17288                       service.decideRaiseSelectionEvent( grid, row, changedRows );
17289                     }
17290                   });
17291                   service.decideRaiseSelectionBatchEvent( grid, changedRows );
17292                 },
17293                 /**
17294                  * @ngdoc function
17295                  * @name selectAllVisibleRows
17296                  * @methodOf  ui.grid.selection.api:PublicApi
17297                  * @description Selects all visible rows.  Does nothing if multiSelect = false
17298                  */
17299                 selectAllVisibleRows: function () {
17300                   if (grid.options.multiSelect === false) {
17301                     return;
17302                   }
17303
17304                   var changedRows = [];
17305                   grid.rows.forEach(function (row) {
17306                     if (row.visible) {
17307                       if (!row.isSelected){
17308                         row.isSelected = true;
17309                         service.decideRaiseSelectionEvent( grid, row, changedRows );
17310                       }
17311                     } else {
17312                       if (row.isSelected){
17313                         row.isSelected = false;
17314                         service.decideRaiseSelectionEvent( grid, row, changedRows );
17315                       }
17316                     }
17317                   });
17318                   service.decideRaiseSelectionBatchEvent( grid, changedRows );
17319                 },
17320                 /**
17321                  * @ngdoc function
17322                  * @name clearSelectedRows
17323                  * @methodOf  ui.grid.selection.api:PublicApi
17324                  * @description Unselects all rows
17325                  */
17326                 clearSelectedRows: function () {
17327                   service.clearSelectedRows(grid);
17328                 },
17329                 /**
17330                  * @ngdoc function
17331                  * @name getSelectedRows
17332                  * @methodOf  ui.grid.selection.api:PublicApi
17333                  * @description returns all selectedRow's entity references
17334                  */
17335                 getSelectedRows: function () {
17336                   return service.getSelectedRows(grid).map(function (gridRow) {
17337                     return gridRow.entity;
17338                   });
17339                 },
17340                 /**
17341                  * @ngdoc function
17342                  * @name getSelectedGridRows
17343                  * @methodOf  ui.grid.selection.api:PublicApi
17344                  * @description returns all selectedRow's as gridRows
17345                  */
17346                 getSelectedGridRows: function () {
17347                   return service.getSelectedRows(grid);
17348                 },
17349                 /**
17350                  * @ngdoc function
17351                  * @name setMultiSelect
17352                  * @methodOf  ui.grid.selection.api:PublicApi
17353                  * @description Sets the current gridOption.multiSelect to true or false
17354                  * @param {bool} multiSelect true to allow multiple rows
17355                  */
17356                 setMultiSelect: function (multiSelect) {
17357                   grid.options.multiSelect = multiSelect;
17358                 },
17359                 /**
17360                  * @ngdoc function
17361                  * @name setModifierKeysToMultiSelect
17362                  * @methodOf  ui.grid.selection.api:PublicApi
17363                  * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
17364                  * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
17365                  */
17366                 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
17367                   grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
17368                 },
17369                 /**
17370                  * @ngdoc function
17371                  * @name getSelectAllState
17372                  * @methodOf  ui.grid.selection.api:PublicApi
17373                  * @description Returns whether or not the selectAll checkbox is currently ticked.  The
17374                  * grid doesn't automatically select rows when you add extra data - so when you add data
17375                  * you need to explicitly check whether the selectAll is set, and then call setVisible rows
17376                  * if it is
17377                  */
17378                 getSelectAllState: function () {
17379                   return grid.selection.selectAll;
17380                 }
17381                 
17382               }
17383             }
17384           };
17385
17386           grid.api.registerEventsFromObject(publicApi.events);
17387
17388           grid.api.registerMethodsFromObject(publicApi.methods);
17389
17390         },
17391
17392         defaultGridOptions: function (gridOptions) {
17393           //default option to true unless it was explicitly set to false
17394           /**
17395            *  @ngdoc object
17396            *  @name ui.grid.selection.api:GridOptions
17397            *
17398            *  @description GridOptions for selection feature, these are available to be  
17399            *  set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17400            */
17401
17402           /**
17403            *  @ngdoc object
17404            *  @name enableRowSelection
17405            *  @propertyOf  ui.grid.selection.api:GridOptions
17406            *  @description Enable row selection for entire grid.
17407            *  <br/>Defaults to true
17408            */
17409           gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
17410           /**
17411            *  @ngdoc object
17412            *  @name multiSelect
17413            *  @propertyOf  ui.grid.selection.api:GridOptions
17414            *  @description Enable multiple row selection for entire grid
17415            *  <br/>Defaults to true
17416            */
17417           gridOptions.multiSelect = gridOptions.multiSelect !== false;
17418           /**
17419            *  @ngdoc object
17420            *  @name noUnselect
17421            *  @propertyOf  ui.grid.selection.api:GridOptions
17422            *  @description Prevent a row from being unselected.  Works in conjunction
17423            *  with `multiselect = false` and `gridApi.selection.selectRow()` to allow
17424            *  you to create a single selection only grid - a row is always selected, you
17425            *  can only select different rows, you can't unselect the row.
17426            *  <br/>Defaults to false
17427            */
17428           gridOptions.noUnselect = gridOptions.noUnselect === true;
17429           /**
17430            *  @ngdoc object
17431            *  @name modifierKeysToMultiSelect
17432            *  @propertyOf  ui.grid.selection.api:GridOptions
17433            *  @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
17434            *  <br/>Defaults to false
17435            */
17436           gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
17437           /**
17438            *  @ngdoc object
17439            *  @name enableRowHeaderSelection
17440            *  @propertyOf  ui.grid.selection.api:GridOptions
17441            *  @description Enable a row header to be used for selection
17442            *  <br/>Defaults to true
17443            */
17444           gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
17445           /**
17446            *  @ngdoc object
17447            *  @name enableSelectAll
17448            *  @propertyOf  ui.grid.selection.api:GridOptions
17449            *  @description Enable the select all checkbox at the top of the selectionRowHeader
17450            *  <br/>Defaults to true
17451            */
17452           gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
17453           /**
17454            *  @ngdoc object
17455            *  @name enableSelectionBatchEvent
17456            *  @propertyOf  ui.grid.selection.api:GridOptions
17457            *  @description If selected rows are changed in bulk, either via the API or
17458            *  via the selectAll checkbox, then a separate event is fired.  Setting this
17459            *  option to false will cause the rowSelectionChanged event to be called multiple times
17460            *  instead  
17461            *  <br/>Defaults to true
17462            */
17463           gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
17464           /**
17465            *  @ngdoc object
17466            *  @name selectionRowHeaderWidth
17467            *  @propertyOf  ui.grid.selection.api:GridOptions
17468            *  @description can be used to set a custom width for the row header selection column
17469            *  <br/>Defaults to 30px
17470            */
17471           gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
17472         },
17473
17474         /**
17475          * @ngdoc function
17476          * @name toggleRowSelection
17477          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17478          * @description Toggles row as selected or unselected
17479          * @param {Grid} grid grid object
17480          * @param {GridRow} row row to select or deselect
17481          * @param {bool} multiSelect if false, only one row at time can be selected
17482          * @param {bool} noUnselect if true then rows cannot be unselected
17483          */
17484         toggleRowSelection: function (grid, row, multiSelect, noUnselect) {
17485           var selected = row.isSelected;
17486
17487           if (!multiSelect && !selected) {
17488             service.clearSelectedRows(grid);
17489           } else if (!multiSelect && selected) {
17490             var selectedRows = service.getSelectedRows(grid);
17491             if (selectedRows.length > 1) {
17492               selected = false; // Enable reselect of the row
17493               service.clearSelectedRows(grid);
17494             }
17495           }
17496           
17497           if (selected && noUnselect){
17498             // don't deselect the row 
17499           } else {
17500             row.isSelected = !selected;
17501             if (row.isSelected === true) {
17502               grid.selection.lastSelectedRow = row;
17503             }
17504             grid.api.selection.raise.rowSelectionChanged(row);
17505           }
17506         },
17507         /**
17508          * @ngdoc function
17509          * @name shiftSelect
17510          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17511          * @description selects a group of rows from the last selected row using the shift key
17512          * @param {Grid} grid grid object
17513          * @param {GridRow} clicked row
17514          * @param {bool} multiSelect if false, does nothing this is for multiSelect only
17515          */
17516         shiftSelect: function (grid, row, multiSelect) {
17517           if (!multiSelect) {
17518             return;
17519           }
17520           var selectedRows = service.getSelectedRows(grid);
17521           var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
17522           var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
17523           //reverse select direction
17524           if (fromRow > toRow) {
17525             var tmp = fromRow;
17526             fromRow = toRow;
17527             toRow = tmp;
17528           }
17529           
17530           var changedRows = [];
17531           for (var i = fromRow; i <= toRow; i++) {
17532             var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
17533             if (rowToSelect) {
17534               if ( !rowToSelect.isSelected ){
17535                 rowToSelect.isSelected = true;
17536                 grid.selection.lastSelectedRow = rowToSelect;
17537                 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows );
17538               }
17539             }
17540           }
17541           service.decideRaiseSelectionBatchEvent( grid, changedRows );
17542         },
17543         /**
17544          * @ngdoc function
17545          * @name getSelectedRows
17546          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17547          * @description Returns all the selected rows
17548          * @param {Grid} grid grid object
17549          */
17550         getSelectedRows: function (grid) {
17551           return grid.rows.filter(function (row) {
17552             return row.isSelected;
17553           });
17554         },
17555
17556         /**
17557          * @ngdoc function
17558          * @name clearSelectedRows
17559          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17560          * @description Clears all selected rows
17561          * @param {Grid} grid grid object
17562          */
17563         clearSelectedRows: function (grid) {
17564           var changedRows = [];
17565           service.getSelectedRows(grid).forEach(function (row) {
17566             if ( row.isSelected ){
17567               row.isSelected = false;
17568               service.decideRaiseSelectionEvent( grid, row, changedRows );
17569             }
17570           });
17571           service.decideRaiseSelectionBatchEvent( grid, changedRows );
17572         },
17573         
17574         /**
17575          * @ngdoc function
17576          * @name decideRaiseSelectionEvent
17577          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17578          * @description Decides whether to raise a single event or a batch event
17579          * @param {Grid} grid grid object
17580          * @param {GridRow} row row that has changed
17581          * @param {array} changedRows an array to which we can append the changed
17582          * row if we're doing batch events
17583          */
17584         decideRaiseSelectionEvent: function( grid, row, changedRows ){
17585           if ( !grid.options.enableSelectionBatchEvent ){
17586             grid.api.selection.raise.rowSelectionChanged(row);
17587           } else {
17588             changedRows.push(row);
17589           }
17590         },
17591         
17592         /**
17593          * @ngdoc function
17594          * @name raiseSelectionEvent
17595          * @methodOf  ui.grid.selection.service:uiGridSelectionService
17596          * @description Decides whether we need to raise a batch event, and 
17597          * raises it if we do.
17598          * @param {Grid} grid grid object
17599          * @param {array} changedRows an array of changed rows, only populated
17600          * if we're doing batch events
17601          */
17602         decideRaiseSelectionBatchEvent: function( grid, changedRows ){
17603           if ( changedRows.length > 0 ){
17604             grid.api.selection.raise.rowSelectionChangedBatch(changedRows);
17605           }
17606         }        
17607       };
17608
17609       return service;
17610
17611     }]);
17612
17613   /**
17614    *  @ngdoc directive
17615    *  @name ui.grid.selection.directive:uiGridSelection
17616    *  @element div
17617    *  @restrict A
17618    *
17619    *  @description Adds selection features to grid
17620    *
17621    *  @example
17622    <example module="app">
17623    <file name="app.js">
17624    var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
17625
17626    app.controller('MainCtrl', ['$scope', function ($scope) {
17627       $scope.data = [
17628         { name: 'Bob', title: 'CEO' },
17629             { name: 'Frank', title: 'Lowly Developer' }
17630       ];
17631
17632       $scope.columnDefs = [
17633         {name: 'name', enableCellEdit: true},
17634         {name: 'title', enableCellEdit: true}
17635       ];
17636     }]);
17637    </file>
17638    <file name="index.html">
17639    <div ng-controller="MainCtrl">
17640    <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
17641    </div>
17642    </file>
17643    </example>
17644    */
17645   module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache',
17646     function (uiGridSelectionConstants, uiGridSelectionService, $templateCache) {
17647       return {
17648         replace: true,
17649         priority: 0,
17650         require: '^uiGrid',
17651         scope: false,
17652         compile: function () {
17653           return {
17654             pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17655               uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
17656               if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
17657                 var selectionRowHeaderDef = {
17658                   name: uiGridSelectionConstants.selectionRowHeaderColName,
17659                   displayName: '',
17660                   width:  uiGridCtrl.grid.options.selectionRowHeaderWidth,
17661                   cellTemplate: 'ui-grid/selectionRowHeader',
17662                   headerCellTemplate: 'ui-grid/selectionHeaderCell',
17663                   enableColumnResizing: false,
17664                   enableColumnMenu: false
17665                 };
17666
17667                 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
17668               }
17669             },
17670             post: function ($scope, $elm, $attrs, uiGridCtrl) {
17671
17672             }
17673           };
17674         }
17675       };
17676     }]);
17677
17678   module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService',
17679     function ($templateCache, uiGridSelectionService) {
17680       return {
17681         replace: true,
17682         restrict: 'E',
17683         template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
17684         scope: true,
17685         require: '^uiGrid',
17686         link: function($scope, $elm, $attrs, uiGridCtrl) {
17687           var self = uiGridCtrl.grid;
17688           $scope.selectButtonClick = function(row, evt) {
17689             if (evt.shiftKey) {
17690               uiGridSelectionService.shiftSelect(self, row, self.options.multiSelect);
17691             }
17692             else if (evt.ctrlKey || evt.metaKey) {
17693               uiGridSelectionService.toggleRowSelection(self, row, self.options.multiSelect, self.options.noUnselect); 
17694             }
17695             else {
17696               uiGridSelectionService.toggleRowSelection(self, row, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
17697             }
17698           };
17699         }
17700       };
17701     }]);
17702
17703   module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
17704     function ($templateCache, uiGridSelectionService) {
17705       return {
17706         replace: true,
17707         restrict: 'E',
17708         template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
17709         scope: false,
17710         link: function($scope, $elm, $attrs, uiGridCtrl) {
17711           var self = $scope.col.grid;
17712           
17713           $scope.headerButtonClick = function(row, evt) {
17714             if ( self.selection.selectAll ){
17715               uiGridSelectionService.clearSelectedRows(self);
17716               if ( self.options.noUnselect ){
17717                 self.api.selection.selectRowByVisibleIndex(0);
17718               }
17719               self.selection.selectAll = false;
17720             } else {
17721               if ( self.options.multiSelect ){
17722                 self.api.selection.selectAllVisibleRows();
17723                 self.selection.selectAll = true;
17724               }
17725             }
17726           };
17727         }
17728       };
17729     }]);
17730
17731   /**
17732    *  @ngdoc directive
17733    *  @name ui.grid.selection.directive:uiGridViewport
17734    *  @element div
17735    *
17736    *  @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
17737    *  for the grid row
17738    */
17739   module.directive('uiGridViewport',
17740     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
17741       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
17742         return {
17743           priority: -200, // run after default  directive
17744           scope: false,
17745           compile: function ($elm, $attrs) {
17746             var rowRepeatDiv = angular.element($elm.children().children()[0]);
17747
17748             var existingNgClass = rowRepeatDiv.attr("ng-class");
17749             var newNgClass = '';
17750             if ( existingNgClass ) {
17751               newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
17752             } else {
17753               newNgClass = "{'ui-grid-row-selected': row.isSelected}";
17754             }
17755             rowRepeatDiv.attr("ng-class", newNgClass);
17756
17757             return {
17758               pre: function ($scope, $elm, $attrs, controllers) {
17759
17760               },
17761               post: function ($scope, $elm, $attrs, controllers) {
17762               }
17763             };
17764           }
17765         };
17766       }]);
17767
17768   /**
17769    *  @ngdoc directive
17770    *  @name ui.grid.selection.directive:uiGridCell
17771    *  @element div
17772    *  @restrict A
17773    *
17774    *  @description Stacks on top of ui.grid.uiGridCell to provide selection feature
17775    */
17776   module.directive('uiGridCell',
17777     ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
17778       function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
17779         return {
17780           priority: -200, // run after default uiGridCell directive
17781           restrict: 'A',
17782           scope: false,
17783           link: function ($scope, $elm, $attrs) {
17784
17785             if ($scope.grid.options.enableRowSelection && !$scope.grid.options.enableRowHeaderSelection) {
17786               $elm.addClass('ui-grid-disable-selection');
17787               registerRowSelectionEvents();
17788             }
17789
17790             function registerRowSelectionEvents() {
17791               var touchStartTime = 0;
17792               var touchTimeout = 300;
17793               var selectCells = function(evt){
17794                 if (evt.shiftKey) {
17795                   uiGridSelectionService.shiftSelect($scope.grid, $scope.row, $scope.grid.options.multiSelect);
17796                 }
17797                 else if (evt.ctrlKey || evt.metaKey) {
17798                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
17799                 }
17800                 else {
17801                   uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
17802                 }
17803                 $scope.$apply();
17804               };
17805
17806               $elm.on('touchstart', function(event) {
17807                 touchStartTime = (new Date()).getTime();
17808               });
17809
17810               $elm.on('touchend', function (evt) {
17811                 var touchEndTime = (new Date()).getTime();
17812                 var touchTime = touchEndTime - touchStartTime;
17813
17814                 if (touchTime < touchTimeout ) {
17815                   // short touch
17816                   selectCells(evt);
17817                 }
17818               });
17819
17820               $elm.on('click', function (evt) {
17821                 selectCells(evt);
17822               });
17823             }
17824           }
17825         };
17826       }]);
17827
17828 })();
17829
17830 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
17831   'use strict';
17832
17833   $templateCache.put('ui-grid/ui-grid-footer',
17834     "<div class=\"ui-grid-footer-panel\"><div ui-grid-group-panel ng-show=\"grid.options.showGroupPanel\"></div><div class=\"ui-grid-footer ui-grid-footer-viewport\"><div class=\"ui-grid-footer-canvas\"><div ng-repeat=\"col in colContainer.renderedColumns track by col.colDef.name\" ui-grid-footer-cell col=\"col\" render-index=\"$index\" class=\"ui-grid-footer-cell clearfix\" ng-style=\"$index === 0 && colContainer.columnStyle($index)\"></div></div></div></div>"
17835   );
17836
17837
17838   $templateCache.put('ui-grid/ui-grid-group-panel',
17839     "<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>"
17840   );
17841
17842
17843   $templateCache.put('ui-grid/ui-grid-header',
17844     "<div class=\"ui-grid-header\"><div class=\"ui-grid-top-panel\"><div ui-grid-group-panel ng-show=\"grid.options.showGroupPanel\"></div><div class=\"ui-grid-header-viewport\"><div class=\"ui-grid-header-canvas\"><div class=\"ui-grid-header-cell clearfix\" ng-repeat=\"col in colContainer.renderedColumns track by col.colDef.name\" ui-grid-header-cell col=\"col\" render-index=\"$index\" ng-style=\"$index === 0 && colContainer.columnStyle($index)\"></div></div></div><div ui-grid-menu></div></div></div>"
17845   );
17846
17847
17848   $templateCache.put('ui-grid/ui-grid-menu-button',
17849     "<div class=\"ui-grid-menu-button\" ng-click=\"toggleMenu()\"><div class=\"ui-grid-icon-container\"><i class=\"ui-grid-icon-menu\">&nbsp;</i></div><div ui-grid-menu menu-items=\"menuItems\"></div></div>"
17850   );
17851
17852
17853   $templateCache.put('ui-grid/ui-grid-no-header',
17854     "<div class=\"ui-grid-top-panel\"></div>"
17855   );
17856
17857
17858   $templateCache.put('ui-grid/ui-grid-row',
17859     "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" ui-grid-cell></div>"
17860   );
17861
17862
17863   $templateCache.put('ui-grid/ui-grid',
17864     "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
17865     "      /* Styles for the grid */\n" +
17866     "    }\n" +
17867     "\n" +
17868     "    .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
17869     "      height: {{ grid.options.rowHeight }}px;\n" +
17870     "    }\n" +
17871     "\n" +
17872     "    .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
17873     "      border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
17874     "    }\n" +
17875     "\n" +
17876     "    {{ grid.verticalScrollbarStyles }}\n" +
17877     "    {{ grid.horizontalScrollbarStyles }}\n" +
17878     "\n" +
17879     "    .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
17880     "      padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
17881     "    }\n" +
17882     "\n" +
17883     "    {{ grid.customStyles }}</style><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></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 ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div>"
17884   );
17885
17886
17887   $templateCache.put('ui-grid/uiGridCell',
17888     "<div class=\"ui-grid-cell-contents\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
17889   );
17890
17891
17892   $templateCache.put('ui-grid/uiGridColumnFilter',
17893     "<li class=\"ui-grid-menu-item ui-grid-clearfix ui-grid-column-filter\" ng-show=\"itemShown()\" ng-click=\"$event.stopPropagation();\"><div class=\"input-container\"><input class=\"column-filter-input\" type=\"text\" ng-model=\"item.model\" placeholder=\"{{ i18n.search.placeholder }}\"><div class=\"column-filter-cancel-icon-container\"><i class=\"ui-grid-filter-cancel ui-grid-icon-cancel column-filter-cancel-icon\">&nbsp;</i></div></div><div style=\"button-container\" ng-click=\"item.action($event)\"><div class=\"ui-grid-button\"><i class=\"ui-grid-icon-search\">&nbsp;</i></div></div></li>"
17894   );
17895
17896
17897   $templateCache.put('ui-grid/uiGridColumnMenu',
17898     "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
17899     "    <div class=\"inner\" ng-show=\"menuShown\">\n" +
17900     "      <ul>\n" +
17901     "        <div ng-show=\"grid.options.enableSorting\">\n" +
17902     "          <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" +
17903     "          <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" +
17904     "          <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
17905     "        </div>\n" +
17906     "      </ul>\n" +
17907     "    </div>\n" +
17908     "  </div> --></div></div>"
17909   );
17910
17911
17912   $templateCache.put('ui-grid/uiGridFooterCell',
17913     "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationValue() }}</div></div>"
17914   );
17915
17916
17917   $templateCache.put('ui-grid/uiGridHeaderCell',
17918     "<div ng-class=\"{ 'sortable': sortable }\"><div class=\"ui-grid-vertical-bar\">&nbsp;</div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><span>{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-visible=\"col.sort.direction\" 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 }\">&nbsp;</span></div><div class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader  && col.colDef.enableColumnMenu !== false\" class=\"ui-grid-column-menu-button\" ng-click=\"toggleMenu($event)\"><i class=\"ui-grid-icon-angle-down\">&nbsp;</i></div><div ng-if=\"filterable\" class=\"ui-grid-filter-container\" ng-repeat=\"colFilter in col.filters\"><input type=\"text\" class=\"ui-grid-filter-input\" ng-model=\"colFilter.term\" ng-click=\"$event.stopPropagation()\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\"><div class=\"ui-grid-filter-button\" ng-click=\"colFilter.term = null\"><i class=\"ui-grid-icon-cancel\" ng-show=\"!!colFilter.term\">&nbsp;</i><!-- use !! because angular interprets 'f' as false --></div></div></div>"
17919   );
17920
17921
17922   $templateCache.put('ui-grid/uiGridMenu',
17923     "<div class=\"ui-grid-menu\" ng-if=\"shown\"><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\"><ul class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" ui-grid-menu-item action=\"item.action\" title=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\"></li></ul></div></div></div>"
17924   );
17925
17926
17927   $templateCache.put('ui-grid/uiGridMenuItem',
17928     "<li class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active' : active() }\"><i ng-class=\"icon\"></i> {{ title }}</li>"
17929   );
17930
17931
17932   $templateCache.put('ui-grid/uiGridRenderContainer',
17933     "<div class=\"ui-grid-render-container\"><div ui-grid-header></div><div ui-grid-viewport></div><div ui-grid-footer ng-if=\"grid.options.showFooter\"></div><!-- native scrolling --><div ui-grid-native-scrollbar ng-if=\"enableVerticalScrollbar\" type=\"vertical\"></div><div ui-grid-native-scrollbar ng-if=\"enableHorizontalScrollbar\" type=\"horizontal\"></div></div>"
17934   );
17935
17936
17937   $templateCache.put('ui-grid/uiGridViewport',
17938     "<div class=\"ui-grid-viewport\"><div class=\"ui-grid-canvas\"><div ng-repeat=\"(rowRenderIndex, row) in rowContainer.renderedRows track by $index\" class=\"ui-grid-row\" ng-style=\"containerCtrl.rowStyle(rowRenderIndex)\"><div ui-grid-row=\"row\" row-render-index=\"rowRenderIndex\"></div></div></div></div>"
17939   );
17940
17941
17942   $templateCache.put('ui-grid/cellEditor',
17943     "<div><form name=\"inputForm\"><input type=\"{{inputType}}\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
17944   );
17945
17946
17947   $templateCache.put('ui-grid/dropdownEditor',
17948     "<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>"
17949   );
17950
17951
17952   $templateCache.put('ui-grid/expandableRow',
17953     "<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() - grid.verticalScrollbarWidth) + 'px'\n" +
17954     "     , height: grid.options.expandableRowHeight + 'px'}\"></div>"
17955   );
17956
17957
17958   $templateCache.put('ui-grid/expandableRowHeader',
17959     "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity)\"></i></div></div>"
17960   );
17961
17962
17963   $templateCache.put('ui-grid/expandableScrollFiller',
17964     "<div ng-if=\"expandableRow.shouldRenderFiller()\" style=\"float:left; margin-top: 2px; margin-bottom: 2px\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px',\n" +
17965     "              height: grid.options.expandableRowHeight + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{ 'margin-top': ( grid.options.expandableRowHeight/2 - 5) + 'px',\n" +
17966     "            'margin-left' : ((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5) + 'px' }\"></i></div>"
17967   );
17968
17969
17970   $templateCache.put('ui-grid/csvLink',
17971     "<span class=\"ui-grid-exporter-csv-link-span\"><a href=\"data:text/csv;charset=UTF-8,CSV_CONTENT\">LINK_LABEL</a></span>"
17972   );
17973
17974
17975   $templateCache.put('ui-grid/importerMenuItem',
17976     "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
17977   );
17978
17979
17980   $templateCache.put('ui-grid/importerMenuItemContainer',
17981     "<div ui-grid-importer-menu-item></div>"
17982   );
17983
17984
17985   $templateCache.put('ui-grid/ui-grid-paging',
17986     "<div class=\"ui-grid-pager-panel\" ui-grid-pager><div class=\"ui-grid-pager-container\"><div class=\"ui-grid-pager-control\"><button type=\"button\" ng-click=\"pageToFirst()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle\"><div class=\"first-bar\"></div></div></button> <button type=\"button\" ng-click=\"pageBackward()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle prev-triangle\"></div></button> <input type=\"number\" ng-model=\"grid.options.pagingCurrentPage\" min=\"1\" max=\"{{currentMaxPages}}\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"currentMaxPages > 0\">/ {{currentMaxPages}}</span> <button type=\"button\" ng-click=\"pageForward()\" ng-disabled=\"cantPageForward()\"><div class=\"last-triangle next-triangle\"></div></button> <button type=\"button\" ng-click=\"pageToLast()\" ng-disabled=\"cantPageToLast()\"><div class=\"last-triangle\"><div class=\"last-bar\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\"><select ng-model=\"grid.options.pagingPageSize\" ng-options=\"o as o for o in grid.options.pagingPageSizes\"></select><span class=\"ui-grid-pager-row-count-label\">&nbsp;{{sizesLabel}}</span></div></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{showingLow}} - {{showingHigh}} of {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
17987   );
17988
17989
17990   $templateCache.put('ui-grid/columnResizer',
17991     "<div ui-grid-column-resizer ng-if=\"grid.options.enableColumnResizing\" class=\"ui-grid-column-resizer\" col=\"col\" position=\"right\" render-index=\"renderIndex\"></div>"
17992   );
17993
17994
17995   $templateCache.put('ui-grid/selectionHeaderCell',
17996     "<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>"
17997   );
17998
17999
18000   $templateCache.put('ui-grid/selectionRowHeader',
18001     "<div class=\"ui-grid-row-header-cell ui-grid-disable-selection\"><div class=\"ui-grid-cell-contents\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div></div>"
18002   );
18003
18004
18005   $templateCache.put('ui-grid/selectionRowHeaderButtons',
18006     "<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>"
18007   );
18008
18009
18010   $templateCache.put('ui-grid/selectionSelectAllButtons',
18011     "<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)\">&nbsp;</div>"
18012   );
18013
18014 }]);